檢測:YOLO數(shù)據(jù)集格式轉(zhuǎn)換與訓(xùn)練避坑指南)
簡介面向YOLO系列目標(biāo)檢測訓(xùn)練的X光安檢圖像數(shù)據(jù)集收錄1000張真實(shí)安檢場景的高質(zhì)量圖片數(shù)據(jù)場景豐富由LabelImg人工精細(xì)標(biāo)注標(biāo)注框質(zhì)量可靠同步提供VOCxml、COCOjson、YOLOtxt三種主流標(biāo)簽格式分別存放于獨(dú)立文件夾可直接導(dǎo)入現(xiàn)有YOLO訓(xùn)練流程省去格式轉(zhuǎn)換與二次處理的麻煩。壓縮包內(nèi)共2000個(gè)文件以xml標(biāo)簽文件與txt標(biāo)簽文件為主體另含html格式的環(huán)境搭建與訓(xùn)練案例教程、py格式的數(shù)據(jù)集劃分腳本以及yaml格式的模型配置說明整體約105.65MB目錄劃分清晰便于按需檢索取用。隨包附贈(zèng)Linux與Windows雙平臺(tái)的YOLO環(huán)境搭建教程及訓(xùn)練案例指導(dǎo)可按案例修改后應(yīng)用于自己的數(shù)據(jù)集同時(shí)提供訓(xùn)練集、驗(yàn)證集、測試集三種劃分腳本便于靈活調(diào)整數(shù)據(jù)比例。該資源目前已有254人學(xué)習(xí)下載適合需要快速上手YOLO安檢目標(biāo)檢測、兼顧標(biāo)注規(guī)范與訓(xùn)練實(shí)操的研究者、學(xué)生及競賽團(tuán)隊(duì)。1. X光安檢目標(biāo)檢測為什么通用YOLO預(yù)訓(xùn)練權(quán)重在這里翻車X光安檢目標(biāo)檢測和日常的街景檢測完全是兩碼事。行李箱里塞滿衣物、充電器、金屬支架層層疊疊互相遮擋刀刃換個(gè)角度就成了一條亮線液體在灰度圖像里和玻璃、陶瓷幾乎一個(gè)顏色。通用YOLO模型在自然圖像上跑得不錯(cuò)一上安檢機(jī)就原形畢露——這時(shí)候你需要的是專門在X光圖上訓(xùn)練過的模型而訓(xùn)練的起點(diǎn)就是一套干凈的數(shù)據(jù)集圖片、三格式標(biāo)簽、劃分腳本和訓(xùn)練教程一起打包的rar方案圖片1000張不算多但足夠把流程跑通VOC、COCO、YOLO三種標(biāo)注格式讓你不管用什么框架都能直接開工。這篇文章就圍繞這套方案的落地展開三種格式怎么互轉(zhuǎn)、劃分腳本怎么用、訓(xùn)練參數(shù)怎么定、X光場景會(huì)踩哪些坑。準(zhǔn)備做安檢智能判圖、或是拿X光圖練手的算法工程師和研究生都適用純小白照著走也能跑通。2. 三種標(biāo)簽格式怎么選VOC、COCO與YOLO的坐標(biāo)換算2.1 三種標(biāo)注格式的“脾氣”不一樣一個(gè)絕對(duì)像素一個(gè)中心歸一化先說結(jié)論VOC、COCO、YOLO這三種格式本質(zhì)是同一批框的不同寫法差別集中在兩點(diǎn)——坐標(biāo)是絕對(duì)像素還是歸一化比例、框是用四角表達(dá)還是用中心點(diǎn)加寬高表達(dá)。VOC格式用XML文件每個(gè)文件對(duì)應(yīng)一張圖??虻奈恢脤懺赽ndbox里給的是xmin、ymin、xmax、ymax四個(gè)絕對(duì)像素值左上角原點(diǎn)往右往下增大。COCO格式用一個(gè)JSON文件裝下整個(gè)數(shù)據(jù)集標(biāo)注數(shù)組里每條記錄的bbox是[x, y, width, height]也是絕對(duì)像素但表達(dá)方式是左上角坐標(biāo)加上寬高。YOLO格式最直接一張圖一個(gè)txt文件每行五個(gè)數(shù)class_id x_center y_center width height其中四個(gè)坐標(biāo)值全部除以圖片寬高做了歸一化取值在0到1之間。實(shí)際項(xiàng)目里最常見的組合是從VOC的XML轉(zhuǎn)到Y(jié)OLO的txt。因?yàn)楹芏喙_數(shù)據(jù)集和標(biāo)注工具比如LabelImg默認(rèn)輸出VOC格式而YOLO訓(xùn)練時(shí)需要的是txt。換算公式不復(fù)雜x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height注意COCO的寬高是直接用xmax - xmin算出來的不需要除以2。三個(gè)格式的索引順序也不一樣YOLO是class_id在前COCO的JSON里類別要單獨(dú)查categories表VOC的類別名直接寫在XML里。我見過太多人轉(zhuǎn)換時(shí)把x_center和box_width搞混記住一句話YOLO的前兩個(gè)數(shù)是中心點(diǎn)后兩個(gè)數(shù)是寬高誰除以2這件事最容易翻車。2.2 VOC轉(zhuǎn)YOLO解析XML并完成歸一化換算如果你拿到的數(shù)據(jù)包里只有VOC的XML或者你想自己標(biāo)一批數(shù)據(jù)這個(gè)腳本可以直接抄。它遍歷指定目錄下所有XML解析出圖片尺寸和每個(gè)目標(biāo)的類別名、邊界框然后寫出對(duì)應(yīng)的YOLO txt文件。import os import xml.etree.ElementTree as ET # 類別清單順序決定了 class_id不要隨意變動(dòng) CLASS_NAMES [knife, gun, lighter, scissors, bottle, other] def voc_xml_to_yolo_txt(xml_path, txt_out_path, class_names): tree ET.parse(xml_path) root tree.getroot() # 圖片尺寸必須從 XML 里讀不要自己去 opencv 讀避免圖片被壓縮過 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_names: continue # 跳過未定義類別 cls_id class_names.index(cls_name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 坐標(biāo)換算并做一次越界裁剪 x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(txt_out_path, w) as f: f.write(\n.join(lines)) # 示例批量轉(zhuǎn)換 xml_dir annotations/voc txt_dir labels/yolo os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue base xml_file.replace(.xml, ) voc_xml_to_yolo_txt( os.path.join(xml_dir, xml_file), os.path.join(txt_dir, base .txt), CLASS_NAMES )核心邏輯就兩步從size節(jié)點(diǎn)拿圖片寬高從object節(jié)點(diǎn)拿類別和框坐標(biāo)。類別名到class_id的映射用列表索引完成所以類別清單的順序一旦定好就不能改否則訓(xùn)練時(shí)類別就對(duì)不上。min(max(...))那兩行做的是越界裁剪有些標(biāo)注工具會(huì)把框拉出圖片邊界幾個(gè)像素不裁的話訓(xùn)練時(shí)可能報(bào)錯(cuò)或者讓損失函數(shù)震蕩。.6f保留六位小數(shù)精度完全夠用YOLO訓(xùn)練時(shí)會(huì)做內(nèi)部歸一化不需要更長的精度。COCO轉(zhuǎn)YOLO的思路一樣區(qū)別在于要先建立image_id到圖片文件名、category_id到class_id的兩張映射表然后遍歷annotations數(shù)組。如果數(shù)據(jù)包里已經(jīng)帶了三種格式那你可以直接跳過轉(zhuǎn)換步驟但我的建議是仍然跑一遍腳本做校驗(yàn)后面會(huì)講為什么。2.3 轉(zhuǎn)換完自檢把標(biāo)簽畫回圖上看一遍格式轉(zhuǎn)換最大的坑不是代碼寫錯(cuò)而是代碼沒報(bào)錯(cuò)但結(jié)果不對(duì)。坐標(biāo)除反了、寬高算成對(duì)角線、類別索引錯(cuò)位這些問題在txt文件里肉眼完全看不出來。我的血淚經(jīng)驗(yàn)是轉(zhuǎn)換完必須把標(biāo)簽畫回圖片上一張一張看至少抽查30張。import cv2 def draw_yolo_boxes(img_path, txt_path, class_names, save_path): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: lines [line.strip().split() for line in f if line.strip()] for parts in lines: if len(parts) ! 5: print(f[警告] 跳過非法行: {parts}) continue cls_id int(parts[0]) x_c, y_c, bw, bh map(float, parts[1:]) # 反算回絕對(duì)像素坐標(biāo) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, max(y1 - 5, 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(save_path, img) print(f已保存: {save_path})這個(gè)腳本把YOLO的五個(gè)數(shù)反算成像素坐標(biāo)用OpenCV畫框并寫上類別名。重點(diǎn)看三類問題框是否貼著目標(biāo)邊緣、類別名是否張冠李戴、有沒有框跑到圖片外面去。X光圖有個(gè)特殊情況——目標(biāo)被遮擋時(shí)標(biāo)注員容易把框畫得比實(shí)際物體大一圈如果你發(fā)現(xiàn)大量框都偏大那說明原始標(biāo)注本身就不干凈不是轉(zhuǎn)換的問題。這時(shí)候要么重新標(biāo)注要么在訓(xùn)練時(shí)對(duì)框做小幅收縮后者后面會(huì)講。3. 劃分腳本怎么用隨機(jī)種子、訓(xùn)練/驗(yàn)證/測試比例與一致性校驗(yàn)3.1 劃分的三個(gè)原則隨機(jī)種子、分層抽樣、標(biāo)簽同步數(shù)據(jù)集劃分看起來是個(gè)人都會(huì)但安檢場景有個(gè)特殊性同一件行李往往拍了多個(gè)角度的圖如果這些圖一部分進(jìn)了訓(xùn)練集一部分進(jìn)了驗(yàn)證集那驗(yàn)證指標(biāo)會(huì)虛高因?yàn)槟P鸵娺^同一件行李的另一張照片。1000張圖的規(guī)模下一張重復(fù)就足以讓mAP虛高兩三個(gè)點(diǎn)。三個(gè)原則必須同時(shí)滿足。第一固定隨機(jī)種子讓每次劃分結(jié)果一致別人復(fù)現(xiàn)你的實(shí)驗(yàn)時(shí)才不會(huì)被“隨機(jī)性”干擾。第二分層抽樣如果數(shù)據(jù)里包含多個(gè)類別先按類別統(tǒng)計(jì)圖片數(shù)量保證訓(xùn)練集、驗(yàn)證集、測試集的類別比例大致一致不要出現(xiàn)某類小刀全在驗(yàn)證集里的情況。第三同步移動(dòng)圖片和標(biāo)簽圖片和對(duì)應(yīng)txt必須一起走標(biāo)簽丟了模型訓(xùn)練直接報(bào)錯(cuò)。這里的“分層”不是嚴(yán)格意義上的按類別分層采樣因?yàn)橐粡垐D可能同時(shí)包含多類目標(biāo)——X光安檢圖尤其如此一個(gè)行李箱里有刀、有打火機(jī)、有充電寶。常見的做法是先跑一次類別統(tǒng)計(jì)確認(rèn)每類在三個(gè)集合里的分布偏差不超過10%偏差大了就調(diào)整隨機(jī)種子重來。3.2 在Linux和Windows下通用的劃分腳本這個(gè)腳本把全部圖片隨機(jī)打亂按設(shè)定的比例切成三份然后把每張圖對(duì)應(yīng)的YOLO格式txt一起復(fù)制過去。如果你用的是VOC或COCO格式只需要把標(biāo)簽的后綴名和目錄改一下。import os import random import shutil random.seed(42) # 固定隨機(jī)種子保證可復(fù)現(xiàn) SRC_IMG_DIR dataset/images SRC_LAB_DIR dataset/labels/yolo DST_IMG_DIR dataset/split RATIO {train: 0.7, val: 0.2, test: 0.1} images [f for f in os.listdir(SRC_IMG_DIR) if f.endswith((.jpg, .png, .jpeg))] random.shuffle(images) num_train int(len(images) * RATIO[train]) num_val int(len(images) * RATIO[val]) splits { train: images[:num_train], val: images[num_train:num_train num_val], test: images[num_train num_val:], } for split_name, img_list in splits.items(): dst_img os.path.join(DST_IMG_DIR, split_name, images) dst_lab os.path.join(DST_IMG_DIR, split_name, labels) os.makedirs(dst_img, exist_okTrue) os.makedirs(dst_lab, exist_okTrue) for img_name in img_list: base os.path.splitext(img_name)[0] src_img os.path.join(SRC_IMG_DIR, img_name) src_lab os.path.join(SRC_LAB_DIR, base .txt) dst_img_path os.path.join(dst_img, img_name) dst_lab_path os.path.join(dst_lab, base .txt) shutil.copy2(src_img, dst_img_path) if os.path.exists(src_lab): shutil.copy2(src_lab, dst_lab_path) else: print(f[警告] 缺少標(biāo)簽: {img_name})注意幾個(gè)細(xì)節(jié)random.shuffle在random.seed(42)之后執(zhí)行這就是復(fù)現(xiàn)的保障。三份比例不要用85/15/0測試集哪怕只有一份也要單獨(dú)切開否則你后續(xù)的置信度閾值和PR曲線都沒得算。圖片用copy2保留原始修改時(shí)間真出問題排查時(shí)能對(duì)上原始數(shù)據(jù)。標(biāo)簽不存在的圖片只警告不中斷但后面的一致性檢查必須單獨(dú)做一遍。還有一點(diǎn)Windows上跑Python腳本經(jīng)常遇到“閃退”的情況多半是路徑分隔符問題。這個(gè)腳本里全部用了os.path.join避免了C:\和/混用的坑。如果你雙擊bat跑時(shí)窗口一閃而過在bat最后加一行pause就能看到報(bào)錯(cuò)內(nèi)容。3.3 劃分完先跑一致性檢查再開訓(xùn)練劃分完成后不要急著訓(xùn)練先跑一遍校驗(yàn)。檢查三件事每個(gè)txt文件都有對(duì)應(yīng)的圖片、txt里每行都恰好是五個(gè)字段、類別id沒有超出類別清單的范圍。前兩件事用腳本自動(dòng)查第三件事肉眼抽查。# 檢查有沒有標(biāo)簽缺失的圖片 for img in dataset/split/train/images/*.jpg; do base$(basename $img .jpg) if [ ! -f dataset/split/train/labels/$base.txt ]; then echo 缺失標(biāo)簽: $img fi done # 統(tǒng)計(jì)每類目標(biāo)數(shù)量確認(rèn)分層效果 for f in dataset/split/*/labels/*.txt; do awk {print $1} $f done | sort | uniq -c第一個(gè)循環(huán)遍歷訓(xùn)練集圖片檢查同名txt是否存在。第二個(gè)命令把所有標(biāo)簽的class_id提取出來做計(jì)數(shù)輸出類似123 0、98 1這樣的統(tǒng)計(jì)數(shù)字代表類別id前面的計(jì)數(shù)代表圖片數(shù)。你只需要確認(rèn)三個(gè)集合里各個(gè)id的相對(duì)比例差不多就行。這一步看起來繁瑣但能攔住大概率的數(shù)據(jù)事故。我自己做過一次劃分后直接訓(xùn)練跑到第30個(gè)epoch才發(fā)現(xiàn)驗(yàn)證集里有一類目標(biāo)的圖片只有張loss曲線一直震蕩——就是分層沒做好驗(yàn)證集類別分布歪了。4. YOLO訓(xùn)練參數(shù)怎么設(shè)data.yaml、損失函數(shù)曲線與六個(gè)必調(diào)項(xiàng)4.1 data.yaml類別清單與路徑別寫錯(cuò)YOLO系列的訓(xùn)練入口是data.yaml不管你是用Ultralytics的YOLOv8還是YOLOv11都是同一個(gè)套路。這個(gè)文件告訴框架訓(xùn)練集在哪里、驗(yàn)證集在哪里、有幾類、每類叫什么。# data.yaml path: D:/airport_dataset/split # 數(shù)據(jù)根目錄建議寫絕對(duì)路徑 train: train/images # 訓(xùn)練集圖片相對(duì)路徑 val: val/images # 驗(yàn)證集圖片相對(duì)路徑 test: test/images # 測試集圖片部分版本可不填 names: 0: knife 1: gun 2: lighter 3: scissors 4: bottle 5: other這里最容易翻車的是path配錯(cuò)。path必須是train和val的父目錄不是images這一層。我見過有人把path寫成D:/airport_dataset/split/train然后訓(xùn)練直接報(bào)AssertionError: train dataset not found。另一個(gè)高頻問題是names的類別數(shù)和順序必須和標(biāo)注時(shí)用的CLASS_NAMES完全一致順序錯(cuò)了一位整個(gè)模型的輸出就全亂了。如果你手里是COCO格式的json里面自帶categories表建議轉(zhuǎn)成YOLO txt之前就把類別順序訂好然后讓所有腳本都用同一個(gè)順序。這個(gè)順序定了就不要再改改一次意味著所有標(biāo)注文件全部要跟著改工作量你自己體會(huì)。4.2 訓(xùn)練命令里的6個(gè)必調(diào)參數(shù)環(huán)境配置是純小白最容易卡住的地方。常見做法是直接在Ultralytics的環(huán)境里跑一條命令裝完依賴pip install ultralytics。如果你在安裝或者運(yùn)行時(shí)報(bào)cmdlet不是可識(shí)別命令之類的錯(cuò)誤多半是Python沒加到PATH里重裝Python時(shí)勾選“Add to PATH”就能解決。yolo detect train \ dataD:/airport_dataset/split/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20 \ workers4 \ project./runs \ namexray_airport六個(gè)必調(diào)參數(shù)逐個(gè)說。model選預(yù)訓(xùn)練權(quán)重1000張圖的數(shù)據(jù)量不建議從隨機(jī)初始化開始訓(xùn)用yolov8n.pt或yolov8s.pt做起點(diǎn)前者約310萬參數(shù)后者約1100萬按你的顯存選6G顯存用n8G以上可以考慮s。epochs設(shè)100起步1000張的小數(shù)據(jù)集訓(xùn)練速度很快100個(gè)epoch足夠看到收斂趨勢不要一上來就300過擬合會(huì)先來找你。imgsz是推理分辨率X光安檢場景建議用640起步如果后面發(fā)現(xiàn)小目標(biāo)檢不出來再試960代價(jià)是訓(xùn)練和推理時(shí)間幾乎翻倍。batch受顯存約束8G顯存跑n模型可以開到32跑s模型建議16。patience是早停參數(shù)驗(yàn)證集指標(biāo)連續(xù)20個(gè)epoch不提升就自動(dòng)停小數(shù)據(jù)集上很實(shí)用防止后期過擬合浪費(fèi)時(shí)間。workers設(shè)成CPU核數(shù)的一半即可設(shè)太大反而會(huì)因?yàn)閿?shù)據(jù)加載瓶頸把GPU餓死。還有一個(gè)容易被忽略的pretrained參數(shù)。如果你明確不想用COCO預(yù)訓(xùn)練權(quán)重可以寫pretrainedFalse但我不建議在小數(shù)據(jù)集上這么干。X光圖和自然圖像差異大COCO權(quán)重遷移過來的效果有限但至少backbone學(xué)到了邊緣、紋理這些底層特征比從零開始強(qiáng)得多。如果發(fā)現(xiàn)遷移效果確實(shí)不行再考慮凍結(jié)backbone前幾層做手動(dòng)遷移后面避坑章會(huì)細(xì)說。4.3 損失函數(shù)曲線怎么讀box_loss、cls_loss與dfl_loss訓(xùn)練啟動(dòng)后Ultralytics在控制臺(tái)實(shí)時(shí)輸出box_loss、cls_loss、dfl_loss三條損失曲線很多人只看mAP就完事。但在數(shù)據(jù)規(guī)模只有1000張的時(shí)候mAP刷得再高也可能是過擬合出來的幻覺損失曲線才是判斷模型有沒有真正學(xué)進(jìn)去的依據(jù)。三條曲線的分工是box_loss管檢測框和真實(shí)框的重合度cls_loss管分類對(duì)不對(duì)dfl_loss是分布焦點(diǎn)損失管邊界框的精確程度。X光場景下最值得盯的是box_loss因?yàn)槟繕?biāo)遮擋嚴(yán)重框的邊界天然模糊box_loss如果一直在0.08以上下不來說明模型對(duì)目標(biāo)邊界的學(xué)習(xí)沒到位——加不了epoch解決不了得從標(biāo)注質(zhì)量或者輸入分辨率入手。正?,F(xiàn)象是三個(gè)loss在前10個(gè)epoch快速下降之后緩慢下降并伴隨小幅度震蕩。如果你看到cls_loss降到接近0但box_loss還在緩慢上升大概率是過擬合的早期信號(hào)這時(shí)候patience的早停機(jī)制會(huì)幫你切斷訓(xùn)練。另外一個(gè)小技巧訓(xùn)練完翻一下runs/xray_airport/目錄下的results.png那個(gè)圖把三條曲線和mAP都畫在一起比你來回翻控制臺(tái)方便得多。5. X光安檢數(shù)據(jù)集避坑五個(gè)實(shí)測翻車場景與解決方案5.1 有目標(biāo)但檢不出來重疊行李讓NMS把真陽框抑制掉了現(xiàn)象驗(yàn)證集上mAP看著很高但在實(shí)際行李圖上一把刀明明擺在書包旁邊模型就是沒框出來。原因X光圖里目標(biāo)疊放嚴(yán)重一個(gè)目標(biāo)的檢測框和另一個(gè)目標(biāo)的重疊度很高。下游的NMS按IoU閾值做抑制兩個(gè)框重疊超過閾值就只留分?jǐn)?shù)高的那個(gè)分?jǐn)?shù)低的小目標(biāo)直接被當(dāng)成重復(fù)框刪掉了。這在自然圖像里很罕見但在安檢圖里是家常便飯。解決把NMS的IoU閾值調(diào)小。YOLO檢測階段默認(rèn)的iou0.7你可以在推理時(shí)顯式改成iou0.45或更低代價(jià)是附近會(huì)出現(xiàn)一些重復(fù)框但對(duì)小目標(biāo)召回率的提升立竿見影。訓(xùn)練階段的nms參數(shù)不需要?jiǎng)油评頃r(shí)改就行了。5.2 COCO預(yù)訓(xùn)練權(quán)重遷移過來效果奇差灰度圖讓通道語義失效現(xiàn)象用yolov8n.pt在X光數(shù)據(jù)上微調(diào)50個(gè)epochmAP0.5連0.5都不到。原因COCO訓(xùn)練數(shù)據(jù)的通道語義是RGBX光是單通道灰度透射圖物體呈現(xiàn)的顏色、紋理和自然圖像完全不同。backbone前幾層學(xué)的是RGB空間的邊緣和顏色組合遷移過來的參數(shù)在灰度圖上用處有限甚至?xí)鸱醋饔?。解決微調(diào)時(shí)凍結(jié)backbone前幾層讓后層重新適應(yīng)X光的灰度特征。Ultralytics里可以用freeze10參數(shù)凍結(jié)前10層。如果凍結(jié)后效果還是不行就換思路把X光圖在三通道上復(fù)制成偽RGB雖然本質(zhì)還是灰度但至少保留了完整輸入維度。這個(gè)方法不保證一定提升但值得一試——準(zhǔn)確說這屬于玄學(xué)范疇不同數(shù)據(jù)集上表現(xiàn)差異很大。5.3 驗(yàn)證集指標(biāo)好看測試集漏檢率翻倍現(xiàn)象val的mAP0.5到0.85test的mAP只有0.6召回率掉了20個(gè)點(diǎn)。原因劃分腳本的問題通常出在“同源圖片泄露”。同一件行李拍的不同角度、不同包裹的相似擺放被隨機(jī)打亂后分到了train和test兩個(gè)集合。測試集里出現(xiàn)訓(xùn)練集的“近親”測試指標(biāo)就被污染了。另一種可能是出現(xiàn)了空標(biāo)簽圖片——安檢數(shù)據(jù)里確實(shí)有完全沒目標(biāo)的高壓圖如果這些圖被分到val里它們不參與損失計(jì)算但會(huì)把mAP拉低。解決劃分前按行李編號(hào)或拍攝批次做分組同一組全進(jìn)同一集合。如果數(shù)據(jù)包里沒有編號(hào)信息就用文件名的前綴做分組比如bag_001_a.jpg和bag_001_b.jpg都?xì)w到bag_001。另外在劃分腳本里加一個(gè)檢查統(tǒng)計(jì)每張圖txt的行數(shù)把行數(shù)為0的圖片單獨(dú)列出來避免空圖進(jìn)入評(píng)測集干擾指標(biāo)。5.4 標(biāo)注框比真實(shí)目標(biāo)大一圈邊緣模糊讓標(biāo)注標(biāo)準(zhǔn)失控現(xiàn)象訓(xùn)練出來的模型框總是偏大尤其在目標(biāo)邊緣不清晰的區(qū)域檢測框比目標(biāo)外包圍盒大了10%左右。原因X光圖的邊緣天然模糊金屬和有機(jī)物邊界過渡區(qū)域?qū)挊?biāo)注員對(duì)“目標(biāo)邊界到底在哪里”有主觀判斷差異。有人從嚴(yán)貼著目標(biāo)有人從寬框到半透明外沿。這種標(biāo)注噪聲在訓(xùn)練中會(huì)讓模型學(xué)到“框大一點(diǎn)更安全”的傾向。解決統(tǒng)一標(biāo)注規(guī)范規(guī)定邊界取“目標(biāo)外緣不透明區(qū)域的外切矩形”然后在訓(xùn)練前對(duì)bbox做收縮處理。YOLO格式的txt直接操作即可把w和h各乘0.9x_center和y_center不變相當(dāng)于把框向中心縮5%。實(shí)際操作時(shí)建議只收縮到0.95倍收縮太狠反而讓邊界學(xué)偏。5.5 訓(xùn)練時(shí)loss降了但PR曲線很難看現(xiàn)象訓(xùn)練曲線顯示loss穩(wěn)步下降但跑測試集時(shí)precision和recall曲線失衡準(zhǔn)確率0.9時(shí)召回率只有0.4。原因類別不平衡。1000張圖里如果bottle類占了大頭gun類只有幾十張模型會(huì)傾向把所有目標(biāo)都預(yù)測成多數(shù)類loss仍然會(huì)降但少數(shù)類的召回率幾乎為零。解決先看類別統(tǒng)計(jì)確認(rèn)每類最少有多少張圖。如果確有類別樣本個(gè)位數(shù)先把這類的圖片復(fù)制增強(qiáng)水平翻轉(zhuǎn)、隨機(jī)裁剪、亮度抖動(dòng)到至少100張或者用weight參數(shù)給少數(shù)類加loss權(quán)重。Ultralytics支持在訓(xùn)練時(shí)傳class_weights但每次都要生成權(quán)重?cái)?shù)組比較麻煩更省事的方案是直接用數(shù)據(jù)增強(qiáng)把少數(shù)類補(bǔ)起來。這個(gè)操作只能緩解問題想根治還是得補(bǔ)數(shù)據(jù)。6. 從mAP到上線置信度閾值與單卡并發(fā)估算6.1 用測試集畫PR曲線選置信度閾值訓(xùn)練完best.pt模型后mAP只是一個(gè)參考真正上線前必須做一件事在測試集上跑一遍畫出PR曲線選定置信度閾值。安檢場景的優(yōu)先級(jí)是“寧可誤檢一百不能漏檢一單”——漏檢一把刀意味著安全事故所以閾值通常定在0.3甚至更低換取更高的召回率。from ultralytics import YOLO model YOLO(runs/xray_airport/weights/best.pt) results model.predict( sourceD:/airport_dataset/split/test/images, conf0.25, # 先放低閾值跑出全部候選框 saveTrue, project./runs/predict, nametest_conf_demo )跑完后逐一翻看輸出圖觀察低置信度區(qū)間里到底有多少真正的目標(biāo)混在誤檢里。如果0.25閾值下誤檢框滿天飛說明模型本身不夠自信要把閾值升到0.4以上如果0.25閾值下真目標(biāo)還是經(jīng)常沒框出來那問題在模型不在閾值回頭去調(diào)訓(xùn)練參數(shù)。這里沒有絕對(duì)正確的數(shù)字每批數(shù)據(jù)和每個(gè)類別的合適閾值都不同。6.2 TensorRT導(dǎo)出與單卡并發(fā)粗估安檢場景跑實(shí)時(shí)視頻流模型推理速度直接決定硬件預(yù)算。常見做法是把YOLO模型導(dǎo)出成TensorRT的engine格式在NVIDIA顯卡上把推理速度壓到極致。yolo export modelruns/xray_airport/weights/best.pt formatengine device0 \ imgsz640 halfTrue workspace4halfTrue開啟FP16精度速度幾乎翻倍但精度損失很小安檢場景完全夠用。workspace4分配4GB顯存給構(gòu)建期使用導(dǎo)出完成后運(yùn)行期只占約1GB。導(dǎo)出成功后拿真實(shí)的X光圖片或視頻流測試單幀推理耗時(shí)。一張T4或類似算力的卡用YOLOv8n的engine跑640分辨率單幀耗時(shí)通常能壓到2到5毫秒之間。按“一路1080p 25fps”算每路需要每40毫秒完成一幀檢測留50%余量的話按80毫秒預(yù)算那基本可以估算2毫秒的推理耗時(shí)對(duì)應(yīng)40路余量5毫秒對(duì)應(yīng)16路——實(shí)際還要算上解碼、前后處理。記住一個(gè)原則不要按理論峰值算并發(fā)跑一次實(shí)際測試最靠譜。說實(shí)話做X光安檢項(xiàng)目這幾年我最大的感受是數(shù)據(jù)質(zhì)量對(duì)結(jié)果的影響遠(yuǎn)大于模型選型。YOLO在安檢場景里已經(jīng)是驗(yàn)證過無數(shù)次的成熟方案真正讓項(xiàng)目失敗的都是標(biāo)注不一致、劃分不嚴(yán)謹(jǐn)、閾值拍腦袋這些基礎(chǔ)環(huán)節(jié)。把本文提到的格式校驗(yàn)、劃分檢查、類別統(tǒng)計(jì)這三件事做扎實(shí)模型的性能至少不會(huì)翻車。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取