新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Python+OpenCV的智能答题卡识别系统:从透视校正到VGG数字识别

发布时间:2026/11/11 20:56:46来源:尧图网络
基于Python+OpenCV的智能答题卡识别系统:从透视校正到VGG数字识别
简介这份资源是面向计算机相关专业毕业设计学生与项目实战学习者的智能答题卡识别系统完整方案基于Python与OpenCV实现融合深度学习与图像识别算法可用于答题卡自动阅卷、选项识别与学号识别等场景难度适中适合作为毕设选题或算法练习项目。压缩包共48个文件约2.99MB包含9个Python源码文件、22张测试图片、5个XML配置、2个HTML页面以及答辩PPT、项目报告PDF和模型训练脚本等覆盖从图像预处理、模板匹配到模型训练与Web展示的完整链路。目前已有376人学习下载。读者可获得经导师认可、评审98分的可运行源码以及配套答辩PPT与报告文档便于快速理解项目结构、复现识别流程并完成毕设答辩准备。1. 从一张拍歪的答题卡说起这套 PythonOpenCV 识别系统到底能跑出什么期末阅卷那几天最崩溃的不是改卷是改答题卡。一个班五十多份涂串行的、擦不干净的、拍照拍歪的全得靠眼睛一份份对。后来我拿到这套「基于 PythonOpenCV 智能答题卡识别系统」的源码包第一反应不是它多高大上而是它能不能把我从这种重复劳动里捞出来。答案是能而且门槛比想象中低——它不依赖 GPU不依赖深度学习框架的复杂训练流程核心就是 OpenCV 的图像处理加一个轻量级的 VGG 网络做数字识别一台普通笔记本就能跑通。这套资源适合三类人正在做计算机相关毕业设计、需要一份能讲清楚原理又能现场演示的项目想练 OpenCV 图像处理实战、但不想从零搭框架的学习者以及需要一套答题卡识别 baseline、准备在此基础上改造成自己业务逻辑的开发者。它解决的核心问题很具体把一张手机拍的答题卡照片经过透视校正、区域分割、填涂判断最终输出每道题的选项和学号。下面我按「先跑起来、再拆原理、最后避坑」的顺序把这份源码包拆开讲。2. 环境搭起来从 Python 安装到 OpenCV 导入不报错2.1 依赖清单与版本选择这套项目用的是 Python 3.9 环境源码里能看到utils.cpython-39.pyc说明作者本地就是 3.9 跑的。我建议直接对齐 3.9 或 3.10别用 3.12因为部分 OpenCV 和 PyTorch 的轮子在新版本上还没跟上容易卡在安装环节。核心依赖就几个依赖包作用建议版本opencv-python图像读取、透视变换、轮廓检测4.5.x 以上numpy矩阵运算、坐标计算1.21 以上torch / torchvisionVGG 网络推理与训练1.10 左右Pillow图像格式转换任意稳定版Flaskapp.py 提供的 Web 演示界面2.x安装命令我一般写成一条方便一次性对齐pip install opencv-python4.5.5.64 numpy1.21.6 torch1.10.2 torchvision0.11.3 Flask2.0.3 Pillow9.0.1这里锁版本不是强迫症是因为 OpenCV 4.6 之后部分findContours的返回值行为有微调老代码里如果按三返回值写升级后会直接报ValueError: not enough values to unpack。锁在 4.5.x 能省掉这类玄学问题。2.2 目录结构与入口文件定位拿到压缩包解压后根目录下能看到这些关键文件answer_card_recognition.py是主流程入口choice_question_recognition.py负责选择题区域识别exam_num_recognition.py负责学号数字识别my_vgg.py定义网络结构train_my_model.py是训练脚本app.py是 Flask 网页演示。图片素材从0.jpg到15.jpg加上answer_card.png基本覆盖了不同角度和光照的测试样本。跑之前先确认一件事template目录里放的是标准答题卡模板图识别时的透视校正要靠它做对齐。如果模板图被误删或者换了分辨率后面所有坐标都会偏。我一般会先跑一遍main.py看整体流程再单独跑answer_card_recognition.py调试单张图。python main.py如果控制台没有报ModuleNotFoundError: No module named cv2说明 OpenCV 装对了。这个报错是新手最高频的翻车点九成是因为 pip 装到了另一个 Python 环境里或者 Anaconda prompt 里没激活对应虚拟环境。解决办法很简单python -c import cv2; print(cv2.__version__)能打印版本号就说明当前解释器能找到 cv2。3. 图像预处理链路透视校正、二值化与填涂区域定位3.1 从拍照到正视图四个角点怎么找答题卡识别的第一步不是识别是把拍歪的图「掰正」。手机拍出来的答题卡通常有透视变形直接切区域会错位。常见做法是灰度化 → 高斯模糊 → Canny 边缘检测 → 找最大轮廓 → 用approxPolyDP逼近出四个角点 → 做透视变换。import cv2 import numpy as np def find_card_contour(image_path): img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5, 5), 0) edged cv2.Canny(blurred, 75, 200) # 只保留面积最大的轮廓通常是答题卡外框 contours, _ cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) card_cnt max(contours, keycv2.contourArea) peri cv2.arcLength(card_cnt, True) approx cv2.approxPolyDP(card_cnt, 0.02 * peri, True) return img, approxGaussianBlur的核大小(5,5)是经验值太大边缘会糊太小噪声压不住。Canny的双阈值 75/200 适合光照均匀的场景如果拍出来偏暗可以把低阈值降到 50。approxPolyDP的0.02 * peri控制逼近精度值越小角点越多0.02 是答题卡这种矩形轮廓的常用系数。返回的approx如果只有 4 个点说明找对了如果多于 4 个说明边缘有干扰得回头调 Canny 阈值。3.2 透视变换与二值化把填涂变成黑白分明找到四个角点后用getPerspectiveTransform和warpPerspective把图拉成正视图尺寸对齐到模板图的宽高。这一步做完答题卡就变成一张规规矩矩的矩形图后面所有区域坐标都可以按比例算。def warp_card(img, approx, target_w600, target_h800): pts approx.reshape(4, 2).astype(float32) # 按左上、右上、右下、左下排序 rect order_points(pts) dst np.array([[0, 0], [target_w - 1, 0], [target_w - 1, target_h - 1], [0, target_h - 1]], dtypefloat32) M cv2.getPerspectiveTransform(rect, dst) warped cv2.warpPerspective(img, M, (target_w, target_h)) return warped def order_points(pts): rect np.zeros((4, 2), dtypefloat32) s pts.sum(axis1) rect[0] pts[np.argmin(s)] # 左上角 xy 最小 rect[2] pts[np.argmax(s)] # 右下角 xy 最大 diff np.diff(pts, axis1) rect[1] pts[np.argmin(diff)] # 右上角 x-y 最小 rect[3] pts[np.argmax(diff)] # 左下角 x-y 最大 return rectorder_points这个函数是整套代码里最容易被忽略但最关键的。如果四个角点顺序错了透视变换会把图翻转或旋转后面区域全废。判断逻辑是左上角坐标和最小右下角和最大右上角横纵坐标差最小左下角差最大。这套排序对标准矩形答题卡稳定有效。变换完之后做自适应二值化把涂黑的区域和空白区域分开def binarize(warped): gray cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) thresh cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) return threshADAPTIVE_THRESH_GAUSSIAN_C比全局阈值更适合光照不均的拍照场景11是邻域块大小2是常数偏移。THRESH_BINARY_INV让涂黑区域变成白色方便后面统计像素。3.3 填涂判断像素占比阈值怎么定每个选项区域切出来后统计白色像素占该区域总面积的比例。比例超过阈值就判定为「涂了」。这个阈值是整套系统里最需要根据实际拍摄条件调的参数。def is_filled(roi, threshold0.35): total roi.shape[0] * roi.shape[1] white cv2.countNonZero(roi) ratio white / total return ratio threshold, ratio0.35是我在标准光照下测出来的经验值。如果拍出来偏暗涂黑区域二值化后白色像素少阈值要降到 0.25 左右如果拍出来反光严重空白区域也会出现白色像素阈值要提到 0.45。建议先用几张样本图跑一遍把每道题的ratio打印出来看分布再定阈值。这一步没有万能值必须按你的拍摄条件标定。4. 学号与选择题识别VGG 数字分类和选项判定怎么接4.1 学号区域分割与数字识别学号部分通常是手写或涂卡数字项目里用exam_num_recognition.py处理。流程是按模板坐标切出学号区域 → 每个数字单独切分 → 送入 VGG 网络推理。my_vgg.py里定义的网络结构不复杂几个卷积层加全连接输入是 28x28 的灰度图输出 0-9 十个类别。import torch from my_vgg import MyVGG def recognize_digit(digit_img, model_pathmy_model.pth): model MyVGG() model.load_state_dict(torch.load(model_path, map_locationcpu)) model.eval() # 缩放到网络输入尺寸并归一化 img cv2.resize(digit_img, (28, 28)) img img.astype(float32) / 255.0 tensor torch.from_numpy(img).unsqueeze(0).unsqueeze(0) with torch.no_grad(): output model(tensor) pred output.argmax(dim1).item() return predmap_locationcpu是必须的因为训练时可能用了 GPU推理时如果机器没有 CUDA不加这个参数会报错。unsqueeze(0).unsqueeze(0)是把单张图变成(1, 1, 28, 28)的四维张量符合卷积层输入要求。如果识别率低先检查数字切分是否准确——切歪了或者带到了边框网络再强也救不回来。4.2 选择题选项判定与结果输出选择题部分在choice_question_recognition.py里逻辑是按模板定义每道题的选项坐标逐个切 ROI调is_filled判断最后汇总成答案列表。模板坐标一般存在template目录的配置文件里或者硬编码在脚本中。def recognize_choices(warped, question_layout): results {} for q_num, options in question_layout.items(): selected [] for opt_label, (x, y, w, h) in options.items(): roi warped[y:yh, x:xw] roi_bin binarize(roi) filled, ratio is_filled(roi_bin) if filled: selected.append(opt_label) results[q_num] selected return resultsquestion_layout是一个字典键是题号值是该题四个选项的坐标和宽高。这套坐标必须和模板图严格对应如果换了答题卡样式得重新标定。输出结果可以存成 JSON 或直接写进result.html展示。app.py里的 Flask 界面就是把这个流程包了一层 Web 壳上传图片后返回识别结果。4.3 训练自己的 VGG 模型如果默认模型对手写数字识别不准可以用train_my_model.py重新训练。my_dataset.py负责加载数据集目录结构是choices/unselected和choices/selected两类分别放未涂和已涂的样本。训练脚本的核心逻辑from my_dataset import MyDataset from my_vgg import MyVGG import torch.nn as nn import torch.optim as optim dataset MyDataset(root_dirchoices) loader torch.utils.data.DataLoader(dataset, batch_size32, shuffleTrue) model MyVGG() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) for epoch in range(20): for imgs, labels in loader: optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step()lr0.001是 Adam 的常用起点如果 loss 震荡厉害就降到 0.0005。batch_size32适合小数据集样本少的话可以降到 16。训练完记得保存state_dict推理时路径要对上。这套训练流程不复杂但数据集质量决定上限——如果样本里混入了切分错误的图训练出来的模型会学偏。5. 避坑与排查那些让我重跑三遍的细节5.1 透视变换后图像颠倒现象识别结果全错打印中间图发现答题卡上下翻转了。原因order_points里四个角点排序逻辑在特定拍摄角度下会失效比如答题卡旋转超过 45 度。解决在排序后加一个校验判断左上角点的 y 坐标是否小于左下角不满足就交换。或者直接用模板匹配的方式找角点稳定性更高。5.2 填涂阈值在不同光照下漂移现象同一批答题卡有的全判为未涂有的全判为涂了。原因is_filled的固定阈值 0.35 只适合均匀光照强光或阴影下像素占比会整体偏移。解决改成动态阈值先统计所有选项区域的 ratio 分布取中位数作为基准再上下浮动。或者加一步光照归一化用cv2.equalizeHist均衡灰度直方图。5.3 学号数字切分粘连现象学号识别结果总是少一位或多一位。原因数字之间间距太小轮廓检测时粘连成一个区域。解决在切分前做一次垂直投影统计每列的白色像素数找到波谷作为切分点。如果还是粘连用形态学腐蚀cv2.erode把数字分开再切。5.4 Flask 上传图片后识别超时现象app.py跑起来后上传大图页面一直转圈最后 500。原因原图分辨率太高透视变换和轮廓检测耗时过长。解决上传后先缩放到宽度 1000 以内再做后续处理识别完再映射回原坐标。或者在 Flask 里加超时限制避免单次请求卡死整个服务。5.5 模型加载报 Missing key(s)现象load_state_dict报 key 不匹配。原因训练时用了nn.DataParallel包了一层保存的 key 前面多了module.前缀。解决加载时用model.load_state_dict({k.replace(module., ): v for k, v in state_dict.items()})去掉前缀或者训练时就不加 DataParallel。6. 把识别结果接进自己的流程批量处理与准确率验证单张识别跑通之后真正省时间的是批量处理。我一般会写一个循环把整个文件夹的答题卡图片过一遍输出 CSV 汇总。这里有个技巧先用少量样本标定好阈值和模板坐标再全量跑不要边跑边调。import os import csv def batch_process(img_dir, output_csvresults.csv): rows [] for fname in sorted(os.listdir(img_dir)): if not fname.lower().endswith((.jpg, .png)): continue path os.path.join(img_dir, fname) img, approx find_card_contour(path) if len(approx) ! 4: rows.append([fname, 角点检测失败, ]) continue warped warp_card(img, approx) choices recognize_choices(warped, question_layout) exam_num recognize_exam_number(warped) rows.append([fname, exam_num, str(choices)]) with open(output_csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([文件名, 学号, 选择题答案]) writer.writerows(rows)len(approx) ! 4这个判断是后悔药遇到角点检测失败的图直接跳过并记录不会让整个批次崩掉。输出 CSV 用utf-8编码避免中文文件名乱码。准确率验证我习惯抽 20 张人工核对算两个指标选择题逐题准确率和学号整串准确率。如果选择题准确率低于 95%优先查阈值如果学号准确率低优先查切分。这套系统在光照均匀、拍摄角度正的情况下选择题准确率能到 98% 以上学号看手写规范程度印刷体基本没问题手写体得看训练样本覆盖。从那以后我每次拿到新的答题卡样式都强制先跑一遍模板标定和阈值标定再批量处理。这套源码包的价值不在于它多完美而在于它把整条链路都摊开了每一环都能改、能调、能替换成自己的逻辑。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

【外设】之大彩串口显示屏 2026/11/11 13:52:51

【外设】之大彩串口显示屏

大彩串口屏初步使用 1 .官网下载 STM32 屏幕 GUI 设计资料 http://www.gz-dc.com/category/typeid/4112 找到 STM32 Keil 工程,移植相关代码因项目而异进行移植,由于项目简单,本人只对用到的指令接口进行修改。 比如:注意事项&…

阅读更多 →
无法下载Windows系统iso文件 2026/11/10 19:42:40

无法下载Windows系统iso文件

当我遇到这个问题的时候,我打开了一个网站: 登录 然后我打算下载的时候: 突然那个官方的连接就可以下载了:

阅读更多 →
【清华代码熊】DeepSeek V4.1 Flash 后训练详解 2026/11/11 9:31:44

【清华代码熊】DeepSeek V4.1 Flash 后训练详解

📌 上期解析了 DeepSeek V4.1 Flash 模型架构改进,本期解析 DeepSeek V4.1 Flash 预训练/后训练技术: 🌟 预训练:45T 文本 多模态混合语料、直接训练 sparse attention(取消 DeepSeek V4 的 dense 冷启动&…

阅读更多 →
Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ... 2026/11/11 5:36:08

Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ...

文章主要内容和创新点 主要内容 本文聚焦于多模态大语言模型(MLLM)强化学习(RL)训练中的效率问题,提出了一个名为Shuffle-R1的框架。研究发现,当前RL训练存在两个关键缺陷: 优势值坍缩(Advantage Collapsing):批次中大多数优势值集中在零附近,导致有效梯度信号被淹…

阅读更多 →
PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction 2026/11/11 15:37:39

PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction

一、文章主要内容总结 本文聚焦于利用大型语言模型(LLMs)实现个人身份信息(PII)脱敏的研究,旨在解决传统脱敏方法(如基于规则的系统、领域特定命名实体识别(NER)模型)泛化能力差、跨格式/跨语境适应性弱的问题。 研究通过全面评估多种LLM架构(包括密集型LLM(D-LLM…

阅读更多 →
LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model 2026/11/10 17:14:10

LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model

文章主要内容和创新点 主要内容 本文聚焦于二进制图像-文本相关性评估任务(判断图像与文本“相关”或“不相关”),针对该任务中文本格式多样、相关性定义随场景变化等挑战,提出了基于多模态大语言模型(MLLM)的解决方案LLaVA-RE。 模型设计:LLaVA-RE基于LLaVA 1.5架构,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉