
简介本资源是一套面向高校人工智能与计算机视觉方向本科生的毕业设计/课程设计实践方案聚焦教育场景下的自动化作业批改问题融合YOLO目标检测、OCR识别与答案比对评分等关键技术。资源包共27个文件含15张标注样本与效果展示PNG图、11个核心Python模块涵盖YOLO检测、答题区域定位、分数计算、UI交互等、1份README.md说明文档整体压缩后仅9.69MB结构清晰、模块解耦便于分步调试与功能扩展。已有87人学习下载适合具备基础PyTorch与OpenCV知识的学习者开展端到端项目实践。读者可直接复现从作业图像输入、题目/答案区域检测、文本识别到自动评分与反馈生成的完整流程同时获得数据整理脚本、模型调用封装及典型手写体适配思路具备较强的教学参考价值与工程迁移潜力。 做作业批改这个项目是我去年临时接的一个小活——帮一个培训机构做自动批改选择题和填空题的小工具。刚开始我以为难点在OCR识别上结果真正动手才发现最大的坑根本不是识别而是定位。学生的作业照片千奇百怪有的拍歪了有的只拍到半页有的答题卡上还有各种涂改痕迹。你连这道题的答案在哪里都找不准后续的识别、比对、判定全是空中楼阁。后来我彻底把思路换成基于YOLO的作业批改设计先把版面上的题号、作答区域、题干这些元素用目标检测模型定位出来再对定位到的区域做内容识别和批改判定整个项目的精度和稳定性一下子就有了质的提升。这篇文章不讲虚的直接把我从数据标注、模型选型、训练调参、后处理到部署的完整链路捋一遍重点记录我在这个场景里踩过的坑。适合正在做作业/试卷批改、考试阅卷、表单自动处理这类项目的开发者参考也适合想了解目标检测在OCR类场景里怎么落地的朋友。1. 为什么作业批改首先要用YOLO从识别文字到定位版面的思路转变1.1 传统OCR方案在作业批改上的核心障碍作业批改这个任务如果直接上OCR全页识别会遇到三个非常现实的问题。第一个问题是版面结构混乱。一张A4作业纸上题干、选项、学生手写的答案、老师的红笔批注是混在一起的。通用OCR引擎做的是把图里所有文字识别出来但它不会告诉你这是第3题的作答区域还是这是第5题的选项C。识别结果是一堆无结构的字符串你要想按题号把它们归位还得额外做版面分析而通用的版面分析算法在作业这种内容密度不规则的版面上效果通常很差。第二个问题是手写与印刷体的混排。印刷体题干识别起来相对容易但学生手写的那部分内容字体大小不一、歪歪扭扭很多OCR引擎在手写数字和汉字的识别上精度会大幅下降尤其当手写内容紧贴着印刷体的题干时识别框会把两者一起圈进去导致识别结果混入大量无用字符。第三个问题是题号和作答区域的对应关系难以建立。批改的核心动作是对第3题的答案去比对标准答案。如果OCR只归还一堆文字你还得靠坐标去猜测哪段文字属于第3题。作业版面不像标准答题卡那样有固定的填涂区域和条形码学生在纸上乱写可能第3题的答案写在第4题旁边这种坐标错位问题在OCR方案里几乎无解。1.2 技术路线的转折把版面分析当作目标检测问题后来我转换了思路既然定位这么难那就专门训练一个模型来做定位把版面分析问题转化成目标检测问题。所谓版面分析在这个场景里可以简化为几个具体的检测目标题号比如1.、2.、3.这些序号、作答区域学生填答案的位置、题干区域题目印刷体部分、甚至批改标记红笔的对勾和叉号。这些目标本质上都是图像里的一个矩形区域用YOLO来处理再自然不过。这样做的好处非常明显。第一你不需要关心文字具体是什么只需要告诉模型这里有一个作答框模型学到的是版面的空间结构特征。第二YOLO的输出自带坐标框你天然就拿到了第3题作答区域在图像的哪个位置这个信息题号和答案的对应关系可以直接从坐标关系里推算。第三作业纸倾斜、透视变形这类问题目标检测的矩形框本身就有一定的鲁棒性再加上后处理的透视校正基本可以解决。1.3 YOLO在批改场景下不可替代的几个优势为什么最终选了YOLO而不是Faster R-CNN、SSD或者DETR我个人的项目经验可以从三个维度来说。一是速度必须快够用。作业批改是批量任务一次可能要处理几十张甚至上百张照片。如果一张图要等一两秒用户体验就崩了。YOLO是单阶段检测器天然在速度上有优势用轻量版本跑CPU也能接受。二是社区生态成熟训练部署的链路都很顺。YOLO的训练管线经过无数人踩过坑数据格式、预训练权重、模型导出ONNX/TensorRT、部署方案都是一套完整体系。做项目最怕的不是算法难而是工程链路里的各种隐性坑成熟的生态意味着这些坑大概率有人替你填过了。三是模型体积和精度之间的平衡可控。YOLO系列从n到x的型号梯度很清晰可以根据部署的设备性能灵活选择。在教室的老电脑上跑就用n或s型号在服务器上做批量处理就用m或l型号。这种灵活性在真实项目中极其重要。2. 标注是项目的命根子作业版面检测的标注规范与数据转换2.1 需要检测的目标类型与边界框规范在做标注规范之前我先把整个批改流程里的检测目标列了一个清单这个清单直接决定了模型要学习什么。作业批改场景通常需要检测以下几类目标题号question_number例如1.、2.、3.这样的序号。它们是建立哪道题对应哪个作答区域的关键锚点。作答区域answer_region学生填写答案的空白位置。在选择题里可能是学生圈出来的选项字母在填空题里可能是横线上方手写的内容。题干区域question_stem印刷体的题目文本区域。检测它主要用于版面排版和后续的OCR识别。批改标记mark如果要做二次批改辅助或老师批改结果录入可以检测红笔的对勾、叉号、半对号。我最初做了一个很傻的设计把作业纸整张作为一个检测类别想让模型输出整张图的范围。后来发现这个类别完全没有意义因为整张作业纸本来就占据图像的大部分区域检测它等于什么都没做。真正有价值的是检测上述细粒度目标。边界框的标注规范上有一个原则非常重要框一定要贴合目标本身不要为了省事把相邻元素也包进来。比如标注题号1.就只框住1.这三个字符的范围不要顺带把后面的题目正文也框一部分进来。因为模型在训练时学到的是框内是被检测目标框外是背景如果框内混入了太多不属于该类别的背景内容模型就会学到错误的特征。在作业这个场景里还有一个特殊情况需要约定如果学生手写的答案超出了作答区域的虚线框应该以哪个范围为准我的经验是区域框始终以印刷的答题区域为准不要因为手写内容溢出就扩大标注框。因为检测模型的目的是定位答题区域在哪里而不是字写到哪里。至于溢出的部分交给后续的OCR裁剪处理裁剪时可以用一定的边距来兜住。2.2 数据标注工具与VOC转YOLO格式转换脚本标注工具我用的是LabelImg和X-AnyLabeling这两个。LabelImg是老牌工具操作简单轻量Windows和Linux都能跑适合快速标一批数据。X-AnyLabeling支持自动标注辅助功能可以先用一个预训练模型做初步检测人工修正后再导出能省不少力气尤其在数据集比较大时效率优势很明显。标注完成后工具默认导出的是PASCAL VOC格式的XML文件而YOLO训练需要的是TXT格式的标签文件每行格式为class_id center_x center_y width height其中坐标值都归一化到0到1之间。这个转换需要自己写个小脚本处理。这里直接给出我用的转换脚本在大多数VOC格式标注目录下都能直接跑通import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, class_names, target_dir): 将单个VOC XML文件转换为YOLO格式的TXT文件。 class_names: 类别列表顺序必须与训练配置中的类别顺序一致。 tree ET.parse(xml_file) root tree.getroot() image_w int(root.find(size/width).text) image_h int(root.find(size/height).text) txt_lines [] for obj in root.findall(object): class_name obj.find(name).text if class_name not in class_names: continue class_id class_names.index(class_name) bbox obj.find(bndbox) x_min float(bbox.find(xmin).text) y_min float(bbox.find(ymin).text) x_max float(bbox.find(xmax).text) y_max float(bbox.find(ymax).text) # 计算YOLO格式的归一化中心坐标和宽高 center_x ((x_min x_max) / 2) / image_w center_y ((y_min y_max) / 2) / image_h width (x_max - x_min) / image_w height (y_max - y_min) / image_h txt_lines.append(f{class_id} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}) # 输出与XML同名的TXT文件到目标目录 base_name os.path.splitext(os.path.basename(xml_file))[0] with open(os.path.join(target_dir, base_name .txt), w) as f: f.write(\n.join(txt_lines)) def convert_all(xml_dir, txt_dir, class_names): os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), class_names, txt_dir) if __name__ __main__: # 类别顺序要记住训练时还要用 CLASSES [question_number, answer_region, question_stem, mark] convert_all(path/to/annotations, path/to/labels, CLASSES)转换脚本里有一个很容易踩的坑类别顺序的索引必须和训练时保持一致。如果你在转换脚本里写的是[question_number, answer_region, question_stem, mark]那训练时的data.yaml里也必须按这个顺序排列类别否则类别标签就全错位了。2.3 针对作业版面的数据增强策略数据增强是训练目标检测模型时绕不开的一环但作业版面有自己的特殊性不能照搬通用检测里的默认增强配置。Mosaic增强的取舍。Ultralytics的训练默认开启Mosaic它把四张图拼接成一张训练图对小目标检测和模型泛化能力提升明显。但在作业批改场景里Mosaic拼接会把题干文本截断、把作答区域切成碎片导致模型学到的版面结构特征失真。我实际测试下来在版面检测任务里完全关闭Mosaic反而更稳。如果非要用可以把Mosaic的概率调到0.3以下且关闭Mosaic的最终epoch阶段ultralytics里会自动在最后10个epoch关闭Mosaic。旋转变换需要克制。作业照片虽然拍摄角度各异但绝大多数还是接近正视角的最多有轻微的倾斜。如果增强时加了大角度的随机旋转比如30度以上模型会学到版面是歪的这种假特征反而降低真实场景的检测精度。常用的做法是把旋转角度限制在正负10度以内再用透视变换模拟手机拍摄时的微小形变。模拟扫描痕迹和拍摄噪声。这一点很多时候被忽略但实战价值很高。真实作业照片往往有阴影、纸张褶皱、甚至背面字迹透过来导致的干扰训练时可以用高斯噪声、亮度扰动、对比度扰动、随机遮挡这些小幅度增强来模拟这些情况。注意扰动幅度不能太大否则会破坏字符边缘的锐利度影响后续对题号、作答区域边界的检测。3. YOLOv5/v8/v11怎么选按作业场景匹配合适的检测方案3.1 主流版本能力对比现在市面上用于目标检测的YOLO版本其实很多很多人问YOLOv8和YOLOv11有什么区别该学哪个。我把自己实际对比过的主流版本列了一个表方便项目选型时参考。版本类型特点适用场景YOLOv5Anchor-based成熟稳定社区资料极多部署生态最完善规整扫描件、标准答题卡的版面检测YOLOv8Anchor-free检测头简化支持旋转框OBB和实例分割收敛速度较快手机拍照、版面倾斜、需要检测批改符号的场景YOLOv10Anchor-free去掉了NMS后处理端到端推理延迟更低对推理延迟极其敏感的实时处理场景YOLOv11Anchor-free使用C3k2模块融合了注意力机制小目标检测能力增强题号、选项标记这类小目标较多的作业版面这里重点说一下Anchor-based和Anchor-free的区别。YOLOv5这类Anchor-based方法相当于在图像上预置了一批不同尺寸、不同长宽比的候选框模型学习的是候选框里是否有目标以及候选框需要怎么微调。Anchor-free方法YOLOv8及以后则不再依赖预置候选框而是直接预测目标的中心点和尺寸理论上泛化能力更好配置也更简单。在作业版面这种目标尺寸差异较大的场景题号很小作答区域很大Anchor-free的检测稳定性会好一些因为你不必费心去调整Anchor的尺寸预设。3.2 三个具体场景的选型建议如果拿不准用哪个版本可以直接参考我的三个选型建议都是我实际对比过的结论。**场景一培训机构打印的作业单扫描仪采集版面规整。**这种场景下作业页面基本没有透视变形文字印刷清晰版面结构固定。YOLOv5s就足够了精度和速度的平衡很好而且部署方案极其成熟C、Java端都有大量现成方案可以参考。不要为了追新非上更高版本稳定的生态对项目交付更重要。**场景二学生用手机拍作业上传存在倾斜、透视、阴影。**这种图对检测框的角度敏感普通矩形框会把倾斜的作答区域框出很多背景。推荐用YOLOv8n的旋转框OBB检测检测头会输出带角度的旋转框配合后处理的透视校正可以更精准地裁剪作答区域。此外YOLOv8的实例分割能力可以顺带检测手写笔迹的覆盖范围这个信息在后续识别阶段的预处理里很有用。**场景三需要做批改结果二次录入即检测老师的红笔对勾和叉号。**推荐YOLOv11分割模型。因为对勾和叉号面积小、形状不规则直接用矩形框检测容易把对错信息判定含糊。分割模型输出的掩膜可以精确区分打了对勾和只画了一条线的差异后续判断正确还是错误时的鲁棒性更高。实测下来YOLOv11在这样的小目标检测精度上确实比v8有明显提升尤其是题号这类小元素误检率降了不少。3.3 理解网络结构差异避免唯参数论很多新手选模型只看参数数量和mAP数值但实际项目中网络结构设计对任务适配性的影响往往比简单的精度数值更重要。YOLOv8相比v5最大的结构变化是C2f模块取代了C3模块。C2f在特征融合层增加了更多的分支连接让梯度流动更充分对小目标特征和长尾特征的提取能力更强。在作业批改场景里题号1.、2.这些元素在整张A4图里占比可能只有千分之几属于典型的小目标需要深层特征金字塔有足够的分辨率去保留它们的空间细节。C2f的多分支结构在这里帮了不小的忙。YOLOv11引入的C3k2模块进一步提升了骨干网络的特征提取效率而且增加了一部分注意力机制让模型更关注题目区域和作答区域之间的空间关系。这一点特别契合版面检测任务因为作业版面本质上是一个高度结构化的空间关系图一道题的题号和它的作答区域之间有很强的相对位置依赖注意力机制能更好地编码这种相对位置信息。当然结构理解归理解落地时还是用工程思维来选型你的部署硬件是什么、数据量大概有多少、精度瓶颈在哪个环节。先确定这些再去挑版本而不是反过来。4. 训练作业检测模型小样本、高细节数据的调参与踩坑记录4.1 训练配置与迁移学习策略作业批改项目的训练数据通常不会像通用目标检测数据集那么大。我从几个培训机构收集来的真实作业照片加上网上能找到的一些公开答题卡数据集总共标注了不到2000张图。这个数据量下最优策略一定是基于COCO预训练权重做迁移学习而不是从零训练。使用Ultralytics训练框架核心命令大概是这样的yolo detect train \ datahomework.yaml \ modelyolov8n.pt \ epochs200 \ imgsz1280 \ batch16 \ lr00.002 \ mosaic0.0 \ patience50这里有几个参数是专门针对作业版面场景调的。**输入分辨率imgsz选择1280而不是默认的640。**作业版面里小目标太多了640分辨率下一个题号可能只有十几个像素宽模型根本看不清。提高到1280后小目标的像素量翻了两番检测精度提升非常明显。代价是显存占用和训练时间大幅增加但这是值得的。如果显存不够可以配合矩形训练ultralytics里默认会启用rect模式来减少填充带来的显存浪费。**初始学习率lr0调低到0.002。**因为是从COCO预训练权重做迁移模型的特征提取骨干网络已经学到很强的通用视觉特征初始学习率过大反而容易破坏这些预训练特征的稳定性导致训练早期loss剧烈震荡。调低初始学习率配合余弦退火整个训练过程会更平稳最终收敛精度也更高。**patience早停设置成50。**作业批改场景的数据量不大模型很容易在100个epoch左右就趋向收敛之后开始过拟合。早停可以省下大量无效训练时间。4.2 训练指标异常的排查思路这里单独说一个高频问题很多人刚开始训练YOLO时发现Loss能跑但mAP全程是0或者训练指标全是0。这个问题我在作业批改项目里也遇到过当时折腾了整整一个晚上最终定位到问题根源。**第一步先检查数据集路径和图片是否成功加载。**很多情况下是训练命令里的data.yaml的path字段写错了数据集压根没加载进来模型在空数据上空转。在Ultralytics里启动训练后日志会显示加载的图片数量和标签数量如果显示是0那肯定是路径或格式的问题。**第二步检查标签文件是否与图片一一对应。**Ultralytics要求每张图片都有一个同名的TXT标签文件且标签文件必须放在与图片名字相同的目录结构里。如果你的图片是images/train/001.jpg标签必须是labels/train/001.txt类别索引从0开始。如果标签放错目录训练时会报警告。**第三步检查标签内容是否有空文件或非法坐标系。**有些标注工具导出的坐标有轻微越界比如x_max略微超出图片宽度训练时会被过滤掉。如果过滤后标签数量为0mAP自然就是0。可以在训练前写一个小脚本检查标签文件里有没有负数、有没有超过1.0的归一化坐标。**第四步检查类别名是不是带了空格或特殊字符。**YOLO的data.yaml里类别名不能有空格比如answer region会报错必须改成answer_region。4.3 数据增强与两阶段训练优化除了基础的增强策略我还做了一件事两阶段训练。第一阶段先用完整数据集、固定的输入尺寸1280px、关闭Mosaic训练一个版面检测模型目标是稳定检测出题号、作答区域、题干区域。第二阶段把这个模型的输出当作区域提议针对每个检测到的作答区域裁剪出局部小图再训练一个小的作答内容分类模型判断这个区域里是A还是B或者是空的。这种方法的好处在于你不需要指望一个模型同时解决定位和识别两个难题。定位问题用YOLO解决识别问题用更简单的分类模型解决每个环节的精度都可以单独优化。最终整个批改流程的精度是两个环节精度的乘积所以任何一个环节做扎实都比一步到位的端到端方案更可控。实际操作中代码层面可以用Ultralytics的model.predict()接口先跑一遍检测把左上角、右下角坐标存下来from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(sourcehomework_images/003.jpg, conf0.5, imgsz1280) for result in results: boxes result.boxes for box in boxes: cls_id int(box.cls[0]) x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) print(fclass_id{cls_id}, conf{conf:.3f}, bbox({x1:.1f}, {y1:.1f}, {x2:.1f}, {y2:.1f}))这些坐标信息就是你后续做版面还原、区域关联、裁剪识别的基础数据。5. 检测只是第一步从检测框到批改结果的完整处理链5.1 版面还原透视校正与坐标映射YOLO输出的通常是整个输入图像坐标系下的边界框但手机拍摄的作业照片基本都是有一定透视变形的不规则四边形。如果直接拿原始的检测框去裁图裁出来的作答区域会有文字的透视倾斜影响后续OCR的精度。我的做法是在版面里找到四个固定的锚点用透视变换把作业版面扳正。锚点从哪里来可以专门标注作业纸的四个角作为特殊类别也可以利用检测出的题号位置做线性映射。更简单通用的方案是如果已知作业纸是A4或者标准信纸直接检测作业纸的四个角点然后做仿射变换。OpenCV里实现透视变换很简单import cv2 import numpy as np def perspective_correct(image, src_pts, dst_size(800, 1200)): src_pts: 原图中作业纸的四个角点坐标顺序为左上、右上、右下、左下。 dst_size: 矫正后的目标图像尺寸宽, 高一般按A4宽高比设置。 dst_pts np.array([[0, 0], [dst_size[0], 0], [dst_size[0], dst_size[1]], [0, dst_size[1]]], dtypenp.float32) matrix cv2.getPerspectiveTransform(src_pts.astype(np.float32), dst_pts) corrected cv2.warpPerspective(image, matrix, dst_size) return corrected透视校正之后YOLO检测出的坐标也需要做对应的映射。最稳妥的做法是先对原图做透视校正再在校正后的图像上跑YOLO检测。这样检测框坐标、裁剪区域的坐标、OCR的输入坐标都在同一套坐标体系下省去大量的坐标转换计算。5.2 题号与作答区域的关联逻辑检测模型输出了一堆题号框和一堆作答区域框之后最重要的一个操作是把它们配对。这一步直接决定批改结果能不能和标准答案对得上。我的关联逻辑很简单也很可靠按空间位置排序。一张作业纸通常有多个题目题号按从上到下、从左到右排列作答区域一般紧随其对应题号的右侧或下方。因此可以按Y坐标排序将题号和作答区域分组。具体做法是将所有检测到的题号框按Y坐标从上到下排序。对每个题号框找到所有与其Y坐标范围有交集的作答区域框。在这些候选作答区域里选择IOU最大且满足水平/垂直邻近关系的那一个作为该题号对应的作答区域。如果某个题号找不到对应的作答区域通常意味着该题学生没有作答或区域检测漏检标记为待复核。这个逻辑在标准作业版面上成功率很高但遇到两列排版、上下左右交错排列的作业纸时需要加上基于X坐标的排序逻辑来避免配错。我的经验是不要完全依赖一种排序策略最好把题号的坐标和作答区域的坐标都输入到一个简单的匹配逻辑里比如先按Y轴粗分组再按X轴细排序。5.3 作答内容识别与批改判定规则作答区域定位好之后识别内容的方法有三种路线我分别试过这里把心得写出来。**路线一纯OpenCV形态学操作。**如果学生的作答是填涂选择题涂卡比如涂黑的方形可以用阈值分割加轮廓检测直接判断涂没涂黑。这个方法速度快、不需要额外模型但只适用于填涂式作答。**路线二接OCR引擎识别手写或印刷文字。**比如用PaddleOCR对裁剪出来的作答区域做文字识别。这个方法对印刷体效果很好对手写体要看具体的工整程度。实测下来手写数字和字母的识别PaddleOCR在精心调优的前提下可以达到90%左右的准确率但对连笔和潦草字体的鲁棒性不够。**路线三轻量分类模型。**如果你能提前知道每道题的所有可选答案选择题的A/B/C/D判断题的对/错那完全可以把问题建模成图像分类把裁剪出来的作答区域输入一个ResNet或MobileNet分类模型输出是分类标签。这也是我前面提到的两阶段方法里第二阶段的模型。实测下来对选择题涂卡和判断题打钩打叉的识别分类模型比OCR稳定得多因为分类模型不用把每个字都读出来只学这个区域的整体视觉模式抗手写噪声能力更强。批改判定的规则相对简单。比如选择题标准答案是B模型识别出学生作答区域的内容类别是B就判对否则判错。但对于学生没作答和识别模型低置信度这两种情况我的处理方式是直接归到待人工复核不要硬判。这个兜底逻辑在真实教学场景里很重要宁可让老师人工看几道题也不要因为误判让学生白白丢分。6. 部署与实测从训练机到教学环境的落地细节6.1 模型导出与推理加速模型训练好了最终要部署到用户的设备上去。作业批改软件的使用环境五花八门有的是Windows电脑有的是Linux服务器还有的要跑在浏览器里。最通用、最稳妥的做法是把PyTorch模型导出为ONNX格式然后再根据目标设备选择推理引擎。Ultralytics框架自带导出命令yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz1280 halfTrue这里halfTrue表示导出半精度FP16模型体积减小一半推理速度明显提升。在NVIDIA显卡上可以继续用TensorRT进行二次优化在CPU设备上可以用OpenVINO加速。实测下来同样的YOLOv8n模型TensorRT优化后推理速度比PyTorch原生推理快5倍以上这对批处理场景帮助巨大。在导出ONNX时有几个细节值得注意。动态尺寸和固定尺寸的选择。如果你的推理设备性能参差不齐建议导出为固定尺寸1280输入按比例缩放后做letterbox填充这样既能保持形状统一又不会引入额外的尺寸推断开销。另外ONNX的输入名和输出名在后续接推理引擎时需要记住不同版本可能有差异导出来后最好用onnxruntime验证一下输出形状。6.2 FastAPI封装批改服务批改应用的实际形态可以是一个Web服务前端上传作业照片后端返回批改结果。我用FastAPI封装了一层推理服务核心逻辑就是检测 - 配对 - 裁剪 - 识别 - 判定 - 返回结果。关键代码骨架大致如下from fastapi import FastAPI, UploadFile, File import onnxruntime as ort import numpy as np import cv2 app FastAPI() session ort.InferenceSession(best.onnx) CLASS_NAMES [question_number, answer_region, question_stem, mark] app.post(/correct) async def correct(file: UploadFile File(...)): # 1. 读图 img_bytes await file.read() img cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) # 2. 预处理 推理简化版实际需要letterbox与坐标还原 input_tensor preprocess(img) outputs session.run(None, {session.get_inputs()[0].name: input_tensor}) # 3. 后处理坐标还原、题号-作答区域配对、裁剪、内容识别、批改判定 ... return {result: 批改结果}这里有一个真实项目里容易忽略的问题预处理必须和后处理里的坐标还原严格对称。训练时图像是letterbox填充到1280的推理时你也必须做一模一样的letterbox且检测出的坐标要从letterbox坐标系还原回原始图像坐标系。很多人ONNX部署后检测框位置偏差大十有八九是这一环节出了问题。6.3 实测结果与迭代优化整个系统跑通之后我在一个培训机构拿了一批真实数据进行测试这里放一组实测数据供参考。项目指标测试图片数量500张手机拍摄的作业照片题号检测召回率IoU0.596.2%作答区域检测精确率94.8%选择题批改准确率含定位和识别91.6%填空题批改准确率84.3%单张图片平均处理耗时YOLOv8n ONNXGTX 1660约0.4秒单张图片平均处理耗时同配置CPU约2.8秒这个数据说明检测环节本身的精度已经够用最终的误差主要出在答区域内容识别上尤其是手写体。在迭代优化过程中我最大的一个体会是要建立一个反馈闭环。具体做法是每次批量批改后把模型低置信度或实际出现误判的样本自动记录下来定期人工复核并修正标签然后补充进训练集做增量训练。实测下来每轮增量训练大约能提升1到2个百分点的批改准确率效果非常稳定。这个闭环机制的价值甚至比模型结构调优更重要因为真实世界的作业样例多样性太强只有不断吸收真实样本模型才会越来越贴合实际场景。另外提一个很实用的点给检测模型设置一个合理的置信度阈值也很有讲究。在作业批改场景里宁可将置信度阈值设低一点比如0.3让凡是有点像作答区域的框都先输出后续靠规则过滤也不要设高阈值导致漏检。因为漏检一个作答区域意味着学生整道题被跳过这在教学场景里是不可接受的。我自己在这个项目里最后悔的一件事是一开始就想让模型一步到位输出对错。后来调整成定位-识别-判定三步拆分之后每个环节的精度都更容易单独提升整个系统也变得更好维护。对做作业批改或者文档分析类项目的朋友这个拆分的思路我认为是值得优先借鉴的。如果你正在做类似的项目建议也先把手里的数据按照这个思路拆好训练一个自己的版面检测模型再逐步迭代出完整的批改流程。本文还有配套的精品资源点击获取