在 C++ 项目中,通过源码使用 PaddlePaddle 实现 OCR 功能
My-PaddleOCR
介绍
如何在 C++ 项目中,通过源码使用 PaddlePaddle 实现 OCR 功能。
本项目的所有源码:gitee: paddleocr
目前,官方提供使用 PaddleOcr 的方案有:
在 Python 项目中,调用
paddlepaddle+paddleocr包。在 C++项目中,调用一个可执行文件。(由编译 PaddleOCR 中的
deploy/cpp_infer下的代码形成)Paddle OCR 提供了一个通过编译
deploy/cpp_infer下的代码为ppocr.exe,然后通过命令行调用获取 OCR 的结果。
具体过程见: 服务器端 C++预测
其它方法:
- 使用 Python 写一个 RESTful 服务,然后让 C++项目调用这个服务功能。
这里主要介绍一个更加直接的方法:
- 将
deploy/cpp_infer的源码引入到我们的 C++项目。 - 做适当修改后,我们就可以直接使用这些 API。
- 当然,如果你愿意的话,也可以将这些源码形成一个新的项目,编译成一个 dll。(这里并不介绍)
说明: - 下面的方法只在 release 版中有效。可能是因为
paddle_inference.dll只支持 release 版。
如果要支持 debug 版,可能需要重新编译 PaddlePaddle。
Paddle OCR C++ 源码
Paddle OCR 的仓库,在github: PaddleOCR 或者 gitee: PaddleOCR
C++ 相关的代码在目录 deploy/cpp_infer 里。
如何引入 Paddle OCR C++ 源码
需要安装的组件
- opencv
我在 opencv 4.6 版本上测试通过。
注意:opencv 4.5 版本存在一些问题,会导致功能异常。
设置环境变量:
@REM 设置opencv目录环境变量
setx OPENCV_ROOT "C:\3rd\opencv4.6\build"
- paddle_inference
我用的是 2.6 版。
设置环境变量:
@REM 设置paddle_inference目录环境变量
setx PADDLE_ROOT "C:\3rd\paddle_inference"
- cuda (optional)
你应该懂得。 - cudnn (optional)
你应该懂得。
创建 C++项目
创建一个空的 C++ 项目,并在项目中添加 opencv、paddle_inference、cuda、cudnn。
在项目中创建文件:
OpenCVDebug.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(OPENCV_ROOT)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(OPENCV_ROOT)\x64\vc15\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>opencv_world460d.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
- 在项目中创建文件:
OpenCVRelease.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(OPENCV_ROOT)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(OPENCV_ROOT)\x64\vc15\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>opencv_world460.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
- 在项目中创建文件:
PaddleDebug.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>./;$(PADDLE_ROOT)/paddle/include;$(PADDLE_ROOT)/third_party/install/zlib/include;$(PADDLE_ROOT)/third_party/boost;$(PADDLE_ROOT)/third_party/eigen3;$(PADDLE_ROOT)/third_party/install/mklml/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(PADDLE_ROOT)/third_party/install/zlib/lib;$(PADDLE_ROOT)/third_party/install/mklml/lib;$(PADDLE_ROOT)/paddle/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>paddle_inference.lib;mklml.lib;libiomp5md.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
- 在项目中创建文件:
PaddleRelease.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>./;$(PADDLE_ROOT)/paddle/include;$(PADDLE_ROOT)/third_party/install/zlib/include;$(PADDLE_ROOT)/third_party/boost;$(PADDLE_ROOT)/third_party/eigen3;$(PADDLE_ROOT)/third_party/install/mklml/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(PADDLE_ROOT)/third_party/install/zlib/lib;$(PADDLE_ROOT)/third_party/install/mklml/lib;$(PADDLE_ROOT)/paddle/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>paddle_inference.lib;mklml.lib;libiomp5md.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
- 修改 C++项目文件:
my-paddleocr.vcxproj
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project=".\OpenCVDebug.props" />
<Import Project=".\PaddleDebug.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project=".\OpenCVRelease.props" />
<Import Project=".\PaddleRelease.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project=".\OpenCVDebug.props" />
<Import Project=".\PaddleDebug.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project=".\OpenCVRelease.props" />
<Import Project=".\PaddleRelease.props" />
</ImportGroup>
引入 Paddle OCR C++ 源码
- 克隆 Paddle OCR 仓库到本地。
- 切换到分支
release/2.6。
git clone https://gitee.com/paddlepaddle/PaddleOCR.git
cd PaddleOCR
git checkout release/2.6
- 将
deploy/cpp_infer目录下的include、src两个目录的内容复制到我们的 C++ 项目中。 - 修改新的
src目录名称为ocr。 - 删除
ocr/main.cpp - 在
include目录下创建ocr_flags.h文件,内容如下:
这文件是为了替换 google 的 gflags 库的使用。
#pragma once
// common args
#include <string>
using std::string;
#define DECLARE_bool(name) extern bool FLAGS_##name;
#define DECLARE_int32(name) extern int FLAGS_##name;
#define DECLARE_string(name) extern string FLAGS_##name;
#define DECLARE_double(name) extern double FLAGS_##name;
#define DEFINE_VARIABLE(type, name, value, help) \
static const type FLAGS_nono##name = value; \
type FLAGS_##name = FLAGS_nono##name; \
static type FLAGS_no##name = FLAGS_nono##name;
#define DEFINE_bool(name, val, txt) DEFINE_VARIABLE(bool, name, val, txt)
#define DEFINE_int32(name, val, txt) DEFINE_VARIABLE(int, name, val, txt)
#define DEFINE_string(name, val, txt) DEFINE_VARIABLE(string, name, val, txt)
#define DEFINE_double(name, val, txt) DEFINE_VARIABLE(double, name, val, txt)
- 修改
include/args.h文件,内容如下:
不使用gflags/gflags.h
注释行// #include <gflags/gflags.h>
增加行#include "ocr_flags.h"
...
// #include <gflags/gflags.h>
#include "ocr_flags.h"
...
- 修改
ocr/args.cpp文件,内容如下:
不使用gflags/gflags.h
注释行// #include <gflags/gflags.h>
增加行#include "include/ocr_flags.h"
...
// #include <gflags/gflags.h>
#include "include/ocr_flags.h"
...
- 修改
include/paddleocr.cpp文件,内容如下:
不使用auto_log/autolog.h
注释行// #include "auto_log/autolog.h"
注释方法PPOCR::benchmark_log的内容。
// #include "auto_log/autolog.h"
...
void PPOCR::benchmark_log(int img_num) {
// if (this->time_info_det[0] + this->time_info_det[1] +
// this->time_info_det[2] >
// 0) {
// AutoLogger autolog_det("ocr_det", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads, 1,
// "dynamic", FLAGS_precision, this->time_info_det,
// img_num);
// autolog_det.report();
// }
// if (this->time_info_rec[0] + this->time_info_rec[1] +
// this->time_info_rec[2] >
// 0) {
// AutoLogger autolog_rec("ocr_rec", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads,
// FLAGS_rec_batch_num, "dynamic", FLAGS_precision,
// this->time_info_rec, img_num);
// autolog_rec.report();
// }
// if (this->time_info_cls[0] + this->time_info_cls[1] +
// this->time_info_cls[2] >
// 0) {
// AutoLogger autolog_cls("ocr_cls", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads,
// FLAGS_cls_batch_num, "dynamic", FLAGS_precision,
// this->time_info_cls, img_num);
// autolog_cls.report();
// }
}
...
- 修改
include/paddlestructure.cpp文件,内容如下:
不使用auto_log/autolog.h
注释行// #include "auto_log/autolog.h"
注释方法PPOCR::benchmark_log的内容。
// #include "auto_log/autolog.h"
...
void PaddleStructure::benchmark_log(int img_num) {
// if (this->time_info_det[0] + this->time_info_det[1] +
// this->time_info_det[2] >
// 0) {
// AutoLogger autolog_det("ocr_det", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads, 1,
// "dynamic", FLAGS_precision, this->time_info_det,
// img_num);
// autolog_det.report();
// }
// if (this->time_info_rec[0] + this->time_info_rec[1] +
// this->time_info_rec[2] >
// 0) {
// AutoLogger autolog_rec("ocr_rec", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads,
// FLAGS_rec_batch_num, "dynamic", FLAGS_precision,
// this->time_info_rec, img_num);
// autolog_rec.report();
// }
// if (this->time_info_cls[0] + this->time_info_cls[1] +
// this->time_info_cls[2] >
// 0) {
// AutoLogger autolog_cls("ocr_cls", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads,
// FLAGS_cls_batch_num, "dynamic", FLAGS_precision,
// this->time_info_cls, img_num);
// autolog_cls.report();
// }
// if (this->time_info_table[0] + this->time_info_table[1] +
// this->time_info_table[2] >
// 0) {
// AutoLogger autolog_table("table", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads,
// FLAGS_cls_batch_num, "dynamic", FLAGS_precision,
// this->time_info_table, img_num);
// autolog_table.report();
// }
// if (this->time_info_layout[0] + this->time_info_layout[1] +
// this->time_info_layout[2] >
// 0) {
// AutoLogger autolog_layout("layout", FLAGS_use_gpu, FLAGS_use_tensorrt,
// FLAGS_enable_mkldnn, FLAGS_cpu_threads,
// FLAGS_cls_batch_num, "dynamic",
// FLAGS_precision, this->time_info_layout,
// img_num);
// autolog_layout.report();
// }
}
...
- 修改
include/utility.cpp文件,内容如下:
不使用dirent.h
注释行// #include <dirent.h>
注释方法Utility::GetAllFiles的内容。
// #include <dirent.h>
...
// list all files under a directory
void Utility::GetAllFiles(const char *dir_name,
std::vector<std::string> &all_inputs) {
// if (NULL == dir_name) {
// std::cout << " dir_name is null ! " << std::endl;
// return;
// }
// struct stat s;
// stat(dir_name, &s);
// if (!S_ISDIR(s.st_mode)) {
// std::cout << "dir_name is not a valid directory !" << std::endl;
// all_inputs.push_back(dir_name);
// return;
// } else {
// struct dirent *filename; // return value for readdir()
// DIR *dir; // return value for opendir()
// dir = opendir(dir_name);
// if (NULL == dir) {
// std::cout << "Can not open dir " << dir_name << std::endl;
// return;
// }
// std::cout << "Successfully opened the dir !" << std::endl;
// while ((filename = readdir(dir)) != NULL) {
// if (strcmp(filename->d_name, ".") == 0 ||
// strcmp(filename->d_name, "..") == 0)
// continue;
// // img_dir + std::string("/") + all_inputs[0];
// all_inputs.push_back(dir_name + std::string("/") +
// std::string(filename->d_name));
// }
// }
}
...
编写调用程序
- 在项目中创建文件:
paddle_util.h
内容如下:
#pragma once
using namespace std;
#include <include/paddleocr.h>
class PaddleUtil {
public:
PaddleUtil();
public:
static PaddleUtil &get();
static void init();
void rec_image(const string &imageFile);
private:
PaddleOCR::PPOCR ocr;
};
- 在项目中创建文件:
paddle_util.cpp
内容如下:
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <vector>
#include "paddle_util.h"
#include <include/args.h>
using namespace PaddleOCR;
PaddleUtil::PaddleUtil() {}
PaddleUtil &PaddleUtil::get() {
static PaddleUtil self;
return self;
}
void PaddleUtil::init() {
FLAGS_det = true;
FLAGS_rec = true;
FLAGS_cls = false;
FLAGS_use_angle_cls = false;
FLAGS_det_model_dir = "model/whl/det/en/en_PP-OCRv3_det_infer";
FLAGS_rec_model_dir = "model/whl/rec/en/en_PP-OCRv4_rec_infer";
FLAGS_rec_char_dict_path = "model/en_dict.txt";
}
void PaddleUtil::rec_image(const string &imageFile) {
if (FLAGS_benchmark) {
ocr.reset_timer();
}
cv::Mat img = cv::imread(imageFile, cv::IMREAD_COLOR);
if (!img.data) {
std::cerr << "[ERROR] image read failed! image path: " << imageFile
<< std::endl;
return;
}
std::vector<OCRPredictResult> ocr_result =
ocr.ocr(img, FLAGS_det, FLAGS_rec, FLAGS_cls);
Utility::print_result(ocr_result);
if (FLAGS_visualize && FLAGS_det) {
std::string file_name = Utility::basename(imageFile);
Utility::VisualizeBboxes(img, ocr_result, FLAGS_output + "/" + file_name);
}
}
- 在项目中创建主程序文件:
my-paddleocr.cpp
内容如下:
#include <iostream>
#include "paddle_util.h"
int main() {
PaddleUtil::init();
PaddleUtil::get().rec_image("model/254.jpg");
std::cout << "Done!\n";
}
编译运行
- 在 Visual Studio 中使用 Release 版本编译。
- 运行
copy_paddle_dll.bat,复制paddle_inference.dll到输出目录。
@REM copy dll to Release
copy /Y %PADDLE_ROOT%\paddle\lib\paddle_inference.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\mkldnn\lib\mkldnn.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\mklml\lib\mklml.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\mklml\lib\libiomp5md.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\paddle2onnx\lib\paddle2onnx.dll .\x64\Release\
copy /Y %PADDLE_ROOT%\third_party\install\onnxruntime\lib\onnxruntime.dll .\x64\Release\
在 C++ 项目中,通过源码使用 PaddlePaddle 实现 OCR 功能的更多相关文章
- centos7中通过源码安装postgresql13.6
下载地址:https://www.postgresql.org/ftp/source/ 0.安装相关依赖库 centos依赖包下载地址:https://developer.aliyun.com/pac ...
- 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”
通过源码了解ASP.NET MVC 几种Filter的执行过程 一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...
- 通过源码分析Java开源任务调度框架Quartz的主要流程
通过源码分析Java开源任务调度框架Quartz的主要流程 从使用效果.调用链路跟踪.E-R图.循环调度逻辑几个方面分析Quartz. github项目地址: https://github.com/t ...
- 通过源码了解ASP.NET MVC 几种Filter的执行过程
一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...
- Linux下通过源码编译安装程序
本文简单的记录了下,在linux下如何通过源码安装程序,以及相关的知识.(大神勿喷^_^) 一.程序的组成部分 Linux下程序大都是由以下几部分组成: 二进制文件:也就是可以运行的程序文件 库文件: ...
- 在centos6.7通过源码安装python3.6.7报错“zipimport.ZipImportError: can't decompress data; zlib not available”
在centos6.7通过源码安装python3.6.7报错: zipimport.ZipImportError: can't decompress data; zlib not available 从 ...
- Kafka详解六:Kafka如何通过源码实现监控
问题导读: 1.kafka的消费者组的消费偏移存储,kafka支持两个版本? 2.ConsumerOffsetChecker类的作用是什么? 3.Kafka如何通过源码实现 ...
- 通过源码编译安装VIM
开发中使用的是Ubuntu 12.04 LTS,通过sudo apt-get install vim安装的版本较低,不支持YCM,所以,用源码编译并安装最新的Vim. 卸载旧版本的Vim: sudo ...
- echarts 通过源码方法 传入对应data数据获取分割步长值
通过源码方法获取这里的分割数字长度 /** * Quantity of a number. e.g. 0.1, 1, 10, 100 * * @param {number} val * @return ...
- 通过源码安装PostgresSQL
通过源码安装PostgresSQL 1.1 下载源码包环境: Centos6.8 64位 yum -y install bison flex readline-devel zlib-devel yum ...
随机推荐
- [转帖]远超DDR4,速度可达DDR5 6400!DDR5内存技术、产品解析
https://www.sohu.com/a/326810241_616364 内存是计算机技术的重要组成部分,经历了长时间的竞争更替和路线选择之后,PC内存技术被稳定在以DDR技术为基础的发展路线上 ...
- chrome谷歌浏览器多开(独立环境 独立cookie)
复制下面代码(路径) "C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="D ...
- 使用 NuGet.Server 创建和部署 ASP.NET Web 应用程序搭建私有Nuget服务器
使用 NuGet.Server 创建和部署 ASP.NET Web 应用程序搭建私有Nuget服务器 在Visual Studio中,选择"新建>文件>"Project ...
- 补发一篇tensorflow下MADDPG环境搭建配置
相关文章: [一]MADDPG-单智能体|多智能体总结(理论.算法) [二]MADDPG多智能体算法实现(parl)[追逐游戏复现] 之前用的都是paddle,现在补一下tf的. github上ope ...
- C/C++ 实现Socket交互式服务端
在 Windows 操作系统中,原生提供了强大的网络编程支持,允许开发者使用 Socket API 进行网络通信,通过 Socket API,开发者可以创建.连接.发送和接收数据,实现网络通信.本文将 ...
- 21.6 Python 构建ARP中间人数据包
ARP中间人攻击(ARP spoofing)是一种利用本地网络的ARP协议漏洞进行欺骗的攻击方式,攻击者会向目标主机发送虚假ARP响应包,使得目标主机的ARP缓存中的IP地址和MAC地址映射关系被篡改 ...
- Git企业开发控制理论和实操-从入门到深入(三)|分支管理
前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...
- 【C语言深度解剖】一篇搞懂结构体内存对齐【结构体的大小你会算了吗】
结构体内存对齐 大家好,我是西城s 文章目录 前言 引入 结构体内存对齐 `offsetof`宏的使用 为什么存在结构体内存对齐 修改默认对齐数 尾声 前言 作者: #西城s 这是我的主页:#西城s ...
- 2022年“腾讯杯”大学生程序设计竞赛 死去的 Elo 突然开始攻击我 题解
题目链接:死去的 Elo 突然开始攻击我 容易知道,如果暴力对某个区间而言进行查询,我们可以考虑使用并查集,开一个桶,每次添加一个数 \(val\),那么如果已经存在了 \(val-1\) 或者 \( ...
- 《ASP.NET Core 微服务实战》-- 读书笔记(第10章)
第 10 章 应用和微服务安全 云应用意味着应用运行所在的基础设施无法掌控,因此安全不能再等到事后再考虑,也不能只是检查清单上毫无意义的复选框 由于安全与云原生应用密切相关,本章将讨论安全话题,并用示 ...