四、模拟英语四六级答题卡识别阅卷评分

一、思路分析

首先拿到答题卡照片的时候,需要对照片进行一系列预处理操作,通过透视变换将图像摆正方便后续的操作。每一道题五个选项,有五道题,通过字典存放准确答案。没有依次对答题卡进行轮廓检测,这里采用的是正方形,宽高比是1:1,当然也可以是矩形,也可以通过指定其他的筛选进行进行过滤筛选。最后通过掩膜操作,因为用户所选择的答案都是被涂过的,也就是通过判断黑色和白色来进行区分是否是用户选择的答案。一行一行的存储,因为一道题是五个选项,每一行是一道题,这里采用从上到下从左到右分别依次存放1-5题的A-E选项。通过与标准答案字典所存放的索引进行对比,从而给出用户得分。

二、导包及其相关函数

#导入工具包
import numpy as np
import argparse
import imutils
import cv2
# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,help="path to the input image")
args = vars(ap.parse_args())# 正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}#存放正确答案BEADB
def order_points(pts):# 一共4个坐标点rect = np.zeros((4, 2), dtype = "float32")# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下# 计算左上,右下s = pts.sum(axis = 1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 计算右上和左下diff = np.diff(pts, axis = 1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef four_point_transform(image, pts):#透视变换# 获取输入坐标点rect = order_points(pts)(tl, tr, br, bl) = rect#拿到答题卡四个顶点坐标# 计算输入的w和h值widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))#因为拿到的答题卡的不一定是正儿八经的矩形,需要对四条边进行计算长度,选出最长的作为最后矩形的长度widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 变换后对应坐标位置dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype = "float32")#看个人需求而定,这里将图像左上角规定为(0,0)位置# 计算变换矩阵M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后结果return warped
def sort_contours(cnts, method="left-to-right"):#从上到下进行排序,因为题目就是一行一行的reverse = Falsei = 0if method == "right-to-left" or method == "bottom-to-top":reverse = Trueif method == "top-to-bottom" or method == "bottom-to-top":i = 1boundingBoxes = [cv2.boundingRect(c) for c in cnts](cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))#排完序之后,前五个是第一题的,之后每五个依次为下一题的return cnts, boundingBoxes
def cv_show(name,img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()  

三、对答题卡进行预处理及透视变换摆正

# 预处理
image = cv2.imread(args["image"])#读取图像
contours_img = image.copy()#为了不改动原始图像,copy一下图像,因为后续需要进行一系列轮廓检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰度图
blurred = cv2.GaussianBlur(gray, (5, 5), 0)#高斯滤波,去除一些噪音点
cv_show('blurred',blurred)
edged = cv2.Canny(blurred, 75, 200)#Canny边缘检测
cv_show('edged',edged)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#轮廓检测完之后会得到三个返回值,这里的[1]存放的是轮廓信息
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) #将图像通过透视变换进行摆正,绘制出答题卡的大致轮廓,拿到的答题卡图像也不一定是正儿八经的矩形
cv_show('contours_img',contours_img)
docCnt = None
# 确保检测到了
if len(cnts) > 0:#因为可能会检测到其他干扰影响,但是答题卡的轮廓是最大的# 根据轮廓大小进行排序cnts = sorted(cnts, key=cv2.contourArea, reverse=True)#把检测到的所有轮廓按面积进行排序# 遍历每一个轮廓for c in cnts:# 近似peri = cv2.arcLength(c, True)#计算一下轮廓周长approx = cv2.approxPolyDP(c, 0.02 * peri, True)#对轮廓进行近似# 准备做透视变换if len(approx) == 4:#多边形顶点有四个也就是矩形,这个就是我们的答题卡轮廓docCnt = approxbreak
# 执行透视变换
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped',warped)

四、对每一道题均进行轮廓检测,遍历筛选

# 自适应阈值处理
thresh = cv2.threshold(warped, 0, 255,	cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_Contours = thresh.copy()
# 找到每一道题轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#这里再次进行轮廓检测,之所以不用霍夫圆检测是因为有可能答题卡会被全部涂满甚至越界
cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3) 
cv_show('thresh_Contours',thresh_Contours)
questionCnts = []
# 遍历,对所有的轮廓进行筛选
for c in cnts:# 计算比例和大小(x, y, w, h) = cv2.boundingRect(c)ar = w / float(h)#因为是圆形,这里是宽高比,外接矩形差不多宽高比是1:1# 根据实际情况指定标准if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:questionCnts.append(c)
# 按照从上到下进行排序
questionCnts = sort_contours(questionCnts,	method="top-to-bottom")[0]
correct = 0

五、对比答案,评分

# 每排有5个选项
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):#因为每一题都有五个选项,q为第几行# 排序cnts = sort_contours(questionCnts[i:i + 5])[0]#第i题的五个结果bubbled = None# 遍历每一个结果for (j, c) in enumerate(cnts):#j为第i道题的第j个选项# 使用mask来判断结果mask = np.zeros(thresh.shape, dtype="uint8")cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充cv_show('mask',mask)# 通过计算非零点数量来算是否选择这个答案mask = cv2.bitwise_and(thresh, thresh, mask=mask)total = cv2.countNonZero(mask)#看下框出来的选项中非零的个数有多少个# 通过阈值判断if bubbled is None or total > bubbled[0]:bubbled = (total, j)# 对比正确答案color = (0, 0, 255)k = ANSWER_KEY[q]#第q道题的答案# 判断正确if k == bubbled[1]:color = (0, 255, 0)correct += 1# 绘图cv2.drawContours(warped, [cnts[k]], -1, color, 3)
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warped, "{:.2f}%".format(score), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", warped)
cv2.waitKey(0)

六、Pycharm参数设定

设置参数指定图像路径
在这里插入图片描述
找到Edit Configurations
将image参数改成自己测试图像路径--image images\test11.png,其中images\test11.png为答题卡路径
在这里插入图片描述

七、完整代码

#导入工具包
import numpy as np
import argparse
import imutils
import cv2# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,help="path to the input image")
args = vars(ap.parse_args())# 正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}#存放正确答案BEADBdef order_points(pts):# 一共4个坐标点rect = np.zeros((4, 2), dtype = "float32")# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下# 计算左上,右下s = pts.sum(axis = 1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 计算右上和左下diff = np.diff(pts, axis = 1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef four_point_transform(image, pts):#透视变换# 获取输入坐标点rect = order_points(pts)(tl, tr, br, bl) = rect#拿到答题卡四个顶点坐标# 计算输入的w和h值widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))#因为拿到的答题卡的不一定是正儿八经的矩形,需要对四条边进行计算长度,选出最长的作为最后矩形的长度widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 变换后对应坐标位置dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype = "float32")#看个人需求而定,这里将图像左上角规定为(0,0)位置# 计算变换矩阵M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后结果return warped
def sort_contours(cnts, method="left-to-right"):#从上到下进行排序,因为题目就是一行一行的reverse = Falsei = 0if method == "right-to-left" or method == "bottom-to-top":reverse = Trueif method == "top-to-bottom" or method == "bottom-to-top":i = 1boundingBoxes = [cv2.boundingRect(c) for c in cnts](cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))#排完序之后,前五个是第一题的,之后每五个依次为下一题的return cnts, boundingBoxes
def cv_show(name,img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()  # 预处理
image = cv2.imread(args["image"])#读取图像
contours_img = image.copy()#为了不改动原始图像,copy一下图像,因为后续需要进行一系列轮廓检测
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#灰度图
blurred = cv2.GaussianBlur(gray, (5, 5), 0)#高斯滤波,去除一些噪音点
cv_show('blurred',blurred)
edged = cv2.Canny(blurred, 75, 200)#Canny边缘检测
cv_show('edged',edged)# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#轮廓检测完之后会得到三个返回值,这里的[1]存放的是轮廓信息
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3) #将图像通过透视变换进行摆正,绘制出答题卡的大致轮廓,拿到的答题卡图像也不一定是正儿八经的矩形
cv_show('contours_img',contours_img)
docCnt = None# 确保检测到了
if len(cnts) > 0:#因为可能会检测到其他干扰影响,但是答题卡的轮廓是最大的# 根据轮廓大小进行排序cnts = sorted(cnts, key=cv2.contourArea, reverse=True)#把检测到的所有轮廓按面积进行排序# 遍历每一个轮廓for c in cnts:# 近似peri = cv2.arcLength(c, True)#计算一下轮廓周长approx = cv2.approxPolyDP(c, 0.02 * peri, True)#对轮廓进行近似# 准备做透视变换if len(approx) == 4:#多边形顶点有四个也就是矩形,这个就是我们的答题卡轮廓docCnt = approxbreak# 执行透视变换
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped',warped)# 自适应阈值处理
thresh = cv2.threshold(warped, 0, 255,	cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_Contours = thresh.copy()# 找到每一道题轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]#这里再次进行轮廓检测,之所以不用霍夫圆检测是因为有可能答题卡会被全部涂满甚至越界
cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3) 
cv_show('thresh_Contours',thresh_Contours)
questionCnts = []# 遍历,对所有的轮廓进行筛选
for c in cnts:# 计算比例和大小(x, y, w, h) = cv2.boundingRect(c)ar = w / float(h)#因为是圆形,这里是宽高比,外接矩形差不多宽高比是1:1# 根据实际情况指定标准if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:questionCnts.append(c)# 按照从上到下进行排序
questionCnts = sort_contours(questionCnts,	method="top-to-bottom")[0]
correct = 0# 每排有5个选项
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):#因为每一题都有五个选项,q为第几行# 排序cnts = sort_contours(questionCnts[i:i + 5])[0]#第i题的五个结果bubbled = None# 遍历每一个结果for (j, c) in enumerate(cnts):#j为第i道题的第j个选项# 使用mask来判断结果mask = np.zeros(thresh.shape, dtype="uint8")cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充cv_show('mask',mask)# 通过计算非零点数量来算是否选择这个答案mask = cv2.bitwise_and(thresh, thresh, mask=mask)total = cv2.countNonZero(mask)#看下框出来的选项中非零的个数有多少个# 通过阈值判断if bubbled is None or total > bubbled[0]:bubbled = (total, j)# 对比正确答案color = (0, 0, 255)k = ANSWER_KEY[q]#第q道题的答案# 判断正确if k == bubbled[1]:color = (0, 255, 0)correct += 1# 绘图cv2.drawContours(warped, [cnts[k]], -1, color, 3)score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warped, "{:.2f}%".format(score), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
cv2.imshow("Original", image)
cv2.imshow("Exam", warped)
cv2.waitKey(0)

答题卡原题:
在这里插入图片描述
这里展示的太大,我就截取了其中一小部分进行展示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接下来就是依次对每道题进行遍历找到掩膜,一共25次,这里就不一一展示了
最后根据设定的字典里面的正确答案给出评分
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 17. 电话号码的字母组合 思考分析

题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 思考与递归程序 解空间树的宽度是输入数字对应的字符的个数&#xff0c;深度是输入的数字的个数…

Blockquotes,引用,html里面,经常用到的一个!

blockquote元素的使用已经非常多样化&#xff0c;但语义上它只适用于一件事–标记了一段你的网页被引用从另一来源。这意味着&#xff0c;如果你想让那些花俏的引文&#xff0c;<blockquote>是不是你应该使用元素。让我们看一看如何你应该使用此元素&#xff1a; <art…

仔细分析了下这7行,貌似时间复杂度,空间复杂度都不大,为嘛就是执行效率这么低?...

for(Girl girl Girls.first(); !myGirlFriend.like(me); girl Girls.next()){if(!girl.hasBoyFriend(now) && i.like(girl)) { GirlFriend myGirlFriend (GirlFriend)girl; }} 转载于:https://www.cnblogs.com/naran/archive/2011/12/28/2305467.html…

BHMS的完整形式是什么?

BHMS&#xff1a;顺势疗法医学和外科学士 (BHMS: Bachelor of Homeopathic Medicine and Surgery) BHMS is an abbreviation of Bachelor of Homeopathic Medicine and Surgery. It is a medical degree program for under graduation in Homeopathy; an alternative move towa…

c++编程思想2 --友元存储控制

友元friend在c中的应用 我们知道在c的类访问权限中,private和 protected在类外面进行访问的时候 会因为权限而不能访问 &#xff0c;友元就解决了这个问题 。 可以这样理解&#xff0c;他为外部的 函数 或者类 进行了 访问授权,其实这已经超出OOP的范畴,但是对于C而言是以实用…

WordPress Event Easy Calendar插件多个跨站请求伪造漏洞

漏洞名称&#xff1a;WordPress Event Easy Calendar插件多个跨站请求伪造漏洞CNNVD编号&#xff1a;CNNVD-201309-083发布时间&#xff1a;2013-09-11更新时间&#xff1a;2013-09-11危害等级&#xff1a; 漏洞类型&#xff1a;跨站请求伪造威胁类型&#xff1a;远程CVE编号&…

XML转txt格式脚本

一、东北大学老师收集的钢材缺陷数据集是XML格式的&#xff0c;但是YOLOv5只允许使用txt文件标签 例如其中一种缺陷图片所对应的标签&#xff1a;crazing_1.xml <annotation><folder>cr</folder><filename>crazing_1.jpg</filename><source&…

python程序生成exe_使用Python程序生成QR代码的Python程序

python程序生成exeQR code is a short form of the quick response code. It is a type of matrix barcode that contains some information like some specific link, important message, email-id, etc. In Python, the qrcode module is used to generate the QR code of so…

leetcode 242. 有效的字母异位词 思考分析

题目 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 我们先考虑低阶版本&#xff0c;认为字符只有26种可能&#xff0c;然后将a ~ z的字符映射到数组的索引0 ~ 25&#xff0c;数组中存放的则是该索引出现的频次。 记录下s的频次和t的频次…

总结一下ERP .NET程序员必须掌握的.NET技术,掌握了这些技术工作起来才得心应手...

从毕业做.NET到现在&#xff0c;有好几年了&#xff0c;自认为只能是达到熟练的水平&#xff0c;谈不上精通。所以&#xff0c;总结一下&#xff0c;自己到底熟练掌握了哪些.NET方面的开发技术&#xff0c;以此对照&#xff0c;看看还有哪些不足&#xff0c;欢迎补充。 1 .NET …

js \n直接显示字符串_显示N个字符的最短时间

js \n直接显示字符串Problem statement: 问题陈述&#xff1a; You need to display N similar characters on a screen. You are allowed to do three types of operation each time. 您需要在屏幕上显示N个相似的字符。 每次允许您执行三种类型的操作。 You can insert a c…

示例 Demo 工程和 API 参考链接

Camera Explorer&#xff1a;有关 Windows Phone8 中有关增强 Camera API 的使用。文章链接 Filter Effects&#xff1a;对拍摄的照片或者图片库中的照片应用 Nokia Imaging SDK 中的滤镜。文章链接 Filter Explorer&#xff1a;演示了对新拍摄图片或者现有图片的编辑功能&…

三、标签准备

所有操作均在anaconda中的自己配置的环境下进行 一、安装labelimg 因为YOLO模型所需要的样本标签必须是txt类型&#xff0c;本人使用labelimg软件进行对图像进行打标签操作。 pip install pycocotools-windows pip install pyqt5 pip install labelimg 通过labelimg命令打…

ubuntu 8.04安装应用软件Can't find X includes错误解决办法

系统很小。应用软件都的自己装。 首先把 APT’s database is not updated. # apt-get update    # apt-get upgrade 再装其它软件。 make xconfigure 无法运行时&#xff1a; apt-get install qt3-dev-tools 编译QVFB  是出现&#xff1a; 出现&#xff1a;C preproces…

leetcode 39. 组合总和 思考分析

目录1、题目2、思考分析3、未经优化代码4、剪枝优化1、题目 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 2、思考分析 解空间树宽度部分即数…

java uuid静态方法_Java UUID equals()方法与示例

java uuid静态方法UUID类equals()方法 (UUID Class equals() method) equals() method is available in java.util package. equals()方法在java.util包中可用。 equals() method is used to check whether this object equals to the given object or not. equals()方法用于检…

一、机器学习概念

一、何为机器学习(Mechine Learning)&#xff1f; 答&#xff1a;利用已有数据(经验)&#xff0c;来训练某种模型&#xff0c;利用此模型来预测未来。机器学习是人工智能的核心Mechine Learning。 例如&#xff1a;你和狗蛋儿7点在老槐树下集合&#xff0c;如何一块约去开黑&a…

Java线程新特征——Java并发库

一、线程池 Sun在Java5中&#xff0c;对Java线程的类库做了大量的扩展&#xff0c;其中线程池就是Java5的新特征之一&#xff0c;除了线程池之外&#xff0c;还有很多多线程相关的内容&#xff0c;为多线程的编程带来了极大便利。为了编写高效稳定可靠的多线程程序&#xff0c;…

第一篇博文

刚刚申请博客&#xff0c;开通了&#xff0c;很高兴。但是由于这几天考试比较多&#xff0c;等考完之后&#xff0c;再开始正式写博客&#xff0c;与诸君共进步&#xff01; 2012/1/1 18:20 转载于:https://www.cnblogs.com/zhenglichina/archive/2012/01/01/2309561.html

leetcode 40. 组合总和 II 思考分析

题目 给定一个数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用一次。 思考以及代码 如果我们直接套用39题的思路&#xff0c;那么就会出现重复的组合。 重复组合的…