字圖像檢測數(shù)據(jù)集制作與YOLO訓(xùn)練全流程實戰(zhàn))
簡介面向目標檢測入門與課程實訓(xùn)的0-9數(shù)字圖像檢測數(shù)據(jù)集按YOLO格式整理適配YOLOv5及后續(xù)系列模型訓(xùn)練。數(shù)據(jù)劃分為訓(xùn)練集約1000張、驗證集約100張、測試集約50張每張圖片均配有txt標簽文件標注采用x_centre、y_centre、w、h相對坐標類別以class文本文件為準另附show.py可視化腳本可將檢測框繪制回原圖便于檢查標注質(zhì)量。壓縮包共2000個文件以jpg圖像與txt標簽為主體另含1個py腳本整體約87.43MB目錄按YOLOV5習(xí)慣組織下載后基本可無縫接入訓(xùn)練流程。圖片素材來源多樣包含不同拍攝角度、水印背景與光照環(huán)境有助于提升真實場景下的數(shù)字識別泛化能力。已有254人學(xué)習(xí)適合數(shù)字識別、OCR前置檢測、目標檢測課程作業(yè)及YOLO框架實操練習(xí)使用。1. 0-9數(shù)字圖像檢測數(shù)據(jù)集1000張圖能做什么怎么做才不浪費做數(shù)字識別的時候很多人第一反應(yīng)是端到端OCR或者圖像分類直接把圖片裁出來丟進分類模型。等真上了產(chǎn)線才發(fā)現(xiàn)表計上的數(shù)字帶旋轉(zhuǎn)、底板反光、相鄰數(shù)字連在一起分類器全崩。這恰恰是目標檢測這條路的用武之地先框出每個數(shù)字再按框裁切或直接輸出類別。0-9數(shù)字圖像檢測數(shù)據(jù)集超過1000張圖片和標簽要解決的正是這件事——為10個數(shù)字類別提供帶邊界框的訓(xùn)練樣本讓模型先學(xué)會找數(shù)字、再判斷是幾。這類數(shù)據(jù)集特別適合三類人剛?cè)腴T想跑通檢測全流程的新手、做工業(yè)表計和儀表讀數(shù)落地的工程師、以及需要快速驗證數(shù)據(jù)標注規(guī)范的團隊。1000張圖不算多但數(shù)字檢測的類別少、目標結(jié)構(gòu)簡單只要劃分、格式和增強處理得當完全能訓(xùn)練出一個可以上手的檢測模型。2. 數(shù)字檢測的數(shù)據(jù)集應(yīng)該是什么樣類別定義、目錄結(jié)構(gòu)與格式轉(zhuǎn)換2.1 數(shù)字圖像檢測和通用目標檢測的差異為什么1000張夠用一半通用目標檢測數(shù)據(jù)集動輒幾萬張因為類別多、場景雜、目標尺度差異大。數(shù)字檢測不一樣類別固定是10個數(shù)字的形態(tài)變化只有字體、粗細、傾斜和模糊這幾項結(jié)構(gòu)特征非常明確。所以 1000 張圖、每張平均兩到三個數(shù)字實例數(shù)通常能到 2000 到 3000 個這個規(guī)模做預(yù)訓(xùn)練權(quán)重微調(diào)是夠的。但前提是實例數(shù)要均衡10 個數(shù)字不能出現(xiàn)某類只有 50 個、另一類有 400 個的情況否則模型會把高頻類別學(xué)得更牢低頻類別在驗證集上 AP 明顯偏低。另一個差異是檢測跟分類的定位粒度不同。分類模型吃的是已經(jīng)裁好的數(shù)字圖檢測模型要從整張圖里找數(shù)字框。這意味著訓(xùn)練樣本里不能只有干凈的數(shù)字特寫還必須有帶背景干擾、多數(shù)字同框、部分遮擋的圖。如果原始數(shù)據(jù)集里全是白底黑字、數(shù)字居中訓(xùn)練出來的模型換個場景大概率漏檢。拿到數(shù)據(jù)后先按場景分類看一眼比直接開訓(xùn)重要得多。2.2 類別定義從0到9classes 文件的順序就是模型輸出的映射這類數(shù)據(jù)集最常見的坑在類別定義上。數(shù)字檢測的類別就是 0、1、2、3、4、5、6、7、8、9 這十類標注工具里通常也叫這個名字但 YOLO 訓(xùn)練讀取的類別編號不是字符串而是整數(shù)索引。data.yaml 里的names列表順序直接決定class_id的映射關(guān)系。# data.yaml train: ./dataset/images/train val: ./dataset/images/val nc: 10 names: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]邏輯說明nc是類別總數(shù)必須和names的長度一致。names列表里第 0 個元素對應(yīng)標注文件里的class_id0也就是數(shù)字 0第 1 個元素對應(yīng)數(shù)字 1依次類推。如果有人在標注工具里把數(shù)字 0 定義成了 zero 而不是 0或者把類別順序?qū)懗闪?1 到 10訓(xùn)練時模型會學(xué)到錯位映射推理輸出也會跟著亂。參數(shù)說明train和val寫絕對路徑最穩(wěn)相對路徑在切換工作目錄后容易報錯。nc10是數(shù)字檢測的固定值不要因為加了背景類就把nc改成 11——背景類在 YOLO 里是隱式學(xué)習(xí)的不需要顯式標注。2.3 把 VOC XML 轉(zhuǎn)成 YOLO txt轉(zhuǎn)換腳本與坐標細節(jié)標注工具如果導(dǎo)出的是 Pascal VOC 格式每個 XML 文件對應(yīng)一張圖片里面記錄filename、圖片寬高和每個目標的name、bndbox。YOLO 訓(xùn)練不認 XML需要轉(zhuǎn)成 txt每行格式是class_id cx cy w h四個坐標全部歸一化到 0 到 1 之間。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_dir, classes): tree ET.parse(xml_path) root tree.getroot() # VOC 原始坐標是像素值先讀出圖片尺寸用于歸一化 img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: print(f[跳過未定義類別] {name}) continue cls_id classes.index(name) bbox obj.find(bndbox) # VOC 的 bndbox 是左上角和右下角兩個點 x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 中心點坐標換算成歸一化值 w x2 - x1 h y2 - y1 cx (x1 w / 2.0) / img_w cy (y1 h / 2.0) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w / img_w:.6f} {h / img_h:.6f}) out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) if __name__ __main__: voc_to_yolo(labels/0001.xml, labels_txt, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])邏輯說明XML 里size節(jié)點存的是原圖寬高如果圖片在標注后被 resize 過這里的寬高必須同步更新否則歸一化坐標全錯。bndbox里四個值取出來先算寬高再算中心點這是 YOLO 和 VOC 兩種格式最關(guān)鍵的習(xí)慣性差異——VOC 存角點YOLO 存中心點和寬高。參數(shù)說明classes列表的順序和前面 data.yaml 的names必須完全一致這里寫的是數(shù)字 0 到 9轉(zhuǎn)換出來的cls_id就從 0 到 9。輸出目錄如果不存在腳本會直接報錯建議先os.makedirs(out_dir, exist_okTrue)。2.4 按圖劃分 train/val/test固定隨機種子與類別均衡1000 張圖的劃分方式直接影響驗證指標的可信度。常見做法是 8:1:1也就是 800 張訓(xùn)練、100 張驗證、100 張測試。劃分的單位是圖片不是目標實例同一張圖不能同時出現(xiàn)在訓(xùn)練集和驗證集里否則會數(shù)據(jù)泄露驗證集 AP 虛高。import os import random import shutil random.seed(42) # 固定種子保證每次切分結(jié)果一致 img_files [f for f in os.listdir(images) if f.endswith(.jpg)] random.shuffle(img_files) n len(img_files) train_files img_files[:int(n * 0.8)] val_files img_files[int(n * 0.8):int(n * 0.9)] test_files img_files[int(n * 0.9):] for f in train_files: shutil.copy(os.path.join(images, f), os.path.join(dataset/train, f)) # val/test 同理標簽文件按同名復(fù)制即可邏輯說明random.seed(42)固定隨機種子后每次跑腳本得到的切分結(jié)果相同方便復(fù)現(xiàn)實驗。shuffle是為了避免原始文件名按數(shù)字順序排列時前 80% 全是小數(shù)字、后 20% 全是大數(shù)字這種分布傾斜。參數(shù)說明如果數(shù)據(jù)集里每個圖片包含數(shù)字的數(shù)量不均勻比如有些圖只有一個 5有些圖有六個數(shù)字建議切分后統(tǒng)計一下各集合里每個類別的實例數(shù)。發(fā)現(xiàn)驗證集里某個數(shù)字一個都沒有就先不做隨機純切分改成按文件名或場景分層抽樣否則那個數(shù)字的驗證 AP 永遠是 0。2.5 不夠用的時候怎么補合成數(shù)據(jù)和公開數(shù)據(jù)集的合并邊界1000 張圖對數(shù)字檢測來說是一個能做但不寬裕的規(guī)模。如果驗證集 AP 一直上不去優(yōu)先考慮補充合成數(shù)據(jù)用 OpenCV 把數(shù)字隨機貼在自然背景或噪點背景上隨機換字體、縮放、旋轉(zhuǎn)。合成數(shù)據(jù)能快速補足背景多樣性和角度變化但要注意合成分布和真實分布的差異合成圖占比不要超過總量的 30%否則模型會學(xué)到數(shù)字邊緣過于干凈的假特征真實場景里反而掉點。公開數(shù)字數(shù)據(jù)集和這里的數(shù)據(jù)合并也可以但先檢查類別定義和標注粒度有些數(shù)據(jù)集把連續(xù)數(shù)字標成一個框有的標成單個數(shù)字混著用模型會分裂。3. 標簽標注實操工具選型、邊界規(guī)范和質(zhì)量檢查腳本3.1 目標檢測常用標注工具怎么選數(shù)字檢測的標注工作量不大選工具的核心標準是導(dǎo)出格式干凈、操作簡單、能批量檢查。常見做法是單人小數(shù)據(jù)集用 labelImg它導(dǎo)出 VOC XML 和 YOLO txt 都直接支持鼠標畫框加快捷鍵標類別學(xué)習(xí)成本最低。需要多人協(xié)作或做半自動預(yù)標注時用 X-AnyLabeling 這類帶模型輔助的工具會更高效先跑一遍檢測模型生成預(yù)標注框人工只改錯框1000 張圖一天能清完。工具選型要避免格式轉(zhuǎn)換地獄。比如在線標注平臺導(dǎo)出的可能是一個大 JSON 或 ZIP 包字段命名和本地工具不一致轉(zhuǎn)換腳本反而比標注還費時間。我的習(xí)慣是先確認導(dǎo)出格式是不是標準 COCO 或 VOC如果都不是優(yōu)先寫一個字段映射腳本而不是手工改文件。工具導(dǎo)出格式適合場景注意點labelImgVOC XML / YOLO txt單人小規(guī)模、快速上手最新版本內(nèi)置了 YOLO 模式labelmeJSON需要多邊形標注時數(shù)字檢測用矩形框就夠不必用它X-AnyLabelingVOC / COCO / YOLO半自動預(yù)標注、大批量需要配置模型權(quán)重機器要有 GPU 或能容忍 CPU 推理3.2 數(shù)字框的標注邊界規(guī)范框多大、重疊怎么算數(shù)字檢測的邊界框規(guī)范和通用目標檢測不一樣。通用物體允許框包含一部分上下文背景因為類別判斷需要環(huán)境信息數(shù)字是純結(jié)構(gòu)符號框太大把旁邊的干擾線、表計指針包含進來模型會誤把那些噪聲當成數(shù)字特征。反過來框太小裁掉數(shù)字的一角類別判斷直接出錯。我的標注規(guī)范是矩形框緊貼數(shù)字可見區(qū)域的外輪廓上下左右各留 2 到 3 個像素的余量不要加額外的安全邊距。對于連在一起的兩個數(shù)字比如表計上的 25兩個框可以緊挨但必須有明確分界線不允許兩個框重疊超過 10%。數(shù)字斜排的時候用帶角度的框比軸對齊框更準——但這取決于標注工具labelImg 原生不支持旋轉(zhuǎn)框就用軸對齊框框住數(shù)字的最小外接范圍即可。另外要專門處理像數(shù)字的干擾物斜杠/、字母 O、字母 l、小數(shù)點都容易讓標注者手滑標成數(shù)字。發(fā)生這類錯誤不會立刻報錯但訓(xùn)練后 0 和 O 會互相搶類別。項目啟動前把難分樣本單獨挑出來放一個文件夾找兩個人各標一遍不一致的框重新討論這是擋掉臟標簽最有效的手段。3.3 標注質(zhì)量檢查腳本一次性揪出越界、錯類和重疊標注完成后先跑一遍質(zhì)量檢查腳本再開始訓(xùn)練比起訓(xùn)練到一半發(fā)現(xiàn) loss 不降再回頭查標簽高效得多。檢查的核心四件事類別是否合法、坐標是否越界、寬高是否為負、兩個框是否大面積重疊。import xml.etree.ElementTree as ET import os def check_voc_label(xml_path, img_dir, classes, iou_thr0.5): tree ET.parse(xml_path) root tree.getroot() img_file root.find(filename).text if not os.path.exists(os.path.join(img_dir, img_file)): print(f[缺圖] {xml_path} - {img_file}) return boxes [] for obj in root.findall(object): name obj.find(name).text if name not in classes: print(f[非法類別] {xml_path} - {name}) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) if x1 x2 or y1 y2: print(f[寬高非法] {xml_path} - {x1},{y1},{x2},{y2}) if x1 0 or y1 0 or x2 0 or y2 0: print(f[坐標越界] {xml_path} - 負坐標) boxes.append((name, x1, y1, x2, y2)) for i in range(len(boxes)): for j in range(i 1, len(boxes)): iou calc_iou(boxes[i][1:], boxes[j][1:]) if iou iou_thr: print(f[重疊框] {xml_path} - {boxes[i][0]}/{boxes[j][0]} IOU{iou:.2f}) def calc_iou(a, b): # 兩個矩形相交面積 / 并集面積越界框在此函數(shù)統(tǒng)一按邊界裁剪 x1 max(a[0], b[0]); y1 max(a[1], b[1]) x2 min(a[2], b[2]); y2 min(a[3], b[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area_a (a[2] - a[0]) * (a[3] - a[1]) area_b (b[2] - b[0]) * (b[3] - b[1]) return inter / (area_a area_b - inter)邏輯說明腳本按 XML 逐個檢查[缺圖]說明文件名對不上通常是標注完又改過圖片名[非法類別]說明標注工具里出現(xiàn)了classes列表之外的類別名重疊框檢查用 IOU 大于 0.5 作為閾值輸出告警同一張圖里兩個數(shù)字連得很近時 IOU 會有 0.1 到 0.3 的波動超過 0.5 基本是復(fù)制粘貼導(dǎo)致的重疊需要人工確認。參數(shù)說明iou_thr0.5是通用目標檢測里經(jīng)驗用的閾值數(shù)字檢測因為框小且密集可以放寬到 0.6但仍要輸出告警讓人確認。跑完腳本后把告警信息導(dǎo)出成文本按 XML 文件名逐條修正不要在訓(xùn)練腳本里用ignore關(guān)鍵字偷偷跳過臟樣本臟標注會污染整體特征學(xué)習(xí)。4. 把 1000 張圖喂進 YOLOv8 訓(xùn)練自己的數(shù)據(jù)集從配置到增強4.1 構(gòu)造 data.yaml 并跑通 YOLOv8 訓(xùn)練流程有了劃分好的目錄和轉(zhuǎn)換好的 txt 標簽剩下的就是用 YOLOv8 拉起訓(xùn)練。先確認目錄結(jié)構(gòu)標準做法是dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ └── val/圖片和標簽按數(shù)據(jù)集分開放不要混在同一目錄。YOLO 訓(xùn)練時自動到 labels 目錄找同名 txt 文件圖片0001.jpg對應(yīng)標簽0001.txt。data.yaml 里已經(jīng)寫好了兩個路徑這里再強調(diào)一個細節(jié)test路徑如果寫在 data.yaml 里訓(xùn)練完成后會額外跑一次測試集推理速度慢且不是必須的測試集可以在驗證完模型后用獨立腳本單獨評估。yolo detect train data./dataset/data.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16 \ lr00.01 \ project./runs/detect邏輯說明modelyolov8n.pt會從 Ultralytics 自動下載預(yù)訓(xùn)練權(quán)重。數(shù)字檢測任務(wù)建議從yolov8n或yolov8s開始模型小、收斂快1000 張圖足夠微調(diào)不需要直接上yolov8l。epochs100配合早停機制validation mAP 連續(xù) 50 輪不漲就會自動停止。參數(shù)說明imgsz640是輸入分辨率。如果數(shù)據(jù)集的圖片尺寸普遍在 200 到 400 像素之間比如攝像頭裁切出的表計圖可以降到imgsz512訓(xùn)練速度更快且不會損失太多精度。batch16在 16GB 顯存上跑得動顯存小就改 8。lr00.01是 YOLOv8 的默認初始學(xué)習(xí)率微調(diào)場景通常不動。4.2 訓(xùn)練前把 GT 框畫回圖上可視化校驗?zāi)_本很多人拿到數(shù)據(jù)集就直接開訓(xùn)訓(xùn)練完 loss 很漂亮但 val mAP 只有 0.2回頭查才發(fā)現(xiàn)標簽坐標整體偏移了 50 像素。訓(xùn)練前把標注框畫回原圖抽查 20 到 30 張是最便宜的后悔藥。import cv2 from glob import glob def visualize_yolo(img_path, label_path, classes): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() cls_id, cx, cy, bw, bh int(parts[0]), *map(float, parts[1:]) # 歸一化坐標還原成像素坐標注意四舍五入后要裁剪到圖像邊界內(nèi) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) x1, y1 max(0, x1), max(0, y1) x2, y2 min(w - 1, x2), min(h - 1, y2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, classes[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(check_ img_path.split(/)[-1], img) for img_path in glob(dataset/images/val/*.jpg)[:30]: label_path img_path.replace(images, labels).replace(.jpg, .txt) visualize_yolo(img_path, label_path, [0,1,2,3,4,5,6,7,8,9])邏輯說明逐行讀取 txt 里的歸一化中心點和寬高還原成像素坐標畫矩形。代碼里max(0, y1 - 5)是防止文字畫到圖像外面去。check_前綴的輸出圖會直接寫到當前目錄方便快速翻看。參數(shù)說明抽查的 30 張圖不要只挑前 30 個文件建議隨機抽樣每類數(shù)字保證至少有一張。如果發(fā)現(xiàn)框整體偏向數(shù)字的左下角或者框比數(shù)字大一圈說明標注規(guī)范沒統(tǒng)一要回到第 3 章復(fù)核一遍標注而不是靠訓(xùn)練去糾正。這一步我每次都會做省下的調(diào)試時間遠超畫框的時間。4.3 數(shù)字檢測的增強策略哪些增強能動、哪些不能動數(shù)據(jù)增強在數(shù)字檢測里是一把雙刃劍。旋轉(zhuǎn)是最大的坑數(shù)字 6 旋轉(zhuǎn) 180 度就是 92 旋轉(zhuǎn) 180 度會接近 5直接做rotate180會讓模型在兩個類別之間搖擺。水平翻轉(zhuǎn)同樣危險雖然數(shù)字本身不隨翻轉(zhuǎn)改變但真實場景里表計數(shù)字的位置語義會被破壞尤其當數(shù)字跟著設(shè)備左右布局時翻轉(zhuǎn)后標簽語義就不再準確。能放心用的是強度擾動類增強亮度、對比度、高斯模糊、隨機縮放、平移和裁剪。這些增強模擬的是攝像頭在不同光照和距離下的成像差異不改變數(shù)字本身的拓撲結(jié)構(gòu)。Mosaic 增強在 YOLOv8 里默認開啟把四張圖拼在一起訓(xùn)練對小目標密集場景很有效但對數(shù)字檢測來說 Mosaic 會把數(shù)字縮小建議保持默認強度或稍微調(diào)低mosaic0.5。yolo detect train data./dataset/data.yaml \ modelyolov8n.pt \ imgsz640 \ epochs100 \ batch16 \ mosaic0.5 \ flipud0.0 \ fliplr0.0 \ hsv_h0.015 \ hsv_s0.5 \ hsv_v0.4邏輯說明flipud和fliplr顯式置 0禁止上下和水平翻轉(zhuǎn)這是數(shù)字檢測和其他目標檢測最大的增強差異。hsv_h0.015是色調(diào)擾動只做微調(diào)過大的色相偏移會讓紅色數(shù)字變成藍色干擾真實場景的顏色特征。hsv_s和hsv_v控制飽和度和明度擾動對應(yīng)光照變化數(shù)值可以按場景自由調(diào)工業(yè)暗光場景把hsv_v調(diào)到 0.6 能提升泛化。參數(shù)說明mosaic0.5的意思是每 50% 的概率使用 Mosaic 拼接。如果發(fā)現(xiàn)訓(xùn)練后期小數(shù)字的 AP 上不去可以關(guān)閉 Mosaic設(shè)為 0再看Mosaic 拼接后數(shù)字尺寸變小模型對中等尺寸的數(shù)字學(xué)得更充分但小尺寸數(shù)字可能被壓縮到難以辨認。1000 張數(shù)據(jù)集的規(guī)模不大增強翻倍到 2000 張跑了也很正常關(guān)鍵是別在增強里把數(shù)字變形到連人都認不出的程度。5. 避坑數(shù)字檢測數(shù)據(jù)集訓(xùn)練中的典型翻車現(xiàn)場5.1 現(xiàn)象loss 降得很順利但驗證集 AP 只有 0.3且 6 和 9 互相混原因訓(xùn)練集中數(shù)字 6 有 400 個實例數(shù)字 9 只有 60 個模型對 9 的特征學(xué)習(xí)不充分更隱蔽的是標注階段 6 和 9 被人為標反了一批。解決先統(tǒng)計labels/train下每個cls_id出現(xiàn)次數(shù)類別不均衡就做實例級重采樣9 號類別復(fù)制三到四份再畫混淆矩陣看具體是哪些類別互混發(fā)現(xiàn)有規(guī)律地混總是 6-9大概率是標簽錯標。5.2 現(xiàn)象訓(xùn)練中途報錯IndexError: index 10 is out of bounds原因txt 標簽里出現(xiàn)了class_id10而nc10只允許 0 到 9。常見來源是標注工具自定義了一個 背景 類別或者 VOC 轉(zhuǎn) txt 時classes列表長度和 data.yaml 的names不一致。解決跑一遍第 3 章的檢查腳本找到[非法類別]對應(yīng)的 XML把多余類別刪掉或改成正確的數(shù)字類別然后重新轉(zhuǎn)換一遍 txt不要在 YOLO 訓(xùn)練代碼里硬改nc11去遷就臟標簽。5.3 現(xiàn)象訓(xùn)練不報錯但推理時所有框都錯位半個圖像寬度原因圖片和標簽尺寸不匹配。比如數(shù)據(jù)集里 PNG 是 640x640標注時工具讀到的也是 640x640但訓(xùn)練腳本里imgsz512會把圖縮放YOLO 能正確處理縮放后的坐標真正的問題在于部分圖片本身尺寸不統(tǒng)一有的是 640、有的是 800而標簽的歸一化坐標基于各自的原始尺寸混在一起訓(xùn)練模型學(xué)到的坐標系是亂的。解決訓(xùn)練前用腳本把所有圖片統(tǒng)一 resize 到同一尺寸標簽不用改本來就是歸一化的如果用的是 VOC XML轉(zhuǎn)換 txt 時要按 XML 里的實際寬高歸一化不要用代碼里寫死的 640。5.4 現(xiàn)象模型把字母 O 當成 0、字母 l 當成 1數(shù)值讀錯但 mAP 很高原因數(shù)據(jù)集里混入了含字母的樣本比如車牌里的省份簡稱、產(chǎn)品編號標注者把 O 標成了 0。模型學(xué)到的是圓環(huán)就是 0的特征不是數(shù)字 0本身。解決這種問題出現(xiàn)在任務(wù)邊界不清晰時如果項目只做數(shù)字檢測把含字母的樣本從數(shù)據(jù)集中直接剔除如果產(chǎn)品需要同時處理字母和數(shù)字正確做法是擴展類別到全字符集而不是讓模型在 10 類里硬分。5.5 現(xiàn)象val mAP0.5 到了 0.95但換一個拍攝角度后漏檢一半原因數(shù)據(jù)分布和真實場景脫節(jié)。1000 張圖如果都來自同一個攝像頭、同一個角度、同一塊表計背景模型學(xué)會的是這個表計的數(shù)字長什么樣而不是任意表計的數(shù)字長什么樣。解決把測試集換成真實場景的樣本哪怕只有 50 張看真實場景的 AP然后往訓(xùn)練集里補充多角度、多光照的樣本或者用合成數(shù)據(jù)把數(shù)字貼到不同背景上做平衡。這一步?jīng)Q定了模型是實驗室準還是現(xiàn)場準后者才是落地項目的驗收標準。6. 落地驗證用評估腳本看 mAP 和單類 AP再用真實樣本做最終檢查訓(xùn)練結(jié)束后不要只看訓(xùn)練日志里最后一行 mAP。YOLOv8 的自帶驗證接口會輸出每個類別的 AP數(shù)字檢測場景里單類 AP 比總均值重要得多——總 mAP 高可能是因為 0 和 1 這類易分數(shù)字拉高了均值6 和 9 的實際 AP 可能剛過 0.5。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.val(datadataset/data.yaml, imgsz640, batch16) print(fmAP50: {results.box.map50:.4f}, mAP50-95: {results.box.map:.4f}) for cls_id, name in results.names.items(): print(fclass {name}: AP50 {results.box.ap50[cls_id]:.4f})邏輯說明results.box.ap50是一個數(shù)組長度為類別數(shù)按cls_id索引。逐類打印能清楚看到拖后腿的是哪個數(shù)字。如果某個數(shù)字的 AP50 明顯低于均值回到第 5 章檢查該類的實例數(shù)和標簽質(zhì)量。另外調(diào)用model.val時注意data.yaml里的test路徑如果沒配 test 就用 val 路徑評估指標會更保守但可復(fù)現(xiàn)。單類 AP 確認沒問題后還有一個習(xí)慣值得堅持拿 50 到 100 張完全沒有參與訓(xùn)練的真實場景圖片人工標注后單獨跑一遍推理腳本對比預(yù)測框和人工框的位置誤差與類別誤差。數(shù)據(jù)集的 1000 張圖再怎么劃分都只是內(nèi)部驗證只有真實場景樣本才能暴露光照、角度和字體變化帶來的分布偏移。這一步發(fā)現(xiàn)問題后把失敗樣本挑出來按困難樣本補充進訓(xùn)練集再微調(diào) 20 到 30 輪往往比增大訓(xùn)練集本身收益大得多。我做數(shù)字檢測項目養(yǎng)成的習(xí)慣是拿到的任何數(shù)據(jù)集第一件事永遠是先跑可視化腳本看 GT 框然后跑第 3 章的檢查腳本最后才談訓(xùn)練。這個流程幫我擋掉過不少因為標簽錯位導(dǎo)致的深夜排查。希望幫到你祝你的數(shù)字檢測模型一次跑通。本文還有配套的精品資源點擊獲取