当前位置:   article > 正文

【opencv】答题卡判分实验

答题卡判分

实验环境: anaconda、jupyter notebook

实验用的包:numpy、matplotlib、opencv

实验的目的还是以熟悉图像的透视变换、轮廓特征提取为主要目的

关于如何判断答题卡被选项:通过几个覆盖备选项的掩膜与原二值图像想与,最终整个图像中白色像素点多的就是被选择的项

根据我的亲身体验cv2.bitwise_and(src1,src2)会把src1中与src2不同的点置为0!!!我排查了一下午,可恶啊!!!!

一、实验使用的图像

实验图片

一、引入包

  1. import cv2
  2. import numpy as np
  3. import matplotlib.pyplot as plt

二、读入图像预处理为二值图片

  1. answer_sheet = cv2.imread('answer_sheet.png')
  2. # 灰度图
  3. answer_sheet_gray = cv2.cvtColor(answer_sheet, cv2.COLOR_BGR2GRAY)
  4. # 二值图
  5. answer_sheet_bin = cv2.threshold(answer_sheet_gray, 127,255,cv2.THRESH_BINARY)[1]
  6. answer_sheet_bin_inv = cv2.threshold(answer_sheet_gray, 127,255,cv2.THRESH_BINARY_INV)[1]
  7. plt.imshow(answer_sheet_bin, 'gray')
  8. plt.show()

二值图

三、获取答题卡轮廓

  1. # 获取轮廓
  2. binary, answer_sheet_contours, hierarchy = cv2.findContours(answer_sheet_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
  3. answer_sheet_contour = None
  4. answer_sheet_contour_length = 0
  5. for c in answer_sheet_contours:
  6. # 做近似
  7. epsilon = 0.1 * cv2.arcLength(c, True)
  8. approx = cv2.approxPolyDP(c, epsilon, True)
  9. # 求周长
  10. answer_sheet_contour_length_temp = cv2.arcLength(approx,True)
  11. # 找周长最大的轮廓
  12. if answer_sheet_contour_length_temp > answer_sheet_contour_length:
  13. answer_sheet_contour_length = answer_sheet_contour_length_temp
  14. answer_sheet_contour = approx
  15. # 展示轮廓
  16. answer_sheet_temp = answer_sheet.copy()
  17. res = cv2.drawContours(answer_sheet_temp, [answer_sheet_contour], -1, (0,0,255),3)
  18. plt.imshow(cv2.cvtColor(res, cv2.COLOR_BGR2RGB))
  19. plt.show()

轮廓展示

四、构建透视变换的两个矩阵

  1. # 取到四个点
  2. answer_sheet_contour_deal = np.float32(answer_sheet_contour[:,0,:])
  3. A,B,C,D = answer_sheet_contour_deal
  4. # 在原始图像上画轮廓
  5. answer_sheet_temp = answer_sheet.copy()
  6. answer_sheet_contour_deal_temp = np.array([[np.int32(A)],[np.int32(B)],[np.int32(C)],[np.int32(D)]])
  7. cv2.drawContours(answer_sheet_temp, [answer_sheet_contour_deal_temp], -1, (0,255,0),10)
  8. plt.imshow(cv2.cvtColor(answer_sheet_temp, cv2.COLOR_BGR2RGB))
  9. plt.show()
  10. W1 = np.sqrt((A[0] - B[0]) ** 2 + (A[1] -B[1]) ** 2)
  11. W2 = np.sqrt((C[0] -D[0]) ** 2 + (C[1] -D[1]) ** 2)
  12. W = max(int(W1), int(W2))
  13. H1 = np.sqrt((A[0] - C[0]) ** 2 + (A[1] -C[1]) ** 2)
  14. H2 = np.sqrt((B[0] -D[0]) ** 2 + (B[1] -D[1]) ** 2)
  15. H = max(int(H1), int(H2))
  16. # 目标坐标
  17. dest = np.array([
  18. [0,0],
  19. [0,H],
  20. [W,H],
  21. [W,0]
  22. ], dtype=np.float32)
  23. # 在原始图像上画轮廓
  24. answer_sheet_temp = answer_sheet.copy()
  25. answer_sheet_contour_deal_temp = np.array([[np.int32(dest[0])],[np.int32(dest[1])],[np.int32(dest[2])],[np.int32(dest[3])]])
  26. cv2.drawContours(answer_sheet_temp, [answer_sheet_contour_deal_temp], -1, (0,255,0),10)
  27. plt.imshow(cv2.cvtColor(answer_sheet_temp, cv2.COLOR_BGR2RGB))
  28. plt.show()

透视变换矩阵

五、透视变换

  1. # 透视变换
  2. M = cv2.getPerspectiveTransform(answer_sheet_contour_deal, dest)
  3. answer_sheet_warped = cv2.warpPerspective(answer_sheet_gray, M, (int(W),int(H)))
  4. # 转为二值图
  5. answer_sheet_warped_bin = cv2.threshold(answer_sheet_warped, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
  6. plt.imshow(answer_sheet_warped_bin, cmap='gray')
  7. plt.show()

透视变换

六、获取每个答案的轮廓

  1. # 获取每个选项的外轮廓
  2. cnts = cv2.findContours(answer_sheet_warped_bin.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
  3. questionCnts = []
  4. for c in cnts:
  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. yt = np.sum(c[:,:,1])
  10. xt = np.sum(c[:,:,0])
  11. questionCnts.append(c)
  12. # 行排序
  13. for i in range(len(questionCnts) - 1):
  14. for j in range(len(questionCnts) - 1 - i):
  15. if questionCnts[j][0][0][1] > questionCnts[j + 1][0][0][1]:
  16. questionCnts[j],questionCnts[j + 1] = questionCnts[j + 1],questionCnts[j]
  17. # 列排序
  18. for i in range(5):
  19. temp_array = questionCnts[i * 5 : (i + 1) * 5]
  20. for j in range(len(temp_array) - 1):
  21. for k in range(len(temp_array) - 1 - j):
  22. if temp_array[k][0][0][0] > temp_array[k + 1][0][0][0]:
  23. temp_array[k],temp_array[k + 1] = temp_array[k + 1],temp_array[k]
  24. questionCnts[i * 5 : (i + 1) * 5] = temp_array
  25. # 展示排序结果
  26. answer_sheet_warped_bin_temp = answer_sheet_warped_bin.copy()
  27. for c in questionCnts:
  28. cv2.drawContours(answer_sheet_warped_bin_temp, np.array([c]),-1,(0,255,0),3)
  29. plt.imshow(answer_sheet_warped_bin_temp, cmap='gray')
  30. plt.show()

可以看到排序后按从左到右,从上到下的顺序排列轮廓

排序

七、统计分数

  1. sum = 0
  2. for i in range(5):
  3. max_count = 0
  4. choose = 0
  5. temp_array = questionCnts[i * 5 : (i + 1) * 5]
  6. for (j,c) in enumerate(temp_array):
  7. #掩码图
  8. # 全黑
  9. mask = np.ones(answer_sheet_warped_bin.shape, dtype='uint8')
  10. # 在全黑图上画出白色圈
  11. cv2.drawContours(mask,[c], -1, 255, -1)
  12. # plt.imshow(mask, cmap='gray')
  13. # plt.show()
  14. t = cv2.bitwise_and(answer_sheet_warped_bin,mask)
  15. # 去除t里的杂色点
  16. for x in range(len(t)):
  17. for y in range(len(t[i])):
  18. if t[x][y] == 1:
  19. t[x][y] = 0
  20. # 非0像素点最多的就是所选项
  21. total = cv2.countNonZero(t)
  22. if total > max_count:
  23. max_count = total
  24. choose = j
  25. # 假定答案为全A
  26. if choose == 0:
  27. sum += 20
  28. answer_sheet_temp = answer_sheet_warped.copy()
  29. cv2.putText(answer_sheet_temp, "{}%".format(sum), (0, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0,0,255),2)
  30. plt.imshow(answer_sheet_temp, 'gray')
  31. plt.show()

统计分数

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

闽ICP备14008679号