物多目標(biāo)檢測數(shù)據(jù)集處理與YOLOv8訓(xùn)練實(shí)戰(zhàn))
簡介目標(biāo)檢測是計(jì)算機(jī)視覺的核心任務(wù)之一在實(shí)際工程中多類別目標(biāo)的統(tǒng)一檢測往往比單類模型串聯(lián)更高效。智能交通、道路巡檢等場景需要同時(shí)識(shí)別車輛、行人和動(dòng)物這對數(shù)據(jù)集的構(gòu)建與訓(xùn)練流程提出了更高要求。理解YOLO標(biāo)注格式、多源數(shù)據(jù)集整合、類別映射與平衡、質(zhì)量排查是提升模型泛化能力的關(guān)鍵。實(shí)踐中需從公開數(shù)據(jù)集如COCO、BDD100K轉(zhuǎn)換格式合并去重并修正類別不平衡再基于YOLOv8進(jìn)行訓(xùn)練與調(diào)參。分類別評估與bad case分析能有效定位誤檢漏檢問題最終通過TensorRT部署實(shí)現(xiàn)邊緣端高效推理。本文系統(tǒng)梳理了從原始數(shù)據(jù)到可部署模型的全流程經(jīng)驗(yàn)為多目標(biāo)檢測項(xiàng)目提供可復(fù)用的方法論。 最近在做智能交通巡檢項(xiàng)目手頭正好整理了一份車輛行人動(dòng)物多目標(biāo)檢測數(shù)據(jù)集壓縮包解壓后十幾個(gè)G覆蓋城區(qū)道路、鄉(xiāng)村公路、園區(qū)封閉道路、牧區(qū)周邊等場景。我在這份數(shù)據(jù)上完整跑通了從格式檢查、多源數(shù)據(jù)整合到Y(jié)OLOv8訓(xùn)練評估的全流程中間踩了不少坑也沉淀了一些能直接復(fù)用的經(jīng)驗(yàn)。如果你正準(zhǔn)備用多目標(biāo)檢測數(shù)據(jù)集訓(xùn)練自己的模型或者剛拿到一份標(biāo)注好的數(shù)據(jù)不知道從哪下手這篇復(fù)盤應(yīng)該能幫你少走不少彎路。以下內(nèi)容不涉及具體項(xiàng)目數(shù)據(jù)只講通用方法。1. 為什么智能交通和巡檢場景認(rèn)準(zhǔn)車輛行人動(dòng)物組合1.1 這類場景的典型需求是什么我先說結(jié)論只看城市路口的監(jiān)控?cái)?shù)據(jù)遠(yuǎn)遠(yuǎn)不夠交通和巡檢場景里最容易被漏掉、也最危險(xiǎn)的目標(biāo)是動(dòng)物。車輛和行人是交通參與者大家很自然會(huì)把它們放進(jìn)檢測列表但牛、羊、馬、駱駝這些動(dòng)物在鄉(xiāng)村公路、牧區(qū)道路、保護(hù)區(qū)邊界、智慧農(nóng)業(yè)園區(qū)里出現(xiàn)頻率非常高。一次夜間鄉(xiāng)村道路巡檢如果模型只檢測車輛和行人一頭橫穿公路的?;镜扔诒粺o視這在輔助駕駛、路側(cè)感知、巡邏機(jī)器人這些場景里是致命的。所以這類任務(wù)的數(shù)據(jù)集設(shè)計(jì)天然要把車輛、行人、動(dòng)物放在一起。不是三個(gè)單獨(dú)模型串起來而是一個(gè)統(tǒng)一的多目標(biāo)檢測模型同時(shí)輸出三類目標(biāo)。原因很直接第一單模型推理延遲低對邊緣設(shè)備更友好第二類別之間在真實(shí)場景中經(jīng)常一起出現(xiàn)模型可以學(xué)到上下文關(guān)系比如看到動(dòng)物群提前預(yù)警第三維護(hù)一套數(shù)據(jù)標(biāo)注標(biāo)準(zhǔn)和一個(gè)模型比維護(hù)三套數(shù)據(jù)和三套模型成本低得多。1.2 它們?yōu)槭裁匆旁谕粋€(gè)模型里可能有讀者會(huì)問車輛檢測、行人檢測、動(dòng)物檢測各自都有成熟模型直接拼不行嗎我之前也這么想過實(shí)際上拼起來的方案很吃虧。三個(gè)模型串行推理每一路的預(yù)處理和后處理都要單獨(dú)過一遍延遲累加非常明顯。在Jetson Orin這種邊緣設(shè)備上三路模型的總耗時(shí)比一個(gè)多類模型高出一大截資源占用也翻倍。更重要的是串行模型之間沒有信息交互。一輛車旁邊站著一個(gè)人單類模型各自出框看起來沒問題但如果動(dòng)物出現(xiàn)在車輛遮擋區(qū)域多目標(biāo)模型能利用整體上下文做更穩(wěn)的預(yù)測單類模型只能各管各的。多目標(biāo)檢測數(shù)據(jù)集的價(jià)值就在于它強(qiáng)迫模型在同一個(gè)特征空間里區(qū)分這三類形態(tài)差異很大的物體。車輛是剛體行人姿態(tài)多變動(dòng)物形態(tài)更復(fù)雜牛、羊、馬的體態(tài)、紋理、顏色差異很大同一個(gè)模型要同時(shí)學(xué)習(xí)這些特征其實(shí)比三個(gè)獨(dú)立模型更難但好在參數(shù)量可以共享訓(xùn)練充分后泛化能力更強(qiáng)。還有一個(gè)語義邊界問題。騎馬的人到底算人還是算動(dòng)物我處理數(shù)據(jù)集時(shí)的做法是騎乘者本身標(biāo)為person馬單獨(dú)標(biāo)為animal兩個(gè)框都出。這樣既保留了人的語義也保留了動(dòng)物目標(biāo)。如果數(shù)據(jù)集里標(biāo)注規(guī)范不統(tǒng)一這一步必須盡早處理否則后面訓(xùn)練就會(huì)在類別邊界上反復(fù)搖擺。2. 數(shù)據(jù)集落地的第一步看懂標(biāo)注格式和類別體系2.1 解壓后的目錄結(jié)構(gòu)和YOLO標(biāo)注格式拿到車輛行人動(dòng)物多目標(biāo)檢測數(shù)據(jù)集.zip這類壓縮包我第一件事不是急著訓(xùn)練而是先把解壓后的目錄結(jié)構(gòu)和標(biāo)注格式摸清楚。很多數(shù)據(jù)集解壓出來不一定是標(biāo)準(zhǔn)YOLO結(jié)構(gòu)有的嵌套多層文件夾有的圖片和標(biāo)簽分開有的混在一起。最常見的一個(gè)結(jié)構(gòu)長這樣dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ └── val/ │ ├── 000100.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 000001.txt │ │ └── ... │ └── val/ │ ├── 000100.txt │ └── ... └── data.yamlimages和labels一一對應(yīng)文件名相同只是擴(kuò)展名不同。labels目錄里每個(gè)txt文件就是一張圖片的標(biāo)注每一行代表一個(gè)目標(biāo)框YOLO格式是五個(gè)數(shù)值class_id x_center y_center width height注意這里的x_center、y_center、width、height是相對圖片寬高的歸一化坐標(biāo)取值在0到1之間。比如一行1 0.456 0.321 0.210 0.385就表示類別1假設(shè)是person的目標(biāo)中心點(diǎn)在圖片橫向45.6%、縱向32.1%的位置寬占圖片寬度21%高占圖片高度38.5%。拿到數(shù)據(jù)后先別管別的用下面這個(gè)命令隨便看幾個(gè)文件head -5 dataset/labels/train/000001.txt cat dataset/data.yaml如果文件里出現(xiàn)負(fù)數(shù)、大于1的數(shù)或者空文件說明標(biāo)注存在問題后面要專門做清洗。2.2 類別體系與映射的確認(rèn)類別體系是數(shù)據(jù)集的靈魂。不同的多目標(biāo)檢測數(shù)據(jù)集對類別定義差異很大有的把動(dòng)物細(xì)分成deer、wild_boar、horse、cattle有的就把所有非人物體籠統(tǒng)標(biāo)成animal。使用之前必須確認(rèn)自己的任務(wù)到底需要哪些類別。我的建議是除非業(yè)務(wù)確實(shí)需要區(qū)分物種否則一律先把細(xì)分動(dòng)物標(biāo)簽合并成統(tǒng)一的animal類。類別越少訓(xùn)練難度越低誤檢率也更容易控制。如果后面發(fā)現(xiàn)特定動(dòng)物類型漏檢嚴(yán)重再拆類訓(xùn)練。類別映射這一步一定要寫成代碼或文檔記錄下來不能靠腦子記。比如原始數(shù)據(jù)集類別定義是0: car 1: truck 2: bus 3: motorcycle 4: bicycle 5: pedestrian 6: animal而你的業(yè)務(wù)只需要vehicle、person、animal三類那就把0、1、2、3、4合并到vehicle5映射到person6映射到animal。合并方式很簡單改txt里每行的第一個(gè)數(shù)字就行但一定要在腳本里做一個(gè)明確的映射字典。2.3 快速統(tǒng)計(jì)圖片和標(biāo)注的腳本在正式使用數(shù)據(jù)集之前我會(huì)先統(tǒng)計(jì)一下各類別的樣本分布避免數(shù)據(jù)嚴(yán)重不均衡而不自知。統(tǒng)計(jì)腳本很簡單用Python的Counter就能搞定import os from collections import Counter def scan_labels(label_dir): counter Counter() empty_files 0 total_files 0 for f in os.listdir(label_dir): if not f.endswith(.txt): continue total_files 1 path os.path.join(label_dir, f) if os.path.getsize(path) 0: empty_files 1 continue with open(path, r) as fh: for line in fh: line line.strip() if not line: continue cls line.split()[0] counter[cls] 1 print(類別統(tǒng)計(jì):, dict(counter)) print(空標(biāo)注文件數(shù):, empty_files, /, total_files) scan_labels(dataset/labels/train)這個(gè)腳本會(huì)告訴你當(dāng)前數(shù)據(jù)集有沒有空標(biāo)注、每個(gè)類別的目標(biāo)數(shù)量、以及類別大致比例。如果vehicle有10萬個(gè)框animal只有3000個(gè)那動(dòng)物類別基本會(huì)被模型忽視后面必須做對應(yīng)的數(shù)據(jù)增強(qiáng)或重采樣。另外需要注意圖片數(shù)量和標(biāo)簽文件數(shù)量是否完全一致。我經(jīng)常遇到某些圖片沒有對應(yīng)txt或者txt里標(biāo)注框數(shù)量遠(yuǎn)少于可見目標(biāo)的情況。數(shù)量對不上基本是數(shù)據(jù)導(dǎo)出過程出了問題建議直接用腳本比對文件名集合。3. 多源數(shù)據(jù)集整合方案從KITTI/BDD100K到自建數(shù)據(jù)3.1 主流公開數(shù)據(jù)集的選型對比單靠一份數(shù)據(jù)集往往很難覆蓋所有場景。我做多目標(biāo)檢測項(xiàng)目時(shí)比較常用的是一個(gè)主數(shù)據(jù)集多個(gè)補(bǔ)充數(shù)據(jù)集的組合策略。先列一下我用過且覺得值得參考的公開數(shù)據(jù)源數(shù)據(jù)集類別特點(diǎn)場景覆蓋原始標(biāo)注格式主要注意點(diǎn)BDD100K車輛、行人、騎行者、交通燈等駕駛視角白天、夜間、雨霧天氣多JSONCOCO風(fēng)格類別多需要篩選KITTI車輛、行人、騎手市區(qū)道路公路場景KITTI格式數(shù)據(jù)年份較早場景偏單一Waymo Open Dataset車輛、行人、騎手多城市道路TFRecord數(shù)據(jù)量大轉(zhuǎn)換成本高UA-DETRAC車輛為主路口監(jiān)控視角XML / MOT車輛密集場景很好用VisDrone行人、車輛無人機(jī)視角小目標(biāo)多VOC / COCO風(fēng)格小目標(biāo)檢測難度高COCO數(shù)據(jù)集部分類別人、貓、狗、馬、牛等通用日常場景JSON動(dòng)物類別豐富適合抽取補(bǔ)充選擇主數(shù)據(jù)集時(shí)我比較看重場景覆蓋度和天氣多樣性。BDD100K是最常用的選擇因?yàn)樗囊归g和雨霧樣本很全。車輛密集部分用UA-DETRAC補(bǔ)動(dòng)物部分從COCO抽取馬、牛、綿羊、狗、貓這些類別再結(jié)合自己采拍的鄉(xiāng)村道路樣本。選型邏輯很樸素模型要部署到哪里數(shù)據(jù)就要覆蓋哪里。如果你最終要跑鄉(xiāng)鎮(zhèn)公路的輔助駕駛訓(xùn)練集里全是城市夜景測試結(jié)果肯定不理想這不是模型問題是數(shù)據(jù)分布沒有對齊。3.2 COCO轉(zhuǎn)YOLO格式的實(shí)操腳本公開數(shù)據(jù)集的標(biāo)注格式五花八門COCO是JSONKITTI是自定義文本BDD100K是JSONTFRecord就更麻煩了。而YOLO系列訓(xùn)練需要的是txt文件所以幾乎每個(gè)項(xiàng)目都必須寫格式轉(zhuǎn)換腳本。以COCO轉(zhuǎn)YOLO為例核心代碼是這樣import json from pathlib import Path # 自定義類別映射比如把COCO里的person映射成1把horse/cow/sheep/dog/cat映射成2 # COCO類別id可以在json的categories字段里查 custom_map { person: 1, horse: 2, cow: 2, sheep: 2, dog: 2, cat: 2, } def coco_to_yolo(coco_json, out_dir): with open(coco_json, r) as f: data json.load(f) cat_id_to_name {cat[id]: cat[name] for cat in data[categories]} images {img[id]: img for img in data[images]} Path(out_dir).mkdir(parentsTrue, exist_okTrue) for img_id, img in images.items(): width img[width] height img[height] txt_path Path(out_dir) / (Path(img[file_name]).stem .txt) with open(txt_path, w) as out: for ann in data[annotations]: if ann[image_id] ! img_id: continue cat_name cat_id_to_name[ann[category_id]] new_cls custom_map.get(cat_name, -1) if new_cls 0: continue x, y, w, h ann[bbox] # COCO bbox 是左上角 x,y 和寬高 x_center (x w / 2) / width y_center (y h / 2) / height w_norm w / width h_norm h / height out.write(f{new_cls} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n) coco_to_yolo(coco_annotations.json, coco_yolo_labels)這段腳本把COCO的類別名映射到自己的類別編號(hào)只輸出需要的類別。注意COCO的bbox是(x, y, w, h)x、y是左上角坐標(biāo)YOLO需要的是中心點(diǎn)坐標(biāo)所以必須做一次換算。3.3 合并、去重與類別平衡多源數(shù)據(jù)合并時(shí)最容易踩的坑是文件名沖突。KITTI的圖片叫000000.pngCOCO的圖片也可能是000000000000.jpg直接放到同一個(gè)目錄會(huì)互相覆蓋。我在處理時(shí)會(huì)給每個(gè)來源加前綴比如kitti_000000.png、coco_000000000000.jpg對應(yīng)txt文件名也得同步改不然匹配不上。合并之后還要去重。同一個(gè)場景可能同時(shí)出現(xiàn)在開源數(shù)據(jù)集的訓(xùn)練集和自采數(shù)據(jù)里如果不處理模型會(huì)過擬合到重復(fù)樣本上。簡單做法是先用文件名做一次哈希比對再用感知哈希對圖片內(nèi)容做近似查重。感知哈??梢运岩幌耰magehash這個(gè)Python庫接口很簡單。類別平衡是另一個(gè)重點(diǎn)。假設(shè)vehicle有10萬框、person有5萬框、animal只有1萬框直接訓(xùn)練會(huì)導(dǎo)致animal類召回很低。我的經(jīng)驗(yàn)是先做兩類處理數(shù)據(jù)層面對稀有類別做Mosaic增強(qiáng)、復(fù)制粘貼增強(qiáng)把稀有目標(biāo)貼到不同背景上。采樣層面訓(xùn)練時(shí)對稀有類別的圖片提高采樣概率比如通過repeats參數(shù)控制或者直接在數(shù)據(jù)集中重復(fù)稀有樣本若干份。需要說明的是Mosaic增強(qiáng)在YOLO訓(xùn)練里默認(rèn)開啟但它的目標(biāo)分布是隨機(jī)的不會(huì)專門照顧稀有類別。所以我更常做的是目標(biāo)級(jí)復(fù)制粘貼從一張圖里把動(dòng)物框摳出來貼到另一張沒有動(dòng)物的背景圖上同時(shí)寫入新標(biāo)注。這樣可以人為增加動(dòng)物樣本數(shù)量效果立竿見影。4. 數(shù)據(jù)質(zhì)量排查模型練不好的鍋多半在數(shù)據(jù)4.1 可視化抽檢和規(guī)則化篩查我每次拿到數(shù)據(jù)集不管來源靠不靠譜都會(huì)先做質(zhì)量排查。第一關(guān)是規(guī)則化篩查用腳本檢查標(biāo)注格式和法律不允許的錯(cuò)誤# 檢查txt文件中是否出現(xiàn)越界或負(fù)數(shù)等非法值 awk -F {if ($20 || $30 || $40 || $50 || $21 || $31 || $41 || $51) print FILENAME, $0} dataset/labels/train/*.txt上面這個(gè)命令能篩出明顯非法標(biāo)注。但更隱蔽的問題是標(biāo)注框內(nèi)容不進(jìn)比如框偏了、把車輛尾巴截掉了、行人被樹擋住一半但框畫過去了這些用規(guī)則檢查不出來。第二關(guān)我會(huì)用OpenCV寫一個(gè)可視化腳本隨機(jī)抽200張圖把GT框畫上去然后拼成網(wǎng)格圖快速瀏覽import cv2 import random import glob from pathlib import Path img_paths random.sample(glob.glob(dataset/images/train/*.jpg), 200) color_map {0: (0, 0, 255), 1: (0, 255, 0), 2: (255, 0, 0)} for img_path in img_paths: img cv2.imread(img_path) h, w img.shape[:2] txt_path str(Path(img_path).with_suffix(.txt)).replace(/images/, /labels/) if not Path(txt_path).exists(): print(f缺少標(biāo)注: {img_path}) continue with open(txt_path, r) as f: for line in f: cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color color_map.get(int(cls), (255, 255, 255)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, str(int(cls)), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(fcheck_{Path(img_path).stem}.jpg, img)拼圖雖然土但真的管用。肉眼看一遍能發(fā)現(xiàn)標(biāo)注規(guī)范不統(tǒng)一、框遺漏、類別錯(cuò)標(biāo)這些連規(guī)則腳本都查不出來的問題。4.2 動(dòng)物類別的特殊處理動(dòng)物檢測和車輛、行人有個(gè)明顯差異形態(tài)多變。車輛是剛體輪廓穩(wěn)定行人姿態(tài)有限但還算規(guī)律動(dòng)物從站、趴、跑到低頭吃草、跳躍輪廓變化范圍很大而且不同物種外觀差異也大。牛和車在某些歪斜角度下輪廓高度相似這是我在實(shí)際混淆矩陣?yán)镉H眼看過的錯(cuò)誤。動(dòng)物的另一個(gè)難點(diǎn)是夜間和低光照。夜間動(dòng)物的眼睛會(huì)反光但身體和背景經(jīng)常融為一體標(biāo)注難度比白天高很多。如果你要部署的場景包括夜間訓(xùn)練集里必須有足夠比例的夜間動(dòng)物樣本不要指望白天訓(xùn)練的模型在夜間泛化好。處理建議動(dòng)物類別盡量框住全身不要把頭和四肢拆開。如果動(dòng)物在圖像邊緣或被樹遮擋一半寧可多標(biāo)一個(gè)框也不要漏。因?yàn)槁?biāo)框等于教模型這個(gè)地方?jīng)]有動(dòng)物對訓(xùn)練是負(fù)向信號(hào)。4.3 類別不平衡的修正策略數(shù)據(jù)集里類別數(shù)量通常嚴(yán)重不均衡。我用過一個(gè)數(shù)據(jù)集vehicle框有8萬animal框只有2000直接訓(xùn)練出來的模型對animal類的mAP幾乎為零。這不是模型強(qiáng)度問題是數(shù)據(jù)問題。修正方法很常用但有幾個(gè)細(xì)節(jié)值得注意復(fù)制粘貼增強(qiáng)把某張圖里的動(dòng)物目標(biāo)摳出來旋轉(zhuǎn)、縮放、亮度調(diào)整后貼到不同背景圖上同時(shí)生成對應(yīng)標(biāo)注。這個(gè)操作簡單有效但要控制貼圖比例否則動(dòng)物目標(biāo)全是合成姿態(tài)和真實(shí)場景分布會(huì)有偏差。尺度重采樣很多動(dòng)物目標(biāo)本身就是小目標(biāo)比如遠(yuǎn)處牛羊群。如果用小尺度訓(xùn)練模型對小目標(biāo)不敏感。我建議對小目標(biāo)圖片做兩倍上采樣讓目標(biāo)在訓(xùn)練時(shí)占更多像素。Loss層面處理YOLOv8自帶類別損失權(quán)重但默認(rèn)所有類別權(quán)重相同。如果數(shù)據(jù)嚴(yán)重不平衡可以在損失函數(shù)中給低頻類別加權(quán)重或者在訓(xùn)練策略上多訓(xùn)練幾個(gè)epoch看早起val指標(biāo)變化決定是否調(diào)整。5. YOLOv8訓(xùn)練多目標(biāo)檢測模型的配置與調(diào)參5.1 數(shù)據(jù)目錄和YAML配置數(shù)據(jù)準(zhǔn)備好后就要把它喂給YOLOv8。首先要保證數(shù)據(jù)目錄結(jié)構(gòu)清晰然后寫data.yaml配置文件。# data.yaml train: dataset/images/train val: dataset/images/val nc: 3 names: 0: vehicle 1: person 2: animal這里需要注意train和val的路徑要用絕對路徑或者相對于你執(zhí)行訓(xùn)練命令的當(dāng)前目錄。項(xiàng)目換機(jī)器后路徑會(huì)變用相對路徑、在項(xiàng)目根目錄統(tǒng)一執(zhí)行是最省心的做法。5.2 訓(xùn)練參數(shù)與資源預(yù)算YOLOv8的訓(xùn)練命令很簡單但參數(shù)選擇直接影響最終效果。我一般這樣起步y(tǒng)olo detect train \ datadata.yaml \ modelyolov8m.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience20幾個(gè)關(guān)鍵參數(shù)的選擇imgsz默認(rèn)640。如果小目標(biāo)多建議變成1280但顯存和訓(xùn)練時(shí)間會(huì)翻倍。我自己的做法是先用640跑通流程再根據(jù)bad case決定要不要換1280。batch按顯存調(diào)整。12G顯存跑yolov8m、imgsz640batch設(shè)為16比較穩(wěn)。如果OOM降到8或者開啟梯度累積但YOLOv8的梯度累積參數(shù)比較繞簡單起見直接降batch。model從yolov8s或yolov8m起步。數(shù)據(jù)量小、目標(biāo)簡單用yolov8s數(shù)據(jù)量大、目標(biāo)復(fù)雜用yolov8m或yolov8l。如果追求極致速度最后再蒸餾到y(tǒng)olov8n做部署。optimizer默認(rèn)AdamW數(shù)據(jù)量大的時(shí)候換成SGD效果也不差但收斂速度會(huì)慢一些。訓(xùn)練過程中還要注意mosaic增強(qiáng)的關(guān)閉時(shí)機(jī)。YOLOv8默認(rèn)訓(xùn)練最后10個(gè)epoch會(huì)關(guān)閉Mosaic這是為了保證模型在正常圖片分布上做收斂。如果發(fā)現(xiàn)驗(yàn)證集loss反彈可以嘗試手動(dòng)提前關(guān)。5.3 訓(xùn)練日志的診斷思路訓(xùn)練跑起來之后不要只看進(jìn)度條。我習(xí)慣實(shí)時(shí)盯著results.csv里的列YOLOv8每輪會(huì)記錄box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95這些指標(biāo)。幾個(gè)常見情況我來幫你排查如果box_loss和cls_loss都在降但mAP50長時(shí)間不升可能是類別標(biāo)注本身有噪聲或者標(biāo)注框位置不準(zhǔn)?;氐降?章做可視化檢查。如果val loss在epoch 60后持續(xù)上升train loss還在降過擬合了。減少epoch或者打開更大的數(shù)據(jù)增強(qiáng)比如增強(qiáng)augment參數(shù)、調(diào)大hsv、degrees。如果loss出現(xiàn)NaN多半是學(xué)習(xí)率太大導(dǎo)致梯度爆炸。把lr0從默認(rèn)值調(diào)小或者降低batch。如果animal類的mAP明顯低于其他類數(shù)據(jù)量不足先做第3章的平衡處理不要盲目加訓(xùn)練輪次。訓(xùn)練日志建議保留CSV這樣每次調(diào)參都能對比。YOLOv8會(huì)在runs/detect/train目錄下自動(dòng)生成results.csv不用自己寫日志。6. 評估與迭代別只看mAP6.1 分類別評估和混淆矩陣怎么用訓(xùn)練完模型第一件事是用驗(yàn)證集評估yolo detect val \ datadata.yaml \ modelruns/detect/train/weights/best.pt \ splitval評估結(jié)果會(huì)自動(dòng)生成confusion_matrix.png和results.png。我建議把總mAP放在一邊先看分類別結(jié)果。只看總mAP很容易被數(shù)量占優(yōu)的類別帶偏動(dòng)物類只有2000個(gè)框、vehicle有8萬個(gè)animal的mAP再低總mAP也不會(huì)太難看。混淆矩陣才是真正有價(jià)值的東西。我遇到過兩個(gè)典型案例牛和SUV互相誤檢主要是訓(xùn)練數(shù)據(jù)里牛的側(cè)面輪廓和SUV的車身側(cè)面太像。解決方法不是增加數(shù)據(jù)增強(qiáng)而是單獨(dú)收集牛的正面、背面、側(cè)躺等不同姿態(tài)樣本專門解決混淆。騎自行車的人被拆成兩個(gè)框或漏檢標(biāo)注規(guī)范里如果沒把騎行者框成personvehicle模型會(huì)學(xué)得非?;靵y。處理辦法是統(tǒng)一標(biāo)注規(guī)范騎行者一定是person框自行車單獨(dú)標(biāo)vehicle。6.2 bad case反哺數(shù)據(jù)的完整路徑評估完之后要系統(tǒng)地分析bad case而不是一個(gè)個(gè)看。我的做法是用驗(yàn)證集跑一次推理保存所有預(yù)測置信度低于閾值但GT存在的圖片。按錯(cuò)誤類型聚類大致分類為小目標(biāo)漏檢遮擋漏檢形變誤檢背景誤檢。針對最嚴(yán)重的錯(cuò)誤類型去補(bǔ)數(shù)據(jù)。是小目標(biāo)多就補(bǔ)小目標(biāo)圖片是夜間差就補(bǔ)夜間圖片是動(dòng)物姿態(tài)多樣就補(bǔ)動(dòng)物圖片。這個(gè)循環(huán)看起來慢但每輪都能把模型往實(shí)際場景推進(jìn)一步。單純增加訓(xùn)練輪次解決不了bad case問題。6.3 部署階段的量化與選型建議訓(xùn)練調(diào)優(yōu)結(jié)束后模型要落地部署邊緣設(shè)備上一般用TensorRT或ONNX Runtime。YOLOv8可以很方便導(dǎo)出yolo export modelbest.pt formatonnx opset12 yolo export modelbest.pt formatengine device0導(dǎo)出engine之前通常會(huì)先做FP16量化精度幾乎不掉但速度提升明顯。如果追求極致速度可以做INT8量化但I(xiàn)NT8對動(dòng)物這種細(xì)節(jié)豐富的小目標(biāo)不太友好我個(gè)人建議先FP16。部署端記得把輸入尺寸固定住動(dòng)態(tài)分辨率會(huì)拖慢推理。在實(shí)測部署時(shí)還要注意類別輸出排序和前后處理的尺寸對齊。YOLOv8輸出的檢測結(jié)果格式在不同版本之間有細(xì)微差別導(dǎo)成engine后建議先跑一張測試圖把框畫出來和原圖對比確認(rèn)坐標(biāo)沒有被拉伸變形。最后再分享一個(gè)我自己一直保留的習(xí)慣每次拿到新的數(shù)據(jù)集先寫一個(gè)統(tǒng)一的處理腳本把目錄結(jié)構(gòu)、類別映射、格式轉(zhuǎn)換、質(zhì)量篩查全部固化下來。這樣即使以后換了一臺(tái)機(jī)器、換了一個(gè)項(xiàng)目只要數(shù)據(jù)和腳本都在半天之內(nèi)就能復(fù)現(xiàn)整套流程。另外數(shù)據(jù)集的標(biāo)注腳本和類別映射表一定要保留尤其是多源合并后的映射關(guān)系過兩周再看就很容易忘那時(shí)候再想追溯某個(gè)框到底是從哪個(gè)數(shù)據(jù)集來的就要花幾倍的時(shí)間。訓(xùn)練前花兩小時(shí)做可視化抽檢遠(yuǎn)比訓(xùn)練后發(fā)現(xiàn)bad case再返工劃算。本文還有配套的精品資源點(diǎn)擊獲取