当前位置:   article > 正文

15- 答题卡识别及分数判定项目 (OpenCV系列) (项目十五)_答题卡 opencv 评分

答题卡 opencv 评分

项目要点

  • 图片读取 : img = cv2.imread('./images/test_01.png')
  • 灰度图:  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  • 高斯模糊:  blurred = cv2.GaussianBlur(gray, (5, 5), 0)     # 去噪点
  • 边缘检测:  edged = cv2.Canny(blurred, 75, 200)
  • 检测轮廓:  cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]    # 两个返回值 contours, hierarchy
  • 描绘轮廓cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 2)
  • 轮廓面积排序:  cnts = sorted(cnts, key = cv2.contourArea, reverse = True)
  • 计算轮廓周长:  perimeter = cv2.arcLength(c, True)
  • 得到近似轮廓:  approx = cv2.approxPolyDP(c, 0.15 * perimeter, True)
  • 计算变换矩阵:  M = cv2.getPerspectiveTransform(rect, dst)  # dst 为目标值
  • 通过坐标透视变换转换: warped = cv2.warpPerspective(image,M,(max_width, max_height))  # 注意传参
  • ret, thresh1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)  二值化处理图片
  • 计算非零个数, 先做与运算:  mask = cv2.bitwise_and(thresh, thresh, mask = mask)
  • 计算非零个数,选中的选项, 非零个数比较多:  total = cv2.countNonZero(mask)
  • 画出正确选项cv2.drawContours(warped, [cnts[k]], -1, color, 3)
  • cv2.putText(warped, f'{score:.2f}',(210,36), cv2.FONT_HERSHEY_SIMPLEX,0.9, (0,0,255), 2)  # 在图片上体现分数
  • 显示图片cv2.imshow(name, img)


1 提取答题卡部分图像内容

1.1 读取图片

  • 定义显示函数
  1. def cv_show(name, img):
  2. cv2.imshow(name, img)
  3. cv2.waitKey(0)
  4. cv2.destroyAllWindows()
  1. import cv2
  2. import numpy as np
  3. # 读图片,预处理
  4. img = cv2.imread('./images/test_01.png')
  5. # 转变为黑白图
  6. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  7. cv_show('img', img)

1.2 边缘检测

  • 定义显示函数
  1. # 高斯模糊去噪点
  2. blurred = cv2.GaussianBlur(gray, (5, 5), 0)
  3. # 边缘检测
  4. edged = cv2.Canny(blurred, 75, 200)
  5. cv_show('img', edged)

        

1.3 检测轮廓

  1. # 检测轮廓
  2. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
  3. # 画轮廓会修改原图
  4. contours_img = img.copy()
  5. cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 2)
  6. cv_show('contours_img', contours_img)

1.4 找出答题卡轮廓

  • 本次其实原本也只检测到了一个轮廓.

  1. # 确保拿到的轮廓是答题卡的轮廓
  2. if cnts:
  3. # 对轮廓面积排序
  4. cnts = sorted(cnts, key = cv2.contourArea, reverse = True)
  5. # 遍历每一个轮廓
  6. for c in cnts:
  7. # 计算周长
  8. perimeter = cv2.arcLength(c, True)
  9. # 得到近似轮廓
  10. approx = cv2.approxPolyDP(c, 0.15 * perimeter, True)
  11. # 应该只剩下四个角的坐标
  12. print(len(c))
  13. print(len(approx))
  14. if len(approx) == 4:
  15. # 保存approx
  16. docCnt = approx
  17. # 找到后直接退出
  18. break
  19. print(docCnt)
  20. contours_img = img.copy()
  21. cv2.drawContours(contours_img, docCnt, -1, (0, 0, 255), 10)
  22. cv_show('contours_img', contours_img)

     

 1.5 透视变换

  1. # 找到进行透视变换的点
  2. # 要求变换矩形的四个坐标
  3. # 先对获取到的4个角点坐标排序
  4. # 排序功能封装为一个函数
  5. def order_points(pts):
  6. # 创建全为0 的矩阵, 来接收找到的坐标
  7. rect = np.zeros((4, 2), dtype = 'float32')
  8. s = pts.sum(axis = 1)
  9. # 左上角的坐标一定是X,Y相加最小的,右下为最大的
  10. rect[0] = pts[np.argmin(s)]
  11. rect[2] = pts[np.argmax(s)]
  12. # 右上角的x,y 相减的差值一定是最小的
  13. # 左下角的x,y 相减,差值一定是最大的
  14. diff = np.diff(pts, axis = 1)
  15. rect[1] = pts[np.argmin(diff)]
  16. rect[3] = pts[np.argmax(diff)]
  17. return rect
  1. # 把透视变换功能封装为一个函数
  2. def four_point_transform(image, pts):
  3. # 对输入的四个坐标排序
  4. rect = order_points(pts) # 调取函数: order_points
  5. (tl, tr, br, bl) = rect
  6. # 空间中两点的距离
  7. widthA = np.sqrt((br[0] - bl[0])** 2 + (br[1] - bl[1]) ** 2)
  8. widthB = np.sqrt((tr[0] - tl[0])** 2 + (tr[1] - tl[1]) ** 2)
  9. max_width = max(int(widthA), int(widthB))
  10. heightA = np.sqrt((tr[0] - br[0])** 2 + (tr[1] - br[1]) ** 2)
  11. heightB = np.sqrt((tl[0] - bl[0])** 2 + (tl[1] - bl[1]) ** 2)
  12. max_wdith = (int(widthA), int(widthB))
  13. max_height =max(int(heightA), int(heightB))
  14. # 构造变换后的空间坐标
  15. dst = np.array([
  16. [0, 0],
  17. [max_width - 1, 0],
  18. [max_width - 1, max_height -1],
  19. [0, max_height - 1]], dtype = 'float32')
  20. # 计算变换矩阵
  21. M = cv2.getPerspectiveTransform(rect, dst) # dst 为目标值
  22. # 透视变换
  23. warped = cv2.warpPerspective(image, M, (max_width, max_height)) # 注意传参
  24. return warped
  25. # 进行透视变换
  26. warped = four_point_transform(gray, docCnt.reshape(4, 2))
  27. cv_show('warped', warped)

2 处理答题卡

2.1 二值化图像  (两个返回值)

  1. # 二值化 # +inv 黑白颠倒
  2. # ret, thresh1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)
  3. thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  4. cv_show('thresh', thresh)

2.2 找每一个圆圈的轮廓

  1. # 找每一个圆圈的轮廓
  2. cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
  3. thresh_contours = thresh.copy()
  4. cv2.drawContours(thresh_contours, cnts, -1, 255, 3) # 255表示填充白色,填充为0时为后图
  5. cv_show('thresh_contours', thresh_contours)

 

 2.3 找出特定轮廓  (选项)

  1. # 遍历所有轮廓, 找到特点宽高和特定比例的轮廓, 及圆圈的轮廓
  2. question_cnts = []
  3. for c in cnts:
  4. # 找到轮廓的外接矩形
  5. (x, y, w, h) = cv2.boundingRect(c)
  6. # 计算高宽比
  7. ar = w / float(h)
  8. # 根据实际情况制定标准
  9. if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
  10. question_cnts.append(c)
  11. print(len(question_cnts)) # 25

3 分数判定

3.1 轮廓排序封装函数

  1. # 设计做法看轮廓,找出选择的答案
  2. # 轮廓排序封装函数
  3. def sort_contours(cnts, method = 'left-to-right'):
  4. reverse = False
  5. # 排序的时候取x轴数据, i= 0, 取y轴数据 i=1
  6. i = 0
  7. if method == 'right-to-left' or method == 'bottom-to-top':
  8. reverse = True
  9. if method == 'top-to-bottom' or method == 'bottom-to-top':
  10. i = 1
  11. # 计算每个轮廓的外接矩形
  12. bounding_boxes = [cv2.boundingRect(c) for c in cnts]
  13. # 计算轮廓的垂直边界最小矩形,矩形是与图像上下边界平行的
  14. (cnts, bounding_boxes) = zip(*sorted(zip(cnts, bounding_boxes),
  15. key = lambda a: a[1][i], reverse = reverse))
  16. # a 表示zip(cnt, bounding_boxes)
  17. # rint(bounding_boxes[1])
  18. return cnts, bounding_boxes
  19. # 按照从上到下的排序 :question_cnts
  20. question_cnts = sort_contours(question_cnts, method = 'top-to-bottom')[0]
  21. print(len(question_cnts)) # 25

3.2 找出选择答案, 然后评分

  1. # 正确答案
  2. ANSWER_KEY = {0:1, 1:2, 2:1, 3:2, 4:1} # 1,4,0,2,1
  3. correct = 0
  4. # enumerate,遍历了所有元素,并从零开始的计为每个元素生成索引, q为index
  5. for (q, i) in enumerate(np.arange(0, 25, 5)):
  6. first_num = True
  7. # 每次取出5个,再按x大小排序
  8. # 每5个一组进行排序, 0表示cnts 原轮廓, 默认排序'left-to-right'
  9. cnts = sort_contours(question_cnts[i: i+ 5])[0]
  10. print('-------------------', len(cnts)) # 5个选项
  11. bubbled = None # 冒泡
  12. # 遍历每一个结果
  13. for (j, c) in enumerate(cnts):
  14. #使用掩膜,即mask
  15. mask = np.zeros(thresh.shape, dtype = 'uint8')
  16. cv2.drawContours(mask, [c], -1, 255, -1) # 255表示填充白色
  17. # 计算非零个数, 先做与运算
  18. mask = cv2.bitwise_and(thresh, thresh, mask = mask)
  19. # cv_show('mask', mask)
  20. # 计算非零个数,选中的选项, 非零个数比较多, 没选中的非零个数小一些
  21. total = cv2.countNonZero(mask)
  22. # print('******', total,j)
  23. if bubbled is None or total > bubbled[0]:
  24. bubbled = (total, j) # 正确选项的 total 值较高
  25. color = (0, 0 ,255)
  26. k = ANSWER_KEY[q] # enumerate 的序列值
  27. # print(len(cnts))
  28. # 判断是否正确
  29. if k == bubbled[1] and first_num == True:
  30. correct += 1
  31. first_num = False
  32. print('正确选项: ',k)
  33. cv2.drawContours(warped, [cnts[k]], -1, color, 3) # 画出正确选项
  34. # 计算得分
  35. score = int((correct / 5)* 100)
  36. print(f'score:{score:.2f} 分')
  37. warped_copy = warped.copy()
  38. cv2.putText(warped_copy, f'{score:.2f}', (210, 36), cv2.FONT_HERSHEY_SIMPLEX,
  39. 0.9, (0, 0, 255), 2)
  40. cv_show('result', warped_copy)

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

闽ICP备14008679号