一、为什么用线程
在这里将ocr识别的这个耗时的操作放在新线程内,这样是为了主线程/UI界面不会卡在那里没有响应。如果说不在乎用户体验,ocr识别也可以放在主线程,卡住就卡住,就是会被骂。
不过关于线程的使用有的时候也要看平台框架的要求,比如android,网络请求就要在新线程内,否则就会报错。
QT内的线程类叫做QThread,看看qthread.h内的部分方法,熟悉Java线程的大概在就会知道用法了(因为挺像的),我们这里采用创建一个新的继承自QThread类的方法使用线程。
二、创建继承自Thread的类
1、创建头文件
起了个名字,有点长,MyThreadForTextRecognition.h。
其中public方法内主要是自定义的init方法,用来传递给线程一些ocr识别的参数,比如语言、用哪个引擎等。
其中private是一些线程内的参数,以及对应两个ocr引擎的识别方法。
其中protected就是我们要覆写的run方法。
其中signals就是我们用来和主线程通信的方法。关于信号和槽可以查看官网说明。其中getRecognitionText方法是用来将识别结果传递给主线程以显示在界面上。其中recognitionFinish方法是告诉主线程识别完了,可以关闭loading了。
#ifndef MYTHREADFORTEXTRECOGNITION_H
#define MYTHREADFORTEXTRECOGNITION_H
// windows sdk api
#define _SILENCE_CLANG_COROUTINE_MESSAGE
#include "winrt/Windows.Globalization.h"
#include "winrt/windows.media.ocr.h"
#include "winrt/windows.graphics.imaging.h"
#include "winrt/Windows.Storage.h"
#include "winrt/Windows.Storage.Streams.h"
#include "winrt/Windows.Foundation.Collections.h"
// QT
#include <QObject>
#include <QThread>
#include "qdebug.h"
// 自定义
#include "mytools.h"
// tesseract
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
class MyThreadForTextRecognition : public QThread
{
Q_OBJECT
public:
MyThreadForTextRecognition();
void init(std::string filepath, std::string newFilepath, int language_index, bool preprocessing, int ocrengine);
private:
char *outText;
std::string _filepath;
int _language_index;
std::string _newFilepath;
bool _preprocessing = false;
std::string _ocrengine;
void _cognizeByWinOCR();
void _cognizeByTesseract();
protected:
void run() override;
signals:
void minNumToamin(int min);
void getRecognitionText(std::string outText);
void recognitionFinish();
};
#endif // MYTHREADFORTEXTRECOGNITION_H
2、创建cpp文件
参考代码如下
#include <MyThreadForTextRecognition.h>
#include <string>
#include <iostream>
#include <locale>
#include <codecvt>
//#pragma execution_character_set("utf-8");
//comment(lib, "windowsapp")
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::Globalization;
using namespace winrt::Windows::Media::Ocr;
using namespace winrt::Windows::Graphics::Imaging;
MyThreadForTextRecognition::MyThreadForTextRecognition()
{
}
/**
* @brief MyThreadForTextRecognition::init 接收一些参数
* @param filepath
* @param newFilepath
* @param language
* @param preprocessing
*/
void MyThreadForTextRecognition::init(std::string filepath, std::string newFilepath, int language_index, bool preprocessing, int ocrengine)
{
_filepath = filepath;
_language_index = language_index;
_newFilepath = newFilepath;
_preprocessing = preprocessing;
_ocrengine = (ocrengine==0) ? "micocr" : "tesseract";
}
/**
* @brief MyThreadForTextRecognition::_cognizeByWinOCR
* 基于windows的ocr
*/
void MyThreadForTextRecognition::_cognizeByWinOCR()
{
//获取图片路径
std::wstring uriImage = MyTools::ConvertUtf8ToWide(_newFilepath);
//获取SoftwareBitmap
RandomAccessStreamReference streamRef = RandomAccessStreamReference::CreateFromFile(StorageFile::GetFileFromPathAsync(uriImage).get());
IAsyncOperation<IRandomAccessStreamWithContentType> stream = streamRef.OpenReadAsync();
Buffer buffer = Buffer((uint)stream.get().Size());
auto decoder = BitmapDecoder::CreateAsync(stream.get().CloneStream());
IAsyncOperation<SoftwareBitmap> softwareBitmap = decoder.get().GetSoftwareBitmapAsync();
//进行识别
Language language(winrt::to_hstring(MyTools::get_Choise_Language(_language_index, _ocrengine)));
//如果不支持的语言
if (OcrEngine::IsLanguageSupported(language)) {
OcrEngine engine = OcrEngine::TryCreateFromLanguage(language);
Collections::IVectorView<OcrLine> lines = engine.RecognizeAsync(softwareBitmap.get()).get().Lines();
if(lines.Size()>0)
{
for (auto p : lines)
{
std::string text = "";
for (OcrWord word : p.Words()) {
text += to_string(word.Text()) + ((_language_index>0)?" ":"");
}
emit getRecognitionText(text/*const_cast<char *>(text.c_str())*/);
qDebug() << "识别结果:" << text.c_str();
}
}
else
{
emit getRecognitionText("");
}
}
else
{
qDebug() << "不支持的语言:" << QString::fromStdString(MyTools::get_Choise_Language(_language_index, _ocrengine));
emit getRecognitionText("");
}
}
/**
* @brief MyThreadForTextRecognition::_cognizeByTesseract
* 基于Tesseract的ocr
*/
void MyThreadForTextRecognition::_cognizeByTesseract()
{
// 初始化api
tesseract::TessBaseAPI *api = new tesseract::TessBaseAPI();
// 初始化ocr识别,使用中文并且指定tessdata路径
if (api->Init("lib/tesseract/tessdata/", MyTools::get_Choise_Language(_language_index, _ocrengine).c_str(), tesseract::OcrEngineMode::OEM_LSTM_ONLY)) {
emit getRecognitionText("");
}
else
{
// 使用leptonica库打开图像
Pix *image = pixRead(_newFilepath.c_str());
api->SetImage(image);
// 【最简单的api】
//outText = api->GetUTF8Text();
//qDebug()<<"outText:"<<outText;
//getRecognitionText(outText);
// 【获取组件图像示例】
Boxa* boxes = api->GetComponentImages(tesseract::RIL_PARA, true, NULL, NULL);
for (int i = 0; i < boxes->n; i++) {
BOX* box = boxaGetBox(boxes, i, L_CLONE);
api->SetRectangle(box->x, box->y, box->w, box->h);
char* ocrResult = api->GetUTF8Text();
int meanConf = api->MeanTextConf();
int *confs = api->AllWordConfidences();
qDebug()<<"meanConf:"<<meanConf;
emit getRecognitionText(ocrResult);
//fprintf(stdout, "Box[%d]: x=%d, y=%d, w=%d, h=%d, confidence: %d, text: %s", i, box->x, box->y, box->w, box->h, conf, ocrResult);
boxDestroy(&box);
}
emit recognitionFinish();
// 销毁使用过的对象并释放内存
api->End();
delete api;
//delete[] outText;
pixDestroy(&image);
}
}
/**
* @brief MyThreadForTextRecognition::run 运行线程
*/
void MyThreadForTextRecognition::run()
{
try
{
//图像预处理,可以没有
if(_preprocessing)
{
MyTools::BinarythresoldByOpenCV(_filepath.c_str(), _newFilepath.c_str());
}
else
{
_newFilepath = _filepath;
}
if(_ocrengine == "micocr")
{
_cognizeByWinOCR();
}
else if(_ocrengine == "tesseract")
{
_cognizeByTesseract();
}
}
catch(_exception e)
{
emit getRecognitionText("");
qDebug()<<"ocr识别异常:"<<0;
}
emit recognitionFinish();
}
三、使用线程
首先实例化线程类,然后调用init传递参数,然后绑定两个信号/槽,最后调用start方法启动线程。
//启动线程
m_thread = new MyThreadForTextRecognition;
m_thread->init(filePath.toStdString(), QString("%1\\screen_action.jpg").arg(qApp->applicationDirPath().replace("/", "\\")).toStdString(),
ui->comboBox->currentIndex(), ui->checkBox_3->isChecked(), ui->comboBox_2->currentIndex());
connect(m_thread, &MyThreadForTextRecognition::getRecognitionText,this,&TextRecognition::getRecognitionText);
connect(m_thread, &MyThreadForTextRecognition::recognitionFinish,this,&TextRecognition::recognitionFinish);
m_thread->start();