轮廓区域定位的应用范围:
-
二维码识别
-
答题卡识别
-
自定义辅助OCR识别
我在网上随便找的一个二维码生成器生成的二维码:
目标:通过轮廓的方法 定位 出二维码的位置:
测试的其他图 定位原图与结果:
程序:
-
读取图片调整适当大小
Mat src = imread("pic\\test.jpg", IMREAD_GRAYSCALE); Mat Frame = imread("pic\\test.jpg"); resize(src, src, Size(src.cols / 2, src.rows / 2)); resize(Frame, Frame, src.size()); imshow("原图", Frame);
-
定义变量
float number[4]; Point2f overcenter[4]; vector<vector<Point>> contours, contours2; vector<Vec4i> hierarchy; //4元组合 Mat draw, draw1,roi; int c = 0, ic = 0, area = 0; int parentIdx = -1; float temp, temp1; vector<vector<Point>> newcontours; int flag = 0; float templine1, templine2, templine3; Point2f start, overtemp; float weight, height; int numberx, numbery;
-
寻找轮廓
threshold(src, src, 20, 255, THRESH_OTSU); //寻找轮廓 //CV_RETR_TREE //检出所有轮廓并且重新建立网状的轮廓结构 //CV_CHAIN_APPROX_NONE 将链码编码中的所有点转换为点 findContours(src, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0)); //创建绘图图像(可省略) draw = Mat(src.rows, src.cols, CV_8UC3, Scalar(0)); draw1 = Mat(src.rows, src.cols, CV_8UC3, Scalar(0)); //画出轮廓 drawContours(draw, contours, 1, Scalar(255, 0, 0), -1);
-
通过四元组找到定位点的轮廓
//轮廓筛选 for (int i = 0; i < contours.size(); i++) { //hierarchy[i][2] != -1 表示不是最外面的轮廓 if (hierarchy[i][2] != -1 && ic == 0) { parentIdx = i; ic++; } else if (hierarchy[i][2] != -1) { ic++; } //最外面的清 0 else if (hierarchy[i][2] == -1) { ic = 0; parentIdx = -1; } //找到定位点信息 if (ic >= 2) { contours2.push_back(contours[parentIdx]); ic = 0; parentIdx = -1; } } //画出定位点信息 drawContours(draw1, contours2, -1, Scalar(255, 255, 255), -1);
-
从图片上可以看出二维码定位点的特性,三个正方形比例一致,基本在一条直线上。
for (int i = 0; i < contours2.size(); i++) { Point2f tempcenter; //通过特性分析,得到该轮廓的分析值(也可以自己找方法去确定) number[i] = calculateCircularity(contours2[i], tempcenter); //第一次进循环 if (i == 0) { temp = number[i]; newcontours.push_back(contours2[i]); overcenter[flag] = tempcenter; flag++; } //正常情况下应该是number[i]>temp 就continue //因为二维码的定位点是一样的所以通过特性分析出来的数值应该是一样的 //这里是因为我测试了一些经过仿射变换后有一些影响的图片后更改了一个比较合适的数值 else if ((number[i] - temp)>0.3) continue; else { newcontours.push_back(contours2[i]); overcenter[flag] = tempcenter; flag++; } }
-
判断是否找到二维码,但是由于我上面有更改但下面代码没有跟着改,所以输出的有一些问题 把等于改成一个范围就可以了
//这边是返回两点之间的距离 通过判定是否相等来确定找到的轮廓是否为二维码 templine1 = getDistance(overcenter[0], overcenter[1]); templine2 = getDistance(overcenter[0], overcenter[2]); templine3 = getDistance(overcenter[1], overcenter[2]); if (templine1 == templine2 || templine1 == templine3 || templine2 == templine3) cout << "矩形圆心中点两条线相等,找到二维码位置" << endl; else cout << "矩形圆心中点两条线不相等,未找到二维码位置" << endl;
-
得到多个点后确定ROI区域的范围
//通过排序比较的方法得到Rect中的起始点和宽高 for (int i = 0; i < 4; i++) { if ((overcenter[i].x == 0) && (overcenter[i].y == 0)) continue; overtemp = overcenter[i]; for (int j = i+1; j < 4-i; j++) { if ((overcenter[j].x == 0) && (overcenter[j].y == 0)) continue; int absflagx = abs(overtemp.x - overcenter[j].x); int absflagy = abs(overtemp.y - overcenter[j].y); int flagx = overtemp.x - overcenter[j].x; int flagy = overtemp.y - overcenter[j].y; if(absflagx <= 0.3) start.x = overcenter[j].x; if (absflagy <= 0.3) start.y = overcenter[j].y; if (flagx >= 10) weight = overcenter[i].x; if (flagy >= 10) height = overcenter[i].y; } } //按比例放大一些roi区域 numberx = (weight - start.x) / 6; numbery = (height - start.y) / 6; //得到roi roi = Frame(Rect(Point(start.x - numberx,start.y- numbery), Size(weight-start.x+2*numberx,height-start.y+2*numbery)));
-
(选择)画线与输出参数
#if 0 drawContours(Frame, newcontours, -1, Scalar(0, 0, 255), -1); line(Frame, overcenter[0], overcenter[1], Scalar(0, 255, 0), 2); line(Frame, overcenter[1], overcenter[2], Scalar(0, 255, 0), 2); line(Frame, overcenter[0], overcenter[2], Scalar(0, 255, 0), 2); printf("Point[0] (%f,%lf)\n", overcenter[0].x, overcenter[0].y); printf("Point[1] (%f,%lf)\n", overcenter[1].x, overcenter[1].y); printf("Point[2] (%f,%lf)\n", overcenter[2].x, overcenter[2].y); printf("Point[3] (%f,%lf)\n", overcenter[3].x, overcenter[3].y); #endif
以上就是二维码的定位
但是不是每次遇到的定位都是正的,所以我们可以之前再加一个仿射变换。
抱着学习的态度,我在网上看到一篇也测试过没什么问题,也很值得学习。
博客地址:https://www.cnblogs.com/skyfsm/p/6902524.html
就是以下这两个函数可以帮助我们二维码矫正,我略微有一些更改。
//适用于图像与背景颜色对比度比较高的
Mat CorrectPicture(Mat Frame)
{
Mat Gray, ThresholdImg;
vector<vector<Point>> contours;
cvtColor(Frame, Gray, COLOR_RGB2GRAY);
threshold(Gray, ThresholdImg,100,200, CV_THRESH_BINARY);
//注意第5个参数为CV_RETR_EXTERNAL,只检索外框
findContours(ThresholdImg, contours , CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
for (int i = 0; i < contours.size(); i++)
{
//需要获取的坐标
CvPoint2D32f rectpoint[4];
CvBox2D rect = minAreaRect(Mat(contours[i]));
//获取4个顶点坐标 与水平线的角度
cvBoxPoints(rect, rectpoint);
float angle = rect.angle;
int line1 = sqrt((rectpoint[1].y - rectpoint[0].y)*(rectpoint[1].y - rectpoint[0].y) + (rectpoint[1].x - rectpoint[0].x)*(rectpoint[1].x - rectpoint[0].x));
int line2 = sqrt((rectpoint[3].y - rectpoint[0].y)*(rectpoint[3].y - rectpoint[0].y) + (rectpoint[3].x - rectpoint[0].x)*(rectpoint[3].x - rectpoint[0].x));
//面积较小
if (line1 * line2 < 600)
continue;
//让正方形横着,所以旋转角度不一样。
//竖着加90度,翻过来
if (line1 > line2)
angle = 90 + angle;
//新建一个感兴趣的区域图,大小跟原图一样大
//注意这里必须选CV_8UC3
Mat RoiSrcImg(Frame.rows, Frame.cols, CV_8UC3);
//颜色都设置为黑色
RoiSrcImg.setTo(0);
//对得到的轮廓填充一下
drawContours(ThresholdImg, contours, -1, Scalar(255), CV_FILLED);
Frame.copyTo(RoiSrcImg, ThresholdImg);
//创建一个旋转后的图像
Mat RatationedImg(RoiSrcImg.rows, RoiSrcImg.cols, CV_8UC1);
RatationedImg.setTo(0);
//对RoiSrcImg进行旋转
Point2f center = rect.center; //中心点
//计算旋转加缩放的变换矩阵
Mat M2 = getRotationMatrix2D(center, angle, 1);
//仿射变换
warpAffine(RoiSrcImg, RatationedImg, M2, RoiSrcImg.size(), 1, 0, Scalar(0));
//返回矫正的图
return RatationedImg;
}
}
可以再对返回的图像进行一个roi区域的选择
//对旋转后的图片进行轮廓提取
vector<vector<Point> > contours2;
Mat raw = Frame.copy();
Mat SecondFindImg;
cvtColor(raw, SecondFindImg, COLOR_BGR2GRAY); //灰度化
threshold(SecondFindImg, SecondFindImg, 80, 200, CV_THRESH_BINARY);
findContours(SecondFindImg, contours2, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
for (int j = 0; j < contours2.size(); j++)
{
//这时候其实就是一个长方形了,所以获取rect
Rect rect = boundingRect(Mat(contours2[j]));
//面积太小的轮廓直接pass,通过设置过滤面积大小,可以保证只拿到外框
if (rect.area() < 600)
{
continue;
}
Mat dstImg = raw(rect);
imshow("dst", dstImg);
imwrite(pDstFileName, dstImg);
}
定位出二维码接下来就是识别了
我是安装了zbar库 在网上找了个demo识别的,很方便。
其实zbar库是自带二维码定位的,并不需要我们前期做这些准备工作
但是我感觉这个项目可以运用在OCR识别上,矫正被识别对象,提高识别精度,原理与二维码定位是一致的。
或者可以运用在一些工业识别项目上都是非常好的。
以上均为学习笔记与自己心得。