答题卡一般是怎么识别的,哪种答题卡识别不了

首页 > 教育培训 > 作者:YD1662023-05-07 01:19:34


我们检测出来外面轮廓之后,接下来想把整个试卷拿出来,做一个透视变换操作。拿到轮廓的坐标。

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 = approx break 123456789101112⭐️二、透视变换

这里我们把轮廓按照面积做了一个排序,然后遍历排序后的轮廓。cv2.approxPolyDP主要功能是把一个连续光滑曲线折线化。如果轮廓检测出来是四个点组成的,那么我们就把他给拿出来。

warped = four_point_transform(gray, docCnt.reshape(4, 2)) cv_show('warped',warped) 12

其中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 12345678910111213141516171819202122232425

首先我们用order_points把四个点的坐标提取出来了。然后我们计算一下透视变换的w和h。选择出来透视变换之后的坐标结果。然后我们基于这两个结果求出一个中间矩阵M,然后使用一个当前矩阵*中间矩阵M就得到了透视变换之后的结果。
其中order_points函数是:

def order_points(pts): 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 rect 123456789101112

对着四个点进行操作,如果相加那么肯定是左上的点是最小的,右下的点是最大的。那么我们把他提取出来。然后在做差,那么很明显就是右上是最大的,左下是最小的,这样我们就把四个点给提取出来了。然后返回回去。

答题卡一般是怎么识别的,哪种答题卡识别不了(5)

⭐️三、阈值处理

我们拿到了透视变换的结果之后,对透视结果进行操作,首先我们进行一次阈值处理。这里的阈值处理如下:

thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] cv_show('thresh',thresh) 123

**这里不是选择阈值为0,再次强调!!!!**而是让计算机随机的给我们提供一个阈值合适的数值。然后进行阈值处理。得到的结果是:

答题卡一般是怎么识别的,哪种答题卡识别不了(6)


然后对结果做一次轮廓检测。

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3) cv_show('thresh_Contours',thresh_Contours) 1234

还是同上面做轮廓检测的结果一致。得到的结果是这样:

答题卡一般是怎么识别的,哪种答题卡识别不了(7)

⭐️四、过滤干扰项

然后我们过滤掉一些干扰项。

questionCnts = [] for c in cnts: (x, y, w, h) = cv2.boundingRect(c) ar = w / float(h)#ar定义一个长宽比 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) 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) 123456789101112131415161718192021222324252627282930313233343536

首先我们定义一个长宽比,然后我们根据长宽比和wh对实际项目进行一个过滤轮廓操作。首先我们进行一次竖直方向的一个排序。分为一排一排的,然后我们在遍历每一排,对每一排进行一个排序。然后我们使用一个掩码和正确答案做一个与操作,然后通过判断其中非0点的个数来判断是都是正确答案。因为涂卡的地方我们处理之后的结果都是白色像素点较多。选择出来结果之后我们和正确答案进行一次对比。正确答案是我们提前定义好的:通过对比索引,我们就可以得到结果。

答题卡一般是怎么识别的,哪种答题卡识别不了(8)

上一页1234下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.