两个idea:
1、霍夫变换找圆在复杂环境并不理想,可以找轮廓,然后限制外接矩形w和h来排除
2、坐标排序时,可能同一行的x有些许误差,则可以这样排序(分两次排序):假设一行5个,
先从上向下排序,则每5个即为一道题的选项,虽然是乱序的,但只要再对每5个从左向右排序即可
主要判断论述:
import cv2 import numpy as np import math def show(img): cv2.imshow('name', img) cv2.waitKey() cv2.destroyAllWindows() # def equal(x, y): #如果相差在15以内,则说明相等 # if abs(x - y) <= 15: # return 1 # return 0 # # def cnt_sort(docCnt): # for i in range(len(docCnt) - 1): # for j in range(len(docCnt) - i - 1): # if equal(docCnt[j][1], docCnt[j + 1][1]) == 1: # if(docCnt[j][0] > docCnt[j + 1][0]): # tmp = docCnt[j + 1].copy() # docCnt[j + 1] = docCnt[j] # docCnt[j] = tmp # else: # if docCnt[j][1] > docCnt[j + 1][1]: # tmp = docCnt[j + 1].copy() # docCnt[j + 1] = docCnt[j] # docCnt[j] = tmp # return docCnt def cnt_sort(docCnt): docCnt = sorted(docCnt, key = lambda x : x[1]) ret = [] for i in range(0, len(docCnt), 2): cnts = docCnt[i : i + 2] cnts = sorted(cnts, key = lambda x : x[0]) ret += cnts return ret def four_point_transform(img, docCnt): # print(docCnt) docCnt = cnt_sort(docCnt) docCnt = np.array(docCnt, dtype = 'float32') coner1, coner2, coner3, coner4 = docCnt # print(docCnt) w = int(max(math.sqrt((coner1[0] - coner2[0]) ** 2 + (coner1[1] - coner2[1]) ** 2), math.sqrt((coner3[0] - coner4[0]) ** 2 + (coner3[1] - coner4[1]) ** 2))) h = int(max(math.sqrt((coner1[0] - coner3[0]) ** 2 + (coner1[1] - coner3[1]) ** 2), math.sqrt((coner2[0] - coner4[0]) ** 2 + (coner2[1] - coner4[1]) ** 2))) rightCnt = np.array([[0, 0], [w, 0], [0, h], [w, h]], dtype = 'float32') # print(docCnt, rightCnt) transform_matrix = cv2.getPerspectiveTransform(docCnt, rightCnt) #输入类型需要一样,都是float32就行 img = cv2.warpPerspective(img, transform_matrix, (w, h)) #w和h需要是整数 return img def questionCnt_sort(questionCnt, flag): # flag为1就是从上向下,0就是从左到右 boundingbox = [cv2.boundingRect(cnt) for cnt in questionCnt] questionCnt, boundingbox = zip(*sorted(zip(questionCnt, boundingbox), key = lambda x : x[1][flag])) return questionCnt if __name__ == '__main__': img = cv2.imread('C:/Users/WTSRUVF/Downloads/card/answer sheet/images/test_01.png') img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) img_gray = cv2.GaussianBlur(img_gray, (5, 5), 0) edged = cv2.Canny(img_gray, 75, 200) # show(edged) contours = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] contours = sorted(contours, key = cv2.contourArea, reverse = True) docCnt = None for cnt in contours: peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, peri * 0.02, True) if len(approx) == 4: docCnt = approx break img_gray_copy = img_gray.copy() # img_copy = cv2.drawContours(img_copy, [docCnt], -1, (0, 0, 255), 2) img_s = four_point_transform(img_gray_copy, docCnt.reshape(4, 2)) # show(img_s) thresh = cv2.threshold(img_s, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] threshContours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] questionCnt = [] for cnt in threshContours: (x, y, w, h) = cv2.boundingRect(cnt) ar = w / float(h) if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1: questionCnt.append(cnt) questionCnt = questionCnt_sort(questionCnt, 1) #先从上向下排序,则每5个即为一道题的选项,虽然是乱序的,但只要再对每5个从左向右排序即可 for k, i in enumerate(range(0, len(questionCnt), 5)): cnts = questionCnt_sort(questionCnt[i : i + 5], 0) answer = None for j, cnt in enumerate(cnts): mask = np.zeros(img_s.shape, dtype = 'uint8') mask = cv2.drawContours(mask, [cnt], -1, 255, -1) #-1为填充 mask = cv2.bitwise_and(thresh, thresh, mask = mask) total = cv2.countNonZero(mask) if answer == None or answer[0] < total: answer = (total, j + 1) print(answer[1])
数据集和代码链接:https://pan.baidu.com/s/1SMJ76fL1sbFmWUGurAjoQQ
提取码:qgz6