PaddleOCR C++学习笔记
Posted OpenCV or Android
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PaddleOCR C++学习笔记相关的知识,希望对你有一定的参考价值。
学更好的别人,
做更好的自己。
——《微卡智享》
前言
上篇提的优化方向
替换通用的OCR识别模型
分割华容道图片,单张识别
替换通用的OCR模型
01
下载通用OCR模型
02
修改Config.txt配置文件
对比效果
通用OCR模型
替换了确实识别率比原来的模型好的,从上图中可以看到,原来数字华容道识别为数字革容道,而通用模型识别就完全没有问题。
问题一解决。
分割数字华容道棋盘
-
用拉普拉斯算子提高图像对比度 -
二值化图像后进行距离变换 -
对距离变换后的再进行归一化 -
查找轮廓并实现分水岭分割
用上面的方法输出的效果也并不是想要的,所以这个也放弃了。
上面的两个效果不好,然后就想已经通过透视变换将图像矫正过来了,所以用直线检测后计算点来定位矩形再分割。使用中霍夫直接的函数调参花了不少时间,效果也不好,并且后续的处理应该也很麻烦,所以暂时也放弃了。
上面三个方法是在图像中查找16个矩形再分割,处理的效果都不理想,所以就考虑透视变换的图像中再做处理,在原来的透视变换中再做迭代的处理,再查找一次里面的最大正方形截取出来,这样直接进行等比分割就简单多了,于是就把原来main.cpp里面预处理透视变换的代码封装了函数,用递归的方式实现自定义迭代次数。
cv::Mat PaddleOcrApi::GetPerspectiveMat(cv::Mat& src, int iterations)
{
cv::Mat tmpsrc, cannysrc, resultMat;
src.copyTo(tmpsrc);
//高斯滤波
cv::GaussianBlur(tmpsrc, tmpsrc, cv::Size(5, 5), 0.5, 0.5);
int srcArea = tmpsrc.size().area();
float maxArea = 0;
int maxAreaidx = -1;
std::vector<cv::Mat> channels;
cv::Mat B_src, G_src, R_src, dstmat;
cv::split(tmpsrc, channels);
int minthreshold = 120, maxthreshold = 200;
//B进行Canny
//大津法求阈值
CvUtils::GetMatMinMaxThreshold(channels[0], minthreshold, maxthreshold, 1);
std::cout << "OTSUmin:" << minthreshold << " OTSUmax:" << maxthreshold << std::endl;
//Canny边缘提取
cv::Canny(channels[0], B_src, minthreshold, maxthreshold);
//大津法求阈值
CvUtils::GetMatMinMaxThreshold(channels[1], minthreshold, maxthreshold, 1);
std::cout << "OTSUmin:" << minthreshold << " OTSUmax:" << maxthreshold << std::endl;
//Canny边缘提取
Canny(channels[1], G_src, minthreshold, maxthreshold);
//大津法求阈值
CvUtils::GetMatMinMaxThreshold(channels[2], minthreshold, maxthreshold, 1);
std::cout << "OTSUmin:" << minthreshold << " OTSUmax:" << maxthreshold << std::endl;
//Canny边缘提取
Canny(channels[2], R_src, minthreshold, maxthreshold);
bitwise_or(B_src, G_src, dstmat);
bitwise_or(R_src, dstmat, dstmat);
//CvUtils::SetShowWindow(dstmat, "dstmat", 700, 20);
//imshow("dstmat", dstmat);
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
findContours(dstmat, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
cv::Mat dstcontour = cv::Mat::zeros(cannysrc.size(), CV_8SC3);
cv::Mat tmpcontour;
dstcontour.copyTo(tmpcontour);
//定义拟合后的多边形数组
std::vector<std::vector<cv::Point>> vtshulls(contours.size());
for (int i = 0; i < contours.size(); ++i) {
//判断轮廓形状,不是四边形的忽略掉
double lensval = 0.01 * arcLength(contours[i], true);
std::vector<cv::Point> convexhull;
approxPolyDP(cv::Mat(contours[i]), convexhull, lensval, true);
//拟合的多边形存放到定义的数组中
vtshulls[i] = convexhull;
//不是四边形的过滤掉
if (convexhull.size() != 4) continue;
//求出最小旋转矩形
cv::RotatedRect rRect = minAreaRect(contours[i]);
//更新最小旋转矩形中面积最大的值
if (rRect.size.height == 0) continue;
if (rRect.size.area() > maxArea && rRect.size.area() > srcArea * 0.1
&& !CvUtils::CheckRectBorder(src, rRect)) {
maxArea = rRect.size.area();
maxAreaidx = i;
}
}
//找到符合条码的最大面积的轮廓进行处理
if (maxAreaidx >= 0) {
std::cout << "iterations:" << iterations << " maxAreaidx:" << maxAreaidx << std::endl;
//获取最小旋转矩形
cv::RotatedRect rRect = minAreaRect(contours[maxAreaidx]);
cv::Point2f vertices[4];
//重新排序矩形坐标点,按左上,右上,右下,左下顺序
CvUtils::SortRotatedRectPoints(vertices, rRect);
std::cout << "Rect:" << vertices[0] << vertices[1] << vertices[2] << vertices[3] << std::endl;
//根据获得的4个点画线
for (int k = 0; k < 4; ++k) {
line(dstcontour, vertices[k], vertices[(k + 1) % 4], cv::Scalar(255, 0, 0));
}
//计算四边形的四点坐标
cv::Point2f rPoints[4];
CvUtils::GetPointsFromRect(rPoints, vertices, vtshulls[maxAreaidx]);
for (int k = 0; k < 4; ++k) {
line(dstcontour, rPoints[k], rPoints[(k + 1) % 4], cv::Scalar(255, 255, 255));
}
//采用离最小矩形四个点最近的重新设置范围,将所在区域的点做直线拟合再看看结果
cv::Point2f newPoints[4];
CvUtils::GetPointsFromFitline(newPoints, rPoints, vertices);
for (int k = 0; k < 4; ++k) {
line(dstcontour, newPoints[k], newPoints[(k + 1) % 4], cv::Scalar(255, 100, 255));
}
//根据最小矩形和多边形拟合的最大四个点计算透视变换矩阵
cv::Point2f rectPoint[4];
//计算旋转矩形的宽和高
float rWidth = CvUtils::CalcPointDistance(vertices[0], vertices[1]);
float rHeight = CvUtils::CalcPointDistance(vertices[1], vertices[2]);
//计算透视变换的左上角起始点
float left = dstcontour.cols;
float top = dstcontour.rows;
for (int i = 0; i < 4; ++i) {
if (left > newPoints[i].x) left = newPoints[i].x;
if (top > newPoints[i].y) top = newPoints[i].y;
}
rectPoint[0] = cv::Point2f(left, top);
rectPoint[1] = rectPoint[0] + cv::Point2f(rWidth, 0);
rectPoint[2] = rectPoint[1] + cv::Point2f(0, rHeight);
rectPoint[3] = rectPoint[0] + cv::Point2f(0, rHeight);
//计算透视变换矩阵
cv::Mat warpmatrix = getPerspectiveTransform(rPoints, rectPoint);
cv::Mat resultimg;
//透视变换
warpPerspective(src, resultimg, warpmatrix, resultimg.size(), cv::INTER_LINEAR);
/*CvUtils::SetShowWindow(resultimg, "resultimg", 200, 20);
imshow("resultimg", resultimg);*/
//载取透视变换后的图像显示出来
cv::Rect cutrect = cv::Rect(rectPoint[0], rectPoint[2]);
resultMat = resultimg(cutrect);
//CvUtils::SetShowWindow(resultMat, "resultMat", 600, 20);
//cv::imshow("resultMat", resultMat);
iterations--;
if (iterations > 0) {
resultMat = GetPerspectiveMat(resultMat, iterations);
}
}
else {
src.copyTo(resultMat);
}
return resultMat;
}
调用时把参数改为2,做两次透视变换。
对比效果
这张效果是一样的
这样检测的就有问题,显示不对
这张效果是一样的
试了多次来说,处理的效果都和预期有差,所以最终考虑了下,还是修改PaddleOCR的源码应该比这个效果好,于是就继续研究PaddleOCR源码。
首先检测的文本框会存放到boxes的容器中
点开RunOCR的函数,遍历boxes的容器后会有个GetRotateCropImage的函数。
在GetRotateCropImage函数中会根据box的点生成截取的图像矩形,所以可以在这里考虑改造函数,将生成的Rect返回来,然后通过坐标和大小来定位及找到对应的识别文本。
源码地址
https://github.com/Vaccae/OpenCVDemoCpp.git
完
扫描二维码
获取更多精彩
微卡智享
以上是关于PaddleOCR C++学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
PaddleOCR 2.6 编译详细步骤 + 踩坑记录(C++ GPU版)
Top Trending Libraries of 2021,PaddleOCR再开源8大前沿顶会论文模型
[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段