哔站唐宇迪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图像处理—项目实战:答题卡识别判卷相关推荐

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

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

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

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

  3. Opencv之答题卡识别判卷

    项目要求 提供一张答题卡图像,通过图像处理识别出答题卡上每个题的选项,与正确答案对比,得出分数并写在答题卡上. 代码实现过程 1.引入需要的库 import numpy as np import cv ...

  4. 17.答题卡识别判卷

    目录 1  项目介绍 2  代码分析 2.1  导入库 2.2  设置参数 2.3  设置正确答案 2.4  定义找到四个角点的函数 2.5  定义变换函数 2.6  定义 sort_contours ...

  5. 人工智能项目实战-使用OMR完成答题卡识别判卷

  6. python OpenCV 答题卡识别判卷

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

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

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

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

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

  9. opencv_python答题卡自动判卷

    opencv_python答题卡自动判卷 设定答题卡模板 该图像为答题卡的答题区域,黑色边框是为了能够在各种环境中轻易的检测,左部分和上部分的黑色矩形,是为能够定位到答题选项的坐标而设置,同时题目数量 ...

最新文章

  1. VC++的应用程序框架中各类之间的访问方法
  2. sparse double型矩阵转为full矩阵
  3. cadence 常见pcb电阻_经验分享|高频PCB设计中出现的干扰分析及对策
  4. java ajax翻页_分页 工具类 前后台代码 Java JavaScript (ajax) 实现 讲解
  5. Reference 字段的详情弹窗icon显示或者隐藏
  6. 关于C#中实现两个应用程序消息通讯的问题
  7. sql不等于0怎么表示_数组真的只能从0开始吗?python表示不同意
  8. HDU/HDOJ 2612 Find a way 双向BFS
  9. 一文带你了解数据中心大二层网络演进之路
  10. 10.30完美笔试题
  11. 执法智能眼镜 android,警用AR智能眼镜解决方案
  12. 公众号刷粉、阅读量作弊
  13. python小游戏——俄罗斯方块
  14. Linux wifi wpa_sup,wifi详解(四)zz
  15. python哪个方向工资高_学完Python的7大就业方向,哪个行业才能赚钱多?
  16. AMD黑苹果解决关机变重启、睡眠重启问题。
  17. 渗透测试笔记 -------------持续更新中~
  18. Swin-Transformer图像分类
  19. Python实现将内容写入文件的五种方法总结
  20. c语言输出一个n行m列的图形

热门文章

  1. 新买的幻16笔记本不激活的情况下联网使用解决方案(双系统)
  2. 【Git常用】之回滚
  3. 通信工程师,到底是干啥的?
  4. linux quota硬盘,Linux系统中quota磁盘命令的相关使用解析
  5. 简述MACD指标以及它的组成MACD、MACDsignal和MACDhist
  6. 闪电网络一周热点回顾-AToken研究院
  7. 一种灵活可靠的工作方式:组件化设计与开发
  8. Wox Github下载太慢了
  9. 网易面试一面【游戏测试工程师】
  10. OpenStack Ironic实现裸机部署