今天来说一下怎么样将这个旋转的文本框矫正,大体思路:
二值化分割+边缘检测+最大外接矩形+透视变换 => 最终图像
ps: 本人用word画的测试图像,另外是基于opencv3.3+vs2015
首先读入图片,并且灰度转换
g_src = imread("rotate.png"); if (g_src.empty()) { cout << "无法打开图片"; return -1; } //imshow("原始图片", g_src); cvtColor(g_src, g_img_gray,COLOR_BGR2GRAY); namedWindow(NAMEWINDOW1, WINDOW_AUTOSIZE);
创建一个滑动条,进行二值化找到阈值
createTrackbar("阈值", NAMEWINDOW1, &threshold_value, threshold_max, On_threshold); On_threshold(0, 0);
能很明显的看到矩形轮廓,接下来进行轮廓查找
vector<Vec4i>hierarchy; vector<vector<Point>>contours; findContours(img_thres, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
for (int i = 0;i < contours.size();i++) { Point2f vertices[4]; //用来存放旋转矩形四个顶点 RotatedRect mr = minAreaRect(Mat(contours[i])); if (VerifySize(mr)) //验证是否是所需要尺寸
这是验证尺寸的函数
bool VerifySize(RotatedRect candidate) { float minError = 0.4; //40%的误差范围 float maxError = 0.8; //80%的误差范围 float srcArea = g_src.rows*g_src.cols; float area = candidate.size.height*candidate.size.width; if (area > (srcArea*minError) && area < (srcArea*maxError)) return true; else return false; }
本来我们只需要根据边缘寻找最小外接矩形,验证尺寸是否是我们想要的,就可以了
但是遇到很一个问题,也是我自己作死,用word画图,他会出现以下情况:
你以为只有一个矩形框?那就错了,其实它是类似这样的:
两个矩形框基本重叠在一起,我试着输出了一下矩形四个顶点
会发现这两个矩形四个顶点非常接近,所以我们需要修改算法,剔除掉其中一个,算法思路:
把矩形四个顶点一一对应相减得到差值,算出距离,如果距离小于某个值,说明这四个顶点是非常接近的,
可以算出矩形周长,将周长小的剔除
vector<vector<Point2f>>detector; //用来存放顶点 for (int i = 0;i < contours.size();i++) { Point2f vertices[4]; //用来存放旋转矩形四个顶点 RotatedRect mr = minAreaRect(Mat(contours[i])); if (VerifySize(mr)) //验证是否是所需要尺寸 { mr.points(vertices); for (int k = 0;k < 4;k++) { //line(result, vertices[k], vertices[(k + 1) % 4], Scalar(0, 0, 255), 2, 8, 0); //cout << "第" << k + 1 << "个点坐标为" << vertices[i] << endl; } Point v1 = vertices[1] - vertices[0]; Point v2 = vertices[2] - vertices[0]; double o = v1.x*v2.y - v2.x*v1.y; if (o < 0.0) //逆时针存储四个点 swap(vertices[1], vertices[3]); cout << "vertices[0]" << vertices[0] << endl; cout << "vertices[1]" << vertices[1] << endl; cout << "vertices[2]" << vertices[2] << endl; cout << "vertices[3]" << vertices[3] << endl; vector<Point2f> m; for(int i=0;i<4;i++) { m.push_back(vertices[i]); } //rotateRects.push_back(mr); detector.push_back(m); } }
vector< pair<int, int> > tooNearCandidates; //移除那些角点互相离的太近的四边形 //移除角点太接近的元素 for (int i = 0;i < detector.size();i++) //选择排序法 { for (int j = i + 1;j < detector.size();j++) { float distSquared = 0; for (int c = 0;c < 4;c++) { Point v = detector[i][c] - detector[j][c]; //四个顶点一一对应相减 distSquared += v.dot(v); //向量点乘,求距离 } distSquared /= 4; if (distSquared < 100) // { tooNearCandidates.push_back(pair<int, int>(i, j)); } } } vector<bool> removalMask(detector.size(), false); for (int i = 0;i < tooNearCandidates.size();i++) { float p1 = perimeter(detector[tooNearCandidates[i].first]); float p2 = perimeter(detector[tooNearCandidates[i].second]); //谁周长小 移除谁 int removalIndex; if (p1 > p2) { removalIndex = tooNearCandidates[i].second; } else { removalIndex = tooNearCandidates[i].first; } removalMask[removalIndex] = true; //cout << removalIndex; } markDetector.clear(); for (int i = 0;i < detector.size();i++) { if (!removalMask[i]) { markDetector.push_back(detector[i]); } } cout << markDetector.size() << endl; vector<RotatedRect>rotateRects; for (int i = 0;i < markDetector.size();i++) { RotatedRect r = minAreaRect(Mat(markDetector[i])); Point2f ver[4]; r.points(ver); for (int k = 0;k < 4;k++) { line(g_src, ver[k], ver[(k + 1) % 4], Scalar(0, 0, 255), 2, 8, 0); //cout << "第" << k + 1 << "个点坐标为" << vertices[i] << endl; } rotateRects.push_back(r); }
这是求周长的函数:
float perimeter(const vector<Point2f> &a)//求多边形周长。 { float sum = 0, dx, dy; for (size_t i = 0;i<a.size();i++) { size_t i2 = (i + 1) % a.size(); dx = a[i].x - a[i2].x; dy = a[i].y - a[i2].y; sum += sqrt(dx*dx + dy*dy); } return sum; }
然后就可以看到结果,原来能检测到的两个矩形,只剩下一个了:
接下来就是进行仿射变换:
for (size_t i = 0; i < rotateRects.size(); i++) { Mat dst(g_src.size(),g_src.type()); Mat rotMat(2, 3, CV_32FC1); float r = (float)rotateRects[i].size.width / (float)rotateRects[i].size.height; float angle = rotateRects[i].angle; if (r < 1) angle = angle + 90; //修正角度 rotMat = getRotationMatrix2D(rotateRects[i].center, angle, 1); warpAffine(g_src, dst, rotMat, dst.size()); imshow("最终图像", dst); Mat dst1; getRectSubPix(dst, rotateRects[i].size, rotateRects[i].center, dst1); //裁剪矩形 imshow("最终图像2", dst1); }
然后得到最终结果:
可以看到我们已经将图片修正
接下来是贴出完整代码:
#include<opencv2\opencv.hpp> #include<iostream> using namespace std; using namespace cv; #define NAMEWINDOW1 "二值化" void On_threshold(int, void*); int threshold_value = 180; int threshold_max = 255; Mat g_src; Mat g_img_gray; float perimeter(const vector<Point2f> &a)//求多边形周长。 { float sum = 0, dx, dy; for (size_t i = 0;i<a.size();i++) { size_t i2 = (i + 1) % a.size(); dx = a[i].x - a[i2].x; dy = a[i].y - a[i2].y; sum += sqrt(dx*dx + dy*dy); } return sum; } bool VerifySize(RotatedRect candidate) { float minError = 0.4; //40%的误差范围 float maxError = 0.8; //80%的误差范围 float srcArea = g_src.rows*g_src.cols; float area = candidate.size.height*candidate.size.width; if (area > (srcArea*minError) && area < (srcArea*maxError)) return true; else return false; } int main() { g_src = imread("rotate.png"); if (g_src.empty()) { cout << "无法打开图片"; return -1; } //imshow("原始图片", g_src); cvtColor(g_src, g_img_gray,COLOR_BGR2GRAY); namedWindow(NAMEWINDOW1, WINDOW_AUTOSIZE); createTrackbar("阈值", NAMEWINDOW1, &threshold_value, threshold_max, On_threshold); On_threshold(0, 0); waitKey(0); return 0; } void On_threshold(int, void*) { Mat img_thres; threshold(g_img_gray, img_thres, threshold_value, threshold_max, THRESH_BINARY); vector<Vec4i>hierarchy; vector<vector<Point>>contours; //imshow(NAMEWINDOW1, img_thres); findContours(img_thres, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); Mat result(g_src.size(), CV_8UC3, Scalar::all(255)); vector<vector<Point2f>>markDetector; vector<vector<Point2f>>detector; //用来存放顶点 for (int i = 0;i < contours.size();i++) { Point2f vertices[4]; //用来存放旋转矩形四个顶点 RotatedRect mr = minAreaRect(Mat(contours[i])); if (VerifySize(mr)) //验证是否是所需要尺寸 { mr.points(vertices); for (int k = 0;k < 4;k++) { //line(result, vertices[k], vertices[(k + 1) % 4], Scalar(0, 0, 255), 2, 8, 0); //cout << "第" << k + 1 << "个点坐标为" << vertices[i] << endl; } Point v1 = vertices[1] - vertices[0]; Point v2 = vertices[2] - vertices[0]; double o = v1.x*v2.y - v2.x*v1.y; if (o < 0.0) //逆时针存储四个点 swap(vertices[1], vertices[3]); /*cout << "vertices[0]" << vertices[0] << endl; cout << "vertices[1]" << vertices[1] << endl; cout << "vertices[2]" << vertices[2] << endl; cout << "vertices[3]" << vertices[3] << endl;*/ vector<Point2f> m; for(int i=0;i<4;i++) { m.push_back(vertices[i]); } //rotateRects.push_back(mr); detector.push_back(m); } } /*cout << rotateRects.size()<<endl; cout << detector.size();*/ //移除那些角点互相离的太近的四边形 //移除角点太接近的元素 vector< pair<int, int> > tooNearCandidates; for (int i = 0;i < detector.size();i++) { for (int j = i + 1;j < detector.size();j++) { float distSquared = 0; for (int c = 0;c < 4;c++) { Point v = detector[i][c] - detector[j][c]; distSquared += v.dot(v); } distSquared /= 4; if (distSquared < 100) { tooNearCandidates.push_back(pair<int, int>(i, j)); } } } vector<bool> removalMask(detector.size(), false); for (int i = 0;i < tooNearCandidates.size();i++) { float p1 = perimeter(detector[tooNearCandidates[i].first]); float p2 = perimeter(detector[tooNearCandidates[i].second]); //谁周长小 移除谁 int removalIndex; if (p1 > p2) { removalIndex = tooNearCandidates[i].second; } else { removalIndex = tooNearCandidates[i].first; } removalMask[removalIndex] = true; //cout << removalIndex; } markDetector.clear(); for (int i = 0;i < detector.size();i++) { if (!removalMask[i]) { markDetector.push_back(detector[i]); } } cout << "检测到的矩形个数为: "<< markDetector.size() << endl; vector<RotatedRect>rotateRects; for (int i = 0;i < markDetector.size();i++) { RotatedRect r = minAreaRect(Mat(markDetector[i])); Point2f ver[4]; r.points(ver); for (int k = 0;k < 4;k++) { line(g_src, ver[k], ver[(k + 1) % 4], Scalar(0, 0, 255), 2, 8, 0); cout << "第" << k + 1 << "个点坐标为" << ver[k] << endl; } rotateRects.push_back(r); } for (size_t i = 0; i < rotateRects.size(); i++) { Mat dst(g_src.size(),g_src.type()); Mat rotMat(2, 3, CV_32FC1); float r = (float)rotateRects[i].size.width / (float)rotateRects[i].size.height; float angle = rotateRects[i].angle; if (r < 1) angle = angle + 90; //修正角度 rotMat = getRotationMatrix2D(rotateRects[i].center, angle, 1); warpAffine(g_src, dst, rotMat, dst.size()); imshow("最终图像", dst); Mat dst1; getRectSubPix(dst, rotateRects[i].size, rotateRects[i].center, dst1); //裁剪矩形 imshow("最终图像2", dst1); } imshow(NAMEWINDOW1, g_src); }