当前位置:   article > 正文

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打印操作

完整代码


方法:试卷扫描-轮廓检测-对每一个位置指定掩码 -统计里面非零值大小-哪个选项值最大就选的是哪个

 导入工具包

  1. import numpy as np
  2. import argparse
  3. import imutils
  4. import cv2

 设置参数

  1. ap=argparse.ArgumentParser()
  2. ap.add_argument("-i","--image",required=True,help="path to the input image")
  3. args=vars(ap.parse_args())

正确答案 

  1. # 正确答案
  2. ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
'
运行

绘图函数 

  1. def cv_show(name,img):
  2. cv2.imshow(name, img)
  3. cv2.waitKey(0)
  4. cv2.destroyAllWindows()
'
运行

 

 


Step1 预处理 

1.1高斯滤波 

  1. #预处理
  2. image = cv2.imread(args["image"])
  3. contours_img = image.copy()
  4. gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#灰度
  5. blurred = cv2.GaussianBlur(gray,(5,5),0)#高斯滤波去噪音
  6. cv_show('blurred',blurred)

高斯滤波 :

 1.2边缘检测

  1. #边缘检测
  2. edges = cv2.Canny(blurred,75,200)
  3. cv_show("edges",edges)

 边缘检测:

 1.3轮廓检测

  1. #轮廓检测
  2. cnts = cv2.findContours(edges.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
  3. cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
  4. cv_show("contours_img",contours_img)
  5. docCnt = None

 

 轮廓检测:

 

 

Step2透视变换 

  1. #确保检测到了
  2. if len(cnts) > 0:
  3. #根据轮廓大小进行排序
  4. cnts = sorted(cnts,key=cv2.contourArea,reverse=True)
  5. #遍历每一个轮廓
  6. for c in cnts:
  7. #近似
  8. peri = cv2.arcLength(c,True)
  9. approx = cv2.approxPolyDP(c,0.02*peri,True) #近似轮廓
  10. # 准备做透视变换
  11. if len(approx) == 4:
  12. docCnt = approx
  13. break #找到了做透视变换的四个坐标了
  14. #执行透视变换
  15. warped = four_point_transform(gray,docCnt.reshape(4,2))
  16. cv_show("warped",warped)

2.1 four_point_transform

  1. def four_point_transform(image,pts):
  2. #获取输入坐标点
  3. rect = order_points(pts)
  4. (tl,tr,br,bl) = rect
  5. #计算输入的w和h值
  6. widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
  7. widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
  8. maxWidth = max(int(widthA),int(widthB))
  9. heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
  10. heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
  11. maxHeight = max(int(heightA),int(heightB))
  12. #变换后对应坐标位置
  13. dst = np.array([
  14. [0, 0],
  15. [maxWidth - 1, 0],
  16. [maxWidth - 1, maxHeight - 1],
  17. [0, maxHeight - 1]], dtype="float32")
  18. #计算变换矩阵
  19. M = cv2.getPerspectiveTransform(rect,dst)
  20. warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))
  21. #返回变换后结果
  22. return warped
'
运行

2.2 order_points 

  1. def order_points(pts):
  2. #根据位置信息定位四个坐标点的位置
  3. rect = np.zeros((4,2),dtype="float32")
  4. s = pts.sum(axis=1)
  5. rect[0] = pts[np.argmin(s)]
  6. rect[2] = pts[np.argmax(s)]
  7. diff = np.diff(pts,axis=1)
  8. rect[1] = pts[np.argmin(diff)]
  9. rect[3] = pts[np.argmax(diff)]
  10. return rect
'
运行

 透视变换:

 

 

Step3 二值处理

  1. #0tsu's阈值处理
  2. #参数:预处理好的图像、0:自动判断、cv2.THRESH_OTSU:自适应
  3. thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
  4. cv_show('thresh',thresh)

 二值结果:

 Step4

4.1寻找圆圈轮廓 

  1. thresh_Contours=thresh.copy()
  2. #找到每一个圆圈轮廓
  3. cnts=cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
  4. cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3)
  5. cv_show('thresh_Contours',thresh_Contours)

 

 4.2寻找选项轮廓

  1. questionCnts = []
  2. #遍历
  3. for c in cnts:
  4. #计算比例和大小
  5. (x,y,w,h) = cv2.boundingRect(c)#圆形的外接矩形
  6. ar = w/float(h)#宽和长的比值
  7. #根据实际情况设定标准
  8. if w>=20 and h>=20 and ar>=0.9 and ar<=1.1:
  9. questionCnts.append(c)

 

 4.3选项轮廓从上到下排序

  1. #将每一个圆形的轮廓按照从上到下进行排序
  2. questionCnts = sort_contours(questionCnts,method="top-to-bottom")[0]

 4.3.1 sort_contours

  1. def sort_contours(cnts,method="left-to-right"):
  2. reverse = False
  3. i = 0
  4. if method == "right-to-left" or method == "bottom-to-top":
  5. reverse = True
  6. if method == "top-to-bottom" or method == "bottom-to-top":
  7. i = 1
  8. # 计算外接矩形 boundingBoxes是一个元组
  9. boundingBoxes = [cv2.boundingRect(c) for c in cnts]#用一个最小的矩形,把找到的形状包起来x,y,h,w
  10. # sorted排序
  11. (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
  12. key=lambda b: b[1][i], reverse=reverse))
  13. return cnts,boundingBoxes# 轮廓和boundingBoxess
'
运行

 

Step5输出结果

给每个题的五个轮廓从左到右排序,每个选项做掩码,与操作,统计选项里面非零值大小,哪个选项值最大就选的是哪个,记录下来与正确选项对比,计算正确的个数。

  1. correct=0
  2. for (q,i) in enumerate(np.arange(0,len(questionCnts),5)):
  3. #排序
  4. cnts = sort_contours(questionCnts[i:i+5])[0]
  5. bubble = None
  6. #遍历每一个结果
  7. for (j,c) in enumerate(cnts):
  8. #使用mask来判断结果
  9. mask = np.zeros(thresh.shape,dtype="uint8")
  10. cv2.drawContours(mask,[c],-1,255,-1)#-1表示填充
  11. cv_show('mask',mask)
  12. #通过计算非零像素点的数量来算是否选择这个答案
  13. mask = cv2.bitwise_and(thresh,thresh,mask=mask)
  14. #通过与操作,只保留了掩码为白色的那一个部分
  15. cv_show('mask',mask)
  16. total = cv2.countNonZero(mask)
  17. #通过阈值判断
  18. if bubble is None or total>bubble[0]:
  19. bubble = (total,j)
  20. #对比正确答案
  21. color = (0,0,255)
  22. k = ANSWER_KEY[q] #q代表现在检查的是第q个题
  23. #k是正确答案
  24. if k == bubble[1]:
  25. color = (0,255,0)
  26. correct += 1

 

Step6打印操作

  1. #绘图
  2. cv2.drawContours(warped,[cnts[k]],-1,color,3)
  3. score = (correct/5.0) * 100
  4. print("[INFO] score:{:.2f}%".format(score)) #这个%只是为了显示为60%,没有其他意思
  5. cv2.putText(warped,"{:.2f}%".format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,3)
  6. cv_show("Original",image)
  7. cv_show("Exam",warped)

 

 

 


完整代码

  1. import numpy as np
  2. import argparse
  3. import imutils
  4. import cv2
  5. ap = argparse.ArgumentParser()
  6. ap.add_argument("-i","--image",required=True,help="path to the input image")
  7. args = vars(ap.parse_args())
  8. # 正确答案
  9. ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
  10. def order_points(pts):
  11. # 一共4个坐标点
  12. rect = np.zeros((4, 2), dtype="float32")
  13. # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
  14. # 计算左上,右下
  15. s = pts.sum(axis=1)
  16. rect[0] = pts[np.argmin(s)]
  17. rect[2] = pts[np.argmax(s)]
  18. # 计算右上和左下
  19. diff = np.diff(pts, axis=1)
  20. rect[1] = pts[np.argmin(diff)]
  21. rect[3] = pts[np.argmax(diff)]
  22. return rect
  23. def four_point_transform(image, pts):
  24. # 获取输入坐标点
  25. rect = order_points(pts)
  26. (tl, tr, br, bl) = rect
  27. # 计算输入的w和h值
  28. widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
  29. widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
  30. maxWidth = max(int(widthA), int(widthB))
  31. heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
  32. heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
  33. maxHeight = max(int(heightA), int(heightB))
  34. # 变换后对应坐标位置
  35. dst = np.array([
  36. [0, 0],
  37. [maxWidth - 1, 0],
  38. [maxWidth - 1, maxHeight - 1],
  39. [0, maxHeight - 1]], dtype="float32")
  40. # 计算变换矩阵
  41. M = cv2.getPerspectiveTransform(rect, dst)
  42. warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
  43. # 返回变换后结果
  44. return warped
  45. def sort_contours(cnts, method="left-to-right"):
  46. reverse = False
  47. i = 0
  48. if method == "right-to-left" or method == "bottom-to-top":
  49. reverse = True
  50. if method == "top-to-bottom" or method == "bottom-to-top":
  51. i = 1
  52. boundingBoxes = [cv2.boundingRect(c) for c in cnts]
  53. (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
  54. key=lambda b: b[1][i], reverse=reverse))
  55. return cnts, boundingBoxes
  56. def cv_show(name, img):
  57. cv2.imshow(name, img)
  58. cv2.waitKey(0)
  59. cv2.destroyAllWindows()
  60. # 预处理
  61. image = cv2.imread(args["image"])
  62. contours_img = image.copy()
  63. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  64. blurred = cv2.GaussianBlur(gray, (5, 5), 0)
  65. cv_show('blurred', blurred)
  66. edged = cv2.Canny(blurred, 75, 200)
  67. cv_show('edged', edged)
  68. # 轮廓检测
  69. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
  70. cv2.CHAIN_APPROX_SIMPLE)[1]
  71. cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
  72. cv_show('contours_img', contours_img)
  73. docCnt = None
  74. # 确保检测到了
  75. if len(cnts) > 0:
  76. # 根据轮廓大小进行排序
  77. cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
  78. # 遍历每一个轮廓
  79. for c in cnts:
  80. # 近似
  81. peri = cv2.arcLength(c, True)
  82. approx = cv2.approxPolyDP(c, 0.02 * peri, True)
  83. # 准备做透视变换
  84. if len(approx) == 4:
  85. docCnt = approx
  86. break
  87. # 执行透视变换
  88. warped = four_point_transform(gray, docCnt.reshape(4, 2))
  89. cv_show('warped', warped)
  90. # Otsu's 阈值处理
  91. thresh = cv2.threshold(warped, 0, 255,
  92. cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  93. cv_show('thresh', thresh)
  94. thresh_Contours = thresh.copy()
  95. # 找到每一个圆圈轮廓
  96. cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
  97. cv2.CHAIN_APPROX_SIMPLE)[1]
  98. cv2.drawContours(thresh_Contours, cnts, -1, (0, 0, 255), 3)
  99. cv_show('thresh_Contours', thresh_Contours)
  100. questionCnts = []
  101. # 遍历
  102. for c in cnts:
  103. # 计算比例和大小
  104. (x, y, w, h) = cv2.boundingRect(c)
  105. ar = w / float(h)
  106. # 根据实际情况指定标准
  107. if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
  108. questionCnts.append(c)
  109. # 按照从上到下进行排序
  110. questionCnts = sort_contours(questionCnts,
  111. method="top-to-bottom")[0]
  112. correct = 0
  113. # 每排有5个选项
  114. for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
  115. # 排序
  116. cnts = sort_contours(questionCnts[i:i + 5])[0]
  117. bubbled = None
  118. # 遍历每一个结果
  119. for (j, c) in enumerate(cnts):
  120. # 使用mask来判断结果
  121. mask = np.zeros(thresh.shape, dtype="uint8")
  122. cv2.drawContours(mask, [c], -1, 255, -1) # -1表示填充
  123. cv_show('mask', mask)
  124. # 通过计算非零点数量来算是否选择这个答案
  125. mask = cv2.bitwise_and(thresh, thresh, mask=mask)
  126. cv_show('mask', mask)
  127. total = cv2.countNonZero(mask)
  128. # 通过阈值判断
  129. if bubbled is None or total > bubbled[0]:
  130. bubbled = (total, j)
  131. # 对比正确答案
  132. color = (0, 0, 255)
  133. k = ANSWER_KEY[q]
  134. # 判断正确
  135. if k == bubbled[1]:
  136. color = (0, 255, 0)
  137. correct += 1
  138. # 绘图
  139. cv2.drawContours(warped, [cnts[k]], -1, color, 3)
  140. score = (correct / 5.0) * 100
  141. print("[INFO] score: {:.2f}%".format(score))
  142. cv2.putText(warped, "{:.2f}%".format(score), (10, 30),
  143. cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
  144. cv2.imshow("Original", image)
  145. cv2.imshow("Exam", warped)
  146. cv2.waitKey(0)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/811975
推荐阅读
相关标签
  

闽ICP备14008679号