答题卡智能批改系统:选择题 OMR 秒出,主观题交给一次大模型调用

技术栈:React + TypeScript + Tailwind / FastAPI + SQLite,前后端同仓,本地部署,数据不出校。

一、先看效果:从"通宵批卷"到"几分钟出一个班"

不铺垫,先看这套系统跑起来是什么样子,再讲为什么这么做。

登录后是工作台,一条完整的批改流水线被组织成左侧的几个模块: 每个模块的职责一句话讲清:

  • 试卷管理------录入题目清单(题号、题型、分值)、选择题标准答案、作文评分细则,也支持从文本文件宽松导入;
  • 答题卡管理------导入答题卡模板(PDF/图片),自动检测四角定位标与气泡网格,可视化标定坐标,一个模板适配任意印刷版式;
  • 答题卡批改------每位学生一个文件夹(文件夹名即姓名),整班拖入即自动归档、逐份逐页识别;
  • OMR 试验台------上传样张单独调试选择题的光标识别参数,调准了再投入正式批改;
  • 成绩中心------复核、发布成绩、导出 CSV;
  • 模型与设置------AI 服务商、密钥、提示词全部在线配置,换服务商不改代码。

识别效果:重点看这张图

自动批改完成后进入成绩中心的复核视图,这是最能体现识别效果的一屏: 左半边是学生的答题卡原图,上面逐框叠加了每道选择题的判定标记------填涂、未填涂、存疑,一目了然。实测选择题识别准确率约 99%:图中这份卷子 84/120,整张卡 66 道选择题,系统只把 3 道题标记为"待复核"。而且这 3 道不是识别错了,而是它"知道自己不确定"------置信度不足、疑似改答案的双填涂、空答案,都会被诚实地举出来,而不是给出一个自信的错误答案。

右半边是复核队列:机器拿不准的题才需要人看,确认一道自动跳下一道。老师的注意力只花在那 3 道题上,而不是 66 道题上------这就是整套系统的价值:**机械判断零成本交给本地 OMR(整班几秒出结果,无 API 费用),模糊判断一次多模态调用交给大模型,最终判断留给人。**所有数据本地存储,不出校。

主观题:AI 作文批改同样可查可改

复核视图往下滑就是主观题部分。作文题由多模态大模型完成三件事:把学生手写原文忠实转写 成文字、按评分细则给出分数 、逐条列出扣分点 (哪里拼错了、哪里语法不对、怎么改):

值得强调的是,评分的"习惯"来自老师:作文题在试卷管理里挂上评分细则(rubric),AI 按老师的标准评,而不是它自己的感觉;转写文本、得分、评语全部可以人工修改,改完实时重算总分。

至于为什么要做它、每条流水线背后有哪些设计考量,下面慢慢展开。

二、从真实痛点说起

每次阶段测试之后,老师的批改流程大概是这样的:抱着几十份答题卡,先对着答案册一题一题圈选择题,再把填空和作文逐份看完、打分、写评语,最后把成绩誊到表格里。一份 100 分的卷子认真批下来要十几分钟,两个班的量就是一个通宵。

更难受的是这个过程中的大把时间是机械劳动------选择题的判断根本不需要"理解",只是比对涂点位置;真正值得老师花脑力的,是作文的立意和语言,是那些机器拿不准的答案。

所以这个系统的设计目标很明确:

  • 机械判断交给机器------选择题在本地几秒内完成,不花一分钱模型调用;
  • 模糊判断交给模型------填空转写、作文评分合并成一次多模态调用;
  • 最终判断留给人------所有低置信度结果进复核队列,老师确认后才发布成绩。

三、完整的工作流:五步走完一个班

系统不只是一个"识别脚本",而是一条完整的批改流水线:

① 建试卷------录入题目清单(题号、题型、分值),选择题/多选题填标准答案(多选支持备选答案集合),作文题挂上评分细则。也支持从文本文件导入答案,格式宽松。

② 上传答题卡------每位学生一个文件夹(文件夹名即学生姓名),可以一次拖入整班的文件夹。系统自动按姓名归档,逐份逐页处理。

③ 自动批改------后台流水线:选择题走本地 OMR 即时出分;填空与作文合并成一次视觉大模型调用;两路结果按题号合并成一份完整答卷记录。

④ 人工复核------批改完成后自动进入成绩中心的复核视图:左边是答题卡原图,上面叠加了每道选择题的判定标记(绿框 = 已通过、琥珀框 = 待复核),点击标记或复核队列直接定位题目;右边是题目面板,可以修改学生答案、手动打分。确认一道自动跳下一道,像流水线一样滑完整个班。

⑤ 发布成绩------全部确认后一键发布,进入成绩中心按试卷查看全班成绩,支持导出 CSV。发布前随时可以撤回修改。

四、OMR 详解:用传统图像处理把选择题做"死"

OMR(Optical Mark Recognition,光学标记识别)在这里不需要任何神经网络------因为标准答题卡的涂点位置在印制时就是已知的几何坐标,这是一个确定性的模板匹配问题。整个流程分四步:

4.1 基准定位标(fiducial markers)与仿射校正

扫描、拍照得到的答题卡普遍存在平移、旋转甚至轻微的透视变形,直接按"印刷坐标"去采样必然错位。解决办法是在答题卡四角印制基准定位标------实心黑方块,它们的位置是全局已知常量。

处理时先做二值化 (binarization),再在图像中检测这些实心黑块,得到它们在当前扫描件中的实际坐标。利用四角定位标计算出仿射变换矩阵 (affine transform),对整页图像做仿射校正,把歪掉的答题卡"掰正"回标准坐标系。这一步之后,所有气泡的印刷坐标都可以直接当采样坐标用。

4.2 感兴趣区域(ROI)与填充率统计

校正后,每个选项气泡对应一个小矩形感兴趣区域 (Region of Interest, ROI)。对每个 ROI 统计暗像素占比(dark pixel ratio)------即区域内灰度低于阈值的像素比例。学生用铅笔、水笔涂出的区域,暗像素占比会显著高于空白气泡;轻涂、半涂则落在中间地带。

arduino 复制代码
填充率 ≥ 涂抹阈值        → 判定为"已填涂"(marked)
填充率 < 涂抹阈值        → 判定为"未填涂"(blank)
填充率处于边界灰区        → 判定为"存疑"(ambiguous)

4.3 判定逻辑与歧义检测

单选题的判定规则:取填充率最高的选项,若其值达标且明显领先第二名 ,判定该选项;若出现双峰(两个选项填充率接近且都达标),大概率是学生改答案没涂干净,标记为 ambiguous。多选题(multiple)则取所有达标选项的集合,再与"该题允许的选项数"做校验。

所有判定都附带一个基于填充率分布的置信度(confidence),它决定了这道题的走向:

  • 高置信度 → 直接计分,前端渲染绿色标记;
  • 低置信度 / 歧义 → 不猜,进复核队列,渲染琥珀色标记。

4.4 坐标回传:让机器的判断"看得见"

这是 OMR 模块里我觉得最值钱的设计:每个气泡的 bbox(bounding box,外接矩形) 连同判定状态一起存进数据库。前端复核时直接把标记叠加渲染在答题卡原图上,老师看到的不是一串数字,而是"这张卷子上第 7 题被圈出来了"------点一下就能跳到对应题目。机器的每个判断都可追溯、可推翻。

这一步纯本地计算:整班的选择题在几秒内出结果,API 成本为零,而且它"知道自己不确定"------所有拿不准的答案都会诚实地举手,而不是给出一个自信的错误答案。

五、LLM 批改:一次多模态调用管所有主观题

5.1 为什么填空和作文合并成一次调用

两者都是"看手写图、输出结构化结果",分开调用纯粹浪费。系统把本次需要识别的全部主观题(题目清单 + 各页图片)打包进同一次 chat completions 请求,指定模型输出统一的 JSON 协议,一次往返拿到整份卷子的主观题结果。

5.2 提示词的结构化组装

提示词不是一大段自由文本,而是分层拼装:

typescript 复制代码
全局角色设定(同一学生的连续多页,跨页读取;只允许清单内题号)
+ 各题型提取规则(按需挂载:填空规则 / 作文规则)
+ 题目清单 JSON(number / section / type / score)
+ 评分细则(作文题的 rubric 原文)
+ 输出协议(仅返回 {"results":[...]},字段与取值范围固定)
+ 逐页图片(base64 内联,标注页码)

其中几条是实测踩坑后加上的硬约束:

  • 题号防漂移:明确禁止模型"按视觉顺序重新编号、连续补号或根据上一题推算下一题"。答题卡的题号经常跨列、跳号、不按视觉顺序排,不锁死这条,模型会好心地把结果重排成连续序号,导致与标准答案全部错位------这是最隐蔽也最致命的错法;
  • 忠实转写:填空题要求照抄学生原文,不纠正拼写语法,有划改取最终保留内容,忽略红叉绿框等批改痕迹;
  • 评分锚定:作文分数被限制在"不超过该题分值"内,评语要求指出扣分点,避免模型给出"人人 90 分"的客气分。

5.3 图片预处理与输出校验

调用前每页图片统一走一道预处理管线:按 EXIF 信息转正方向 → 宽度归一到 1600--2400px(太小看不清涂写,太大浪费 token)→ 自动对比度增强 → JPEG 质量 92 重压缩。实测对手机拍照件尤其有效。

返回侧同样不信任模型:强制 response_format: json_object、temperature=0;解析后做结构校验------题号必须存在于清单、每题最多出现一次、字段类型正确,任何越界直接判为本次识别失败并提示重试,绝不把半可信的数据静默写库。

5.4 成本账

一份 8 页答题卡,模型调用次数 = 1 。max_tokens 按配置截断,图片经压缩后单页 token 可控。以整班 40 人计,主观题部分就是 40 次调用;而如果选择题也走模型,这个数字会膨胀到 40 × 题目页数,且每次都是整图的高分辨率输入------成本差一个数量级,还更慢。

六、通用性:这套系统本来就不依赖"英语"

设计时刻意把"科目知识"从代码里剥离了出去:

  • OMR 只关心几何:它认的是定位标和气泡网格,不关心涂点旁边印的是 A/B/C/D 还是 ①②③④,也不关心题目内容是英语、数学还是理综。换一张标准答题卡模板,选择题照样跑;
  • 评分细则随卷走:作文/主观题的评分依据来自试卷上挂载的 rubric 字段,是数据而不是代码------语文作文、文综论述题,换一份细则就是换一个科目;
  • 提示词可在界面调整:提取规则(全局规则 + 各题型规则)在「模型与设置」页在线编辑、即时生效,改科目不需要碰代码;
  • 题目清单驱动:整条批改流水线以录入的题目清单为准绳,识别、判分、复核、统计全部围绕这份清单展开,题型扩展只需要新增题型枚举。

换句话说,"英语智评"只是当前的一套预设;把它当作一个可配置的答题卡批改平台来看更准确。

七、复核闭环与成绩管理:价值落地的最后一公里

再准的自动批改,落地场景里也必须回答一个问题:**老师敢直接用吗?**系统的答案是构建信任闭环:

  1. 可见------每道选择题的判定都以标记形式叠加在原图上,对错一眼可查;
  2. 可改------复核面板支持改学生答案(选择题点选项、主观题直接编辑转写文本)、改分数,修改后实时重算总分;
  3. 可控------低置信度答案强制进入复核队列,未复核完的记录不能发布,杜绝"AI 分数直接进成绩单";
  4. 可追溯------每份答卷记录保留 AI 初评分(ai_score)与发布分(score),谁改过、改了多少有据可查。

发布后的成绩在成绩中心按试卷分组查看,支持 CSV 导出接回学校现有的成绩管理流程。

八、模型接入:换服务商不改代码

模型的 Base URL、API Key、模型名、超时全部在设置页配置,存入数据库,环境变量作为兜底。接口对密钥只回显末四位掩码,任何保存操作传入掩码值都视为"保持不变"。内置连接测试会真的发一张测试图走一次多模态请求,验证接口连通性与图片输入能力,配置错了当场暴露。

九、小结

这个系统想证明的事情其实很简单:AI 批改的正确姿势不是"把卷子整个扔给大模型",而是按确定性分层------

  • 涂点位置是确定性的,用 OMR 的几何方法做,快、准、零成本;
  • 手写内容是模糊的,用一次多模态调用做,锁协议、强校验;
  • 成绩的公信力是人的责任,用复核闭环做,机器标记、老师裁决。

代码结构(前后端同仓):

bash 复制代码
app/
├── backend/              # FastAPI 服务端
│   ├── omr_service.py        # 定位标检测、仿射校正、ROI 填充率判定
│   ├── ai_service.py         # 提示词组装、多模态调用、结果校验
│   ├── grading.py            # 客观题判分与作文分数约束
│   └── main.py               # 试卷/上传/复核/发布/设置 API
└── src/                  # React 前端
    ├── pages/Upload.tsx      # 整班文件夹上传与批改进度
    ├── pages/Results.tsx     # 成绩中心(列表 + 复核详情)
    └── components/           # 答题卡标记叠加等组件

如果说还有下一步,是把 OMR 的答题卡模板做成可视化标定工具------上传一张样张、框选定位标和气泡网格,就能接入任意印刷模板,让"换科目"变成"换模板"。

代码地址

完整代码已开源于 GitHub(前后端同仓,README 含本地部署步骤):

正在优化

相关推荐
海螺_AIHCC8 分钟前
AGENTS.md 别手写,让它自己从仓库里长出来
人工智能
用户241015057020811 分钟前
给 100 块的 AI 玩具写了一套英语启蒙 App:100 个词、看图选词、开口打分
人工智能
黄啊码25 分钟前
从 0 到 1 做 FDE:如何获客、成交、交付?百万级 AI 项目实战复盘
人工智能
玩美数据35 分钟前
海外市场数据服务常用术语与名词解释权威指南
大数据·人工智能
Lansonli1 小时前
Claude Code 写 Shell 脚本翻车实录:一份避坑指南
人工智能
IT_陈寒1 小时前
Vue的这个响应式陷阱,差点让我加班到天亮
前端·人工智能·后端
东离与糖宝2 小时前
Spring AI 大模型容易混淆概念一次性讲透
人工智能
复杂网络与机器学习3 小时前
全双工 160 毫秒一格:李沐让模型不再「一问一答」
人工智能
武子康3 小时前
Qwen-Drive 的 91.4 分,究竟选中了什么?
大数据·人工智能·agent