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

  • 前言
  • 一、图像矫正
    • 1.源码
  • 二、获取选项区域
    • 1.扣出每题选项
    • 2.源码
  • 三、获取答案
    • 1.思路
    • 2.辅助函数
    • 3.源码
    • 4.效果
  • 总结

前言

本文将使用OpenCV C++ 进行答题卡识别。

一、图像矫正


原图如图所示。我们拿到图像首先要进行图像预处理。本文目的是进行答题卡选项识别。所以,第一步我们需要将答题卡区域切割出来以进行后续识别工作。在上一篇文章我已经做过图像矫正案例OpenCV C++案例实战四《图像透视矫正》,详细内容大家可以参考,这里就不再赘述。

1.源码

void Answer::GetWarpImg(Mat src, Mat& WarpImg)
{Mat gray;cvtColor(src, gray, COLOR_BGR2GRAY);Mat blur;GaussianBlur(gray, blur, Size(3, 3), 0);Mat canny;Canny(blur, canny, 100, 200);vector<vector<Point>>contours;findContours(canny, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//找到最大矩形轮廓int index = 0;double maxArea = 0;for (int i = 0; i < contours.size(); i++){double area = contourArea(contours[i]);if (maxArea < area){maxArea = area;index = i;}}//多边形近似vector<vector<Point>>conPloy(contours.size());double peri = arcLength(contours[index], true);approxPolyDP(contours[index], conPloy[index], 0.02*peri, true);//找到矩形四个顶点vector<Point>srcPts;for (int i = 0; i < conPloy[index].size(); i++){srcPts.push_back(Point(conPloy[index][i].x, conPloy[index][i].y));}int width = src.cols / 2;int height = src.rows / 2;//将矩形四个顶点按T_L, B_L, B_R, T_R区分int T_L, B_L, B_R, T_R;for (int i = 0; i < srcPts.size(); i++){if (srcPts[i].x < width && srcPts[i].y < height){T_L = i;}if (srcPts[i].x < width && srcPts[i].y > height){B_L = i;}if (srcPts[i].x > width && srcPts[i].y > height){B_R = i;}if (srcPts[i].x > width && srcPts[i].y < height){T_R = i;}}/*变换后,图像的长和宽应该变为:长 = max(变换前左边长,变换前右边长)宽 = max(变换前上边长,变换前下边长)设变换后图像的左上角位置为原点位置。*/double upWidth = EuDis(srcPts[T_R], srcPts[T_L]);double downWidth = EuDis(srcPts[B_R], srcPts[B_L]);double maxWidth = max(upWidth, downWidth);double leftHeight = EuDis(srcPts[B_L], srcPts[T_L]);double rightHeight = EuDis(srcPts[B_R], srcPts[T_R]);double maxHeight = max(leftHeight, rightHeight);Point2f AffineSrcPts[4] = { Point2f(srcPts[T_L]) ,Point2f(srcPts[T_R]) ,Point2f(srcPts[B_L]) ,Point2f(srcPts[B_R]) };Point2f AffineDstPts[4] = { Point2f(0, 0),Point2f(maxWidth , 0),Point2f(0, maxHeight),Point2f(maxWidth , maxHeight) };Mat M;//计算仿射变换矩阵M = getPerspectiveTransform(AffineSrcPts, AffineDstPts);//对加载图形进行仿射变换操作warpPerspective(src, WarpImg, M, Point(maxWidth, maxHeight));
}


如图就是经图像透视矫正切割出来的答题卡区域。接下来,我们就需要对该图进行后续识别处理。

二、获取选项区域

1.扣出每题选项

我对于该案例的处理思路是:先对原图进行透视矫正;然后将每一题号选项都切割出来;最后对这些切割出来的选项一一识别。

 Mat gray;cvtColor(WarpImg, gray, COLOR_BGR2GRAY);Mat bin;threshold(gray, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);//使用 Size(15, 5)闭操作目的是为了将每一题选项连接起来。Mat close;Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 5));morphologyEx(bin, close, MORPH_CLOSE, kernel);


如图所示,经过上述图像预处理,我们就可以利用轮廓筛选出每一题号选项。

 vector<vector<Point>>contours;findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);RotatedRect rect;Rect box;for (int i = 0; i < contours.size(); i++){Mat mask = Mat::zeros(WarpImg.size(), WarpImg.type());mask = Scalar::all(255);double area = contourArea(contours[i]);if (area > 1000){rect = minAreaRect(contours[i]);box = rect.boundingRect();double ratio = rect.size.height / rect.size.width;//将每一选项都单独抠出来放进AnswerROI容器,以便后续识别。if (ratio > 0.1){//rectangle(WarpImg, Rect(box.tl(), box.br()), Scalar(0, 255, 0), 2);Mat ROI = WarpImg(Rect(box.tl(), box.br()));ROI.copyTo(mask(box));AnswerROI.push_back(mask);}}}


如图为扣出的题号选项,这里只展示其中之一。接下来我们对每一题的选项进行识别就可以了。

2.源码

void  Answer::GetAnswerArea(Mat&WarpImg, vector<Mat>&AnswerROI)
{Mat gray;cvtColor(WarpImg, gray, COLOR_BGR2GRAY);Mat bin;threshold(gray, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);//使用 Size(15, 5)闭操作目的是为了将每一题选项连接起来。Mat close;Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 5));morphologyEx(bin, close, MORPH_CLOSE, kernel);vector<vector<Point>>contours;findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);RotatedRect rect;Rect box;for (int i = 0; i < contours.size(); i++){Mat mask = Mat::zeros(WarpImg.size(), WarpImg.type());mask = Scalar::all(255);double area = contourArea(contours[i]);if (area > 1000){rect = minAreaRect(contours[i]);box = rect.boundingRect();double ratio = rect.size.height / rect.size.width;//将每一选项都单独抠出来放进AnswerROI容器,以便后续识别。if (ratio > 0.1){//rectangle(WarpImg, Rect(box.tl(), box.br()), Scalar(0, 255, 0), 2);Mat ROI = WarpImg(Rect(box.tl(), box.br()));ROI.copyTo(mask(box));AnswerROI.push_back(mask);}}}reverse(AnswerROI.begin(), AnswerROI.end());}

三、获取答案

1.思路


由于之前我们已经将图像透视矫正,并且将每一题号选项都一一抠出来作为一张新图像存储。所以,这里我们可以将每个选项按A,B,C,D,E划分区域,然后计算每个选项区域像素点个数,选项涂抹区域必定是像素点最多的,由此我们可以判定出每一题号的选项。

2.辅助函数

//计算ABCDE区域所有像素点个数
int get_pixsum(Mat image, int pixstart, int pixend)
{int sum = 0;for (int i = pixstart; i < pixend; i++){for (int j = 0; j < image.rows; j++){if (image.at<uchar>(j, i) != 0){sum++;}}}return sum;
}//找到像素点最多区域的索引
int getMaxIndex(vector<int>Answer)
{int max = 0;int index = -1;for (int i = 0; i < Answer.size(); i++){if (Answer[i] > max){max = Answer[i];index = i;}}return index;
}

3.源码

void Answer::GetAnswer(Mat&WarpImg, vector<Mat>&AnswerROI, vector<int>Answers, int &Score)
{vector<int>Results;//正确结果选项索引vector<vector<Point>>ResultContours;//正确结果选项轮廓for (int i = 0; i < AnswerROI.size(); i++){Mat gray;cvtColor(AnswerROI[i], gray, COLOR_BGR2GRAY);Mat bin;threshold(gray, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);//计算ABCDE区域所有像素点个数int A = get_pixsum(bin,  60,  110);int B = get_pixsum(bin, 110, 160);int C = get_pixsum(bin,  160,  210);int D = get_pixsum(bin,  210, 260);int E = get_pixsum(bin,  260, 310);vector<int>Answer = { A,B,C,D,E };//找到像素点最多区域的索引int Index = getMaxIndex(Answer);Results.push_back(Index);  //正确结果选项索引//获取选项轮廓vector<vector<Point>>contours;vector<vector<Point>>AnswerContours;findContours(bin, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//由于提取到的轮廓是倒序的,故reverse反转一下reverse(contours.begin(), contours.end());for (int j = 0; j < contours.size(); j++){//通过面积条件,只提取选项(ABCDE)轮廓if (contourArea(contours[j]) > 200){AnswerContours.push_back(contours[j]);}}ResultContours.push_back(AnswerContours[Index]);//正确结果选项轮廓}//结果绘制for (int i = 0; i < Answers.size(); i++){if (Answers[i] == Results[i]){//答题正确绘制绿色drawContours(WarpImg, ResultContours, i, Scalar(0, 255, 0), 2);}else{//答题错误绘制红色drawContours(WarpImg, ResultContours, i, Scalar(0, 0, 255), 2);}}//统计得分double Count = 0;       //答对数量for (int i = 0; i < Answers.size(); i++){if (Results[i] == Answers[i]){Count++;}}Score = (Count / Answers.size()) * 100;}

4.效果


这里我设置的正确答案为:B,E,A,C,D。故只答对4题,得80分。


总结

本文使用OpenCV C++ 进行答题卡识别,关键步骤有以下几点。
1、图像透视矫正,将答题卡区域正确切割出来。
2、将每一题号分别抠出来存为新图像,待后续识别。
3、对每一题号确定其A,B,C,D,E选项区域,统计其像素点个数,故而匹配选项。

以上就是我对整个案例是思路以及处理手法,如果大家有更好地想法欢迎讨论。

OpenCV C++案例实战五《答题卡识别》相关推荐

  1. opencv图像处理—项目实战:答题卡识别判卷

    哔站唐宇迪opencv课程--项目实战:答题卡识别判卷 [计算机视觉-OpenCV]唐宇迪博士教会了我大学四年没学会的OpenCV OpenCV计算机视觉实战全套课程(附带课程课件资料+课件笔记+源码 ...

  2. OpenCV实战(二)——答题卡识别判卷

    代码见 https://github.com/skyerhxx/Answer-card-recognition-and-judgment 答题卡识别判卷 识别出考生选择的答案并能自动判分 Python ...

  3. opencv项目实践一(答题卡识别)

    答题卡素材图片 思路 读入图片,做一些预处理工作. 进行轮廓检测,然后找到该图片最大的轮廓,就是答题卡部分. 进行透视变换,以去除除答题卡外的多于部分,并且可以对答题卡进行校正. 再次检测轮廓,定位每 ...

  4. 深入学习OpenCV文档扫描OCR识别及答题卡识别判卷(文档扫描,图像矫正,透视变换,OCR识别)

    人工智能学习离不开实践的验证,推荐大家可以多在FlyAI-AI竞赛服务平台多参加训练和竞赛,以此来提升自己的能力.FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台.每周免费提供 ...

  5. 答题卡识别任务--opencv python(附代码)

    答题卡识别 项目理论和源码来自唐宇迪opencv项目实战 记一篇python-opencv 完成答题卡识别 项目的学习笔记 输入一张特定格式的答题卡图片(答题卡中题目数量和选项个数是固定的),能够输出 ...

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

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

  7. opencv学习笔记八--答题卡识别

    opencv学习笔记八--答题卡识别 导入工具包 定义函数 扫描 自适应阈值处理 检测每一个选项的轮廓 对轮廓进行排序以获取序号 打印结果 参考 导入工具包 #导入工具包 import numpy a ...

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

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

  9. 用Python+OpenCV+PyQt开发的答题卡识别软件

    用Python+OpenCV+PyQt开发的答题卡识别软件 软件使用说明 软件设计思路 如何设置答案 界面风格 备注 这是一个可以识别定制答题卡的软件,它可以根据用户自定的答案来进行识别,校对正误并统 ...

最新文章

  1. 近一半企业曾遭遇云计算安全问题
  2. ASP.Net防止页面刷新重复提交
  3. python作用域——LEGB规则
  4. LINQ TO SQL 动态查询
  5. lunix入侵别人电脑_记录一次Linux的实战入侵过程
  6. IDEA官方中文版插件
  7. es拼音分词 大帅哥_Elasticsearch中文分词加拼音
  8. 应用程序正常初始化失败 VS2005
  9. 编写程序求解百鸡百钱问题。公鸡5元一只,母鸡3元一只,小鸡一元3只,问100元钱买100只鸡,可买公鸡、母鸡、小鸡各多少只?
  10. Android API 中文(13) —— ToggleButton
  11. python中文文本信息提取_PyMuPDF提取文本信息
  12. HR在线揭秘:面试总被虐?这 12 个必问题请记好答案!
  13. 如何将webp批量转换jpg?
  14. 【集训队作业】IOI 2020 集训队作业 试题泛做 13
  15. 计算机网络学习笔记:多路复用(频分多路复用、时分多路复用、波分多路复用、码分多路复用)
  16. 8086汇编学习之[BX],CX寄存器与loop指令,ES寄存器等
  17. html 状态栏显示,网页屏蔽状态栏 打开的网页怎么隐藏浏览器的状态栏
  18. 计算机英语讲课笔记08
  19. 全球及中国家庭自动化行业前景动态及投资趋势预测报告(新版)2022-2027
  20. visio2003无法找到加载项的解决办法

热门文章

  1. 按钮控制LED灯的亮灭
  2. 关于前端代码埋点数据上报的实现
  3. 供应链协作平台产品设计思维导图
  4. 内存小的浏览器有哪些,好不好用?分享一些使用感受
  5. oracle vm 鼠标切换,VirtualBox的Linux虚拟机文本模式和图形模式的切换问题
  6. 第四章 大数定律与中心极限定理(总结)
  7. 在页面点击“生成二维码”,直接把二维码图片下载下来
  8. 清华教授:多年以来,我对我的学生都不太满意
  9. Web与排版学上的字体问题【转】
  10. MacBooster 7 mac 破解版永久激活方法无需激活码