前言
本文记录了在 Windows 环境下编译 PaddleOCR 源码并封装为 C++ 动态库的过程。
准备工作
在对 paddleocr 进行编译前,需要提前准备需要的文件:
- CMake 4.2.1 版本较新即可
- OpenCv 库,官网下载最新的 Windows 安装包即可
- paddle_inference 库,同样的在百度飞桨官网上可以找到,也是 windows 预编译版,下载下来即可使用。地址 https://www.paddleocr.ai/latest/version3.x/pipeline_usage/OCR.html#1-ocr
- VS2019 及以上。
检测模型和识别模型,在 paddleocr GitHub 上可以下载到,分为服务端模型和移动端模型,如下:
开始编译
首先,在 PaddleOcr 源码 PaddleOCR-3.3.0\deploy\cpp_infer 目录下创建一个 build 目录,打开 Cmake 的 gui,将此目录下的 cmakelist 拖入其中。将编译完保存的目录改成 PaddleOCR-3.3.0\deploy\cpp_infer\build。然后点击 Configure,等执行失败后,设置 openCv 库,以及 paddle_inference 库的目录。
这里的设置千万不要错,否则后面生成的 vs 工程很多东西需要手动去改,烦的很。 设置完成后继续点击 Configure,中途可能会出现提示某些 CMake 版本较低,无需多言,根据提示手动找到对应文件的 CMakeList 将 CMake 版本标识改成 4.2.1,然后继续编译即可。 这里大致上就会显示成功,然后点击 Generate 生成对应的 VS 工程,生成的 VS 工程位于 build 目录下 ppocr.sln。打开此项目,将编译版本改为 Release X64,点击编译。中途会提示缺少某个头文件。下载 dirent.h,并拷贝到 Visual Studio 的 include 文件夹下,如 C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include。(此头文件可以在 此地址 里面找到)。 继续点击编译,不出意外,此时会显示编译成功。 在 build\release 目录下,会生成一个 ppocr.exe 的 demo 程序。在命令行中可以验证是否编译成功。
PS F:\project\paddleOcr\deploy\cpp_infer\build\Release> ./ppocr ocr --input ./test10.jpg --save_path F:/project/paddleOcr/output --text_detection_model_dir F:/project/paddleOcr/models/PP-OCRv5_server_det --text_recognition_model_dir \F:/project/paddleOcr/models\PP-OCRv5_server_rec --use_doc_orientation_classify False --use_doc_unwarping False --use_textline_orientation False --device cpu
相关参数很简单。执行,然后就会显示识别的内容。在此过程中如果提示缺少某某动态库,需要手动将其放置到 ppocr.exe 目录下。
封装自己的动态库
经过上面的操作,已经能够在 c++ 中进行推理识别了。但是此时是一个 ppocr.exe 的程序,用起来肯定是不方便的。我们需要将其封装成动态库,更方便我们使用。在 ppocr 项目下有个 cli.cc 文件。此程序的 main 函数就在这里面。 此时在 ppocr 项目中新建一个 myocr.h 和 myocr.cpp 文件。在里面自己封装识别的逻辑,代码如下:
#pragma once
#include<string>
#include<opencv2/opencv.hpp>
#define OCR_API __declspec(dllexport)
namespace ocr {
struct OCR_API Result { cv::Mat img; std::string text; float score; cv::Rect rect;};
OCR_API bool init(const std::string &det_name,const std::string& det_dir,const std::string& rec_name,const std::string&rec_dir);
OCR_API std::vector<Result> run_det(cv::Mat& input);
OCR_API void run_rec(Result& result);
}
#include"paddle_ocr.h"
#include"src/api/pipelines/ocr.h"
static std::unique_ptr<TextDetPredictor> det_Predictor;
static std::unique_ptr<TextRecPredictor> rec_Predictor;
bool ocr::init(const std::string& det_name,const std::string& det_dir,const std::string& rec_name,const std::string& rec_dir){
try{ TextDetPredictorParams det_Params; det_Params.model_name = det_name; det_Params.model_dir = det_dir; det_Predictor = std::make_unique<TextDetPredictor>(det_Params);}
catch(std::exception &e){return false;}
try{ TextRecPredictorParams rec_Params; rec_Params.model_name = rec_name; rec_Params.model_dir = rec_dir; rec_Predictor = std::make_unique<TextRecPredictor>(rec_Params);}
catch(std::exception& e){return false;}
return true;}
std::vector<ocr::Result> ocr::run_det(cv::Mat& input){
std::vector<cv::Mat>inputs ={ input };
det_Predictor->ResetResult();
det_Predictor->Process(inputs);
std::vector<TextDetPredictorResult>det_results = det_Predictor->PredictorResult();
std::vector<ocr::Result>result;
for(int i =0; i < det_results.size(); i++){
TextDetPredictorResult det_result = det_results[i];
for(int j =0; j < det_result.dt_scores.size(); j++){
ocr::Result r; r.score = det_result.dt_scores[j];
std::vector<cv::Point2f>item = det_result.dt_polys[j];
r.rect = cv::Rect(item[0], item[2]);
r.img = det_result.input_image(r.rect).clone();
result.push_back(r);
}}
return result;}
void ocr::run_rec(ocr::Result& result){
std::vector<cv::Mat>inputs ={result.img};
rec_Predictor->ResetResult();
rec_Predictor->Process(inputs);
std::vector<TextRecPredictorResult> rec_results = rec_Predictor->PredictorResult();
result.text = rec_results[0].rec_text;
result.score = rec_results[0].rec_score;}
可以根据自己的需求,随意封装,此处只为一个例子,优化的地方还很多。 可以在 cil.cc 中验证自己的封装。将里面的代码注释掉,例如添加如下代码:
#include"./build/paddle_ocr.h"
#include<Windows.h>
int main(){
SetConsoleOutputCP(CP_UTF8);
bool ret = ocr::init("PP-OCRv5_server_det","F:/project/paddleOcr/models/PP-OCRv5_server_det","PP-OCRv5_server_rec","F:/project/paddleOcr/models/PP-OCRv5_server_rec");
std::cout << "ret is" << ret << std::endl;
cv::Mat img = cv::imread("F:/project/paddleOcr/deploy/cpp_infer/build/Release/test10.png", cv::IMREAD_COLOR_BGR);
std::vector<ocr::Result>results = ocr::run_det(img);
for(int i=0;i<results.size();i++){
ocr::Result item = results[i];
ocr::run_rec(item);
std::cout << item.score << "\t["<< item.rect.x <<","<< item.rect.y <<","<< item.rect.width <<","<< item.rect.height <<"]\t"<< item.text << std::endl;
}}
用来检验自己封装的正确性。 在成功之后,在 VS 工程的配置属性中,将输出文件拓展名改成.dll,配置类型改为动态库。然后重新编译生成此项目。
如何在自己的项目中使用
以上已经完成对 paddleocr 动态库的封装,下面介绍如何使用。 首先对动态库的头文件进行稍加修改。如下:
#pragma once
// ocr_api.h
#include<string>
#include<vector>
#include<opencv2/opencv.hpp>
// 注意:这里不能用 __declspec(dllexport),而是 dllimport!
#ifdef OCR_DLL
#define OCR_API __declspec(dllexport)
#else
#define OCR_API __declspec(dllimport)
#endif
namespace ocr {
struct OCR_API Result { cv::Mat img; std::string text; float score; cv::Rect rect;};
OCR_API bool init(const std::string& det_name,const std::string& det_dir,const std::string& rec_name,const std::string& rec_dir);
OCR_API std::vector<Result> run_det(cv::Mat& input);
OCR_API void run_rec(Result& result);
}
具体原因,不多赘述。 然后在需要使用的地方进行测试,例如在 MFC 项目中:
void CPaddocrTestDlg::OnBnClickedButton1(){
//server mobile
bool ret = ocr::init("PP-OCRv5_mobile_det","F:/project/paddleOcr/models/PP-OCRv5_mobile_det","PP-OCRv5_mobile_rec","F:/project/paddleOcr/models/PP-OCRv5_mobile_rec");
CString initMsg; initMsg.Format(_T("ocr::init() 返回:%s\\n"), ret ?_T("true"):_T("false"));
OutputDebugString(initMsg);
if(!ret){
OutputDebugString(_T("OCR 初始化失败!请检查模型路径和依赖 DLL。\\n"));
return;}
cv::Mat img = cv::imread("F:/project/paddleOcr/deploy/cpp_infer/build/Release/test10.png", cv::IMREAD_COLOR);
if(img.empty()){
OutputDebugString(_T("无法加载图像 test10.png!\\n"));
return;}
std::vector<ocr::Result> results = ocr::run_det(img);
CString detMsg;
detMsg.Format(_T("检测到 %d 个文本区域\\n"),(int)results.size());
OutputDebugString(detMsg);
for(auto& r : results){
ocr::run_rec(r);
CString text = CA2T(r.text.c_str(), CP_UTF8);
CString msg;
msg.Format(_T("文本:\"%s\", 置信度:%.2f, 位置:[%d,%d,%d,%d]\\n"), text, r.score, r.rect.x, r.rect.y, r.rect.width, r.rect.height );
OutputDebugString(msg);
}}
此处同样需要对库目录的链接加载配置正确。 注:此封装的 dll 只能在 release 状态下使用,对调试有一定的限制。
