目前(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 right
    PaddleOCR::Utility::sort_boxes(ocr_results);

    // 按照区域裁切图片
    // crop image
    std::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模型试试。
用mobile的话,速度是快了,但是效果差了好多。因地制宜,灵活选择吧。

4.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;
}
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐