当前位置:   article > 正文

计算机视觉知识点-答题卡识别

答题卡识别

 

         之前跟同事聊过答题卡识别的原理,自己调研了一下,高考那种答题卡是通过一个专门的答题卡阅读器进行识别的,采用红外线扫描答题卡,被涂过2B碳的区域会被定位到,再加上一些矫正逻辑就能试下判卷的功能.这种方法的准确度很高.淘宝上查了下光标机的误码率是0.9999999(7个9).见下图.

准确率高的离谱,机器长这个样子

这台机器的价格是15000, 有些小贵.  如果我想用简单的视觉方法做这个任务,不用外红设备,我应该怎么做呢.是不是用万能的yolo来做, 检测方形或者圆形的黑点行吗?这个方案当然可以,但是比较费劲,且效果不一定好.

我今天要说的是采用传统的计算机视觉的方法来做,几行代码就能达到99%的准确率,虽然没有光标机的准确率高,但是我们只是玩一玩.我参考了这个这篇博客, 博主的名字叫Adrian, Adian的博客专注计算机视觉,推荐大家看看.

传统的方法大概有着几个步骤:

1 检测到答题纸的位置, 如下图

2 进行拉伸变化,把答题纸拉成矩形

3 检测黑色的区域和白色的区域

4 答案判定.

下面是一个python实现

  1. # import the necessary packages
  2. from imutils.perspective import four_point_transform
  3. from imutils import contours
  4. import numpy as np
  5. import argparse
  6. import imutils
  7. import cv2
  8. # construct the argument parse and parse the arguments
  9. ap = argparse.ArgumentParser()
  10. ap.add_argument("-i", "--image", default='image.jpg',
  11. help="path to the input image")
  12. args = vars(ap.parse_args())
  13. # define the answer key which maps the question number
  14. # to the correct answer
  15. ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
  16. # load the image, convert it to grayscale, blur it
  17. # slightly, then find edges
  18. image = cv2.imread(args["image"])
  19. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  20. blurred = cv2.GaussianBlur(gray, (5, 5), 0)
  21. edged = cv2.Canny(blurred, 75, 200)
  22. # find contours in the edge map, then initialize
  23. # the contour that corresponds to the document
  24. cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
  25. cv2.CHAIN_APPROX_SIMPLE)
  26. cnts = imutils.grab_contours(cnts)
  27. docCnt = None
  28. # ensure that at least one contour was found
  29. if len(cnts) > 0:
  30. # sort the contours according to their size in
  31. # descending order
  32. cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
  33. # loop over the sorted contours
  34. for c in cnts:
  35. # approximate the contour
  36. peri = cv2.arcLength(c, True)
  37. approx = cv2.approxPolyDP(c, 0.02 * peri, True)
  38. # if our approximated contour has four points,
  39. # then we can assume we have found the paper
  40. if len(approx) == 4:
  41. docCnt = approx
  42. break
  43. # apply a four point perspective transform to both the
  44. # original image and grayscale image to obtain a top-down
  45. # birds eye view of the paper
  46. paper = four_point_transform(image, docCnt.reshape(4, 2))
  47. warped = four_point_transform(gray, docCnt.reshape(4, 2))
  48. # apply Otsu's thresholding method to binarize the warped
  49. # piece of paper
  50. thresh = cv2.threshold(warped, 0, 255,
  51. cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
  52. # find contours in the thresholded image, then initialize
  53. # the list of contours that correspond to questions
  54. cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
  55. cv2.CHAIN_APPROX_SIMPLE)
  56. cnts = imutils.grab_contours(cnts)
  57. questionCnts = []
  58. # loop over the contours
  59. for c in cnts:
  60. # compute the bounding box of the contour, then use the
  61. # bounding box to derive the aspect ratio
  62. (x, y, w, h) = cv2.boundingRect(c)
  63. ar = w / float(h)
  64. # in order to label the contour as a question, region
  65. # should be sufficiently wide, sufficiently tall, and
  66. # have an aspect ratio approximately equal to 1
  67. if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1:
  68. questionCnts.append(c)
  69. # sort the question contours top-to-bottom, then initialize
  70. # the total number of correct answers
  71. questionCnts = contours.sort_contours(questionCnts,
  72. method="top-to-bottom")[0]
  73. correct = 0
  74. # each question has 5 possible answers, to loop over the
  75. # question in batches of 5
  76. for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
  77. # sort the contours for the current question from
  78. # left to right, then initialize the index of the
  79. # bubbled answer
  80. cnts = contours.sort_contours(questionCnts[i:i + 5])[0]
  81. bubbled = None
  82. # loop over the sorted contours
  83. for (j, c) in enumerate(cnts):
  84. # construct a mask that reveals only the current
  85. # "bubble" for the question
  86. mask = np.zeros(thresh.shape, dtype="uint8")
  87. cv2.drawContours(mask, [c], -1, 255, -1)
  88. # apply the mask to the thresholded image, then
  89. # count the number of non-zero pixels in the
  90. # bubble area
  91. mask = cv2.bitwise_and(thresh, thresh, mask=mask)
  92. total = cv2.countNonZero(mask)
  93. # if the current total has a larger number of total
  94. # non-zero pixels, then we are examining the currently
  95. # bubbled-in answer
  96. if bubbled is None or total > bubbled[0]:
  97. bubbled = (total, j)
  98. # initialize the contour color and the index of the
  99. # *correct* answer
  100. color = (0, 0, 255)
  101. k = ANSWER_KEY[q]
  102. # check to see if the bubbled answer is correct
  103. if k == bubbled[1]:
  104. color = (0, 255, 0)
  105. correct += 1
  106. # draw the outline of the correct answer on the test
  107. cv2.drawContours(paper, [cnts[k]], -1, color, 3)
  108. # grab the test taker
  109. score = (correct / 5.0) * 100
  110. print("[INFO] score: {:.2f}%".format(score))
  111. cv2.putText(paper, "{:.2f}%".format(score), (10, 30),
  112. cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
  113. cv2.imshow("Original", image)
  114. cv2.imshow("Exam", paper)
  115. cv2.waitKey(0)

测试图片

代码运行方法:

python test.py --image images/test.jpg

需要注意的事:

1 为什么不用圆检测?

圆检测代码有些复杂,而且要是涂抹的不是圆,或者不像圆该就难办了.

2 如何扩展成多选题?

修改是否被涂黑的判定逻辑就可以.

传统的计算机视觉是不是很好玩,传统的计算机视觉算法在一些限定场景下,效果还是不错的,比如室内光照恒定的情况,相机角度固定的情况.

感慨:

刚才看了下,我的上一篇博客还是在2015年,5年过去了,物是人非啊.2015年的时候我换了一个城市工作,压力很大,几年来,有顺心的事,也有烦心的事,不过总体还不错,祝福一下自己.

最后的话:

我是一个工作10年的程序员,工作中经常会遇到需要查一些关键技术,但是很多技术名词的介绍都写的很繁琐,为什么没有一个简单的/5分钟能说清楚的博客呢. 我打算有空就写写这种风格的指南文档.CSDN上搜蓝色的杯子, 没事多留言,指出我写的不对的地方,写的排版风格之类的问题,让我们一起爱智求真吧.wisdomfriend@126.com是我的邮箱,也可以给我邮箱留言.

 

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

闽ICP备14008679号