在Qt中使用PaddleOCR进行文本识别
文章目录
- 1.下载资源
- 1.1.PaddlePaddle推理库
- 1.2.PaddleOCR SDK
- 1.3.PaddleOCR模型
- 2.使用
- 3.结果
- Qt中字符串相似度计算(Levenshtein距离)
目前(20250819),最新的 PaddleOCR是V3.1.1

1.下载资源
假如我们想在Qt中调用PaddleOCR来进行字符识别,需要准备三样东西:
1.1.PaddlePaddle推理库
【下载安装 Windows 推理库】
目前windows下貌似只有CPU版本,也行,先用着。
因为PaddleOCR也是基于PaddlePaddle这个深度学习架构开发出来的东西,要运行其模型,必须要用其推理引擎。
1.2.PaddleOCR SDK
虽然我们使用的PaddleOCR是基于PaddlePaddle,但是也有很多预处理、后处理的操作,因此,还需要下载PaddleOCR的SDK来简化操作。
我们可以到【官方仓库】中,下载PaddleOCR/deploy/cpp_infer/
,这里面的源码,就是我们要用到的SDK
1.3.PaddleOCR模型
模型到PaddleOCR模型库下载
【通用OCR产线使用教程】
展开上图所示的页面便可以按照你自己的需求下载相应版本的模型文件。
在 PaddleOCR 中,Classifier、DBDetector 和 CRNNRecognizer 这三个处理器(或模型)分别承担了光学字符识别流程中不同阶段的关键任务。
目前需要用到文本检测(文字在哪里)、文本识别(文文字是什么)这两个模型;Classifier是用来进行文字方向判断的,暂时不知道模型在哪里下载以及怎么用,先不管。
我直接下载最新V5版本的推理模型
2.使用
使用PaddleOCR SDK时,需要将cpp_infer文件夹拷贝到我们的工程目录中,然后将其中除了include、src这两个文件夹外的其他东西删除,然后将文件夹重命名为ocr(叫其他名也可以)
最后,将文件加到工程中。(有部分文件因为没用到,我没有将他们加进来)
屏蔽掉utility.h utility.cpp
中的函数GetAllFiles
,因为这个函数对unix是强依赖
修改pro文件
其中路径按照你实际的路径修改
# opencv
INCLUDEPATH += D:/Qt/opencv4.4.0/include
INCLUDEPATH += D:/Qt/opencv4.4.0/include/opencv2
CONFIG(release, debug|release){
LIBS += -LD:/Qt/opencv4.4.0/x64/vc15/lib -lopencv_world440
LIBS += -LD:/Qt/opencv4.4.0/x64/vc15/bin
}
else{
LIBS += -LD:/Qt/opencv4.4.0/x64/vc15/lib -lopencv_world440d
LIBS += -LD:/Qt/opencv4.4.0/x64/vc15/bin
}# paddlePaddle
PADDLEDIR = D:/Qt/paddleOCR/paddle_inference
INCLUDEPATH += $$PADDLEDIR/paddle/include
LIBS += -L$$PADDLEDIR/paddle/lib\
-lcommon -lpaddle_inference -llibpaddle_inference
LIBS += -L$$PADDLEDIR/third_party/install/onednn/lib
LIBS += -L$$PADDLEDIR/third_party/install/mklml/lib
# 下载下来的paddlePaddle中的第三方库是静态库,用的话会报错,
# 所以要自己用vcpkg来安装必要的库
INCLUDEPATH += D:\Qt\vcpkg\vcpkg\packages\yaml-cpp_x64-windows\include
LIBS += -LD:\Qt\vcpkg\vcpkg\packages\yaml-cpp_x64-windows\lib -lyaml-cpp
LIBS += -LD:\Qt\vcpkg\vcpkg\packages\yaml-cpp_x64-windows\bin# ocr
INCLUDEPATH += $$PWD/ocr
这里直接给出一段直接运行的代码,假如想要更好的体验,还是得封装一下的。
#include "ocr/include/ocr_det.h"
#include "ocr/include/ocr_rec.h"
#include "ocr/include/utility.h"using namespace cv;
void ocrTest()
{cv::Mat img = cv::imread("../general_ocr_002.png");img = img(cv::Rect(0, 0, 200, 200));imshow("the img", img);std::string model_dir = "D:/Qt/paddleOCR/model/PP-OCRv5_server_det_infer";bool use_gpu = false;int gpu_id = 0;int gpu_mem = 4000;int cpu_math_library_num_threads = 4;bool use_mkldnn = false;std::string limit_type = "max";int limit_side_len = 960;double det_db_thresh = 0.3;double det_db_box_thresh = 0.5;double det_db_unclip_ratio = 2.0;std::string det_db_score_mode = "slow";bool use_dilation = false;bool use_tensorrt = false;std::string precision = "fp32";PaddleOCR::DBDetector detector(model_dir,use_gpu,gpu_id,gpu_mem,cpu_math_library_num_threads,use_mkldnn,limit_type,limit_side_len,det_db_thresh,det_db_box_thresh,det_db_unclip_ratio,det_db_score_mode,use_dilation,use_tensorrt,precision);std::vector<PaddleOCR::OCRPredictResult> ocr_results;std::vector<std::vector<std::vector<int>>> boxes;std::vector<double> times;detector.Run(img, boxes, times);qDebug() << boxes.size() << times.size();for (size_t i = 0; i < boxes.size(); ++i) {PaddleOCR::OCRPredictResult res;res.box = std::move(boxes[i]);ocr_results.emplace_back(std::move(res));}// sort boex from top to bottom, from left to rightPaddleOCR::Utility::sort_boxes(ocr_results);// 按照区域裁切图片// crop imagestd::vector<cv::Mat> img_list;for (size_t j = 0; j < ocr_results.size(); ++j) {cv::Mat crop_img = PaddleOCR::Utility::GetRotateCropImage(img, ocr_results[j].box);img_list.emplace_back(std::move(crop_img));}for (int i = 0; i < img_list.size(); ++i) {imshow(QString::number(i).toStdString(), img_list[i]);}std::vector<std::string> rec_texts(img_list.size(), std::string());std::vector<float> rec_text_scores(img_list.size(), 0);model_dir = "D:/Qt/paddleOCR/model/PP-OCRv5_server_rec_infer";std::string label_path = "../../ppocr/utils/ppocr_keys_v1.txt"; // 不需要存在这个文件,这样写就行;具体查看官方源码int rec_batch_num = 6;int rec_img_h = 32;int rec_img_w = 320;PaddleOCR::CRNNRecognizer recognizer(model_dir,use_gpu,gpu_id,gpu_mem,cpu_math_library_num_threads,use_mkldnn,label_path,use_tensorrt,precision,rec_batch_num,rec_img_h,rec_img_w);recognizer.Run(img_list, rec_texts, rec_text_scores, times);for (int i = 0; i < rec_texts.size(); ++i) {qDebug() << rec_texts.at(i).c_str() << rec_text_scores.at(i);}
}
3.结果
感觉结果还行,但是耗时需要比较久。可能我用的是server模型的问题,切换mobile模型试试。
Qt中字符串相似度计算(Levenshtein距离)
目前的项目要求在检测出字符串后,计算一下检测出的字符串与目标字符串的相似度,这里提供一个基于Levenshtein距离计算相似度的函数。实测效果有那么点意思。
int levenshteinDistance(const QString &s1, const QString &s2) {// 步骤1: 获取字符串长度const int len1 = s1.size();const int len2 = s2.size();// 步骤2: 创建二维DP表 (len1+1 行, len2+1 列)QVector<QVector<int>> dp(len1 + 1, QVector<int>(len2 + 1, 0));// 步骤3: 初始化边界条件// 第一列:s1[0..i] 变成空字符串需要 i 次删除for (int i = 0; i <= len1; ++i) dp[i][0] = i;// 第一行:空字符串变成 s2[0..j] 需要 j 次插入for (int j = 0; j <= len2; ++j) dp[0][j] = j;// 步骤4: 填充DP表for (int i = 1; i <= len1; ++i) {for (int j = 1; j <= len2; ++j) {// 计算替换成本int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;// 状态转移方程:取三种操作的最小值dp[i][j] = std::min({dp[i - 1][j] + 1, // 删除操作dp[i][j - 1] + 1, // 插入操作dp[i - 1][j - 1] + cost // 替换操作});}}// 步骤5: 返回最终结果return dp[len1][len2]; // 右下角的值即为编辑距离
}// 计算相似度百分比(0~100)
double calculateSimilarity(const QString &s1, const QString &s2) {int maxLen = std::max(s1.length(), s2.length());if (maxLen == 0) return 100.0; // 空字符串视为相同int distance = levenshteinDistance(s1, s2);return (1.0 - static_cast<double>(distance) / maxLen) * 100.0;
}