1.H = cv2.getPerspectiveTransform(rect, transform_axes) 获得投射变化后的H矩阵
参数说明:rect表示原始的位置左上,右上,右下,左下, transform_axes表示变换后四个角的位置
2.cv2.warpPerspective(gray, H, (width, height)) 根据H获得变化后的图像
参数说明: gray表示输入的灰度图像, H表示变化矩阵,(width, height)表示变换后的图像大小
3. cv2.approxPloyDP(contour, length*0.02, True) # 计算轮廓的近似值
参数说明:contour表示输入的轮廓值, length表示轮廓的大小, True表示是否闭合
4. cv2.threshold(wrap, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_AUTO) # 进行图像的二值化操作
参数说明: wrap表示输入图片,0表示最小值,255表示最大值,THRESH_BINARY_INV | cv2.THRESH_AUTO表示方法,即大于最小阈值的为0, 小于最小阈值的为255
5. cv2.draw_contours(image, [contour], -1, 255, -1) # 对轮廓进行画图
参数说明:image表示输入图片. [contour] 表示单个轮廓, -1表示轮廓的index, 255表示颜色,-1表示对轮廓进行填充
6. cv2.bitwise(image, image, mask=mask) # 与判断,如果两个都大于1才为1,否则都为0
参数说明:image表示输入图片,image表示输出图片, mask=mask表示掩膜
7. cv2.countNoneZeros(mask) # 用于计算图像中不等于0的像素点
参数说明:mask表示经过图像与掩膜的与操作以后的图像
8.cv2.putText(image, text, org, font, font_scale, color, ticking) # 在图像上加上文字
参数说明:image表示输入图片,text表示文字,org表示文字位置,font表示字体的格式,font_scale表示字体的胖瘦,color表示颜色,ticking表示粗细
答题卡识别判卷, 需要做的就是将画圈圈的地方识别出来。
原始图像 涂了铅笔的比没涂白色部分要多,使用掩膜进行逐个遍历
第一部分:对图片进行透视变化,使得图片中的答题卡可以凸显出来
第一步:读入图片
第二步:使用cv2.gray() 进行灰度值的变化
第三步:使用cv2.GuassianBlur() 进行高斯滤波操作
第四步:使用cv2.Canny找出图片的边缘信息,为了下一步找出轮廓值做准备
第五步:使用cv2.findCountor() 找出图片的轮廓值,使用key = cv2.ContourArea对轮廓值进行排序
第六步:对轮廓值进行遍历,使用cv2.approxPolyDP(c, 0.02*lenght, True) 获得轮廓的近似值,如果近似值的维度为4,即为最外层答题卡的四个角的维度,则跳出循环
第七步:构造函数,对求得的轮廓值,根据其位置信息,将其变成左上,右上,右下,左上的形式的rect
第八步:计算width,height,计算出变换后的四个角的位置(transform_axes)
第九步:使用H = cv2.getPerspectiveTransform(rect, transform_axes) rect为变化前的位置,transform_axes为变化后的位置,以x,y表示,获得变化矩阵H
第十步:使用cv2.warpPerspective(thresh, H, (width, height)) 获得最终的变化结果
第二步:根据正确答案和图卡的答案来判断正确率
第一步:根据上述获得的wrapped,进行二值化操作, 使用cv2.COLOR_BINARY_INV将图了颜色的地方变成白色
第二步:使用cv2.findContours进行轮廓检测
第三步:对获得的轮廓,使用外接矩形cv2.boundingRect的(x, y, w, h)对轮廓进行筛选,找出答案的轮廓
第四步:对轮廓进行从上到下的排序
第五步:分别对每一行进行遍历,np.arange(0, len(question_cnts, 5)), 遍历每一行的轮廓值,根据图像构造掩膜mask图像,使用cv2.draw_contours在掩膜上画出对应位置的白色轮廓, 使用cv2.bitwise_and 获得原始图像当前位置等于1的点,使用cv2.countNoneZero计算出不是零的点,我们发现涂了颜色的要比没涂颜色的要大
第六步:计算出颜色值最大的位置和Nonezeros的值,correct+= 1
第七步:使用cv2.draw_contours在正确位置上进行画图操作
第八步:使用cv2.putText将正确率打印在图像上
代码:
# 第一部分代码:
import cv2 import numpy as np import matplotlib.pyplot as plt # 正确答案 ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1} def cv_show(name, image): cv2.imshow(name, image) cv2.waitKey(0) cv2.destroyAllWindows() def four_point_transform(image, dotCnt): # 第七步:按照轮廓近似的位置,将结果变成左上,右上,右下,左下的位置 rect = sort_dotCnt(dotCnt) (tl, tr, br, bl) = rect # 第八步:计算轮廓的宽和长,构造出变化后四个角的位置 widthA = np.sqrt(((tl[0]-tr[0])**2) + ((tl[1]-tr[1])**2)) widthB = np.sqrt(((bl[0]-br[0])**2) + ((bl[1] - br[1])**2)) widthMax = max(int(widthB), int(widthA)) heightA = np.sqrt(((tl[0]-bl[0])**2) + ((tl[1]-bl[1])**2)) heightB = np.sqrt(((tr[0]-br[0])**2) + ((tr[1] - br[1])**2)) heightMax = max(int(heightA), int(heightB)) # 四个角的位置信息 transform_axis = np.array([ [0, 0], [widthMax-1, 0], [widthMax-1, heightMax-1], [0, heightMax-1], ], dtype='float32') # 第九步:使用cv2.getPerspectiveTransform构造变化矩阵H H = cv2.getPerspectiveTransform(rect, transform_axis) # 第十步:使用cv2.warpPerspective()获得变化后的矩阵 wrapped = cv2.warpPerspective(image, H, (widthMax, heightMax)) cv_show('wrapped', wrapped) return wrapped def sort_dotCnt(kps): rect = np.zeros((4, 2), dtype='float32') s = kps.sum(axis=1) # 找出左上和右下 rect[0] = kps[np.argmin(s)] rect[2] = kps[np.argmax(s)] # 找出右上和左下 diff = np.diff(kps, axis=1) rect[1] = kps[np.argmin(diff)] rect[3] = kps[np.argmax(diff)] return rect def sorted_contours(cnt, model='left-to-right'): if model == 'top-to-bottom': cnt = sorted(cnt, key=lambda x:cv2.boundingRect(x)[1]) elif model == 'left-to-right': cnt = sorted(cnt, key=lambda x:cv2.boundingRect(x)[0]) return cnt # 第一部分:使用投射变化进行图像的转换, 将答题卡放在图片的正中央 # 第一步:读入图片 image = cv2.imread('images/test_01.png') # 第二步:进行灰度化操作 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cv_show('gray', gray) # 第三步: 进行高斯变化 Guass = cv2.GaussianBlur(gray, (5, 5), 0) # 第四步:使用cv2.Canny 找出图像的边缘部分 edges = cv2.Canny(Guass, 50, 200) cv_show('edges', edges) # 第五步:使用cv2.findCountours找出图像的轮廓, 并对轮廓进行排序 cnt = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] ret = cv2.drawContours(image.copy(), cnt, -1, (0, 0, 255), 2) cv_show('ret', ret) dotCnt = None if len(cnt) > 0: cnt = sorted(cnt, key=cv2.contourArea, reverse=True) # 第六步: 遍历轮廓,获得cv2.approxPolyDP获得轮廓的近似值, 如果approx=4,即为填图卡的轮廓近似 for c in cnt: length = cv2.arcLength(c, True) # 获得轮廓的近似值 approx = cv2.approxPolyDP(c, 0.02*length, True) # 如果轮廓的维度是4,即由4个点组成,就是一个方框,那就是外面的方框 if len(approx) == 4: dotCnt = approx break wrapped = four_point_transform(gray, dotCnt.reshape(4, 2)) cv_show('wrapped', wrapped)
原始图片 灰度变化后图片 边缘检测的图片 检测到的轮廓图 最终透射变化结果
# 第二部分代码:
# 第二部分:对变化后的wrapped进行操作,构造mask根据有无填涂的特性,进行位置的计算 #第一步:对图像进行二值化操作 thresh = cv2.threshold(wrapped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] cv_show('thresh', thresh) # 第二步:对图像进行轮廓检测 cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] # 第三步:使用cv2.boudingRect将答案信息的轮廓进行筛选 questionCnts = [] for c in cnts: (x, y, w, h) = cv2.boundingRect(c) arc = w / float(h) # 根据实际情况找出合适的轮廓 if w > 20 and h > 20 and arc >= 0.9 and arc<=1.1: questionCnts.append(c) # 第四步:将轮廓进行从上到下的排序 questionCnts = sorted_contours(questionCnts, model='top-to-bottom') # 第五步:对每一行的轮廓进行遍历,构造掩码,对每一行答案的轮廓进行遍历,使用cv2.countNoneZeros()计算出涂了颜色的答案,并计算最终的结果 correct = 0 for j, qC in enumerate(np.arange(0, len(questionCnts), 5)): questionCnt = questionCnts[qC:qC+5] # 进行排序 questionCnt = sorted_contours(questionCnt) questionCnt = np.array(questionCnt) # 每个问题的答案 bubbled = None for k, c in enumerate(questionCnt): # 根据轮廓值构造掩膜, 因为有涂答案的地方是白色的 mask = np.zeros(thresh.shape, np.uint8) # 使用cv2.drawContours画出轮廓值 cv2.drawContours(mask, [c], -1, 255, -1) cv_show('mask', mask) mask = cv2.bitwise_and(thresh, thresh, mask=mask) # 第六步:使用cv2.countNoneZero计算出白色最多的位置 Nonezero = cv2.countNonZero(mask) if bubbled == None or Nonezero > bubbled[0]: bubbled = (Nonezero, k) question_answer = int(bubbled[1]) answer_True = ANSWER_KEY[j] # 如果结果和正确结果相同,就+1 if question_answer == answer_True: correct += 1 # 第七步:对正确答案的轮廓进行画图操作 cv2.drawContours(wrapped, [questionCnt[answer_True]], -1, 0, 2) cv_show('wrapped', wrapped) score = (correct / 5) * 100 # 第八步:使用cv2.putText将正确率打印在图上 cv2.putText(wrapped, 'correct_score:%d' %score, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, 0, 2) cv2.imshow('orginal', image) cv2.imshow('wrapped', wrapped) cv2.waitKey(0) cv2.destroyAllWindows()
Binary_Inv二值化的结果 draw_contours画出掩膜轮廓 最终的结果图