今天来说一下怎么样将这个旋转的文本框矫正,大体思路:
二值化分割+边缘检测+最大外接矩形+透视变换 => 最终图像
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);
能很明显的看到矩形轮廓,接下来进行轮廓查找
vectorhierarchy;
vector>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>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 m;
for(int i=0;i<4;i++)
{
m.push_back(vertices[i]);
}
//rotateRects.push_back(mr);
detector.push_back(m);
}
}
vector< pair > 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(i, j));
}
}
}
vector 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;
vectorrotateRects;
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 &a)//求多边形周长。
{
float sum = 0, dx, dy;
for (size_t i = 0;i
然后就可以看到结果,原来能检测到的两个矩形,只剩下一个了:
接下来就是进行仿射变换:
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
#include
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 &a)//求多边形周长。
{
float sum = 0, dx, dy;
for (size_t i = 0;i (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);
vectorhierarchy;
vector>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>markDetector;
vector>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 m;
for(int i=0;i<4;i++)
{
m.push_back(vertices[i]);
}
//rotateRects.push_back(mr);
detector.push_back(m);
}
}
/*cout << rotateRects.size()< > 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(i, j));
}
}
}
vector 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;
vectorrotateRects;
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);
}