最近有项目用到了字符和数字识别,调研后,先拿tesseract -ogr试试,效果还不错
基于 tesseract 实现英文字符和数字识别
tesseract 主要依赖于leptonica 和 tiff 库
leptonica 依赖于 zlib tiff png jpeg
先列一下我用到的库和版本号
1.jpg库没有cmake.txt,没法直接用cmake编译,我们直接用libjpeg-turbo库,可以用
2.tesseract 版本没用特别高,我的mingw版本还没法完全适应C++17,编译不过去,所以降了版本
3.leptonica 版本要升,我最开始用的版本比我下面的低,有些头文件没有
4.png库也要高,低点试了下不行,我试了三个版本
综上,如果你的mingw版本和我一样(mingw730_64),可以直接用下面的版本,我试过了可以

编译过程
1.tesseract 如下设置


2.leptonica 如下设置,其他库自己编吧 很简单 下载后,直接构建就行,一遍过

工程构建
QT 的.pro文件,这个动态库填写有先后顺序,烦死了,试了好多次,按我这样往下顺,要不然几百个报错
下面是我试出来的,能正常用

C++接口调用
1.设置识别语言和语言文件存放路径
2.读取图像,我使用opencv 或者用QT 的QImage,转换成unsigned char*即可
3.选择是否划定识别区域,图像太大可能识别效率也上不来,而且误识别区域过多
#include "opencv2/opencv.hpp"
#include "baseapi.h"
using namespace tesseract;
int main()
{
char* outText;
tesseract::TessBaseAPI* api = new tesseract::TessBaseAPI();
if (api->Init("E:/Software/tesseract/AZ/tessdata", "eng"))
{
fprintf(stderr, "Could not initialize tesseract.\n");
exit(1);
}
// cv::Mat image = cv::imread("C:/Users/lixia/Desktop/789897897897.png");
cv::Mat image = cv::imread("C:/Users/lixia/Desktop/11111111.png");
// 3. 设置图像参数
int width = image.cols;
int height = image.rows;
int channels = image.channels();
int bytes_per_pixel = channels;
int bytes_per_line = static_cast<int>(image.cols * bytes_per_pixel);
// bytes_per_pixel:每个像素占用的字节数
// 对于8位图像(0-255),通常等于通道数
// 例如:灰度图(1通道)= 1,RGB彩图(3通道)= 3,RGBA(4通道)= 4
// bytes_per_line:每行图像数据占用的字节数(步长)
// 通常 = 宽度 × 每像素字节数 + 可能的填充字节
// 在OpenCV中,image.step就是每行的字节数
//传入影像
api->SetImage(image.data,width,height,bytes_per_pixel,bytes_per_line);
//划定范围
// api->SetRectangle(692,670,1100,150);
// Get OCR result
outText = api->GetUTF8Text();
printf("OCR output:\n%s", outText);
// Destroy used object and release memory
api->End();
delete api;
delete[] outText;
return 0;
}
处理效果
分析:
1.字体和背景色要有足够的颜色区分
2.实际使用过程中,还是要使用YOLO识别字符区域,或者使用传统的图像处理算法,先大致预识别一个识别位置,要不然场景很复杂
3.还有待测试的是,不同颜色组合的字体和背景,是否还需要传统方法进行二值化处理后再传入识别函数
4.还需要有一个角度矫正的策略在预处理上,我测试了文字旋转超过20度感觉识别就很不稳定了

识别结果


识别结果

后续…2025/12/17
实际使用中 遇到了几个问题
1.最好是配合检测,再裁剪影像进行分析,我的项目由于字符和数字都是水平的,所以就是yolo正框,如果是倾斜字体,可以检测斜框,然后矫正成水平方向再识别
2.这个算法说实话不是很稳定,尤其是稍微有点背景干扰就识别不好,但是贵在快,十几毫秒,黑白字体效果不错,所以裁剪后的影像,做灰度变化,做二值化,可能效果更好点,或者再加点腐蚀膨胀运算,就是多一些预处理
3.我上面的工程,没法嵌到我的大工程里面,很无语,所以我单读压了一个exe,主程序开线程调用exe,采用共享内存进行数据和结果交换,可以正常使用
实际上除了这个库以外,我也使用了opencv自带的场景字符检测和识别的相关接口,如:TextDetectionModel 和 TextRecognitionModel
检测模型支持:
“DB” 模型:检测精度高,正框斜框都可以,很满意,就是效率很低(1s开外了,甚至2-3s),不支持实时场景,我没法用,我起码要3-5帧的效率,难受;
“EAST” 模型:检测精度低,反正老框不准,漏字符,但是实时性比较好,320*320图像能达到7帧左右
反正都不是很好,没法直接拿来用
cv::d nn::TextDetectionModel_DB “DB” 模型
cv::dnn::TextDetectionModel_EAST “EAST” 模型
识别模型支持:
crnn_cs模型:效果还不错,不挑影像,只要框出来差不多都能识别,调用前需要矫正成水平方向,重置成100*30的影像
贴个图吧 还是挺不赖的 “DB” 模型 + crnn_cs模型,效率就不提了…

我想用的是YOLO + tesseract 或者是 YOLO + crnn_cs,实际效果还是需要测试的,我暂时还没标样本,直接给一个框算了,输入给tesseract /crnn_cs都能正常输出结果,展示,如下:
还想再提一句,crnn_cs比tesseract 要慢,但是似乎crnn_cs稳定性更高,这个后面再看吧,下图tesseract 也就10-20ms,crnn_cs得70-90ms,从效率上看,还是tesseract 更符合我的项目,追求快为目的
2025/12/18
注:tesseract 这个也要看输入的影像框是多大,影像大,自然效率就会慢点,我实测下面这个图30-45ms,给到crnn.onnx(36类,1-9,a-z),大概也就是60ms左右,其实也不错了,这个是重置影像到100*32,所以再大的框,我估计也就这个时间,此外,这个模型好点,且支持吃重新训练,感觉可靠性还是比较好的

新增 2025/12/18
使用crnn模型进行预测,开始我用的是直接下载好的onnx直接使用的,我下载了开源的crnn模型后,crnn.pth转crnn.onnx后无法使用,经过数据查询,得到了以下的解决思路:
参数用下面的设置
1.参数verbose=True,
2.opset_version=12
import torch
import models.crnn as crnn
model = crnn.CRNN(32, 1, 37, 256)
model.load_state_dict(torch.load("crnn.pth"))
dummy_input = torch.randn(1, 1, 32, 100)
# torch.onnx.export(model, dummy_input, "crnn.onnx",dynamic=False, opset=12)
torch.onnx._export(model, dummy_input, "crnn.onnx",
verbose=True,
opset_version=12)
转出来的crnn.onnx,无法直接使用,需要简化一下,安装
pip install onnx-simplifier
简化 压缩
***\Scripts\onnxsim.exe crnn.onnx crnn_smp.onnx
简化后的模型就可以用了
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
dnn::Net net = dnn::readNet("***/crnn.pytorch-master/crnn_smp.onnx");
// dnn::Net net = dnn::readNet("C:/Users/lixia/Desktop/crnn.pytorch-master/crnn.onnx");
cout <<"------------------------------"<<endl;
Mat in = imread("***/Desktop/123.png",0);
auto end1 = std::chrono::high_resolution_clock::now();
in.convertTo(in,CV_32F,1.0/255);
in -= 0.5;
in /= 0.5;
resize(in,in,Size(100, 32)); // fixed !
Mat blob = dnn::blobFromImage(in);
net.setInput(blob);
Mat res = net.forward(); // 26 x 1 x 37
string alphabet = "0123456789abcdefghijklmnopqrstuvwxyz"; // 10 nums + 26 letters + 1 = 37
for (size_t r = 0; r < res.size[0]; r++) {
Mat slice = Mat(1,res.size[2],CV_32F,res.ptr<float>(r)); // 37 one-hot encoded pins
Point p; double m;
minMaxLoc(slice, 0, &m, 0, &p);
char c = p.x>0 ? alphabet[p.x-1] : '-'; // '-' == no detection
cout << r << "\t" << c << "\t" << p << "\t" << m << endl;
}
auto end = std::chrono::high_resolution_clock::now();
double elapsed = std::chrono::duration<double, std::milli>(end - end1).count();
cout <<elapsed<<endl;
return 0;
}

6122

被折叠的 条评论
为什么被折叠?



