前言
PaddleOCR 提供了基于深度学习的文本检测、识别和方向检测等功能。其主要推荐的 PP-OCR 算法在国内外的企业开发者中得到广泛应用。在短短的几年时间里,PP-OCR 的累计 Star 数已经超过了32.2k,常常出现在 GitHub Trending 和 Paperswithcode 的日榜和月榜第一位,被认为是当前OCR领域最热门的仓库之一。
PaddleOCR 最初主打的 PP-OCR 系列模型在去年五月份推出了 v3 版本。最近,飞桨 AI 套件团队对 PP-OCRv3 进行了全面改进,推出了重大更新版本 PP-OCRv4。这个新版本预计带来了更先进的技术、更高的性能和更广泛的适用性,将进一步推动OCR技术在各个领域的应用。
PP-OCRv4在速度可比情况下,中文场景端到端 Hmean 指标相比于 PP-OCRv3提升4.25%,效果大幅提升。具体指标如下表所示:
测试环境:CPU 型号为 Intel Gold 6148,CPU 预测时使用 OpenVINO。
除了更新中文模型,本次升级也优化了英文数字模型,在自有评估集上文本识别准确率提升6%,如下表所示:
同时,也对已支持的80余种语言识别模型进行了升级更新,在有评估集的四种语系识别准确率平均提升5%以上,如下表所示:
一、模型转换
1.模型下载
从https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.7 下载要用到的模型,要下载的模型有文本检测模型、文字方向模型、文字识别模型,我这里只下下载了文本检测与文字识别的模型。
下载好的模型nference.pdiparams为模型参数文件,inference.pdmodel为模型结构文件,这两个文件在转换onnx的时候都要用到。
2.模型转成onnx
使用paddle2ONNX进行模型转换,git地址:https://github.com/paddlepaddle/paddle2onnx, 下载源码然后编译转换,也可以使用在线转换的方法,如果嫌麻烦,最好使用在线的转换方法,在线地址:https://www.paddlepaddle.org.cn/paddle/visualdl/modelconverter/x2paddle
3. onnx转ncnn模型
这里为了之后在移动部署做准备,选择使用NCNN做最终的模型推理,NCNN封装了很高效的API接品,可以方便地在移动设备和嵌入式系统上进行神经网络的部署和推理。适用于移动设备和嵌入式设备。它被设计用于在各种硬件平台上高效地运行神经网络推断(inference)。NCNN主要特点包括:
-
轻量级和高效性: NCNN被设计为一个轻量级框架,具有高度优化的推断性能。它的设计目标是在移动设备和嵌入式设备上实现高效的神经网络推理。
-
跨平台支持: NCNN支持多种硬件平台,包括CPU、GPU、DSP等,并且可以在各种操作系统上运行,如Windows、Android、iOS、Linux等。
-
优化和硬件加速: NCNN对各种硬件进行了优化,并利用硬件加速特性提高了神经网络推断的性能。
-
丰富的模型支持: NCNN支持各种常见的深度学习模型,如AlexNet、VGG、ResNet、MobileNet等,并且兼容一些深度学习框架导出的模型,Caffe、TensorFlow、ONNX等。
可以从https://github.com/Tencent/ncnn 获取源码进行编译,也可以下载官方编译好的lib进行转换,还可以使用在线接口进行转换。在线接地址:https://convertmodel.com/。
转出来的模型后缀是.param和.bin文件。
二、文本检测
文本检测是旨在从图像或视频中准确地检测和定位文本的位置和边界框,OCR系统中的一个重要组成部分,它为后续的文本识别提供了定位和定界的信息。
-
预处理:对输入的图像进行预处理,可能包括图像增强、去噪、尺寸标准化等操作,以便更好地适应文本检测算法。
-
文本区域检测:使用特定的算法或模型来检测图像中可能包含文本的区域。常见的方法包括基于区域的方法(如基于区域的CNN(R-CNN)系列)、基于锚点的方法(如SSD和YOLO)、以及基于注意力机制的方法(如EAST、TextBoxes++等)。
-
后处理:在获取文本区域的初始预测结果后,可以进行后处理步骤来提高检测的准确性和稳定性。这可能包括非极大值抑制(NMS)来消除重叠的边界框、边框回归以精细调整边界框的位置等。
文本检测类:
#ifndef __OCR_DBNET_H__ #define __OCR_DBNET_H__ #include "base_struct.h" #include #include #include namespace NCNNOCR { class DbNet { public: DbNet(); ~DbNet() {}; int read_model(std::string param_path = "data/det.param", std::string bin_path = "data/det.bin", bool use_gpu = true); bool detect(cv::Mat& src, std::vector& results, int _target_size = 1024); private: ncnn::Net net; const float meanValues[3] = { 0.485 * 255, 0.456 * 255, 0.406 * 255 }; const float normValues[3] = { 1.0 / 0.229 / 255.0, 1.0 / 0.224 / 255.0, 1.0 / 0.225 / 255.0 }; float boxThresh = 0.3f; float boxScoreThresh = 0.5f; float unClipRatio = 2.0f; int target_size; }; } #endif //__OCR_DBNET_H__
类实现:
#include "db_net.h" #include "tools.h" namespace NCNNOCR { int DbNet::read_model(std::string param_path, std::string bin_path, bool use_gpu) { ncnn::set_cpu_powersave(2); ncnn::set_omp_num_threads(ncnn::get_big_cpu_count()); net.opt = ncnn::Option(); #if NCNN_VULKAN net.opt.use_vulkan_compute = use_gpu; #endif net.opt.lightmode = true; net.opt.num_threads = ncnn::get_big_cpu_count(); int rp = net.load_param(param_path.c_str()); int rb = net.load_model(bin_path.c_str()); if (rp == 0 || rb == 0) { return false; } return true; } std::vector inline findRsBoxes(const cv::Mat& fMapMat, const cv::Mat& norfMapMat, const float boxScoreThresh, const float unClipRatio) { const float minArea = 3; std::vector rsBoxes; rsBoxes.clear(); std::vector contours; cv::findContours(norfMapMat, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); for (int i = 0; i h) { scale = (float)target_size / w; w = target_size; h = h * scale; } else { scale = (float)target_size / h; w = w * scale; h = target_size; } } ncnn::Extractor extractor = net.create_extractor(); ncnn::Mat out; cv::Size in_pad_size; int wpad = (w + 31) / 32 * 32 - w; int hpad = (h + 31) / 32 * 32 - h; ncnn::Mat in_pad_; ncnn::Mat input = ncnn::Mat::from_pixels_resize( src.data, ncnn::Mat::PIXEL_RGB, width, height, w, h); // pad to target_size rectangle ncnn::copy_make_border(input, in_pad_, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 0.f); in_pad_.substract_mean_normalize(meanValues, normValues); in_pad_size = cv::Size(in_pad_.w, in_pad_.h); extractor.input("x", in_pad_); extractor.extract("sigmoid_0.tmp_0", out); // ncnn::Mat flattened_out = out.reshape(out.w * out.h * out.c); //-----boxThresh----- cv::Mat fMapMat(in_pad_size.height, in_pad_size.width, CV_32FC1, (float*)out.data); cv::Mat norfMapMat; norfMapMat = fMapMat > boxThresh; cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2)); cv::dilate(norfMapMat, norfMapMat, element, cv::Point(-1, -1), 1); std::vector results = findRsBoxes(fMapMat, norfMapMat, boxScoreThresh, unClipRatio); for (int i = 0; i