顺的做网站便宜吗,成都高端室内设计公司,年终总结汇报ppt模板免费,北京市网站开发公司目录
透视变换矫正
选项识别匹配
QT 界面设计 引言#xff1a;随着信息化的发展#xff0c;计算机阅卷已经成为一种常规操作。在大型考试中#xff0c;客观题基本不再 需要人工阅卷。本项目旨在开发一个基于OpenCV的高效答题卡识别系统#xff0c;通过先进的图像处理和模…目录
透视变换矫正
选项识别匹配
QT 界面设计 引言随着信息化的发展计算机阅卷已经成为一种常规操作。在大型考试中客观题基本不再 需要人工阅卷。本项目旨在开发一个基于OpenCV的高效答题卡识别系统通过先进的图像处理和模式识别技术实现对答题卡的快速准确分析。 文章所有资源请看文末 透视变换矫正
假如有一张答题卡平放在地面上那我们怎样去找到答题卡的边界轮廓呢
答案是透视变换。首先我们需要找到答题卡的轮廓才能对选项做各种处理呀接下来就是对透视变换的方法说明了。
假设原始图像中的点为目标图像中的对应点为。透视变换可以用一个 3x3 的矩阵来描述 其中矩阵的元素取决于原始四边形和目标四边形顶点的坐标。其核心原理在于通过建立原始图像和目标图像之间的对应点关系来计算一个变换矩阵。
综上所述使用透视变换扫描得到答题卡边界具体步骤如下 找到原始图像的4个顶点和目标图像的4个顶点根据8个顶点构造原始图像到目标图像的变换矩阵依据变换矩阵实现原始图像到目标图像的变换完成倾斜矫正 注意用于构造变换矩阵使用的原始图像的4个顶点和目标图像的4个顶点的位置必须是匹配的也就是说要将左上、右上、左下、右下4个顶点按照相同的顺序排列。 OK下面我们直接根据代码来进行说明。
import cv2
import math
import numpy as np# x坐标
def sortBy_x(pt):return pt[0]# y坐标
def sortBy_y(pt):return pt[1]def correct(path):try:answerSheet cv2.imread(path)gray cv2.cvtColor(answerSheet, cv2.COLOR_BGR2GRAY)blurred cv2.GaussianBlur(gray, (3, 3), 0)canny cv2.Canny(blurred, 75, 200)contours, Hierarchy cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)if len(contours) 1:result_contour contours[0]else:max_length -1index -1for i, contour in enumerate(contours):length cv2.arcLength(contour, True)if length max_length:max_length lengthindex iresult_contour contours[index]pts cv2.approxPolyDP(result_contour, 0.02 * cv2.arcLength(result_contour, True), True)if len(pts) ! 4:raise ValueError(透视变换需要四个点但检测到的点数量为{}.format(len(pts)))pts np.array([pt[0] for pt in pts]) # 提取点坐标print(pts)pts sorted(pts, keysortBy_x)print(pts)pts sorted(pts, keysortBy_y)print(pts)print(pts[0][0])width1 math.sqrt((pts[0][0] - pts[1][0]) ** 2 (pts[0][1] - pts[1][1]) ** 2)width2 math.sqrt((pts[2][0] - pts[3][0]) ** 2 (pts[2][1] - pts[3][1]) ** 2)width int(max(width1, width2))height1 math.sqrt((pts[0][0] - pts[3][0]) ** 2 (pts[0][1] - pts[3][1]) ** 2)height2 math.sqrt((pts[2][0] - pts[1][0]) ** 2 (pts[2][1] - pts[1][1]) ** 2)height int(max(height1, height2))pts_dst np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtypefloat32)pts_src np.array(pts, dtypefloat32)M cv2.getPerspectiveTransform(pts_src, pts_dst)birdMat cv2.warpPerspective(answerSheet, M, (width, height))return birdMatexcept Exception as e:print(fError in correct: {e})return None
1、首先对读取的图像进行一系列预处理操作灰度转换、滤波、边缘检测等以凸显图像特征
2、使用cv2.findContours查找图像轮廓
当轮廓数量为1时直接将其结果作为轮廓。
否则通过计算 每个轮廓的弧长找到弧长最长的轮廓作为结果轮廓。
3、使用cv2.approxPolyDP函数对结果轮廓进行多边形逼近得到近似的顶点坐标
4、将顶点坐标提取出来并分别按照x坐标和y坐标进行排序同时计算相邻两点之间的距离取最大值作为宽度和高度并据此计算目标顶点
5、cv2.getPerspectiveTransfor计算变换矩阵cv2.warpPerspective根据变换矩阵对原始图像进行透视变换得到矫正后的图像
效果如下 选项识别匹配
答题卡轮廓边界得到之后就是对选项的处理了。
import cv2
import numpy as np
import mathdef sortBy_x(pt):return pt[0]def sortBy_y(pt):return pt[1]def recognition(path, imageIndex):try:answerSheet cv2.imread(path)gray cv2.cvtColor(answerSheet, cv2.COLOR_BGR2GRAY)blurred cv2.GaussianBlur(gray, (3, 3), 0)canny cv2.Canny(blurred, 75, 200)contours, _ cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)if len(contours) 1:result_contour contours[0]else:max_length -1index -1for i, contour in enumerate(contours):length cv2.arcLength(contour, True)if length max_length:max_length lengthindex iresult_contour contours[index]pts cv2.approxPolyDP(result_contour, 0.02 * cv2.arcLength(result_contour, True), True)if len(pts) ! 4:raise ValueError(识别需要四个点但检测到的点数量为{}.format(len(pts)))pts np.array([pt[0] for pt in pts])pts sorted(pts, keysortBy_x)pts sorted(pts, keysortBy_y)width1 math.sqrt((pts[0][0] - pts[1][0]) ** 2 (pts[0][1] - pts[1][1]) ** 2)width2 math.sqrt((pts[2][0] - pts[3][0]) ** 2 (pts[2][1] - pts[3][1]) ** 2)width int(max(width1, width2))height1 math.sqrt((pts[0][0] - pts[3][0]) ** 2 (pts[0][1] - pts[3][1]) ** 2)height2 math.sqrt((pts[2][0] - pts[1][0]) ** 2 (pts[2][1] - pts[1][1]) ** 2)height int(max(height1, height2))pts_dst np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtypefloat32)pts_src np.array(pts, dtypefloat32)M cv2.getPerspectiveTransform(pts_src, pts_dst)birdMat cv2.warpPerspective(answerSheet, M, (width, height))cv2.imshow(original, birdMat)################# 识别 ##############################gray_birdMat cv2.cvtColor(birdMat, cv2.COLOR_BGR2GRAY)_, target cv2.threshold(gray_birdMat, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU)cv2.imshow(Img, target)element cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))target cv2.dilate(target, element)cv2.imshow(image, target)# 提取选项contours, _ cv2.findContours(target, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# print(contours)selected_contours [c for c in contours if cv2.boundingRect(c)[2] 20 and cv2.boundingRect(c)[3] 20]answerSheet_con cv2.cvtColor(target, cv2.COLOR_GRAY2BGR)cv2.drawContours(answerSheet_con, selected_contours, -1, (0, 0, 255), 2)# 选项定位、二维数组存储radius []center []for contour in selected_contours:(x, y), r cv2.minEnclosingCircle(contour)radius.append(r)center.append((int(x), int(y)))x_min min(center, keylambda x: x[0])[0]x_max max(center, keylambda x: x[0])[0]x_interval (x_max - x_min) // 4y_min min(center, keylambda x: x[1])[1]y_max max(center, keylambda x: x[1])[1]y_interval (y_max - y_min) // 4classed_contours [[[] for _ in range(5)] for _ in range(5)]for i, point in enumerate(center):index_x round((point[0] - x_min) / x_interval)index_y round((point[1] - y_min) / y_interval)classed_contours[index_y][index_x] selected_contours[i]colors [(0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 0, 0), (0, 255, 0)]test_result cv2.cvtColor(target, cv2.COLOR_GRAY2BGR)for i in range(5):for j in range(5):if len(classed_contours[i][j]) 0:cv2.drawContours(test_result, classed_contours[i][j], -1, colors[i], 2)# 答案自定义只有 5个选项correct_answers [0, 4, 4, 2, 1]# 定义选项位置result_count np.zeros((5, 5), dtypeint)re_rect [[cv2.boundingRect(contour) for contour in row] for row in classed_contours]count_roi np.zeros((5, 5), dtypenp.float32)min_count 999max_count -1for i in range(5):for j in range(5):if len(classed_contours[i][j]) 0:rect re_rect[i][j]tem target[rect[1]:rect[1] rect[3], rect[0]:rect[0] rect[2]]count cv2.countNonZero(tem)if count max_count:max_count countif count min_count:min_count countcount_roi[i][j] countmean (max_count - min_count) // 2option_diff np.abs(count_roi - max_count)for i in range(5):for j in range(5):if option_diff[i][j] mean:result_count[i][j] 1# 进行审阅label_answer birdMat.copy()correct_count 0wrong_answers {}for i in range(5):selected []for j in range(5):if result_count[i][j] 1:selected.append(j)if j correct_answers[i]:cv2.drawContours(label_answer, classed_contours[i][j], -1, (255, 0, 0), 2)else:cv2.drawContours(label_answer, classed_contours[i][j], -1, (0, 0, 255), 2)# 记录题目数量、正确题数、错题if len(selected) 0:continue # 未作答不做任何处理elif len(selected) 1:if selected[0] correct_answers[i]:correct_count 1else:wrong_answers[i 1] chr(65 selected[0]) # 错误选项else:blue_count sum(1 for j in selected if j correct_answers[i])red_count len(selected) - blue_countif blue_count 0 and red_count 0:wrong_answers[i 1] 多选total_questions len(correct_answers)score correct_count / total_questions * 100data {序号: {:02}.format(imageIndex 1),成绩: score,题目总数: total_questions,错题: str(wrong_answers),正确题数: correct_count}return label_answer, dataexcept Exception as e:print(fError in recognition: {e})return None, None
1、首先仍就是图像预处理这通常会使得我们更易于提取选项得到其位置。将变换后的图像转为灰度图并进行反二阈值化凸显选项随后进行膨胀操作以连接断开的部分或填充小的空洞。
2、提取选项轮廓。通过cv2.findContours得到所有轮廓随后对每个轮廓进行筛选只有宽度和高度均大于20像素的轮廓才会被保留下来这样就能够得到选项了。
3、选项定位与分类。计算每个符合条件的轮廓的最小外接圆的圆心和半径。根据圆心坐标将选项按照水平和垂直方向进行分类并存储到二维数组中。
4、答案识别与审阅。 自定义正确答案用数字标识答案位置默认从0开始。为每个选项区域计算非零像素的数量。通过计算得到的数量与平均值确定每个选项的选择情况并存储到二维数组中。 5、审阅结果展示与数据统计。比较二维数组与正确答案绘制正确和错误选项的轮廓正确为蓝色错误为红色同时统计正确题数、计算分数并将相关数据存储到字典中。
效果如下 QT 界面设计 本次界面设计使用的是pyqt5我也只是初学所以做的界面不是很好但也勉强还算看的过眼吧。这个界面其实就是把变换后的图像和识别检测的结果弄到展示窗口然后把记录的数据信息这些保存到excel表而已说实在的还是太简陋了呀。OK下面我们直接看效果吧。 答题卡识别 好的以上就是本次项目的所有内容了希望对大家有所帮助呀有疑问的可以评论或私聊我解答哟 文章所有资源有需要的可自取 百度网盘链接: https://pan.baidu.com/s/1pFeaKRGAwF1zfip_wqt_dQ 提取码: 0bw7