批改設(shè)計(jì):從版面定位到識(shí)別判定全流程解析)
簡(jiǎn)介本資源是一套面向高校人工智能與計(jì)算機(jī)視覺方向本科生的畢業(yè)設(shè)計(jì)/課程設(shè)計(jì)實(shí)踐方案聚焦教育場(chǎng)景下的自動(dòng)化作業(yè)批改問題融合YOLO目標(biāo)檢測(cè)、OCR識(shí)別與答案比對(duì)評(píng)分等關(guān)鍵技術(shù)。資源包共27個(gè)文件含15張標(biāo)注樣本與效果展示PNG圖、11個(gè)核心Python模塊涵蓋YOLO檢測(cè)、答題區(qū)域定位、分?jǐn)?shù)計(jì)算、UI交互等、1份README.md說明文檔整體壓縮后僅9.69MB結(jié)構(gòu)清晰、模塊解耦便于分步調(diào)試與功能擴(kuò)展。已有87人學(xué)習(xí)下載適合具備基礎(chǔ)PyTorch與OpenCV知識(shí)的學(xué)習(xí)者開展端到端項(xiàng)目實(shí)踐。讀者可直接復(fù)現(xiàn)從作業(yè)圖像輸入、題目/答案區(qū)域檢測(cè)、文本識(shí)別到自動(dòng)評(píng)分與反饋生成的完整流程同時(shí)獲得數(shù)據(jù)整理腳本、模型調(diào)用封裝及典型手寫體適配思路具備較強(qiáng)的教學(xué)參考價(jià)值與工程遷移潛力。 做作業(yè)批改這個(gè)項(xiàng)目是我去年臨時(shí)接的一個(gè)小活——幫一個(gè)培訓(xùn)機(jī)構(gòu)做自動(dòng)批改選擇題和填空題的小工具。剛開始我以為難點(diǎn)在OCR識(shí)別上結(jié)果真正動(dòng)手才發(fā)現(xiàn)最大的坑根本不是識(shí)別而是定位。學(xué)生的作業(yè)照片千奇百怪有的拍歪了有的只拍到半頁(yè)有的答題卡上還有各種涂改痕跡。你連這道題的答案在哪里都找不準(zhǔn)后續(xù)的識(shí)別、比對(duì)、判定全是空中樓閣。后來我徹底把思路換成基于YOLO的作業(yè)批改設(shè)計(jì)先把版面上的題號(hào)、作答區(qū)域、題干這些元素用目標(biāo)檢測(cè)模型定位出來再對(duì)定位到的區(qū)域做內(nèi)容識(shí)別和批改判定整個(gè)項(xiàng)目的精度和穩(wěn)定性一下子就有了質(zhì)的提升。這篇文章不講虛的直接把我從數(shù)據(jù)標(biāo)注、模型選型、訓(xùn)練調(diào)參、后處理到部署的完整鏈路捋一遍重點(diǎn)記錄我在這個(gè)場(chǎng)景里踩過的坑。適合正在做作業(yè)/試卷批改、考試閱卷、表單自動(dòng)處理這類項(xiàng)目的開發(fā)者參考也適合想了解目標(biāo)檢測(cè)在OCR類場(chǎng)景里怎么落地的朋友。1. 為什么作業(yè)批改首先要用YOLO從識(shí)別文字到定位版面的思路轉(zhuǎn)變1.1 傳統(tǒng)OCR方案在作業(yè)批改上的核心障礙作業(yè)批改這個(gè)任務(wù)如果直接上OCR全頁(yè)識(shí)別會(huì)遇到三個(gè)非?,F(xiàn)實(shí)的問題。第一個(gè)問題是版面結(jié)構(gòu)混亂。一張A4作業(yè)紙上題干、選項(xiàng)、學(xué)生手寫的答案、老師的紅筆批注是混在一起的。通用OCR引擎做的是把圖里所有文字識(shí)別出來但它不會(huì)告訴你這是第3題的作答區(qū)域還是這是第5題的選項(xiàng)C。識(shí)別結(jié)果是一堆無結(jié)構(gòu)的字符串你要想按題號(hào)把它們歸位還得額外做版面分析而通用的版面分析算法在作業(yè)這種內(nèi)容密度不規(guī)則的版面上效果通常很差。第二個(gè)問題是手寫與印刷體的混排。印刷體題干識(shí)別起來相對(duì)容易但學(xué)生手寫的那部分內(nèi)容字體大小不一、歪歪扭扭很多OCR引擎在手寫數(shù)字和漢字的識(shí)別上精度會(huì)大幅下降尤其當(dāng)手寫內(nèi)容緊貼著印刷體的題干時(shí)識(shí)別框會(huì)把兩者一起圈進(jìn)去導(dǎo)致識(shí)別結(jié)果混入大量無用字符。第三個(gè)問題是題號(hào)和作答區(qū)域的對(duì)應(yīng)關(guān)系難以建立。批改的核心動(dòng)作是對(duì)第3題的答案去比對(duì)標(biāo)準(zhǔn)答案。如果OCR只歸還一堆文字你還得靠坐標(biāo)去猜測(cè)哪段文字屬于第3題。作業(yè)版面不像標(biāo)準(zhǔn)答題卡那樣有固定的填涂區(qū)域和條形碼學(xué)生在紙上亂寫可能第3題的答案寫在第4題旁邊這種坐標(biāo)錯(cuò)位問題在OCR方案里幾乎無解。1.2 技術(shù)路線的轉(zhuǎn)折把版面分析當(dāng)作目標(biāo)檢測(cè)問題后來我轉(zhuǎn)換了思路既然定位這么難那就專門訓(xùn)練一個(gè)模型來做定位把版面分析問題轉(zhuǎn)化成目標(biāo)檢測(cè)問題。所謂版面分析在這個(gè)場(chǎng)景里可以簡(jiǎn)化為幾個(gè)具體的檢測(cè)目標(biāo)題號(hào)比如1.、2.、3.這些序號(hào)、作答區(qū)域?qū)W生填答案的位置、題干區(qū)域題目印刷體部分、甚至批改標(biāo)記紅筆的對(duì)勾和叉號(hào)。這些目標(biāo)本質(zhì)上都是圖像里的一個(gè)矩形區(qū)域用YOLO來處理再自然不過。這樣做的好處非常明顯。第一你不需要關(guān)心文字具體是什么只需要告訴模型這里有一個(gè)作答框模型學(xué)到的是版面的空間結(jié)構(gòu)特征。第二YOLO的輸出自帶坐標(biāo)框你天然就拿到了第3題作答區(qū)域在圖像的哪個(gè)位置這個(gè)信息題號(hào)和答案的對(duì)應(yīng)關(guān)系可以直接從坐標(biāo)關(guān)系里推算。第三作業(yè)紙傾斜、透視變形這類問題目標(biāo)檢測(cè)的矩形框本身就有一定的魯棒性再加上后處理的透視校正基本可以解決。1.3 YOLO在批改場(chǎng)景下不可替代的幾個(gè)優(yōu)勢(shì)為什么最終選了YOLO而不是Faster R-CNN、SSD或者DETR我個(gè)人的項(xiàng)目經(jīng)驗(yàn)可以從三個(gè)維度來說。一是速度必須快夠用。作業(yè)批改是批量任務(wù)一次可能要處理幾十張甚至上百?gòu)堈掌H绻粡垐D要等一兩秒用戶體驗(yàn)就崩了。YOLO是單階段檢測(cè)器天然在速度上有優(yōu)勢(shì)用輕量版本跑CPU也能接受。二是社區(qū)生態(tài)成熟訓(xùn)練部署的鏈路都很順。YOLO的訓(xùn)練管線經(jīng)過無數(shù)人踩過坑數(shù)據(jù)格式、預(yù)訓(xùn)練權(quán)重、模型導(dǎo)出ONNX/TensorRT、部署方案都是一套完整體系。做項(xiàng)目最怕的不是算法難而是工程鏈路里的各種隱性坑成熟的生態(tài)意味著這些坑大概率有人替你填過了。三是模型體積和精度之間的平衡可控。YOLO系列從n到x的型號(hào)梯度很清晰可以根據(jù)部署的設(shè)備性能靈活選擇。在教室的老電腦上跑就用n或s型號(hào)在服務(wù)器上做批量處理就用m或l型號(hào)。這種靈活性在真實(shí)項(xiàng)目中極其重要。2. 標(biāo)注是項(xiàng)目的命根子作業(yè)版面檢測(cè)的標(biāo)注規(guī)范與數(shù)據(jù)轉(zhuǎn)換2.1 需要檢測(cè)的目標(biāo)類型與邊界框規(guī)范在做標(biāo)注規(guī)范之前我先把整個(gè)批改流程里的檢測(cè)目標(biāo)列了一個(gè)清單這個(gè)清單直接決定了模型要學(xué)習(xí)什么。作業(yè)批改場(chǎng)景通常需要檢測(cè)以下幾類目標(biāo)題號(hào)question_number例如1.、2.、3.這樣的序號(hào)。它們是建立哪道題對(duì)應(yīng)哪個(gè)作答區(qū)域的關(guān)鍵錨點(diǎn)。作答區(qū)域answer_region學(xué)生填寫答案的空白位置。在選擇題里可能是學(xué)生圈出來的選項(xiàng)字母在填空題里可能是橫線上方手寫的內(nèi)容。題干區(qū)域question_stem印刷體的題目文本區(qū)域。檢測(cè)它主要用于版面排版和后續(xù)的OCR識(shí)別。批改標(biāo)記mark如果要做二次批改輔助或老師批改結(jié)果錄入可以檢測(cè)紅筆的對(duì)勾、叉號(hào)、半對(duì)號(hào)。我最初做了一個(gè)很傻的設(shè)計(jì)把作業(yè)紙整張作為一個(gè)檢測(cè)類別想讓模型輸出整張圖的范圍。后來發(fā)現(xiàn)這個(gè)類別完全沒有意義因?yàn)檎麖堊鳂I(yè)紙本來就占據(jù)圖像的大部分區(qū)域檢測(cè)它等于什么都沒做。真正有價(jià)值的是檢測(cè)上述細(xì)粒度目標(biāo)。邊界框的標(biāo)注規(guī)范上有一個(gè)原則非常重要框一定要貼合目標(biāo)本身不要為了省事把相鄰元素也包進(jìn)來。比如標(biāo)注題號(hào)1.就只框住1.這三個(gè)字符的范圍不要順帶把后面的題目正文也框一部分進(jìn)來。因?yàn)槟P驮谟?xùn)練時(shí)學(xué)到的是框內(nèi)是被檢測(cè)目標(biāo)框外是背景如果框內(nèi)混入了太多不屬于該類別的背景內(nèi)容模型就會(huì)學(xué)到錯(cuò)誤的特征。在作業(yè)這個(gè)場(chǎng)景里還有一個(gè)特殊情況需要約定如果學(xué)生手寫的答案超出了作答區(qū)域的虛線框應(yīng)該以哪個(gè)范圍為準(zhǔn)我的經(jīng)驗(yàn)是區(qū)域框始終以印刷的答題區(qū)域?yàn)闇?zhǔn)不要因?yàn)槭謱憙?nèi)容溢出就擴(kuò)大標(biāo)注框。因?yàn)闄z測(cè)模型的目的是定位答題區(qū)域在哪里而不是字寫到哪里。至于溢出的部分交給后續(xù)的OCR裁剪處理裁剪時(shí)可以用一定的邊距來兜住。2.2 數(shù)據(jù)標(biāo)注工具與VOC轉(zhuǎn)YOLO格式轉(zhuǎn)換腳本標(biāo)注工具我用的是LabelImg和X-AnyLabeling這兩個(gè)。LabelImg是老牌工具操作簡(jiǎn)單輕量Windows和Linux都能跑適合快速標(biāo)一批數(shù)據(jù)。X-AnyLabeling支持自動(dòng)標(biāo)注輔助功能可以先用一個(gè)預(yù)訓(xùn)練模型做初步檢測(cè)人工修正后再導(dǎo)出能省不少力氣尤其在數(shù)據(jù)集比較大時(shí)效率優(yōu)勢(shì)很明顯。標(biāo)注完成后工具默認(rèn)導(dǎo)出的是PASCAL VOC格式的XML文件而YOLO訓(xùn)練需要的是TXT格式的標(biāo)簽文件每行格式為class_id center_x center_y width height其中坐標(biāo)值都?xì)w一化到0到1之間。這個(gè)轉(zhuǎn)換需要自己寫個(gè)小腳本處理。這里直接給出我用的轉(zhuǎn)換腳本在大多數(shù)VOC格式標(biāo)注目錄下都能直接跑通import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, class_names, target_dir): 將單個(gè)VOC XML文件轉(zhuǎn)換為YOLO格式的TXT文件。 class_names: 類別列表順序必須與訓(xùn)練配置中的類別順序一致。 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) # 計(jì)算YOLO格式的歸一化中心坐標(biāo)和寬高 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文件到目標(biāo)目錄 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__: # 類別順序要記住訓(xùn)練時(shí)還要用 CLASSES [question_number, answer_region, question_stem, mark] convert_all(path/to/annotations, path/to/labels, CLASSES)轉(zhuǎn)換腳本里有一個(gè)很容易踩的坑類別順序的索引必須和訓(xùn)練時(shí)保持一致。如果你在轉(zhuǎn)換腳本里寫的是[question_number, answer_region, question_stem, mark]那訓(xùn)練時(shí)的data.yaml里也必須按這個(gè)順序排列類別否則類別標(biāo)簽就全錯(cuò)位了。2.3 針對(duì)作業(yè)版面的數(shù)據(jù)增強(qiáng)策略數(shù)據(jù)增強(qiáng)是訓(xùn)練目標(biāo)檢測(cè)模型時(shí)繞不開的一環(huán)但作業(yè)版面有自己的特殊性不能照搬通用檢測(cè)里的默認(rèn)增強(qiáng)配置。Mosaic增強(qiáng)的取舍。Ultralytics的訓(xùn)練默認(rèn)開啟Mosaic它把四張圖拼接成一張訓(xùn)練圖對(duì)小目標(biāo)檢測(cè)和模型泛化能力提升明顯。但在作業(yè)批改場(chǎng)景里Mosaic拼接會(huì)把題干文本截?cái)?、把作答區(qū)域切成碎片導(dǎo)致模型學(xué)到的版面結(jié)構(gòu)特征失真。我實(shí)際測(cè)試下來在版面檢測(cè)任務(wù)里完全關(guān)閉Mosaic反而更穩(wěn)。如果非要用可以把Mosaic的概率調(diào)到0.3以下且關(guān)閉Mosaic的最終epoch階段ultralytics里會(huì)自動(dòng)在最后10個(gè)epoch關(guān)閉Mosaic。旋轉(zhuǎn)變換需要克制。作業(yè)照片雖然拍攝角度各異但絕大多數(shù)還是接近正視角的最多有輕微的傾斜。如果增強(qiáng)時(shí)加了大角度的隨機(jī)旋轉(zhuǎn)比如30度以上模型會(huì)學(xué)到版面是歪的這種假特征反而降低真實(shí)場(chǎng)景的檢測(cè)精度。常用的做法是把旋轉(zhuǎn)角度限制在正負(fù)10度以內(nèi)再用透視變換模擬手機(jī)拍攝時(shí)的微小形變。模擬掃描痕跡和拍攝噪聲。這一點(diǎn)很多時(shí)候被忽略但實(shí)戰(zhàn)價(jià)值很高。真實(shí)作業(yè)照片往往有陰影、紙張褶皺、甚至背面字跡透過來導(dǎo)致的干擾訓(xùn)練時(shí)可以用高斯噪聲、亮度擾動(dòng)、對(duì)比度擾動(dòng)、隨機(jī)遮擋這些小幅度增強(qiáng)來模擬這些情況。注意擾動(dòng)幅度不能太大否則會(huì)破壞字符邊緣的銳利度影響后續(xù)對(duì)題號(hào)、作答區(qū)域邊界的檢測(cè)。3. YOLOv5/v8/v11怎么選按作業(yè)場(chǎng)景匹配合適的檢測(cè)方案3.1 主流版本能力對(duì)比現(xiàn)在市面上用于目標(biāo)檢測(cè)的YOLO版本其實(shí)很多很多人問YOLOv8和YOLOv11有什么區(qū)別該學(xué)哪個(gè)。我把自己實(shí)際對(duì)比過的主流版本列了一個(gè)表方便項(xiàng)目選型時(shí)參考。版本類型特點(diǎn)適用場(chǎng)景YOLOv5Anchor-based成熟穩(wěn)定社區(qū)資料極多部署生態(tài)最完善規(guī)整掃描件、標(biāo)準(zhǔn)答題卡的版面檢測(cè)YOLOv8Anchor-free檢測(cè)頭簡(jiǎn)化支持旋轉(zhuǎn)框OBB和實(shí)例分割收斂速度較快手機(jī)拍照、版面傾斜、需要檢測(cè)批改符號(hào)的場(chǎng)景YOLOv10Anchor-free去掉了NMS后處理端到端推理延遲更低對(duì)推理延遲極其敏感的實(shí)時(shí)處理場(chǎng)景YOLOv11Anchor-free使用C3k2模塊融合了注意力機(jī)制小目標(biāo)檢測(cè)能力增強(qiáng)題號(hào)、選項(xiàng)標(biāo)記這類小目標(biāo)較多的作業(yè)版面這里重點(diǎn)說一下Anchor-based和Anchor-free的區(qū)別。YOLOv5這類Anchor-based方法相當(dāng)于在圖像上預(yù)置了一批不同尺寸、不同長(zhǎng)寬比的候選框模型學(xué)習(xí)的是候選框里是否有目標(biāo)以及候選框需要怎么微調(diào)。Anchor-free方法YOLOv8及以后則不再依賴預(yù)置候選框而是直接預(yù)測(cè)目標(biāo)的中心點(diǎn)和尺寸理論上泛化能力更好配置也更簡(jiǎn)單。在作業(yè)版面這種目標(biāo)尺寸差異較大的場(chǎng)景題號(hào)很小作答區(qū)域很大Anchor-free的檢測(cè)穩(wěn)定性會(huì)好一些因?yàn)槟悴槐刭M(fèi)心去調(diào)整Anchor的尺寸預(yù)設(shè)。3.2 三個(gè)具體場(chǎng)景的選型建議如果拿不準(zhǔn)用哪個(gè)版本可以直接參考我的三個(gè)選型建議都是我實(shí)際對(duì)比過的結(jié)論。**場(chǎng)景一培訓(xùn)機(jī)構(gòu)打印的作業(yè)單掃描儀采集版面規(guī)整。**這種場(chǎng)景下作業(yè)頁(yè)面基本沒有透視變形文字印刷清晰版面結(jié)構(gòu)固定。YOLOv5s就足夠了精度和速度的平衡很好而且部署方案極其成熟C、Java端都有大量現(xiàn)成方案可以參考。不要為了追新非上更高版本穩(wěn)定的生態(tài)對(duì)項(xiàng)目交付更重要。**場(chǎng)景二學(xué)生用手機(jī)拍作業(yè)上傳存在傾斜、透視、陰影。**這種圖對(duì)檢測(cè)框的角度敏感普通矩形框會(huì)把傾斜的作答區(qū)域框出很多背景。推薦用YOLOv8n的旋轉(zhuǎn)框OBB檢測(cè)檢測(cè)頭會(huì)輸出帶角度的旋轉(zhuǎn)框配合后處理的透視校正可以更精準(zhǔn)地裁剪作答區(qū)域。此外YOLOv8的實(shí)例分割能力可以順帶檢測(cè)手寫筆跡的覆蓋范圍這個(gè)信息在后續(xù)識(shí)別階段的預(yù)處理里很有用。**場(chǎng)景三需要做批改結(jié)果二次錄入即檢測(cè)老師的紅筆對(duì)勾和叉號(hào)。**推薦YOLOv11分割模型。因?yàn)閷?duì)勾和叉號(hào)面積小、形狀不規(guī)則直接用矩形框檢測(cè)容易把對(duì)錯(cuò)信息判定含糊。分割模型輸出的掩膜可以精確區(qū)分打了對(duì)勾和只畫了一條線的差異后續(xù)判斷正確還是錯(cuò)誤時(shí)的魯棒性更高。實(shí)測(cè)下來YOLOv11在這樣的小目標(biāo)檢測(cè)精度上確實(shí)比v8有明顯提升尤其是題號(hào)這類小元素誤檢率降了不少。3.3 理解網(wǎng)絡(luò)結(jié)構(gòu)差異避免唯參數(shù)論很多新手選模型只看參數(shù)數(shù)量和mAP數(shù)值但實(shí)際項(xiàng)目中網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計(jì)對(duì)任務(wù)適配性的影響往往比簡(jiǎn)單的精度數(shù)值更重要。YOLOv8相比v5最大的結(jié)構(gòu)變化是C2f模塊取代了C3模塊。C2f在特征融合層增加了更多的分支連接讓梯度流動(dòng)更充分對(duì)小目標(biāo)特征和長(zhǎng)尾特征的提取能力更強(qiáng)。在作業(yè)批改場(chǎng)景里題號(hào)1.、2.這些元素在整張A4圖里占比可能只有千分之幾屬于典型的小目標(biāo)需要深層特征金字塔有足夠的分辨率去保留它們的空間細(xì)節(jié)。C2f的多分支結(jié)構(gòu)在這里幫了不小的忙。YOLOv11引入的C3k2模塊進(jìn)一步提升了骨干網(wǎng)絡(luò)的特征提取效率而且增加了一部分注意力機(jī)制讓模型更關(guān)注題目區(qū)域和作答區(qū)域之間的空間關(guān)系。這一點(diǎn)特別契合版面檢測(cè)任務(wù)因?yàn)樽鳂I(yè)版面本質(zhì)上是一個(gè)高度結(jié)構(gòu)化的空間關(guān)系圖一道題的題號(hào)和它的作答區(qū)域之間有很強(qiáng)的相對(duì)位置依賴注意力機(jī)制能更好地編碼這種相對(duì)位置信息。當(dāng)然結(jié)構(gòu)理解歸理解落地時(shí)還是用工程思維來選型你的部署硬件是什么、數(shù)據(jù)量大概有多少、精度瓶頸在哪個(gè)環(huán)節(jié)。先確定這些再去挑版本而不是反過來。4. 訓(xùn)練作業(yè)檢測(cè)模型小樣本、高細(xì)節(jié)數(shù)據(jù)的調(diào)參與踩坑記錄4.1 訓(xùn)練配置與遷移學(xué)習(xí)策略作業(yè)批改項(xiàng)目的訓(xùn)練數(shù)據(jù)通常不會(huì)像通用目標(biāo)檢測(cè)數(shù)據(jù)集那么大。我從幾個(gè)培訓(xùn)機(jī)構(gòu)收集來的真實(shí)作業(yè)照片加上網(wǎng)上能找到的一些公開答題卡數(shù)據(jù)集總共標(biāo)注了不到2000張圖。這個(gè)數(shù)據(jù)量下最優(yōu)策略一定是基于COCO預(yù)訓(xùn)練權(quán)重做遷移學(xué)習(xí)而不是從零訓(xùn)練。使用Ultralytics訓(xùn)練框架核心命令大概是這樣的yolo detect train \ datahomework.yaml \ modelyolov8n.pt \ epochs200 \ imgsz1280 \ batch16 \ lr00.002 \ mosaic0.0 \ patience50這里有幾個(gè)參數(shù)是專門針對(duì)作業(yè)版面場(chǎng)景調(diào)的。**輸入分辨率imgsz選擇1280而不是默認(rèn)的640。**作業(yè)版面里小目標(biāo)太多了640分辨率下一個(gè)題號(hào)可能只有十幾個(gè)像素寬模型根本看不清。提高到1280后小目標(biāo)的像素量翻了兩番檢測(cè)精度提升非常明顯。代價(jià)是顯存占用和訓(xùn)練時(shí)間大幅增加但這是值得的。如果顯存不夠可以配合矩形訓(xùn)練ultralytics里默認(rèn)會(huì)啟用rect模式來減少填充帶來的顯存浪費(fèi)。**初始學(xué)習(xí)率lr0調(diào)低到0.002。**因?yàn)槭菑腃OCO預(yù)訓(xùn)練權(quán)重做遷移模型的特征提取骨干網(wǎng)絡(luò)已經(jīng)學(xué)到很強(qiáng)的通用視覺特征初始學(xué)習(xí)率過大反而容易破壞這些預(yù)訓(xùn)練特征的穩(wěn)定性導(dǎo)致訓(xùn)練早期loss劇烈震蕩。調(diào)低初始學(xué)習(xí)率配合余弦退火整個(gè)訓(xùn)練過程會(huì)更平穩(wěn)最終收斂精度也更高。**patience早停設(shè)置成50。**作業(yè)批改場(chǎng)景的數(shù)據(jù)量不大模型很容易在100個(gè)epoch左右就趨向收斂之后開始過擬合。早??梢允∠麓罅繜o效訓(xùn)練時(shí)間。4.2 訓(xùn)練指標(biāo)異常的排查思路這里單獨(dú)說一個(gè)高頻問題很多人剛開始訓(xùn)練YOLO時(shí)發(fā)現(xiàn)Loss能跑但mAP全程是0或者訓(xùn)練指標(biāo)全是0。這個(gè)問題我在作業(yè)批改項(xiàng)目里也遇到過當(dāng)時(shí)折騰了整整一個(gè)晚上最終定位到問題根源。**第一步先檢查數(shù)據(jù)集路徑和圖片是否成功加載。**很多情況下是訓(xùn)練命令里的data.yaml的path字段寫錯(cuò)了數(shù)據(jù)集壓根沒加載進(jìn)來模型在空數(shù)據(jù)上空轉(zhuǎn)。在Ultralytics里啟動(dòng)訓(xùn)練后日志會(huì)顯示加載的圖片數(shù)量和標(biāo)簽數(shù)量如果顯示是0那肯定是路徑或格式的問題。**第二步檢查標(biāo)簽文件是否與圖片一一對(duì)應(yīng)。**Ultralytics要求每張圖片都有一個(gè)同名的TXT標(biāo)簽文件且標(biāo)簽文件必須放在與圖片名字相同的目錄結(jié)構(gòu)里。如果你的圖片是images/train/001.jpg標(biāo)簽必須是labels/train/001.txt類別索引從0開始。如果標(biāo)簽放錯(cuò)目錄訓(xùn)練時(shí)會(huì)報(bào)警告。**第三步檢查標(biāo)簽內(nèi)容是否有空文件或非法坐標(biāo)系。**有些標(biāo)注工具導(dǎo)出的坐標(biāo)有輕微越界比如x_max略微超出圖片寬度訓(xùn)練時(shí)會(huì)被過濾掉。如果過濾后標(biāo)簽數(shù)量為0mAP自然就是0。可以在訓(xùn)練前寫一個(gè)小腳本檢查標(biāo)簽文件里有沒有負(fù)數(shù)、有沒有超過1.0的歸一化坐標(biāo)。**第四步檢查類別名是不是帶了空格或特殊字符。**YOLO的data.yaml里類別名不能有空格比如answer region會(huì)報(bào)錯(cuò)必須改成answer_region。4.3 數(shù)據(jù)增強(qiáng)與兩階段訓(xùn)練優(yōu)化除了基礎(chǔ)的增強(qiáng)策略我還做了一件事兩階段訓(xùn)練。第一階段先用完整數(shù)據(jù)集、固定的輸入尺寸1280px、關(guān)閉Mosaic訓(xùn)練一個(gè)版面檢測(cè)模型目標(biāo)是穩(wěn)定檢測(cè)出題號(hào)、作答區(qū)域、題干區(qū)域。第二階段把這個(gè)模型的輸出當(dāng)作區(qū)域提議針對(duì)每個(gè)檢測(cè)到的作答區(qū)域裁剪出局部小圖再訓(xùn)練一個(gè)小的作答內(nèi)容分類模型判斷這個(gè)區(qū)域里是A還是B或者是空的。這種方法的好處在于你不需要指望一個(gè)模型同時(shí)解決定位和識(shí)別兩個(gè)難題。定位問題用YOLO解決識(shí)別問題用更簡(jiǎn)單的分類模型解決每個(gè)環(huán)節(jié)的精度都可以單獨(dú)優(yōu)化。最終整個(gè)批改流程的精度是兩個(gè)環(huán)節(jié)精度的乘積所以任何一個(gè)環(huán)節(jié)做扎實(shí)都比一步到位的端到端方案更可控。實(shí)際操作中代碼層面可以用Ultralytics的model.predict()接口先跑一遍檢測(cè)把左上角、右下角坐標(biāo)存下來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}))這些坐標(biāo)信息就是你后續(xù)做版面還原、區(qū)域關(guān)聯(lián)、裁剪識(shí)別的基礎(chǔ)數(shù)據(jù)。5. 檢測(cè)只是第一步從檢測(cè)框到批改結(jié)果的完整處理鏈5.1 版面還原透視校正與坐標(biāo)映射YOLO輸出的通常是整個(gè)輸入圖像坐標(biāo)系下的邊界框但手機(jī)拍攝的作業(yè)照片基本都是有一定透視變形的不規(guī)則四邊形。如果直接拿原始的檢測(cè)框去裁圖裁出來的作答區(qū)域會(huì)有文字的透視傾斜影響后續(xù)OCR的精度。我的做法是在版面里找到四個(gè)固定的錨點(diǎn)用透視變換把作業(yè)版面扳正。錨點(diǎn)從哪里來可以專門標(biāo)注作業(yè)紙的四個(gè)角作為特殊類別也可以利用檢測(cè)出的題號(hào)位置做線性映射。更簡(jiǎn)單通用的方案是如果已知作業(yè)紙是A4或者標(biāo)準(zhǔn)信紙直接檢測(cè)作業(yè)紙的四個(gè)角點(diǎn)然后做仿射變換。OpenCV里實(shí)現(xiàn)透視變換很簡(jiǎn)單import cv2 import numpy as np def perspective_correct(image, src_pts, dst_size(800, 1200)): src_pts: 原圖中作業(yè)紙的四個(gè)角點(diǎn)坐標(biāo)順序?yàn)樽笊?、右上、右下、左下?dst_size: 矯正后的目標(biāo)圖像尺寸寬, 高一般按A4寬高比設(shè)置。 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檢測(cè)出的坐標(biāo)也需要做對(duì)應(yīng)的映射。最穩(wěn)妥的做法是先對(duì)原圖做透視校正再在校正后的圖像上跑YOLO檢測(cè)。這樣檢測(cè)框坐標(biāo)、裁剪區(qū)域的坐標(biāo)、OCR的輸入坐標(biāo)都在同一套坐標(biāo)體系下省去大量的坐標(biāo)轉(zhuǎn)換計(jì)算。5.2 題號(hào)與作答區(qū)域的關(guān)聯(lián)邏輯檢測(cè)模型輸出了一堆題號(hào)框和一堆作答區(qū)域框之后最重要的一個(gè)操作是把它們配對(duì)。這一步直接決定批改結(jié)果能不能和標(biāo)準(zhǔn)答案對(duì)得上。我的關(guān)聯(lián)邏輯很簡(jiǎn)單也很可靠按空間位置排序。一張作業(yè)紙通常有多個(gè)題目題號(hào)按從上到下、從左到右排列作答區(qū)域一般緊隨其對(duì)應(yīng)題號(hào)的右側(cè)或下方。因此可以按Y坐標(biāo)排序?qū)㈩}號(hào)和作答區(qū)域分組。具體做法是將所有檢測(cè)到的題號(hào)框按Y坐標(biāo)從上到下排序。對(duì)每個(gè)題號(hào)框找到所有與其Y坐標(biāo)范圍有交集的作答區(qū)域框。在這些候選作答區(qū)域里選擇IOU最大且滿足水平/垂直鄰近關(guān)系的那一個(gè)作為該題號(hào)對(duì)應(yīng)的作答區(qū)域。如果某個(gè)題號(hào)找不到對(duì)應(yīng)的作答區(qū)域通常意味著該題學(xué)生沒有作答或區(qū)域檢測(cè)漏檢標(biāo)記為待復(fù)核。這個(gè)邏輯在標(biāo)準(zhǔn)作業(yè)版面上成功率很高但遇到兩列排版、上下左右交錯(cuò)排列的作業(yè)紙時(shí)需要加上基于X坐標(biāo)的排序邏輯來避免配錯(cuò)。我的經(jīng)驗(yàn)是不要完全依賴一種排序策略最好把題號(hào)的坐標(biāo)和作答區(qū)域的坐標(biāo)都輸入到一個(gè)簡(jiǎn)單的匹配邏輯里比如先按Y軸粗分組再按X軸細(xì)排序。5.3 作答內(nèi)容識(shí)別與批改判定規(guī)則作答區(qū)域定位好之后識(shí)別內(nèi)容的方法有三種路線我分別試過這里把心得寫出來。**路線一純OpenCV形態(tài)學(xué)操作。**如果學(xué)生的作答是填涂選擇題涂卡比如涂黑的方形可以用閾值分割加輪廓檢測(cè)直接判斷涂沒涂黑。這個(gè)方法速度快、不需要額外模型但只適用于填涂式作答。**路線二接OCR引擎識(shí)別手寫或印刷文字。**比如用PaddleOCR對(duì)裁剪出來的作答區(qū)域做文字識(shí)別。這個(gè)方法對(duì)印刷體效果很好對(duì)手寫體要看具體的工整程度。實(shí)測(cè)下來手寫數(shù)字和字母的識(shí)別PaddleOCR在精心調(diào)優(yōu)的前提下可以達(dá)到90%左右的準(zhǔn)確率但對(duì)連筆和潦草字體的魯棒性不夠。**路線三輕量分類模型。**如果你能提前知道每道題的所有可選答案選擇題的A/B/C/D判斷題的對(duì)/錯(cuò)那完全可以把問題建模成圖像分類把裁剪出來的作答區(qū)域輸入一個(gè)ResNet或MobileNet分類模型輸出是分類標(biāo)簽。這也是我前面提到的兩階段方法里第二階段的模型。實(shí)測(cè)下來對(duì)選擇題涂卡和判斷題打鉤打叉的識(shí)別分類模型比OCR穩(wěn)定得多因?yàn)榉诸惸P筒挥冒衙總€(gè)字都讀出來只學(xué)這個(gè)區(qū)域的整體視覺模式抗手寫噪聲能力更強(qiáng)。批改判定的規(guī)則相對(duì)簡(jiǎn)單。比如選擇題標(biāo)準(zhǔn)答案是B模型識(shí)別出學(xué)生作答區(qū)域的內(nèi)容類別是B就判對(duì)否則判錯(cuò)。但對(duì)于學(xué)生沒作答和識(shí)別模型低置信度這兩種情況我的處理方式是直接歸到待人工復(fù)核不要硬判。這個(gè)兜底邏輯在真實(shí)教學(xué)場(chǎng)景里很重要寧可讓老師人工看幾道題也不要因?yàn)檎`判讓學(xué)生白白丟分。6. 部署與實(shí)測(cè)從訓(xùn)練機(jī)到教學(xué)環(huán)境的落地細(xì)節(jié)6.1 模型導(dǎo)出與推理加速模型訓(xùn)練好了最終要部署到用戶的設(shè)備上去。作業(yè)批改軟件的使用環(huán)境五花八門有的是Windows電腦有的是Linux服務(wù)器還有的要跑在瀏覽器里。最通用、最穩(wěn)妥的做法是把PyTorch模型導(dǎo)出為ONNX格式然后再根據(jù)目標(biāo)設(shè)備選擇推理引擎。Ultralytics框架自帶導(dǎo)出命令yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz1280 halfTrue這里halfTrue表示導(dǎo)出半精度FP16模型體積減小一半推理速度明顯提升。在NVIDIA顯卡上可以繼續(xù)用TensorRT進(jìn)行二次優(yōu)化在CPU設(shè)備上可以用OpenVINO加速。實(shí)測(cè)下來同樣的YOLOv8n模型TensorRT優(yōu)化后推理速度比PyTorch原生推理快5倍以上這對(duì)批處理場(chǎng)景幫助巨大。在導(dǎo)出ONNX時(shí)有幾個(gè)細(xì)節(jié)值得注意。動(dòng)態(tài)尺寸和固定尺寸的選擇。如果你的推理設(shè)備性能參差不齊建議導(dǎo)出為固定尺寸1280輸入按比例縮放后做letterbox填充這樣既能保持形狀統(tǒng)一又不會(huì)引入額外的尺寸推斷開銷。另外ONNX的輸入名和輸出名在后續(xù)接推理引擎時(shí)需要記住不同版本可能有差異導(dǎo)出來后最好用onnxruntime驗(yàn)證一下輸出形狀。6.2 FastAPI封裝批改服務(wù)批改應(yīng)用的實(shí)際形態(tài)可以是一個(gè)Web服務(wù)前端上傳作業(yè)照片后端返回批改結(jié)果。我用FastAPI封裝了一層推理服務(wù)核心邏輯就是檢測(cè) - 配對(duì) - 裁剪 - 識(shí)別 - 判定 - 返回結(jié)果。關(guān)鍵代碼骨架大致如下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. 預(yù)處理 推理簡(jiǎn)化版實(shí)際需要letterbox與坐標(biāo)還原 input_tensor preprocess(img) outputs session.run(None, {session.get_inputs()[0].name: input_tensor}) # 3. 后處理坐標(biāo)還原、題號(hào)-作答區(qū)域配對(duì)、裁剪、內(nèi)容識(shí)別、批改判定 ... return {result: 批改結(jié)果}這里有一個(gè)真實(shí)項(xiàng)目里容易忽略的問題預(yù)處理必須和后處理里的坐標(biāo)還原嚴(yán)格對(duì)稱。訓(xùn)練時(shí)圖像是letterbox填充到1280的推理時(shí)你也必須做一模一樣的letterbox且檢測(cè)出的坐標(biāo)要從letterbox坐標(biāo)系還原回原始圖像坐標(biāo)系。很多人ONNX部署后檢測(cè)框位置偏差大十有八九是這一環(huán)節(jié)出了問題。6.3 實(shí)測(cè)結(jié)果與迭代優(yōu)化整個(gè)系統(tǒng)跑通之后我在一個(gè)培訓(xùn)機(jī)構(gòu)拿了一批真實(shí)數(shù)據(jù)進(jìn)行測(cè)試這里放一組實(shí)測(cè)數(shù)據(jù)供參考。項(xiàng)目指標(biāo)測(cè)試圖片數(shù)量500張手機(jī)拍攝的作業(yè)照片題號(hào)檢測(cè)召回率IoU0.596.2%作答區(qū)域檢測(cè)精確率94.8%選擇題批改準(zhǔn)確率含定位和識(shí)別91.6%填空題批改準(zhǔn)確率84.3%單張圖片平均處理耗時(shí)YOLOv8n ONNXGTX 1660約0.4秒單張圖片平均處理耗時(shí)同配置CPU約2.8秒這個(gè)數(shù)據(jù)說明檢測(cè)環(huán)節(jié)本身的精度已經(jīng)夠用最終的誤差主要出在答區(qū)域內(nèi)容識(shí)別上尤其是手寫體。在迭代優(yōu)化過程中我最大的一個(gè)體會(huì)是要建立一個(gè)反饋閉環(huán)。具體做法是每次批量批改后把模型低置信度或?qū)嶋H出現(xiàn)誤判的樣本自動(dòng)記錄下來定期人工復(fù)核并修正標(biāo)簽然后補(bǔ)充進(jìn)訓(xùn)練集做增量訓(xùn)練。實(shí)測(cè)下來每輪增量訓(xùn)練大約能提升1到2個(gè)百分點(diǎn)的批改準(zhǔn)確率效果非常穩(wěn)定。這個(gè)閉環(huán)機(jī)制的價(jià)值甚至比模型結(jié)構(gòu)調(diào)優(yōu)更重要因?yàn)檎鎸?shí)世界的作業(yè)樣例多樣性太強(qiáng)只有不斷吸收真實(shí)樣本模型才會(huì)越來越貼合實(shí)際場(chǎng)景。另外提一個(gè)很實(shí)用的點(diǎn)給檢測(cè)模型設(shè)置一個(gè)合理的置信度閾值也很有講究。在作業(yè)批改場(chǎng)景里寧可將置信度閾值設(shè)低一點(diǎn)比如0.3讓凡是有點(diǎn)像作答區(qū)域的框都先輸出后續(xù)靠規(guī)則過濾也不要設(shè)高閾值導(dǎo)致漏檢。因?yàn)槁z一個(gè)作答區(qū)域意味著學(xué)生整道題被跳過這在教學(xué)場(chǎng)景里是不可接受的。我自己在這個(gè)項(xiàng)目里最后悔的一件事是一開始就想讓模型一步到位輸出對(duì)錯(cuò)。后來調(diào)整成定位-識(shí)別-判定三步拆分之后每個(gè)環(huán)節(jié)的精度都更容易單獨(dú)提升整個(gè)系統(tǒng)也變得更好維護(hù)。對(duì)做作業(yè)批改或者文檔分析類項(xiàng)目的朋友這個(gè)拆分的思路我認(rèn)為是值得優(yōu)先借鑒的。如果你正在做類似的項(xiàng)目建議也先把手里的數(shù)據(jù)按照這個(gè)思路拆好訓(xùn)練一個(gè)自己的版面檢測(cè)模型再逐步迭代出完整的批改流程。本文還有配套的精品資源點(diǎn)擊獲取