目录
该博客整编于:https://www.pyimagesearch.com/
1.问题描述
现在,我们需要识别一张简易的答题卡,如图1-1所示。
图1-1 简易答题卡
最终的识别结果如图1-2所示。其中,选对的答案用绿色表示,错选的用红色表示。
那么在答题卡识别的问题中有哪些待续解决的问题呢?我的理解是这样的:
1.答题卡区域的分割问题:想要进行答题卡识别总得先把答题卡区域和环境区域分割出来吧。
2.答题卡纸张背景和答案的分离问题:我们需要的只有答案的区域,因此需要解决答案和答题卡背景的分割问题。
3.轮廓的筛选问题:筛选出我们想要的轮廓,排除那些不需要的轮廓信息。
4.轮廓的排序和定位问题:如何对轮廓进行行和列的定位,这很重要。
5.检测答题者所选择的选项:检测漏选、多选的情况。
2.解决思路
2.1 答题卡区域的分割问题
在这里,由于环境色的一致性,我们使用了canny边缘检测算子,检测出答题卡的边界信息。
分割代码如下:
Mat answerSheet = imread("answerSheet.png");
//灰度转化
Mat gray;
cvtColor(answerSheet,gray,CV_BGR2GRAY);
//进行高斯滤波
Mat blurred;
GaussianBlur(gray,blurred,Size(3,3),0);
//进行canny边缘检测
Mat canny;
Canny(blurred,canny,75,200);
计算的图像如图2-1:
图 2-1 canny算子计算图
2.2 分割答题卡的纸张和答题区域
首先,我们要找到答题卡轮廓区域的边界,利用DP算法计算出轮廓的角点,最后基于透视变化对图像进行矫正,即转化为鸟瞰图。实现的代码如下:
//排序算子
bool sortBy_x( Point &a, Point &b)
{
return a.x < b.x;
}
bool sortBy_y( Point &a, Point &b)
{
return a.y < b.y;
}
//寻找矩形边界
vector<vector<Point>> contours;
findContours(canny, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<Point>result_contour;
if (contours.size() == 1)
{
result_contour = contours[0];
}
else
{
int max = -1;
int index = -1;
for (int i = 0; i < contours.size(); i++)
{
int tem = arcLength(contours[i], true);
if (tem > max) max = tem;
index = i;
}
result_contour = contours[index];
}
//使用DP算法拟合答题卡的几何轮廓,保存点集pts并顺时针排序
vector<Point> pts;
approxPolyDP(result_contour,pts,(int)arcLength(result_contour,true)*0.02,true);
if (pts.size() != 4) return 1;
sort(pts.begin(), pts.end(), sortBy_x);
sort(pts.begin(), pts.end(), sortBy_y);
//进行透视变换
//1.确定变化尺寸的宽度
int width;
int width1 = (pts[0].x - pts[1].x)*(pts[0].x - pts[1].x) + (pts[0].y - pts[1].y)*(pts[0].y - pts[1].y);
int width2= (pts[2].x - pts[3].x)*(pts[2].x - pts[3].x) + (pts[2].y - pts[3].y)*(pts[2].y - pts[3].y);
if (width1 > width2) width = sqrt(width1);
else width = sqrt(width2);
//2.确定变化尺寸的高度
int height;
int height1 = (pts[0].x - pts[3].x)*(pts[0].x - pts[3].x) + (pts[0].y - pts[3].y)*(pts[0].y - pts[3].y);
int height2 = (pts[2].x - pts[1].x)*(pts[2].x - pts[1].x) + (pts[2].y - pts[1].y)*(pts[2].y - pts[1].y);
if (height1 > height2) height= sqrt(height1);
else height = sqrt(height2);
//3.计算透视变换矩阵
vector<Point2f> Pts(4);
Pts[0]=(Point2f(0,0));
Pts[1]=(Point2f(width-1, 0));
Pts[2]=(Point2f(width-1, height-1));
Pts[3]=(Point2f(0, height-1));
//4.计算透视变换矩阵
//4.1类型转化
Mat src = Mat(pts);
vector<Point2f> Pt;
src.convertTo(src,CV_32F)