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

基于opencv答题卡识别相关推荐

  1. 图像识别——(java使用opencv答题卡识别)

    具体算法实现:具体算法实现--opencvhttps://blog.csdn.net/qq_39246466/article/details/123819795 1.安装: 配置linux或windo ...

  2. 基于MATLAB答题卡识别(GUI,论文)

    本课题为基于MATLAB的不变矩答题卡识别.可识别学号,学科,答案,并进行分数统计,以及判断是否及格.后台可设置标准excel答案.经过灰度变换形成灰度图像.二值化处理.图像滤波.边缘检测.hough ...

  3. opencv答题卡识别 (一)

    背景:答题卡阅卷需要光标阅读机,有些小学校买不起光标阅读机. 主要开源库:opencv,版本3.0. 识别原理:把答题卡放在深色背景中,用查找轮廓定位好答题卡位置,用透视变换取出答题卡图像,根据位置判 ...

  4. OpenCV 答题卡识别

    1.预处理.轮廓检测 import cv2 import numpy as np # 正确答案 ANSWER_KEY = {0:1,1:4,2:0,3:3,4:1} def cv_show(name, ...

  5. python OpenCV 答题卡识别判卷

    完整代码: #导入工具包 import numpy as np import argparse import imutils import cv2# 设置参数 ap = argparse.Argume ...

  6. 基于 SpringMvc + OpenCV 实现的答题卡识别系统(附源码)

    点击关注公众号,实用技术文章及时了解 java_opencv 项目介绍 OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,它提供了一系列图像处理和计算机视觉方面很多通用算法.是研究图像 ...

  7. 基于 SpringMvc+OpenCV 实现的答题卡识别系统(附源码)

    java_opencv 项目介绍 OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,它提供了一系列图像处理和计算机视觉方面很多通用算法.是研究图像处理技术的一个很不错的工具.最初开始接 ...

  8. 基于Android和OpenCV的答题卡识别软件

    基于Android和OpenCV的答题卡识别软件 1. 软件介绍 设计目标是可以添加不同的考试,在不同考试下可以设置模板,包括题目数量.答题卡样式.每题分值以及每题答案:扫描结果按列表显示,并讲识别出 ...

  9. 基于 Java 的答题卡识别系统

    这张答题卡想必伴随了大家的学生时代,不管是中考.高考.考研,都会用 2B 铅笔涂好卡上的红框框. 今天推荐一个基于 Spring MVC + OpenCV 的答题卡识别系统,OpenCV 是一个计算机 ...

  10. OpenCV C++案例实战五《答题卡识别》

    OpenCV C++案例实战五<答题卡识别> 前言 一.图像矫正 1.源码 二.获取选项区域 1.扣出每题选项 2.源码 三.获取答案 1.思路 2.辅助函数 3.源码 4.效果 总结 前 ...

最新文章

  1. Python的零基础超详细讲解(第五天)-Python的运算符
  2. 谁在引领中国制造?中国智能制造发展解析
  3. BGP建立邻居的详细过程
  4. 尚硅谷k8s安装文档_Kubernetes(k8s)中文文档 从零开始k8s_Kubernetes中文社区
  5. Integer和int的区别
  6. Oracle性能调优
  7. 开源GIS软件初探(转载)
  8. Hyper-V 3.0 - 关于存储迁移的一些说明
  9. python爬取快手视频_【原创开源】快手爬虫,根据id批量爬取用户的所有图集和视频...
  10. MATLAB——DEMATEL代码(转载)
  11. 猜姓氏c语言题目,猜姓氏的谜语及答案
  12. C语言arduino密码锁实验报告,Arduino密码锁设计
  13. 山东省第七届ACM大赛E题题解
  14. poj3259 Wormholes floyd求负环
  15. 10大程序员必逛网站,良心推荐,建议收藏!
  16. 美杂志公布全球最重要六大科学实验(组图)
  17. HTML5通过js调用手机摄像头
  18. fatal error LNK1169: one or more multiply defined symbols found解决方法
  19. Matlab LCL滤波器设计验证
  20. 2022年武汉东湖高新区高新技术和专精特新企业跨境融资需求征集申报条件及时间

热门文章

  1. 深圳神州行今日起单向收费 零月租成套餐亮点
  2. 什么是服务器的高并发
  3. 路由器设置显示服务器拒绝访问,路由器设置服务器拒绝访问
  4. 《SteamVR2.2.0快速入门》(Yanlz+Unity+XR+OpenVR+OpenXR+SteamVR+Valve+Vive+Oculus+Quickstart+HMD+立钻哥哥++ok++)
  5. 分类--ROC 和曲线下面积
  6. codeigniter 使用
  7. 54张扑克牌,除去两张大小王剩下52张扑克牌。问红桃A和黑桃A同时被一个人拿到的概率是多少?
  8. 906. 超级回文数
  9. 超详细解析:Python输出水仙花数
  10. 游戏对战平台原理终结篇(转自)