基于opencv答题卡识别

1.问题描述:

模拟考试答题卡的识别:如下图所示的一张答题卡,需要自动识别并标记出考生选择的选项,以及标记出正确答案。考生选择的选项用蓝色标记,标准答案用绿色标记。

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        基于opencv答题卡识别_第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);

基于opencv答题卡识别_第2张图片

2.3进行轮廓检测,筛选出答题卡纸张的区域,便于接下来的透视变换

vector> bigcontour;
	vectorareas;
	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]);
	vectorapp;
	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)
	);

	vectorcurrs;
	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);

基于opencv答题卡识别_第3张图片

 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);

基于opencv答题卡识别_第4张图片

 膨胀后的图形区域:

基于opencv答题卡识别_第5张图片

 2.5再进行轮廓筛选,筛选出答题区域:

//再进行轮廓检测
	vector>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);
			
			
		}
	}
	vectorcirclecenters;
	vectorline1center;
	vectorline2center;
	vectorline3center;
	vectorline4center;
	vectorline5center;

	vector>line1contour;
	vector>line2contour;
	vector>line3contour;
	vector>line4contour;
	vector>line5contour;


	vectorcirciers;


	for (int n=0;n

        ​​​​​​​        ​​​​​​​        ​​​​​​​基于opencv答题卡识别_第6张图片

 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);
		});
	
	vectorsortedcenter;
	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

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);
			}
		}
	}

 基于opencv答题卡识别_第7张图片

 2.8最后标记考生答题的选项:

//用蓝色标记考生答案
	Mat erodM = getStructuringElement(0, Size(17,17));
	erode(outdial, selectout, erodM);
	
	vector>contours;
	vectortempcenters;

	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); 
}

                        基于opencv答题卡识别_第8张图片

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> bigcontour;
	vectorareas;
	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]);
	vectorapp;
	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)
	);

	vectorcurrs;
	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>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);
			
			
		}
	}
	vectorcirclecenters;
	vectorline1center;
	vectorline2center;
	vectorline3center;
	vectorline4center;
	vectorline5center;

	vector>line1contour;
	vector>line2contour;
	vector>line3contour;
	vector>line4contour;
	vector>line5contour;

	vectorcirciers;

	for (int n=0;nsortedcenter;
	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>contours;
	vectortempcenters;
	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); 
}

你可能感兴趣的:(opencv个人练习小项目,opencv,计算机视觉)