赞
踩
pip install --index-url https://pypi.douban.com/simple package
""" 程序思路: 1.首先参考网上试卷检测的相关例程,模仿着来,网上大部分检测部分基本用的Canny算法, 但是我用了试过调Canny函数的参数、以及通过图像增强进行边缘检测,但是效果不佳,出现断断续续的效果,不能用于检测图片。 2.出现这种情况,我对各种边缘检测算法进行了尝试,其中sobel的边缘检测算法效果比较好,laplacian算法较为一般。 3.当轮廓提取出来后,下一步就是对轮廓进行了框选,我一开始的想法只是外接矩形,或者一个正接的矩形,先是保留自己的看法, 参考网上,基本都是Canny边缘检测算法后进行霍夫直线的检测。我也就跟着进行霍夫直线试了很多次,但是效果一直不佳, 一方面是我答题卷四条边直线度的问题,由于不是纯的直线,所以进行直线检测的时候就会出现很多杂乱的曲线。网上的例子能检测出直线, 然后进行求四个交点,方便后面进行透视变换。由于这个方法行不通,所以准备换另一种方法进行检测框。 4.这个时候我就参数了二值化的两种方法,其中就是全局二值化,另一个是局部阈值化,全局二值化只是用一个阈值进行分割,局部阈值化能进行手动设置图片的阈值个数 二值化之后进行进行框选了,这个时候我就开始按照一开始的想法进行狂框选了,用的是最小外接矩形,与正接矩形,但是因为框选出来的误差太大,除了试卷还有很多背景 后面进行发现最大轮廓————>填充颜色————>sobel边缘检测(看看会不胡边缘会端正了很多,但是结果跟没天赐差不多) ————>发现可以用拟合矩形进行检测。于是我就尝试了利用sobel+拟合矩形的方式进行检测,结果效果不错,但是存在另一个问题,一方面答题卷没有完全拍摄到的时候就会出现框选不出来。、 5.改进,为什么不直接用二值化直接检测答题卷呢,最后直接二值化+矩形拟合,检测多张图片后效果良好。 """ #遇到问题:1-3图片角度跟7——16的图片角度不一样,导致识别方向出现了问题,故可以利用获取坐标点进行排序。 # 功能:利用二值化+矩形拟合 # 链接:https://blog.csdn.net/xue_csdn/article/details/97616177 # 矩形拟合:https://segmentfault.com/a/1190000015663722 # 透视变换坐标对应:https://blog.csdn.net/xxxy502/article/details/89668722#:~:text=python%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%EF%BC%9A%E9%80%8F%E8%A7%86%E5%8F%98%E6%8D%A2%E5%8E%9F%E7%90%86%E6%95%88%E6%9E%9C%E4%BB%A3%E7%A0%81%20%E5%8E%9F%E7%90%86%20%E9%80%8F%E8%A7%86%E5%8F%98%E6%8D%A2%28Perspective%20Transformation%29%E6%98%AF%E5%B0%86%E5%9B%BE%E7%89%87%E6%8A%95%E5%BD%B1%E5%88%B0%E4%B8%80%E4%B8%AA%E6%96%B0%E7%9A%84%E8%A7%86%E5%B9%B3%E9%9D%A2%28Viewing,Plane%29%EF%BC%8C%E4%B9%9F%E7%A7%B0%E4%BD%9C%E6%8A%95%E5%BD%B1%E6%98%A0%E5%B0%84%28Projective%20Mapping%29%E3%80%82%20%E9%80%9A%E7%94%A8%E7%9A%84%E5%8F%98%E6%8D%A2%E5%85%AC%E5%BC%8F%E4%B8%BA%EF%BC%9A%20%20u%2Cv%E6%98%AF%E5%8E%9F%E5%A7%8B%E5%9B%BE%E7%89%87%E5%9D%90%E6%A0%87%EF%BC%8C%E5%AF%B9%E5%BA%94%E5%BE%97%E5%88%B0%E5%8F%98%E6%8D%A2%E5%90%8E%E7%9A%84%E5%9B%BE%E7%89%87%E5%9D%90%E6%A0%87x%2Cy%2C%E5%85%B6%E4%B8%AD%E3%80%82 # coding=utf-8 # 导入相关的库 import cv2 import cv2 as cv import numpy as np from functools import reduce import operator import math def ad_number(pos): """准考证号识别""" list_y = [190,245,300,355,400,460,520,570,630,680] list_num = [0,1,2,3,4,5,6,7,8,9] for i,y in enumerate(list_y): if pos >y-25 and pos <y+25: return list_num[i] def l_optioon(pos,bb,rn): """对左边选择题轮廓的中心坐标与选项坐标进行比较,对选择正确与否进行判断""" b = bb list_x = [400, 460, 525, 590, 656, 715, 780, 840, 900, 950, 1020, 1080, 1130, 1200, 1260, 1330, 1400, 1460, 1525] answer = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S'] right_answer = ['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D','A', 'B', 'C', 'D', 'A' ] for i,x in enumerate(list_x): if pos >x-20 and pos <x+20: if answer[i]==right_answer[b]: color = (255,0,0) rn +=1 else: color = (0, 0, 255) return answer[i],color,rn def r_optioon(pos,bb,rn): """对右边选择轮廓形心进行判断所在位置的选项,以及跟正确答案做比较""" b = bb # print(b) list_x = [ 1140, 1210, 1270, 1330] answer = ['A', 'B', 'C', 'D'] right_answer = [ 'B', 'C', 'D','A','B'] for i,x in enumerate(list_x): if pos >x-20 and pos <x+20: if answer[i] == right_answer[b]: color = (255, 0, 0) rn +=1 else: color = (0, 0, 255) return answer[i], color,rn def begin(): """打印一些提示的信息""" print("\n\n***************************************************************************************************") print("* 欢迎使用 *") print("* 六级客观题自动判卷系统 *") print("***************************************************************************************************") print("-" * 40) print("1.选择标准答案为:\t") test_answer = ['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D', 'A', 'B'] for i, answer in enumerate(test_answer): print(str(i + 36) + ":" + answer, end="\t") if (i + 36) % 5 == 0: """对打印进行每几个打一行""" print() print("-" * 40) def Perspective_transformation(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 进行灰度化、减少运算量 blurred = cv2.GaussianBlur(gray, (7, 7), 0) # 对图片进行高斯模糊、防止椒盐噪声的干扰 retval, binary = cv2.threshold(blurred, 120, 255, cv2.THRESH_BINARY) # 利用全局二值化对答题卷进行识别 kernel_1 = cv.getStructuringElement(cv.MORPH_RECT, (17, 17)) # 定义卷积核 erode = cv.erode(binary, kernel_1) # 进行腐蚀,去杂点 dilate = cv.dilate(erode, kernel_1) # 进行膨胀还原 contours = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] # 发现最外边轮廓 area_list = [] for c in contours: """获取面接列表""" area_list.append(cv.contourArea(c)) # -----------------------------------进行透视变换---------------------------------# dist = 30 for c in contours: if cv.contourArea(c) == max(area_list): epsilon = 0.1 * cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, epsilon, True) cv2.polylines(img, [approx], True, (0, 255, 255), 18) coords = [[[approx][0][0][0][0], [approx][0][0][0][1]], [[approx][0][1][0][0], [approx][0][1][0][1]], [[approx][0][2][0][0], [approx][0][2][0][1]], [[approx][0][3][0][0], [approx][0][3][0][1]]] center = tuple( map(operator.truediv, reduce(lambda x, y: map(operator.add, x, y), coords), [len(coords)] * 2)) x_y = sorted(coords, key=lambda coord: (-135 - math.degrees( math.atan2(*tuple(map(operator.sub, coord, center))[::-1]))) % 360, reverse=True) # print("[approx]:\n%s"%[approx])#打印获取的坐标 # print(x_y) # 打印顺时针坐标 former = np.float32([x_y[0], x_y[3], x_y[2], x_y[1]]) # 变换之后的四个角点坐标 pts = np.float32([ [0, 0], [0, x_y[3][1] - x_y[0][0]], [x_y[1][0] - x_y[0][0], x_y[3][1] - x_y[0][0]], [x_y[1][0] - x_y[0][0], 0]]) # 变换矩阵M M = cv2.getPerspectiveTransform(former, pts) # 当然这一过程可以用cv2中函数实现 po1 = cv2.perspectiveTransform(np.array([[[100, 50]]], dtype=np.float32), M) result = cv2.warpPerspective(img, M, (x_y[1][0] - x_y[0][0], x_y[3][1] - x_y[0][0])) return result def get_roi(result): gray = cv.cvtColor(result, cv.COLOR_BGR2GRAY) b_h = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY_INV, 101, 6) # 局部大津算法二值化 kernel_hb = cv.getStructuringElement(cv.MORPH_RECT, (23, 23)) b_h = cv.morphologyEx(b_h, cv.MORPH_OPEN, kernel_hb, iterations=1) # ----------------------------------提取框架---------------------------------# l_b = cv.GaussianBlur(result, (5, 5), 1) # 进行模糊去噪点 l_hsv = cv.cvtColor(l_b, cv.COLOR_BGR2HSV) # 转换为HSV色彩空间 lower = np.array([134, 21, 11]) # 红色低阈值 upper = np.array([179, 255, 255]) # 红色高阈值 l_mask = cv2.inRange(l_hsv, lower, upper) # 获取目标颜色单通道图片 contours = cv2.findContours(l_mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[ 1] # 获取图片的轮廓 对HSV的图像进行二值化需要用到这个 content = [] # 获取上面三个矩形框 line = [] # 三条线条 # i = 0 # 线条序号 # j = 0 # 内容序号 for c in contours: if cv.contourArea(c) > 5000: """过滤一些不必要的轮廓""" x, y, w, h = cv.boundingRect(c) # 获取矩形框的四个参数 mm = cv.moments(c) # 几何重心的获取 cx = mm['m10'] / mm['m00'] # 获取几何重心的x cy = mm['m01'] / mm['m00'] # 获取几何重心的y if w / h > 2: """对长条形红色线框进行追加""" # i+=1 # cv2.circle(result, (np.int(cx), np.int(cy)), 15, (0, 255, 0), -1) # 画出中心 # cv.rectangle(result, (x, y), (x + w, y + h), (255, 0, 0), 10) # 画外接矩形 # cv.putText(result, str(i), (np.int(cx), np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 4,(255,255,0), 10) line.append(c) else: """对内容框进行追加""" # j += 1 # cv.rectangle(result, (x, y), (x + w, y + h), (255, 255, 0), 10) # 画外接矩形 # cv.putText(result, str(j), (np.int(cx), np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 4,(255,255,0), 30) content.append(c) # ------------------------------获取学号以及选项区域------------------------------------# x1, y1, w1, h1 = cv.boundingRect(content[2]) # 获取学号位置线框轮廓正接矩形 x2, y2, w2, h2 = cv.boundingRect(line[1]) # 获取第二条红色框轮廓正接矩形 x3, y3, w3, h3 = cv.boundingRect(line[0]) # 获取第条红色框轮廓正接矩形 cv.rectangle(result, (x1, y1), (x1 + w1, y1 + h1), (255, 0, 0), 20) # 画外接矩形 cv.rectangle(result, (x2, y2 + h2), (x2 + w2, y3), (255, 0, 0), 20) # 画外接矩形 # --------------------------------选取roi-----------------------------------# roi_1_b_h = b_h[y1:y1 + h1, x1:x1 + w1] roi_2_b_h = b_h[y2 + h2:y3, x2:x2 + w2] roi_1 = result[y1:y1 + h1, x1:x1 + w1] roi_2 = result[y2 + h2:y3, x2:x2 + w2] return roi_1_b_h,roi_2_b_h,roi_1,roi_2,b_h def sort_stu_num_oul(roi_1_b_h): # -----------------------------学号轮廓排序------------------------------------# contours_1 = cv2.findContours(roi_1_b_h, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[ 1] # 获取图片的轮廓 对HSV的图像进行二值化需要用到这个 list_x1 = [] # 定义一个列表、用于储存每个轮廓对应的X坐标 sort_contours_1 = [] # 用于储存排序好的轮廓 for n, c_1 in enumerate(contours_1): """遍历轮廓对x坐标及逆行排序""" x, y, w, h = cv.boundingRect(c_1) list_x1.append(x) list_x2 = list_x1.copy() # copy出来的列表用来排序 list_x2.sort(reverse=False) for i in range(len(list_x1)): """对应的轮廓进行按找list_x2的顺序追加,行程按照x顺序排列的轮廓,sort_contours_1 """ sort_contours_1.append(contours_1[list_x1.index(list_x2[i])]) l = 0 test_number = [] for c_1 in sort_contours_1: """在轮廓中画出序号、""" l += 1 x, y, w, h = cv.boundingRect(c_1) # 获取矩形框的四个参数 mm = cv.moments(c_1) # 几何重心的获取 cx = mm['m10'] / mm['m00'] # 获取几何中心的x cy = mm['m01'] / mm['m00'] # 获取几何重心的y cv.rectangle(roi_1, (x, y), (x + w, y + h), (0, 255, 0), 5) # 画外接矩形 # cv.putText(roi_1, str(l), (np.int(cx), np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 2,(255,0,0), 3) num = ad_number(cy) test_number.append(num) # 准考证号 cv.putText(roi_1, str(num), (x, 80), cv.FONT_HERSHEY_SIMPLEX, 2.4, (0, 0, 255), 5) print("2.考生准考证号码为:\t", end="") for i in test_number: print(i, end="") print() def opt(roi_2_b_h): # -----------------------------学号选择发现轮廓------------------------------------# contours_2 = cv2.findContours(roi_2_b_h, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[1] # 获取图片的轮廓 对HSV的图像进行二值化需要用到这个 # -----------------------------选择轮廓排序------------------------------------# yl_contours_2 = [] # 储存左边轮廓 yr_contours_2 = [] # 储存右边轮廓 sort_contours_l2 = [] # 用于储存排序好的轮廓 sort_contours_r2 = [] # 用于储存排序好的轮廓 list_yl1 = [] # 定义一个列表、用于储存每个轮廓对应的X坐标 list_yr1 = [] # 定义一个列表、用于储存每个轮廓对应的X坐标 list_x3 = [] for i, c_2 in enumerate(contours_2): x, y, w, h = cv.boundingRect(c_2) # 获取矩形框的四个参数 mm = cv.moments(c_2) # 几何重心的获取 cx = mm['m10'] / mm['m00'] # 获取几何中心的x cy = mm['m01'] / mm['m00'] # 获取几何重心的y # print( str(i)+"\t"+str(y) +"\t"+ str(x)) if y < 1650 or x < 1000: list_yl1.append(y) yl_contours_2.append(c_2) else: list_yr1.append(y) list_x3.append(x) yr_contours_2.append(c_2) # --------------对左边轮廓进行排序--------------------------- list_yl2 = list_yl1.copy() list_yl2.sort(reverse=False) for i in range(len(list_yl1)): sort_contours_l2.append(yl_contours_2[list_yl1.index(list_yl2[i])]) # ----------------------画出左边轮廓---------------------------------- m = 35 answer_l_list = [] rn = 0 for c_2 in sort_contours_l2: m += 1 x, y, w, h = cv.boundingRect(c_2) # 获取矩形框的四个参数 mm = cv.moments(c_2) # 几何重心的获取 cx = mm['m10'] / mm['m00'] # 获取几何中心的x cy = mm['m01'] / mm['m00'] # 获取几何重心的y cv.putText(roi_2, str(m), (200, np.int(cy) + 20), cv.FONT_HERSHEY_SIMPLEX, 1.7, (255, 0, 0), 4) answer_l, color, rn = l_optioon(cx, (m - 36), rn) answer_l_list.append(answer_l) cv.rectangle(roi_2, (x, y), (x + w, y + h), color, 5) # 画外接矩形 cv.putText(roi_2, answer_l, (np.int(cx) + 100, np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 2, color, 4) # ----------------------画出右边轮廓----------------------------- list_yr2 = list_yr1.copy() list_yr2.sort(reverse=False) for i in range(len(list_yr1)): sort_contours_r2.append(yr_contours_2[list_yr1.index(list_yr2[i])]) n = 60 answer_r_list = [] for c_2 in sort_contours_r2: n += 1 x, y, w, h = cv.boundingRect(c_2) # 获取矩形框的四个参数 mm = cv.moments(c_2) # 几何重心的获取 cx = mm['m10'] / mm['m00'] # 获取几何中心的x cy = mm['m01'] / mm['m00'] # 获取几何重心的y cv.putText(roi_2, str(n), (920, np.int(cy) + 20), cv.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 4) # cv.putText(roi_2, str(m), (130, np.int(cy)-20), cv.FONT_HERSHEY_SIMPLEX, 1.7,(255,0,0), 4) answer_r, color, rn = r_optioon(cx, n - 61, rn) answer_r_list.append(answer_r) cv.putText(roi_2, answer_r, (np.int(cx) + 100, np.int(cy)), cv.FONT_HERSHEY_SIMPLEX, 2, color, 4) cv.rectangle(roi_2, (x, y), (x + w, y + h), color, 5) # 画外接矩形 # print(answer_r_list) print("-" * 40) your_ansewer = answer_l_list + answer_r_list print("3.学生填选答案为:\t") for i, answer in enumerate(your_ansewer): print(str(i + 36) + ":" + answer, end="\t") if (i + 36) % 5 == 0: print() print("-"*40) print("4.分数为:\t",end='') print(rn*2) print("-"*50+"试卷批改完毕"+"-"*50) #------------------图像预处理-------------------------------- img = cv2.imread("F:\\img\\test_paper\\15.jpg") # 读取图片、为BGR格式 begin() result = Perspective_transformation(img) roi_1_b_h,roi_2_b_h,roi_1,roi_2,b_h = get_roi(result) sort_stu_num_oul(roi_1_b_h) opt(roi_2_b_h) # result[y1:y1+h1,x1:x1+w1]=roi_1 # result[y2+h2:y3,x2:x2+w2]=roi_2 cv.imwrite(".\\img\\result_11.jpg",result) cv2.namedWindow("result",0) cv2.imshow("result",result) cv2.namedWindow("roi_2_b_h",0) cv2.imshow("roi_2_b_h",roi_2_b_h) cv2.namedWindow("roi_1_b_h",0) cv2.imshow("roi_1_b_h",roi_1_b_h) cv2.namedWindow("img",0) cv2.imshow("img",img) cv2.waitKey(0) cv2.destroyAllWindows()
1.本次程序存在缺陷,由于纸张的问题,以及背景的问题,手机拍摄的问题,还有我技术的问题,目前只能处理我上次的那张照片。由于时间的关系,后面我会利用某宝购买答题纸,利用USB工业相机进行拍摄,对处理算法进行优化。到时候再跟大家讲解一些实现思路。
2.本次代码没有用太多时间去整理,以及备注更多的内容,就是直接写完copy上来而已。所以有些乱。
3.对本文需要改进的地方大家可以多多留言、
4.觉得不错的小伙伴帮我帮我点个赞或者关注我支持一下哦!
1.理论系列:
第一章:pycharm、anaconda、opencv、pytorch、tensorflow、paddlex等环境配置大全总结【图像处理py版本】
第二章:OpenCv算法的基本介绍与应用
第三章:OpenCv图片、视频读写操作与基本应用
第四章:OpenCv阈值分割/二值化(单通道、多通道图片)总结
2.项目系列:
》》》项目一:四六级改卷系统
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。