无需深度学习!OpenCV 实现全自动答题卡识别打分系统(完整可运行源码)
·
目录
一、项目介绍
在计算机视觉学习路径中,答题卡识别是最经典、最适合新手入门、最适合做课程设计的实战项目。
目前网上很多方案都是深度学习训练模型,门槛高、需要数据集、训练耗时。 本项目 不使用任何深度学习、不训练模型、无需 GPU,纯传统机器视觉算法实现:
- 自动矫正倾斜、透视变形的答题卡
- 自动检测试卷边框
- 自动定位所有答题圆圈
- 自动排序题目顺序
- 自动识别学生填涂答案
- 自动对比标准答案、打分、可视化对错
适合:OpenCV 练手、计算机视觉课程设计、Python 期末大作业、毕设入门项目。
二、项目优势(为什么推荐你学这个)
✅ 零模型、零训练、零数据集 ✅ 代码量适中、逻辑清晰、适合新手吃透 CV 基础 ✅ 涵盖 透视变换、轮廓检测、轮廓排序、掩码运算、二值化、边缘检测 六大核心 CV 知识点 ✅ 识别稳定、速度快、可直接部署 ✅ 效果直观,可视化效果满分,作业颜值极高
三、整体算法详细流程
我把每一步给你讲的非常细:
- 图像读取:读取本地答题卡图片
- 灰度化:彩色图转灰度图,减少计算量
- 高斯模糊:去除图像噪点,防止误检测轮廓
- Canny 边缘检测:提取答题卡边缘轮廓
- 轮廓查找:找出图片中所有轮廓
- 筛选最大四边形轮廓:自动找到答题卡外框
- 四点透视变换:矫正倾斜试卷,生成俯视图(本项目核心难点)
- 灰度 + 自适应二值化:将填涂黑色选项转为白色,背景变黑
- 轮廓筛选答题圆圈:通过尺寸、圆度筛选有效选项
- 轮廓排序:从上到下排题目、从左到右排选项
- 掩码 mask 像素统计:判断哪个选项被填涂
- 匹配标准答案:自动判对错、打分
- 可视化结果:对错红绿标注、输出总分
四、完整可运行代码
python
运行
import numpy as np
import cv2
# 自定义标准答案库:对应5道选择题
# key=题号,value=正确选项(01234对应ABCDE)
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
def order_points(pts):
"""
【核心函数1】答题卡四点坐标排序
任意倾斜四边形,自动排序为固定顺序:左上、右上、右下、左下
"""
rect = np.zeros((4, 2), dtype="float32")
# x+y 最小 = 左上,x+y最大 = 右下
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# y-x 最小 = 右上,y-x最大 = 左下
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
def four_point_transform(image, pts):
"""
【核心函数2】四点透视变换,矫正倾斜试卷
输入倾斜试卷四点,输出垂直俯视的标准答题卡
"""
# 排序四点
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算图像宽度(上下两组宽度取最大)
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) + ((tl[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
def sort_contours(cnts, method='left-to-right'):
"""
【核心函数3】通用轮廓排序工具
支持:从左到右、从右到左、从上到下、从下到上
"""
reverse = False
i = 0
if method == "right-to-left" or method == 'bottom-to-top':
reverse = True
if method == 'top-to-bottom' or method == 'bottom-to-top':
i = 1
# 获取所有轮廓外接矩形
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
# 按坐标排序轮廓
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
key=lambda b: b[1][i],
reverse=reverse))
return cnts, boundingBoxes
def cv_show(name,img):
"""自定义窗口显示,按任意键关闭"""
cv2.imshow(name,img)
cv2.waitKey(0)
# ========================= 1.图像预处理 =========================
# 读取原图
image = cv2.imread(r'./images/test_01.png')
contours_img = image.copy()
# 1.灰度化:减少通道,简化计算
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 2.高斯模糊:去除噪点,平滑图像
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
cv_show('blurred', blurred)
# 3.Canny边缘检测:提取所有物体边缘
edged = cv2.Canny(blurred, 75, 200)
cv_show('edged', edged)
# ========================= 2.定位答题卡外框 =========================
# 查找所有外层轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
cv_show('contours_img', contours_img)
docCnt = None
# 轮廓按面积从大到小排序,试卷一定是最大轮廓
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)
# 最大轮廓且是4个顶点=答题卡矩形
if len(approx) == 4:
docCnt = approx
break
# 透视变换矫正整张试卷
warped_t = four_point_transform(image, docCnt.reshape(4, 2))
warped_new = warped_t.copy()
cv_show('warped', warped_t)
# 矫正后图像灰度化
warped = cv2.cvtColor(warped_t, cv2.COLOR_BGR2GRAY)
# ========================= 3.OTSU自适应二值化 =========================
# THRESH_BINARY_INV 反向二值化:
# 亮的地方变黑、暗的填涂区域变白,方便识别填涂圆圈
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh', thresh)
# ========================= 4.筛选答题选项圆圈轮廓 =========================
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
warped_Contours = cv2.drawContours(warped_t, cnts, -1, (0, 255, 0), 1)
cv_show('warped_Contours', warped_Contours)
questionCnts = []
for c in cnts:
# 获取外接矩形
x, y, w, h = cv2.boundingRect(c)
# 计算宽高比,筛选圆形
ar = w / float(h)
# 尺寸+比例双重筛选,过滤噪点、小轮廓
if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:
questionCnts.append(c)
print("有效答题圆圈数量:",len(questionCnts))
# ========================= 5.轮廓排序 =========================
# 整体题目从上到下排序
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
# ========================= 6.Mask掩码识别填涂答案 =========================
for (j, c) in enumerate(cnts):
# 创建纯黑掩码图
mask = np.zeros(thresh.shape, dtype="uint8")
# 将当前选项轮廓填充白色
cv2.drawContours(mask, [c], -1, 255, -1)
cv_show('mask', mask)
# 与原图与运算,只保留当前圆圈区域
thresh_mask_and = cv2.bitwise_and(thresh, thresh, mask=mask)
cv_show('thresh_mask_and', thresh_mask_and)
# 统计白色像素数量
total = cv2.countNonZero(thresh_mask_and)
# 白色像素最多 = 被填涂的选项
if bubbled is None or total > bubbled[0]:
bubbled = (total, j)
# ========================= 7.标准答案匹配打分 =========================
color = (0, 0, 255)
k = ANSWER_KEY[q]
# 判断是否答对
if k == bubbled[1]:
color = (0, 255, 0)
correct += 1
# 绘制对错轮廓
cv2.drawContours(warped_new, [cnts[k]], -1, color, 3)
cv_show('warpeding', warped_new)
# 计算得分百分比
score = (correct / 5.0) * 100
print("[INFO] 最终得分: {:.2f}%".format(score))
cv2.putText(warped_new, "{:.2f}%".format(score), (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
# 展示最终结果
cv2.imshow("Original", image)
cv2.imshow("Exam Result", warped_new)
cv2.waitKey(0)
五、逐模块超详细原理解析
1、四点排序原理(重点)
拍摄的答题卡永远是倾斜的,四个点顺序混乱。 通过数学规律强制排序:
x+y最小 → 左上x+y最大 → 右下y-x最小 → 右上y-x最大 → 左下
保证透视变换永远不会错乱。
2、透视变换原理
普通仿射变换只能旋转缩放,透视变换可以矫正拍摄透视变形。 通过计算原图四边形 → 标准矩形的映射矩阵,还原俯视正视图。
3、高斯模糊作用
图像拍摄会有椒盐噪点、颗粒噪点,不模糊会导致检测出大量无效小轮廓,干扰答题圈识别。
4、Canny 边缘检测
灰度梯度计算,精准提取物体边缘,只保留轮廓信息,去除纹理信息。
5、OTSU 二值化
自动寻找最佳阈值,不需要手动调参。 反向二值化让:
- 答题卡白纸 → 黑色
- 铅笔填涂区域 → 白色 极大方便像素统计判断填涂。
6、轮廓筛选圆原理
圆形外接矩形 宽高比无限接近 1。 通过 0.9~1.1 比例筛选,完美过滤长方形、不规则噪点轮廓。
7、Mask 掩码判题核心原理
这是整段代码最精髓、面试常问的地方:
- 单独抠出每一个选项圆圈
- 统计圈内白色像素多少
- 填涂越重、白色像素越多
- 像素最多的就是考生选择答案
抗干扰能力极强!
六、运行部署教程(超详细)
- 安装依赖
plaintext
pip install opencv-python numpy
- 项目结构
plaintext
project
├─ images
│ └─ test_01.png
└─ main.py
- 替换自己的答题卡图片
- 修改
ANSWER_KEY为你的标准答案 - 直接运行即可
七、项目拓展方向(适合毕设加分)
- 支持 多题、多选项 自动适配
- 增加 去反光、形态学操作
- 支持 多选、缺考、空题判断
- 批量识别多张答题卡
- 输出 Excel 成绩报表
八、总结
本项目完整覆盖 OpenCV 最核心、最常用 的全部知识点: 图像预处理、边缘检测、轮廓操作、透视变换、二值化、掩码运算、轮廓排序。
非常适合:新手入门、课程设计、期末大作业、毕设基础项目。
更多推荐
所有评论(0)