opencv图像处理—项目实战:答题卡识别判卷
哔站唐宇迪opencv课程——项目实战:答题卡识别判卷
【计算机视觉-OpenCV】唐宇迪博士教会了我大学四年没学会的OpenCV OpenCV计算机视觉实战全套课程(附带课程课件资料+课件笔记+源码)_哔哩哔哩_bilibili
目录
Step1 预处理
1.1高斯滤波
1.2边缘检测
1.3轮廓检测
Step2透视变换
2.1 four_point_transform
2.2 order_points
Step3 二值处理
Step4
4.1寻找圆圈轮廓
4.2寻找选项轮廓
4.3选项轮廓从上到下排序
4.3.1 sort_contours
Step5输出结果
Step6打印操作
完整代码
方法:试卷扫描-轮廓检测-对每一个位置指定掩码 -统计里面非零值大小-哪个选项值最大就选的是哪个
导入工具包
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}
绘图函数
def cv_show(name,img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()
Step1 预处理
1.1高斯滤波
#预处理
image = cv2.imread(args["image"])
contours_img = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#灰度
blurred = cv2.GaussianBlur(gray,(5,5),0)#高斯滤波去噪音
cv_show('blurred',blurred)
高斯滤波 :
1.2边缘检测
#边缘检测
edges = cv2.Canny(blurred,75,200)
cv_show("edges",edges)
边缘检测:
1.3轮廓检测
#轮廓检测
cnts = cv2.findContours(edges.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show("contours_img",contours_img)
docCnt = None
轮廓检测:
Step2透视变换
#确保检测到了
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)
2.1 four_point_transform
def 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")#计算变换矩阵M = cv2.getPerspectiveTransform(rect,dst)warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))#返回变换后结果return warped
2.2 order_points
def order_points(pts):
#根据位置信息定位四个坐标点的位置rect = np.zeros((4,2),dtype="float32")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 rect
透视变换:
Step3 二值处理
#0tsu's阈值处理
#参数:预处理好的图像、0:自动判断、cv2.THRESH_OTSU:自适应
thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
二值结果:
Step4
4.1寻找圆圈轮廓
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)
4.2寻找选项轮廓
questionCnts = []
#遍历
for c in cnts:#计算比例和大小(x,y,w,h) = cv2.boundingRect(c)#圆形的外接矩形ar = w/float(h)#宽和长的比值#根据实际情况设定标准if w>=20 and h>=20 and ar>=0.9 and ar<=1.1:questionCnts.append(c)
4.3选项轮廓从上到下排序
#将每一个圆形的轮廓按照从上到下进行排序
questionCnts = sort_contours(questionCnts,method="top-to-bottom")[0]
4.3.1 sort_contours
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 = 1# 计算外接矩形 boundingBoxes是一个元组boundingBoxes = [cv2.boundingRect(c) for c in cnts]#用一个最小的矩形,把找到的形状包起来x,y,h,w# sorted排序(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),key=lambda b: b[1][i], reverse=reverse))return cnts,boundingBoxes# 轮廓和boundingBoxess
Step5输出结果
给每个题的五个轮廓从左到右排序,每个选项做掩码,与操作,统计选项里面非零值大小,哪个选项值最大就选的是哪个,记录下来与正确选项对比,计算正确的个数。
correct=0
for (q,i) in enumerate(np.arange(0,len(questionCnts),5)):#排序cnts = sort_contours(questionCnts[i:i+5])[0]bubble = None#遍历每一个结果for (j,c) in enumerate(cnts):#使用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)#通过与操作,只保留了掩码为白色的那一个部分cv_show('mask',mask)total = cv2.countNonZero(mask)#通过阈值判断if bubble is None or total>bubble[0]:bubble = (total,j)#对比正确答案color = (0,0,255)k = ANSWER_KEY[q] #q代表现在检查的是第q个题#k是正确答案if k == bubble[1]:color = (0,255,0)correct += 1
Step6打印操作
#绘图
cv2.drawContours(warped,[cnts[k]],-1,color,3)score = (correct/5.0) * 100
print("[INFO] score:{:.2f}%".format(score)) #这个%只是为了显示为60%,没有其他意思
cv2.putText(warped,"{:.2f}%".format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,3)
cv_show("Original",image)
cv_show("Exam",warped)
完整代码
import numpy as np
import argparse
import imutils
import cv2ap = 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}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")# 计算变换矩阵M = cv2.getPerspectiveTransform(rect, dst)warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后结果return warpeddef 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, boundingBoxesdef cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()# 预处理
image = cv2.imread(args["image"])
contours_img = image.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)
cv_show('edged', edged)# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[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)
# Otsu's 阈值处理
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)# 根据实际情况指定标准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)):# 排序cnts = sort_contours(questionCnts[i:i + 5])[0]bubbled = None# 遍历每一个结果for (j, c) in enumerate(cnts):# 使用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)cv_show('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]# 判断正确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)
opencv图像处理—项目实战:答题卡识别判卷相关推荐
- OpenCV实战(二)——答题卡识别判卷
代码见 https://github.com/skyerhxx/Answer-card-recognition-and-judgment 答题卡识别判卷 识别出考生选择的答案并能自动判分 Python ...
- 深入学习OpenCV文档扫描OCR识别及答题卡识别判卷(文档扫描,图像矫正,透视变换,OCR识别)
人工智能学习离不开实践的验证,推荐大家可以多在FlyAI-AI竞赛服务平台多参加训练和竞赛,以此来提升自己的能力.FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台.每周免费提供 ...
- Opencv之答题卡识别判卷
项目要求 提供一张答题卡图像,通过图像处理识别出答题卡上每个题的选项,与正确答案对比,得出分数并写在答题卡上. 代码实现过程 1.引入需要的库 import numpy as np import cv ...
- 17.答题卡识别判卷
目录 1 项目介绍 2 代码分析 2.1 导入库 2.2 设置参数 2.3 设置正确答案 2.4 定义找到四个角点的函数 2.5 定义变换函数 2.6 定义 sort_contours ...
- 人工智能项目实战-使用OMR完成答题卡识别判卷
- python OpenCV 答题卡识别判卷
完整代码: #导入工具包 import numpy as np import argparse import imutils import cv2# 设置参数 ap = argparse.Argume ...
- 用Python+OpenCV+PyQt开发的答题卡识别软件
用Python+OpenCV+PyQt开发的答题卡识别软件 软件使用说明 软件设计思路 如何设置答案 界面风格 备注 这是一个可以识别定制答题卡的软件,它可以根据用户自定的答案来进行识别,校对正误并统 ...
- opencv学习笔记八--答题卡识别
opencv学习笔记八--答题卡识别 导入工具包 定义函数 扫描 自适应阈值处理 检测每一个选项的轮廓 对轮廓进行排序以获取序号 打印结果 参考 导入工具包 #导入工具包 import numpy a ...
- opencv_python答题卡自动判卷
opencv_python答题卡自动判卷 设定答题卡模板 该图像为答题卡的答题区域,黑色边框是为了能够在各种环境中轻易的检测,左部分和上部分的黑色矩形,是为能够定位到答题选项的坐标而设置,同时题目数量 ...
最新文章
- VC++的应用程序框架中各类之间的访问方法
- sparse double型矩阵转为full矩阵
- cadence 常见pcb电阻_经验分享|高频PCB设计中出现的干扰分析及对策
- java ajax翻页_分页 工具类 前后台代码 Java JavaScript (ajax) 实现 讲解
- Reference 字段的详情弹窗icon显示或者隐藏
- 关于C#中实现两个应用程序消息通讯的问题
- sql不等于0怎么表示_数组真的只能从0开始吗?python表示不同意
- HDU/HDOJ 2612 Find a way 双向BFS
- 一文带你了解数据中心大二层网络演进之路
- 10.30完美笔试题
- 执法智能眼镜 android,警用AR智能眼镜解决方案
- 公众号刷粉、阅读量作弊
- python小游戏——俄罗斯方块
- Linux wifi wpa_sup,wifi详解(四)zz
- python哪个方向工资高_学完Python的7大就业方向,哪个行业才能赚钱多?
- AMD黑苹果解决关机变重启、睡眠重启问题。
- 渗透测试笔记 -------------持续更新中~
- Swin-Transformer图像分类
- Python实现将内容写入文件的五种方法总结
- c语言输出一个n行m列的图形