1.问题描述:
模拟考试答题卡的识别:如下图所示的一张答题卡,需要自动识别并标记出考生选择的选项,以及标记出正确答案。考生选择的选项用蓝色标记,标准答案用绿色标记。
??????????????????????????????????????????????????????????????????????????????????????????????????????????
2.解决思路:
2.1 首先打开相机拍摄一张答题卡
//用相机拍一张答题卡
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "open failed !";
}
Mat frame, gray;
cap.read(frame);
cvtColor(frame, gray, COLOR_BGR2GRAY);
imwrite("C:\\Users\\86176\\Downloads\\visionimage\\testdetect.jpg", gray);
imshow("frame", frame);
waitKey(0);
2.2对拍摄的答题卡图像进行预处理
颜色空间转换--------直方图均衡进行图像增强------高斯模糊去噪------canny边缘检测
Mat img = imread("C:\\Users\\86176\\Downloads\\visionimage\\testdetect.jpg");
Mat binary, gry, equalhist;
cvtColor(img, gry, COLOR_BGR2GRAY);
equalizeHist(gry, equalhist);
//imshow("eq", equalhist);
GaussianBlur(equalhist, equalhist, Size(3, 3),10,20);
Canny(equalhist, binary, 50, 300);
imshow("can", binary);
2.3进行轮廓检测,筛选出答题卡纸张的区域,便于接下来的透视变换
vector<vector<Point>> bigcontour;
vector<double>areas;
double max = 0;
int index = -1;
findContours(binary, bigcontour, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
cout << bigcontour.size() << endl;
for (int i = 0; i < bigcontour.size(); ++i)
{
double area = contourArea(bigcontour[i]);
if (area > max)
{
max = area;
index = i;
}
areas.push_back(area);
//drawContours(img, bigcontour, i, Scalar( 0,0,200), 2);
}
cout << max << endl;
cout << index << endl;
?筛选出最大的轮廓后,对该轮廓进行多边形逼近;从而得到透视变换前的四个顶点坐标;再计算出透视变化矩形的宽高,最后进行透视变换,将答题卡区域提取出来:
Mat result;
Rect rec=boundingRect(bigcontour[index]);
vector<Point2f>app;
approxPolyDP(bigcontour[index], app, 10, true);
//cout << app << endl;
int curwidth = sqrt(
pow((app[0].x - app[3].x), 2) + pow((app[0].y - app[3].y), 2)
);
int curheight = sqrt(
pow((app[0].x - app[1].x), 2) + pow((app[0].y - app[1].y), 2)
);
vector<Point2f>currs;
currs.emplace_back(Point(55, 30));
currs.emplace_back(Point(55, 30+curheight));
currs.emplace_back(Point(55+curwidth, 30+curheight));
currs.emplace_back(Point(55 + curwidth, 30));
Mat pers = getPerspectiveTransform(app, currs);
Mat perspect;
warpPerspective(img, perspect, pers, img.size());
Mat out;
Rect roi = Rect(55, 30, curwidth, curheight);
perspect(roi).copyTo(out);
?2.4采用OTSU阈值分割的方法将答题卡的图形信息找出来
Mat otsu, outgray, outdial;
cvtColor(out, outgray,COLOR_BGR2GRAY);
threshold(outgray, otsu, 100, 220, THRESH_OTSU|THRESH_BINARY_INV);
imshow("otsu", otsu);
//对图像进行膨胀
Mat mod = getStructuringElement(MorphShapes::MORPH_RECT, Size(5, 5));
morphologyEx(otsu, outdial, MORPH_DILATE, mod);
?膨胀后的图形区域:
?2.5再进行轮廓筛选,筛选出答题区域:
//再进行轮廓检测
vector<vector<Point>>contoursagin, selectcontours;
findContours(outdial, contoursagin, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (auto m : contoursagin)
{
Rect bound = boundingRect(m);
if ((bound.height < 45 && bound.height>20) && (bound.width < 45 && bound.width>20))
{
selectcontours.push_back(m);
}
}
vector<Point2f>circlecenters;
vector<Point2f>line1center;
vector<Point2f >line2center;
vector<Point2f>line3center;
vector<Point2f>line4center;
vector<Point2f>line5center;
vector<vector<Point>>line1contour;
vector<vector<Point>>line2contour;
vector<vector<Point>>line3contour;
vector<vector<Point>>line4contour;
vector<vector<Point>>line5contour;
vector<float>circiers;
for (int n=0;n<selectcontours.size();++n)
{
//画出所有选项的轮廓
drawContours(out, selectcontours, n, Scalar(200, 0, 0), 2);
//确定每个轮廓外接圆圆心
Point2f center;
float circler;
minEnclosingCircle(selectcontours[n], center, circler);
circiers.push_back(circler);
if (center.y < 85)
{
line1center.push_back(center);
line1contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(200, 0, 0), 2);
}
else if (center.y < 145)
{
line2center.push_back(center);
line2contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(0, 200, 0), 2);
}
else if (center.y < 205)
{
line3center.push_back(center);
line3contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar( 0, 0,200), 2);
}
else if (center.y < 265)
{
line4center.push_back(center);
line4contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(20, 0, 50), 2);
}
else if (center.y < 325)
{
line5center.push_back(center);
line5contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(200, 200, 0), 2);
}
?????????????????????????????????????????????
?2.6根据轮廓外接圆圆心的x,y坐标对轮廓进行排序:
//对每一行排序
sort(line1center.begin(), line1center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line2center.begin(), line2center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line3center.begin(), line3center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line4center.begin(), line4center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line5center.begin(), line5center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
vector<Point2f>sortedcenter;
sortedcenter.insert(sortedcenter.end(), line1center.begin(), line1center.end());
sortedcenter.insert(sortedcenter.end(), line2center.begin(), line2center.end());
sortedcenter.insert(sortedcenter.end(), line3center.begin(), line3center.end());
sortedcenter.insert(sortedcenter.end(), line4center.begin(), line4center.end());
sortedcenter.insert(sortedcenter.end(), line5center.begin(), line5center.end());
float meanrr=0;
float tempr=0;
for (int k=0;k<circiers.size();++k)
{
tempr += circiers[k];
meanrr = tempr / circiers.size();
}
2.7已知答题卡选项的标准答案是A,DE,BE,C,D;用绿色标记标准答案:
//答题卡答案A,DE,BE,C,D
int answer[5][5] = {
1,0,0,0,0,
0,0,0,1,1,
0,1,0,0,1,
0,0,1,0,0,
0,0,0,1,0
};
//用绿色标记处标准答案
Mat resultout, selectout,studentsel;
perspect(roi).copyTo(resultout);
perspect(roi).copyTo(selectout);
perspect(roi).copyTo( studentsel);
for (int ii = 0; ii < 5; ++ii)
{
for (int jj = 0; jj < 5; ++jj)
{
if (answer[ii][jj] == 1)
{
Point2f temp = sortedcenter[ii * 5 + jj];
circle(resultout, temp, meanrr, Scalar(0, 200, 0), 2);
}
}
}
?
?2.8最后标记考生答题的选项:
//用蓝色标记考生答案
Mat erodM = getStructuringElement(0, Size(17,17));
erode(outdial, selectout, erodM);
vector<vector<Point>>contours;
vector<Point2f>tempcenters;
findContours(selectout, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int t = 0; t < contours.size(); ++t)
{
Point2f tempcenter;
float tempr;
minEnclosingCircle(contours[t], tempcenter, tempr);
circle(studentsel, tempcenter, meanrr, Scalar(200, 0, 0),2);
}
//cout << contours.size() << endl;
//cout << selectcontours.size();
imshow("outdial", outdial);
imshow("out", out);
imshow("res", resultout);
imshow("studentsel" , studentsel);
//imshow("img", img);
//imshow("gry", gry);
waitKey(0);
}
????????????????????????
3.全部代码实现:
void VisionTest:: testdetect()
{
//用相机拍一张答题卡
/*
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "open failed !";
}
Mat frame, gray;
cap.read(frame);
cvtColor(frame, gray, COLOR_BGR2GRAY);
imwrite("C:\\Users\\86176\\Downloads\\visionimage\\testdetect.jpg", gray);
imshow("frame", frame);
waitKey(0);*/
Mat img = imread("C:\\Users\\86176\\Downloads\\visionimage\\testdetect.jpg");
Mat binary, gry, equalhist;
cvtColor(img, gry, COLOR_BGR2GRAY);
equalizeHist(gry, equalhist);
//imshow("eq", equalhist);
GaussianBlur(equalhist, equalhist, Size(3, 3),10,20);
Canny(equalhist, binary, 50, 300);
imshow("can", binary);
vector<vector<Point>> bigcontour;
vector<double>areas;
double max = 0;
int index = -1;
findContours(binary, bigcontour, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
cout << bigcontour.size() << endl;
for (int i = 0; i < bigcontour.size(); ++i)
{
double area = contourArea(bigcontour[i]);
if (area > max)
{
max = area;
index = i;
}
areas.push_back(area);
//drawContours(img, bigcontour, i, Scalar( 0,0,200), 2);
}
cout << max << endl;
cout << index << endl;
Mat result;
Rect rec=boundingRect(bigcontour[index]);
vector<Point2f>app;
approxPolyDP(bigcontour[index], app, 10, true);
//cout << app << endl;
int curwidth = sqrt(
pow((app[0].x - app[3].x), 2) + pow((app[0].y - app[3].y), 2)
);
int curheight = sqrt(
pow((app[0].x - app[1].x), 2) + pow((app[0].y - app[1].y), 2)
);
vector<Point2f>currs;
currs.emplace_back(Point(55, 30));
currs.emplace_back(Point(55, 30+curheight));
currs.emplace_back(Point(55+curwidth, 30+curheight));
currs.emplace_back(Point(55 + curwidth, 30));
Mat pers = getPerspectiveTransform(app, currs);
Mat perspect;
warpPerspective(img, perspect, pers, img.size());
Mat out;
Rect roi = Rect(55, 30, curwidth, curheight);
perspect(roi).copyTo(out);
imshow("roi", perspect(roi));
Mat otsu, outgray, outdial;
cvtColor(out, outgray,COLOR_BGR2GRAY);
threshold(outgray, otsu, 100, 220, THRESH_OTSU|THRESH_BINARY_INV);
imshow("otsu", otsu);
//对图像进行膨胀
Mat mod = getStructuringElement(MorphShapes::MORPH_RECT, Size(5, 5));
morphologyEx(otsu, outdial, MORPH_DILATE, mod);
//再进行轮廓检测
vector<vector<Point>>contoursagin, selectcontours;
findContours(outdial, contoursagin, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (auto m : contoursagin)
{
Rect bound = boundingRect(m);
if ((bound.height < 45 && bound.height>20) && (bound.width < 45 && bound.width>20))
{
selectcontours.push_back(m);
}
}
vector<Point2f>circlecenters;
vector<Point2f>line1center;
vector<Point2f >line2center;
vector<Point2f>line3center;
vector<Point2f>line4center;
vector<Point2f>line5center;
vector<vector<Point>>line1contour;
vector<vector<Point>>line2contour;
vector<vector<Point>>line3contour;
vector<vector<Point>>line4contour;
vector<vector<Point>>line5contour;
vector<float>circiers;
for (int n=0;n<selectcontours.size();++n)
{
//画出所有选项的轮廓
drawContours(out, selectcontours, n, Scalar(200, 0, 0), 2);
//确定每个轮廓外接圆圆心
Point2f center;
float circler;
minEnclosingCircle(selectcontours[n], center, circler);
circiers.push_back(circler);
if (center.y < 85)
{
line1center.push_back(center);
line1contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(200, 0, 0), 2);
}
else if (center.y < 145)
{
line2center.push_back(center);
line2contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(0, 200, 0), 2);
}
else if (center.y < 205)
{
line3center.push_back(center);
line3contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar( 0, 0,200), 2);
}
else if (center.y < 265)
{
line4center.push_back(center);
line4contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(20, 0, 50), 2);
}
else if (center.y < 325)
{
line5center.push_back(center);
line5contour.push_back(selectcontours[n]);
drawContours(out, selectcontours, n, Scalar(200, 200, 0), 2);
}
}
//对每一行排序
sort(line1center.begin(), line1center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line2center.begin(), line2center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line3center.begin(), line3center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line4center.begin(), line4center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
sort(line5center.begin(), line5center.end(), [](const Point2f& center1, const Point2f& center2)
{
return (center1.x < center2.x);
});
vector<Point2f>sortedcenter;
sortedcenter.insert(sortedcenter.end(), line1center.begin(), line1center.end());
sortedcenter.insert(sortedcenter.end(), line2center.begin(), line2center.end());
sortedcenter.insert(sortedcenter.end(), line3center.begin(), line3center.end());
sortedcenter.insert(sortedcenter.end(), line4center.begin(), line4center.end());
sortedcenter.insert(sortedcenter.end(), line5center.begin(), line5center.end());
float meanrr=0;
float tempr=0;
for (int k=0;k<circiers.size();++k)
{
tempr += circiers[k];
meanrr = tempr / circiers.size();
}
//答题卡答案A,DE,BE,C,D
int answer[5][5] = {
1,0,0,0,0,
0,0,0,1,1,
0,1,0,0,1,
0,0,1,0,0,
0,0,0,1,0
};
//用绿色标记处标准答案
Mat resultout, selectout,studentsel;
perspect(roi).copyTo(resultout);
perspect(roi).copyTo(selectout);
perspect(roi).copyTo( studentsel);
for (int ii = 0; ii < 5; ++ii)
{
for (int jj = 0; jj < 5; ++jj)
{
if (answer[ii][jj] == 1)
{
Point2f temp = sortedcenter[ii * 5 + jj];
circle(resultout, temp, meanrr, Scalar(0, 200, 0), 2);
}
}
}
//用蓝色标记考生答案
Mat erodM = getStructuringElement(0, Size(17,17));
erode(outdial, selectout, erodM);
vector<vector<Point>>contours;
vector<Point2f>tempcenters;
findContours(selectout, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int t = 0; t < contours.size(); ++t)
{
Point2f tempcenter;
float tempr;
minEnclosingCircle(contours[t], tempcenter, tempr);
circle(studentsel, tempcenter, meanrr, Scalar(200, 0, 0),2);
}
cout << contours.size() << endl;
imshow("outdial", outdial);
imshow("out", out);
imshow("res", resultout);
imshow("studentsel" , studentsel);
waitKey(0);
}
|