據(jù)集從驗(yàn)貨到訓(xùn)練:7203張帶標(biāo)簽圖像完整避坑指南)
簡介面向yolo系列算法目標(biāo)檢測(cè)任務(wù)的行人數(shù)據(jù)集已劃分訓(xùn)練/驗(yàn)證/測(cè)試集并附帶data.yaml配置文件可無縫適配yolov5、yolov7、yolov8、yolov9、yolov10及yolo11等常見框架。標(biāo)簽同時(shí)提供yolo格式與voc格式分別存放于獨(dú)立文件夾中其中yolo格式采用歸一化中心點(diǎn)與寬高坐標(biāo)便于直接訓(xùn)練voc格式則適合在標(biāo)注工具或遷移場景中使用。壓縮包共2000個(gè)文件以XML標(biāo)注文件為主整體大小約233.76MB目錄結(jié)構(gòu)清晰免去自行整理標(biāo)簽、劃分?jǐn)?shù)據(jù)及編寫配置的步驟適合計(jì)算機(jī)視覺初學(xué)者快速上手行人檢測(cè)也適合研究者對(duì)比不同yolo版本的訓(xùn)練效果。已有138人學(xué)習(xí)下載是一份實(shí)用的目標(biāo)檢測(cè)入門訓(xùn)練數(shù)據(jù)。1. 7203張帶標(biāo)簽的行人數(shù)據(jù)集到底值不值得下做目標(biāo)檢測(cè)的工程師尤其是剛把YOLO環(huán)境配置跑通的那批人最??ㄗ〉牡胤讲皇悄P驼{(diào)參而是手里沒有一份能直接開訓(xùn)的數(shù)據(jù)。自己拿labelImg一張張框框到第200張就想砸電腦去網(wǎng)上找開源數(shù)據(jù)集要么類別太雜要么標(biāo)注格式要轉(zhuǎn)半天。這個(gè)標(biāo)題里給出的“yolo算法-行人數(shù)據(jù)集-7203張圖像帶標(biāo)簽”就是沖著這個(gè)痛點(diǎn)來的圖像數(shù)量固定、標(biāo)簽齊全、壓縮包直接下載理論上解壓就能往YOLO的訓(xùn)練腳本里喂。但我要先潑一盆冷水“帶標(biāo)簽”這三個(gè)字在真實(shí)數(shù)據(jù)集里含義差別很大。有的包給的是YOLO格式的txt有的給的是VOC的xml還有的只給一個(gè)csv甚至標(biāo)簽內(nèi)容跟圖像內(nèi)容對(duì)不上。所以這篇筆記不打算替這個(gè)包背書而是給你一套拿到手之后從頭驗(yàn)貨、清洗、轉(zhuǎn)換到真正跑通訓(xùn)練的完整流程。無論這個(gè)壓縮包里的標(biāo)注是哪種格式、類別是“人”還是“主人”你都能量一遍它的底細(xì)再?zèng)Q定它值不值得占用你的硬盤和顯卡。適合誰看正在做行人檢測(cè)入門、準(zhǔn)備給項(xiàng)目找一份干凈數(shù)據(jù)集的工程師以及想把一張張零散圖片整理成可訓(xùn)練資產(chǎn)的初學(xué)者。2. 從壓縮包到可訓(xùn)練數(shù)據(jù)集先做三個(gè)驗(yàn)證再談?dòng)?xùn)練2.1 文件結(jié)構(gòu)摸底別急著解壓先看目錄樹拿到“yolo算法-行人數(shù)據(jù)集-7203張圖像帶標(biāo)簽-人-主人.zip”這個(gè)文件第一件事不是雙擊解壓而是先看它的內(nèi)部結(jié)構(gòu)。很多數(shù)據(jù)集翻車都翻在路徑層級(jí)上——你以為解壓出來就是images和labels兩個(gè)平級(jí)目錄結(jié)果里面嵌套了三層文件夾訓(xùn)練腳本一跑就報(bào)找不到圖片。我一般會(huì)先在終端里列一下壓縮包內(nèi)容確認(rèn)目錄形態(tài)后再解壓# 查看壓縮包內(nèi)部結(jié)構(gòu)不直接解壓 unzip -l yolo算法-行人數(shù)據(jù)集-7203張圖像帶標(biāo)簽-人-主人.zip | head -50 # 統(tǒng)計(jì)各類文件數(shù)量確認(rèn)圖像和標(biāo)簽的對(duì)應(yīng)關(guān)系 unzip -l yolo算法-行人數(shù)據(jù)集-7203張圖像帶標(biāo)簽-人-主人.zip | awk {print $4} | grep -E \.(jpg|jpeg|png|txt|xml)$ | sed s/.*\.// | sort | uniq -c第一條命令只看前50行目的是摸清目錄層級(jí)。第二條命令統(tǒng)計(jì)所有圖像和標(biāo)簽文件的擴(kuò)展名數(shù)量這樣你立刻知道包里是jpg還是png、標(biāo)簽是txt還是xml。如果標(biāo)簽文件數(shù)量和圖像數(shù)量差很多說明有漏標(biāo)后續(xù)訓(xùn)練要小心如果標(biāo)簽是xml先別急后面我教你怎么轉(zhuǎn)成YOLO能直接讀的txt。提示如果壓縮包是rar格式unzip命令會(huì)失效Windows下可以用WinRAR或7-Zip先轉(zhuǎn)成zip再處理Linux下裝unrar即可對(duì)應(yīng)解壓。這個(gè)摸底過程看起來瑣碎但能省掉后面至少半小時(shí)的排錯(cuò)時(shí)間。另一個(gè)常見情況是壓縮包內(nèi)有頂層文件夾比如“dataset/”或“行人數(shù)據(jù)集/”這時(shí)候解壓后要記得把路徑層級(jí)調(diào)整成YOLO訓(xùn)練腳本默認(rèn)的形態(tài)——通常是一個(gè)根目錄下并列images和labels。不要小看這一步Y(jié)OLO系列對(duì)數(shù)據(jù)目錄的約定非常嚴(yán)格。2.2 圖像與標(biāo)簽是否一一對(duì)應(yīng)寫個(gè)腳本做交叉檢查解壓完成后最大的坑是“有圖無標(biāo)簽”或“有標(biāo)簽無圖”。很多人拿到數(shù)據(jù)集直接開訓(xùn)訓(xùn)到一半發(fā)現(xiàn)損失函數(shù)異常回頭排查才發(fā)現(xiàn)是數(shù)據(jù)加載階段就丟了樣本。這一步必須用腳本硬校驗(yàn)不能用眼睛掃。import os from pathlib import Path # 請(qǐng)按實(shí)際解壓路徑修改 image_dir Path(./dataset/images) label_dir Path(./dataset/labels) # 提取不帶擴(kuò)展名的文件名集合 image_stems {p.stem for p in image_dir.iterdir() if p.suffix.lower() in [.jpg, .jpeg, .png, .bmp]} label_stems {p.stem for p in label_dir.iterdir() if p.suffix.lower() in [.txt, .xml]} # 找出缺失項(xiàng) missing_labels image_stems - label_stems missing_images label_stems - image_stems print(f圖像數(shù)量: {len(image_stems)}) print(f標(biāo)簽數(shù)量: {len(label_stems)}) print(f缺標(biāo)簽的圖像: {len(missing_labels)}) print(f缺圖像的標(biāo)簽: {len(missing_images)}) # 抽樣展示前5個(gè)缺標(biāo)簽的文件名如果有 if missing_labels: print(缺標(biāo)簽示例:, list(missing_labels)[:5])這段腳本的邏輯很直白把images目錄下所有圖像的文件名去掉擴(kuò)展名和labels目錄下所有標(biāo)簽的文件名分別取集合然后做差集。兩邊嚴(yán)格相等才算數(shù)據(jù)對(duì)齊。對(duì)不上的樣本分兩種情況處理——缺標(biāo)簽的直接刪掉圖像缺圖像的刪除孤立標(biāo)簽因?yàn)檫@種樣本喂進(jìn)網(wǎng)絡(luò)只會(huì)讓訓(xùn)練過程更糟?,F(xiàn)實(shí)里的數(shù)據(jù)集很少能一次通過這個(gè)檢查。有的包會(huì)把標(biāo)注文件放在子目錄里有的是“每一張圖像配一個(gè)同名txt”但txt里內(nèi)容是空的這種也要在交叉檢查階段識(shí)別出來。空標(biāo)簽文件不算有效標(biāo)注后面訓(xùn)練時(shí)YOLO會(huì)跳過造成“看似有標(biāo)簽、實(shí)際沒參與訓(xùn)練”的假象。2.3 標(biāo)簽內(nèi)容解析類別數(shù)、標(biāo)注框數(shù)量、格式判定交叉檢查通過之后還要確認(rèn)標(biāo)簽內(nèi)容本身。YOLO格式的txt每一行代表一個(gè)目標(biāo)格式是“class_id x_center y_center width height”注意這里的坐標(biāo)是歸一化后的值范圍在0~1之間。VOC格式的xml則是像素坐標(biāo)的絕對(duì)值和類別名。兩種格式差別巨大混用的話訓(xùn)練直接報(bào)錯(cuò)。from collections import Counter import os label_dir ./dataset/labels label_format None # 自動(dòng)判斷txt還是xml box_count Counter() # 統(tǒng)計(jì)每個(gè)類別的樣本數(shù) empty_files [] # 記錄空標(biāo)簽文件 for root, dirs, files in os.walk(label_dir): for fname in files: path os.path.join(root, fname) if fname.endswith(.txt): label_format txt with open(path, r, encodingutf-8, errorsignore) as f: lines [line.strip() for line in f.readlines() if line.strip()] if not lines: empty_files.append(fname) continue for line in lines: parts line.split() # 兼容逗號(hào)分割的情況 if len(parts) 1 and , in line: parts line.replace(,, ).split() if len(parts) 5: class_id int(parts[0]) box_count[class_id] 1 elif fname.endswith(.xml): label_format xml # xml解析需要引入ElementTree這里只做計(jì)數(shù)占位 box_count[xml_待解析] 1 print(f標(biāo)簽格式: {label_format}) print(f各類別目標(biāo)數(shù)量(前10): {box_count.most_common(10)}) if empty_files: print(f空標(biāo)簽文件數(shù)量: {len(empty_files)}) print(空標(biāo)簽示例:, empty_files[:5])這個(gè)腳本幫你在訓(xùn)練之前就摸清楚這個(gè)數(shù)據(jù)集到底標(biāo)注了“人”和“主人”兩個(gè)類別還是只有“person”一個(gè)類別。類別ID從0開始計(jì)數(shù)如果txt里的class_id是1而不是0訓(xùn)練時(shí)很容易錯(cuò)位。如果標(biāo)簽里class_id范圍超過了類別列表長度YOLO訓(xùn)練會(huì)直接報(bào)index out of bounds這一步提前發(fā)現(xiàn)就能省下大量排查時(shí)間。類別不均衡是另一個(gè)值得關(guān)注的問題。標(biāo)題里“人-主人”這種寫法如果真的是兩個(gè)類別那么“主人”的樣本數(shù)很可能只有“人”的零頭。檢測(cè)模型對(duì)類別數(shù)量少的類會(huì)嚴(yán)重欠擬合后面訓(xùn)練之前要做過采樣或類別加權(quán)我在第4章里會(huì)給出具體方案。3. 行人數(shù)據(jù)集在YOLO訓(xùn)練中的定位場景與局限性3.1 7203張圖像是什么量級(jí)對(duì)比COCO和VOC剛?cè)腴T的同學(xué)聽到“7203張圖像”可能覺得很多但放到目標(biāo)檢測(cè)任務(wù)的上下文里這個(gè)量級(jí)屬于小而精的數(shù)據(jù)集。COCO數(shù)據(jù)集有超過33萬張圖像VOC20072012合計(jì)也有2萬多張。7203張圖像放在通用目標(biāo)檢測(cè)里不夠看但放在單一類別行人檢測(cè)里配合合理的遷移學(xué)習(xí)策略完全夠用前提是圖像場景差異夠大。關(guān)鍵是看這7203張圖的來源。如果圖像是從監(jiān)控視頻里抽幀得到的那么相鄰幀之間高度相似實(shí)際有效信息量可能只有2000張的水平如果是網(wǎng)絡(luò)爬蟲采集的多樣場景那么這7203張能覆蓋各種光照、姿態(tài)和遮擋情況。拿到數(shù)據(jù)后我一般會(huì)隨機(jī)抽個(gè)100張圖出來看一遍重點(diǎn)觀察場景多樣性這比看任何統(tǒng)計(jì)數(shù)字都直觀。對(duì)于“yolo預(yù)訓(xùn)練模型下載”這個(gè)熱搜詞背后的需求這里要說明白了7203張圖像訓(xùn)練出來的模型精度上限取決于預(yù)訓(xùn)練權(quán)重的質(zhì)量和訓(xùn)練策略。用Pascal VOC上預(yù)訓(xùn)練好的YOLO權(quán)重做遷移學(xué)習(xí)比從零訓(xùn)練效果好得多因?yàn)樾腥藱z測(cè)本質(zhì)上是在通用特征上加一層“人”的語義理解。冷啟動(dòng)訓(xùn)練在7203張圖上大概率過擬合這是數(shù)據(jù)量決定的不是你的調(diào)參水平問題。3.2 “人”與“主人”的類別定義語義歧義怎么處理標(biāo)題里的“人-主人”這個(gè)寫法有點(diǎn)曖昧。如果原數(shù)據(jù)集是在“主人遛狗”的場景下采集的那么“主人”可能是指牽著寵物的人語義與“人”重疊會(huì)造成類別邊界模糊。這種標(biāo)簽在訓(xùn)練時(shí)是最麻煩的因?yàn)橥粋€(gè)目標(biāo)在不同圖像里可能被標(biāo)成不同的class_id模型學(xué)到的特征會(huì)混亂。處理這類歧義有兩條路第一條是合并類別把“人”和“主人”統(tǒng)一成一個(gè)class犧牲粒度換穩(wěn)定第二條是檢查所有標(biāo)注框按圖像上下文重新劃分這個(gè)工作量在7203張圖上是幾十個(gè)小時(shí)除非你已經(jīng)確定用途否則不值得動(dòng)手。我傾向于先按原標(biāo)簽訓(xùn)一個(gè)版本看效果如果驗(yàn)證集的混淆矩陣?yán)镞@兩個(gè)類互相串再回頭做合并。還有一層要留意“人”這個(gè)類別在不同數(shù)據(jù)集中定義不一樣。COCO里person只指一個(gè)人但有些數(shù)據(jù)集會(huì)把人群、人群中的部分人體也算成person。這個(gè)包的標(biāo)注是否遵循“一個(gè)框一個(gè)人”的約定直接決定模型在密集場景下的表現(xiàn)——如果標(biāo)注框把三五個(gè)人圈在一起模型學(xué)出來的是“一群人”而不是“行人”。3.3 在YOLO預(yù)訓(xùn)練模型下載與數(shù)據(jù)集之間的選擇邏輯很多人搜“yolo預(yù)訓(xùn)練模型下載”時(shí)其實(shí)真正要找的不是模型而是一個(gè)完整的起步方案。預(yù)訓(xùn)練模型給出的是網(wǎng)絡(luò)的初始權(quán)重訓(xùn)練賦予它針對(duì)你手頭數(shù)據(jù)集的能力兩者是一個(gè)整體。如果只有7203張行人圖像但沒有合理的預(yù)訓(xùn)練權(quán)重訓(xùn)練時(shí)間會(huì)拉長最終精度也會(huì)受限。我的建議是優(yōu)先使用YOLOv8或YOLOv5官方發(fā)布的COCO預(yù)訓(xùn)練權(quán)重作為起點(diǎn)哪怕你的目標(biāo)類別和COCO不一致也沒關(guān)系。原因很簡單卷積神經(jīng)網(wǎng)絡(luò)的前幾層學(xué)的是通用特征邊緣、紋理、形狀這些特征與行人檢測(cè)是共享的只有最后幾層需要針對(duì)你的類別微調(diào)。用COCO預(yù)訓(xùn)練權(quán)重初始化相當(dāng)于站在別人的肩膀上做遷移訓(xùn)練收斂速度快很多。但要注意一個(gè)“yolo訓(xùn)練中bn崩潰”的熱搜詞恰好在這里高發(fā)預(yù)訓(xùn)練權(quán)重的BN層的均值方差是針對(duì)COCO數(shù)據(jù)的如果新數(shù)據(jù)集的分布差距很大前幾輪訓(xùn)練里BN統(tǒng)計(jì)量會(huì)出現(xiàn)劇烈波動(dòng)表現(xiàn)為損失震蕩甚至爆成NaN。解決方式有幾種我在第6章展開講先記住核心原則——用預(yù)訓(xùn)練權(quán)重訓(xùn)練時(shí)學(xué)習(xí)率不能太大否則BN層容易翻車。4. 從原始標(biāo)注到Y(jié)OLO訓(xùn)練集三步預(yù)處理完整路徑4.1 標(biāo)注格式轉(zhuǎn)換把VOC XML轉(zhuǎn)成YOLO TXT如果標(biāo)簽文件是xml你需要做的第一件事就是把VOC格式的像素坐標(biāo)轉(zhuǎn)換成YOLO需要的歸一化坐標(biāo)。轉(zhuǎn)換公式看起來簡單但實(shí)現(xiàn)時(shí)有兩個(gè)容易出錯(cuò)的點(diǎn)一是類別ID要從0開始VOC里可能從1開始二是width和height除以圖像寬高時(shí)分母搞錯(cuò)會(huì)讓整個(gè)標(biāo)注框偏移。import xml.etree.ElementTree as ET import os from pathlib import Path def convert_voc_xml_to_yolo(xml_path, output_dir, class_names, image_width, image_height): 單張VOC XML轉(zhuǎn)YOLO TXT class_names: 類別名列表下標(biāo)即class_id tree ET.parse(xml_path) root tree.getroot() # 從XML里讀取圖像尺寸優(yōu)先于外部傳入值 size root.find(size) if size is not None: image_width int(size.find(width).text) image_height int(size.find(height).text) yolo_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue # 跳過未定義類別 class_id class_names.index(name) bndbox obj.find(bndbox) x_min float(bndbox.find(xmin).text) y_min float(bndbox.find(ymin).text) x_max float(bndbox.find(xmax).text) y_max float(bndbox.find(ymax).text) # 邊界保護(hù)防止坐標(biāo)越界 x_min max(0, min(x_min, image_width - 1)) x_max max(0, min(x_max, image_width - 1)) y_min max(0, min(y_min, image_height - 1)) y_max max(0, min(y_max, image_height - 1)) # 計(jì)算中心坐標(biāo)和寬高并歸一化 box_width x_max - x_min box_height y_max - y_min x_center (x_min x_max) / 2.0 / image_width y_center (y_min y_max) / 2.0 / image_height box_width / image_width box_height / image_height yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) # 輸出txt文件名與原xml同名 output_path Path(output_dir) / (Path(xml_path).stem .txt) with open(output_path, w, encodingutf-8) as f: f.write(\n.join(yolo_lines)) # 使用示例 class_names [person, owner] # 按你的數(shù)據(jù)集實(shí)際類別調(diào)整 xml_dir ./dataset/annotations yolo_dir ./dataset/labels os.makedirs(yolo_dir, exist_okTrue) for xml_file in Path(xml_dir).glob(*.xml): convert_voc_xml_to_yolo(str(xml_file), yolo_dir, class_names, 0, 0)這個(gè)轉(zhuǎn)換腳本的核心邏輯在坐標(biāo)縮放那一行x_center用圖像寬度做分母y_center用圖像高度做分母兩者不能互換。很多開源轉(zhuǎn)換腳本偷懶默認(rèn)所有圖像都是同一個(gè)尺寸但如果你的數(shù)據(jù)集里有不同分辨率的圖像分母錯(cuò)了整個(gè)標(biāo)注框就會(huì)偏移。腳本里優(yōu)先從XML讀取size標(biāo)簽就是為了避免手寫分辨率導(dǎo)致的踩坑。4.2 數(shù)據(jù)集劃分腳本訓(xùn)練集/驗(yàn)證集/測(cè)試集比例與隨機(jī)種子數(shù)據(jù)集劃分是最簡單但也最容易出問題的一步。很多人直接用了shutil把文件復(fù)制到不同目錄結(jié)果驗(yàn)證集和訓(xùn)練集的文件夾里出現(xiàn)了同名文件互相覆蓋。正確做法是用shutil.move或者在復(fù)制前檢查目標(biāo)路徑是否已存在。另外要保證劃分的隨機(jī)性加random.seed讓結(jié)果可復(fù)現(xiàn)否則每次跑出來的模型評(píng)估結(jié)果都不一樣。import random import shutil from pathlib import Path random.seed(42) # 固定隨機(jī)種子保證可復(fù)現(xiàn) image_dir Path(./dataset/images) label_dir Path(./dataset/labels) train_img_dir Path(./dataset/train/images) train_lbl_dir Path(./dataset/train/labels) val_img_dir Path(./dataset/val/images) val_lbl_dir Path(./dataset/val/labels) # 創(chuàng)建目錄結(jié)構(gòu) for d in [train_img_dir, train_lbl_dir, val_img_dir, val_lbl_dir]: d.mkdir(parentsTrue, exist_okTrue) image_files list(image_dir.glob(*.jpg)) list(image_dir.glob(*.png)) random.shuffle(image_files) # 按 8:2 劃分 val_ratio 0.2 val_count int(len(image_files) * val_ratio) val_images image_files[:val_count] train_images image_files[val_count:] def move_sample(image_path: Path, target_img_dir: Path, target_lbl_dir: Path, label_dir: Path): 移動(dòng)圖像和對(duì)應(yīng)的標(biāo)簽文件到目標(biāo)目錄 dest_img target_img_dir / image_path.name if dest_img.exists(): print(f警告: {dest_img} 已存在跳過) return shutil.move(str(image_path), str(dest_img)) # 對(duì)應(yīng)的標(biāo)簽文件同stem擴(kuò)展名不同 label_file label_dir / (image_path.stem .txt) if label_file.exists(): shutil.move(str(label_file), str(target_lbl_dir / label_file.name)) else: print(f警告: {image_path.name} 沒有對(duì)應(yīng)標(biāo)簽已移動(dòng)圖像但標(biāo)簽缺失) # 執(zhí)行劃分 for img in train_images: move_sample(img, train_img_dir, train_lbl_dir, label_dir) for img in val_images: move_sample(img, val_img_dir, val_lbl_dir, label_dir) print(f訓(xùn)練集: {len(train_images)} 張) print(f驗(yàn)證集: {len(val_images)} 張)這段腳本會(huì)按8:2比例把所有樣本拆成訓(xùn)練集和驗(yàn)證集并自動(dòng)把標(biāo)簽文件一起移動(dòng)。劃分完成后建議再跑一次第2章的交叉檢查腳本確認(rèn)兩邊目錄里的樣本仍然一一對(duì)應(yīng)。這里有個(gè)隱藏雷點(diǎn)如果圖像是jpg但標(biāo)簽是txt直接用stem拼接沒問題但如果圖像是jpg、標(biāo)簽是xml你需要先把xml轉(zhuǎn)換完再劃分否則會(huì)移動(dòng)不到標(biāo)簽。4.3 數(shù)據(jù)集YAML配置與YOLO環(huán)境配置要點(diǎn)YOLO系列訓(xùn)練前都要一個(gè)data.yaml文件來描述數(shù)據(jù)集信息。文件里只需要三個(gè)路徑、一個(gè)類別列表但路徑寫法經(jīng)常有人出錯(cuò)。YOLOv5/v8要求相對(duì)路徑是相對(duì)于yaml文件所在目錄的不能寫絕對(duì)路徑否則換臺(tái)機(jī)器就要改配置。# dataset.yaml 示例配置 path: ./dataset # 數(shù)據(jù)集根目錄相對(duì)路徑 train: train/images # 訓(xùn)練圖像目錄 val: val/images # 驗(yàn)證圖像目錄 nc: 2 # 類別數(shù)量person owner 共2類 names: 0: person 1: owner這個(gè)配置文件的寫法要注意path字段指向數(shù)據(jù)集根目錄train和val是相對(duì)path的路徑。如果訓(xùn)練腳本找不到數(shù)據(jù)集先看終端里打印的絕對(duì)路徑再回去檢查path是不是寫對(duì)了。有些時(shí)候YOLO會(huì)默認(rèn)在yaml文件所在目錄下拼接path如果yaml放在另一個(gè)位置相對(duì)路徑就會(huì)錯(cuò)位。我習(xí)慣把yaml文件直接放在數(shù)據(jù)集根目錄下這樣path字段填一個(gè)小數(shù)點(diǎn)就行減少路徑拼接的心智負(fù)擔(dān)。YOLO環(huán)境配置反而是最不該焦慮的一步。常見的“yolo環(huán)境配置”熱搜詞其實(shí)大部分問題集中在PyTorch版本與CUDA版本不匹配。跑檢測(cè)訓(xùn)練不是煉丹CUDA 11.8配PyTorch 2.0算是保險(xiǎn)組合。如果你用的是yolo的pip安裝包它會(huì)自動(dòng)裝好對(duì)應(yīng)依賴手動(dòng)裝PyTorch反而容易翻車別動(dòng)那個(gè)依賴版本就好。5. YOLO訓(xùn)練行人數(shù)據(jù)集的必調(diào)參數(shù)與訓(xùn)練策略5.1 必改的三個(gè)超參數(shù)imgsz、batch、epochs遷移學(xué)習(xí)場景里YOLO默認(rèn)的推理尺寸是640x640但行人檢測(cè)任務(wù)里目標(biāo)往往比較瘦長而且圖像里的行人高度占比差異很大。保留默認(rèn)的640通常沒問題但如果你訓(xùn)練集里行人大多數(shù)是小目標(biāo)可以考慮把imgsz降到416或512小尺寸輸入在同等顯存下能塞進(jìn)更大的batch訓(xùn)練迭代更快小目標(biāo)的召回率反而會(huì)更高。batch大小受顯存限制。以一張12GB顯存的RTX 3060為例YOLOv8m在640分辨率下batch最多開到8左右但如果調(diào)小imgsz到416batch可以開到16。訓(xùn)練epochs數(shù)不要盲目套用官方默認(rèn)的3007203張圖像的數(shù)據(jù)規(guī)模下從COCO預(yù)訓(xùn)練權(quán)重開始微調(diào)100到150個(gè)epoch就夠收斂了。epochs設(shè)太大容易過擬合尤其是類別不平衡的“主人”這類少數(shù)類。# YOLOv8 訓(xùn)練命令示例Ultralytics版 yolo train datadv.yaml modelyolov8n.pt epochs120 imgsz640 batch16 device0 patience20 # YOLOv5 訓(xùn)練命令示例 python train.py --data dv.yaml --weights yolov5s.pt --epochs 120 --batch-size 16 --img 640 --patience 20命令里的patience參數(shù)是早停輪數(shù)驗(yàn)證集指標(biāo)連續(xù)20個(gè)epoch不提升就自動(dòng)停止訓(xùn)練這個(gè)參數(shù)能省時(shí)間也能防過擬合。device0指定第一張GPU沒有GPU就用devicecpu但120epochs會(huì)跑很久建議有預(yù)算就租云GPU。5.2 類別不均衡損失調(diào)整從默認(rèn)loss到Focal Loss如果第2章統(tǒng)計(jì)發(fā)現(xiàn)“主人”類只有幾百個(gè)標(biāo)注框而“人”類有近萬框直接用默認(rèn)損失函數(shù)訓(xùn)練模型會(huì)把“主人”直接學(xué)成背景。YOLOv8的loss本身帶了focal損失分支能緩解正負(fù)樣本不均衡但對(duì)類別間不均衡幫助有限。更直接的辦法是在自定義訓(xùn)練腳本里改class weights。# 修改YOLOv8訓(xùn)練數(shù)據(jù)加載器的類別權(quán)重 from ultralytics.utils.loss import BCEWithLogitsLoss class WeightedBCEWithLogitsLoss(BCEWithLogitsLoss): def __init__(self, pos_weight): super().__init__() self.pos_weight pos_weight # 每個(gè)類別的正樣本權(quán)重 # 假設(shè)person: 8000個(gè)框, owner: 400個(gè)框 # 權(quán)重反比owner權(quán)重 8000/400 20 pos_weight torch.tensor([1.0, 20.0], devicecuda) criterion WeightedBCEWithLogitsLoss(pos_weight)改loss后的訓(xùn)練會(huì)比默認(rèn)慢一點(diǎn)但少數(shù)類的召回率往往能提升10個(gè)點(diǎn)以上。如果沒有精力改代碼也可以用過采樣來近似把“主人”類的圖像復(fù)制三到五份再放進(jìn)訓(xùn)練集這個(gè)策略在YOLO的訓(xùn)練數(shù)據(jù)配置階段就能做代價(jià)是訓(xùn)練會(huì)變慢但邏輯更簡單不容易改崩。5.3 yolo損失函數(shù)和訓(xùn)練日志怎么盯Loss曲線與mAP的配合訓(xùn)練不是把命令跑起來就可以去睡覺。你需要學(xué)會(huì)盯日志。YOLOv8訓(xùn)練時(shí)每輪都會(huì)輸出box_loss、cls_loss、dfl_loss以及precision、recall、mAP50等指標(biāo)這些指標(biāo)的意義分別是box_loss收斂說明框回歸穩(wěn)定cls_loss收斂說明分類沒問題mAP50從0跳到0.8以上訓(xùn)練基本成功。比較常見的現(xiàn)象是loss在下降但mAP50一直很低這說明模型雖然在擬合訓(xùn)練集但驗(yàn)證集上效果差——過擬合信號(hào)。另一個(gè)現(xiàn)象是loss直接NaN多半是學(xué)習(xí)率太大或數(shù)據(jù)里有損壞圖像損壞圖像的特征會(huì)在預(yù)處理階段傳個(gè)空數(shù)組給網(wǎng)絡(luò)YOLO對(duì)這類輸入不會(huì)報(bào)錯(cuò)只會(huì)靜默產(chǎn)生NaN。我常用的觀察方法是每訓(xùn)練25個(gè)epoch保存一次驗(yàn)證集預(yù)測(cè)結(jié)果圖直接看預(yù)測(cè)框和真實(shí)框的貼合程度。指標(biāo)會(huì)騙人但可視化不會(huì)。如果mAP50已經(jīng)0.85了但預(yù)測(cè)框在密集人群中互相重疊、腰帶以下全漏檢那說明還需要調(diào)整NMS閾值或回歸分支的權(quán)重。6. 避坑清單行人數(shù)據(jù)集與YOLO訓(xùn)練的五條血淚經(jīng)驗(yàn)6.1 標(biāo)簽坐標(biāo)歸一化后出現(xiàn)0或負(fù)數(shù)數(shù)據(jù)清洗的第一關(guān)卡現(xiàn)象訓(xùn)練過一半損失驟降后不再下降驗(yàn)證集mAP很低。檢查標(biāo)注文件后發(fā)現(xiàn)部分txt行里有0.000000的坐標(biāo)或負(fù)數(shù)寬高有些是標(biāo)注框邊緣超出圖像邊界導(dǎo)致的。原因VOC轉(zhuǎn)YOLO時(shí)沒有做邊界保護(hù)標(biāo)注框坐標(biāo)是負(fù)數(shù)或超出圖像寬高時(shí)轉(zhuǎn)換公式依然歸一化結(jié)果得到負(fù)數(shù)或大于1的坐標(biāo)YOLO訓(xùn)練時(shí)把這些越界框當(dāng)成了異常樣本。解決在轉(zhuǎn)換腳本里加上邊界鉗制對(duì)x_min、x_max、y_min、y_max做max/min裁剪然后再計(jì)算中心點(diǎn)和寬高。第4章的轉(zhuǎn)換腳本已經(jīng)包含這個(gè)保護(hù)邏輯直接抄就行。6.2 跟yolo訓(xùn)練中bn崩潰說再見BN層爆炸怎么自救現(xiàn)象訓(xùn)練前10個(gè)epoch里cls_loss偶爾跳成NaN恢復(fù)后mAP掉一大截后面很難回升。原因從COCO預(yù)訓(xùn)練權(quán)重切換到這個(gè)行人數(shù)據(jù)集時(shí)輸入圖像分布差異大BN層統(tǒng)計(jì)量被帶偏梯度異常放大。常見誘因是learning rate設(shè)置過高或lr在warmup階段上升太快。解決把學(xué)習(xí)率降到默認(rèn)值的一半比如YOLOv8的lr0默認(rèn)是0.01改成0.005開啟warmup并延長warmup_epochs到10個(gè)epoch。如果已經(jīng)爆過一次最好的后悔藥是把模型權(quán)重倒回去重新訓(xùn)練不要硬續(xù)因?yàn)锽N層的統(tǒng)計(jì)量已經(jīng)污染了后續(xù)所有層。6.3 圖像里有多個(gè)目標(biāo)但標(biāo)簽只框了一個(gè)漏標(biāo)注對(duì)訓(xùn)練的影響現(xiàn)象mAP50接近0.9但mAP50-95掉到0.5以下。觀察預(yù)測(cè)結(jié)果發(fā)現(xiàn)密集場景中漏檢嚴(yán)重。原因數(shù)據(jù)集的標(biāo)注質(zhì)量低于預(yù)期。7203張圖里如果有一部分只標(biāo)了“人”沒標(biāo)“主人”或者人群圖像只框了前景人物模型見到的正樣本數(shù)虛高但語義理解其實(shí)很弱。解決先用訓(xùn)練好的模型對(duì)訓(xùn)練集做一輪偽標(biāo)注把模型高置信度檢出的框提取出來與原始標(biāo)簽比對(duì)找出明顯漏標(biāo)的圖像進(jìn)行修正。這個(gè)方法有個(gè)前提——訓(xùn)練好的模型本身質(zhì)量可接受不然會(huì)把錯(cuò)誤學(xué)進(jìn)去。6.4 訓(xùn)練集與驗(yàn)證集出現(xiàn)重疊樣本評(píng)估結(jié)果虛高現(xiàn)象訓(xùn)練時(shí)mAP50升到0.95你覺得模型已經(jīng)很好了但換個(gè)視頻測(cè)試效果差得離譜。原因劃分?jǐn)?shù)據(jù)集時(shí)沒有檢查重復(fù)性。7203張圖里如果有大量連續(xù)幀抽幀圖像隨機(jī)劃分8:2后驗(yàn)證集里很可能出現(xiàn)與訓(xùn)練集幾乎相同的圖像評(píng)估指標(biāo)虛高。解決在劃分之前按圖像感知哈希值去重對(duì)相似度超過閾值的圖像只保留一張?jiān)賱澐钟?xùn)練驗(yàn)證集。感知哈??梢杂胕magehash庫幾行代碼就能算出每張圖的指紋對(duì)比Hamming距離篩選重復(fù)樣本。6.5 預(yù)訓(xùn)練權(quán)重選擇失誤yolo目標(biāo)檢測(cè)效果上限被鎖死現(xiàn)象同樣的數(shù)據(jù)、同樣的超參數(shù)用yolov8n效果明顯比預(yù)期差換yolov8m后精度提升但訓(xùn)練時(shí)間翻倍。原因Nano版本參數(shù)量太小對(duì)行人這種細(xì)長目標(biāo)的特征表達(dá)力不足。數(shù)據(jù)有7203張不算少用nano會(huì)把模型能力鎖在較低上限。解決優(yōu)先用yolov8s或yolov8m起步先用小尺寸數(shù)據(jù)試訓(xùn)50個(gè)epoch看mAP正常程度再?zèng)Q定是否加量。如果你目標(biāo)檢測(cè)場景是移動(dòng)端部署那就老實(shí)從nano開始接受精度上限受限的現(xiàn)實(shí)。7. 讓行人檢測(cè)更實(shí)用的進(jìn)階技巧從訓(xùn)練完成到場景可用訓(xùn)練完拿到的權(quán)重文件到真正被業(yè)務(wù)場景接受還有一段路。這段路的核心是確認(rèn)模型的魯棒性和邊界行為——它到底在什么條件下會(huì)失效而不是在驗(yàn)證集上刷到多高mAP。我習(xí)慣在訓(xùn)練后做三件事第一是找?guī)讉€(gè)真實(shí)場景視頻直接跑預(yù)測(cè)觀察模型在人群密集、逆光、遮擋場景下的表現(xiàn)第二是畫混淆矩陣重點(diǎn)看“人”和“主人”兩個(gè)類別是不是互相串第三是統(tǒng)計(jì)預(yù)測(cè)框的寬高比直方圖看模型是不是學(xué)到了行人的先驗(yàn)形狀。實(shí)際部署時(shí)還有一個(gè)細(xì)節(jié)容易忽略YOLO的輸入圖像尺寸在訓(xùn)練時(shí)是固定的比如640x640但部署時(shí)的視頻流分辨率可能是1080p甚至4K。你可以在推理前做簡單的等比縮放和letterbox處理這比直接用640跑全分辨率圖像再縮放預(yù)測(cè)框更穩(wěn)。有個(gè)更省事的方案是用YOLOv8的predict接口它自動(dòng)處理尺寸縮放和letterbox你只需要傳入原圖就行。如果項(xiàng)目要做持續(xù)迭代可以考慮用這個(gè)7203張的數(shù)據(jù)集作為第一版模型的基礎(chǔ)然后部署后收集誤檢漏檢的樣本補(bǔ)充到訓(xùn)練集里形成閉環(huán)。這個(gè)流程比盲目追求更大數(shù)據(jù)集更務(wù)實(shí)——你只需要讓模型拿高分不是目的讓它在目標(biāo)場景里穩(wěn)定可用才是終點(diǎn)。我以前吃過虧訓(xùn)了一個(gè)驗(yàn)證集mAP很高的模型上線后被普通小巷的逆光畫面打得滿地找牙后來老老實(shí)實(shí)去采集現(xiàn)場數(shù)據(jù)補(bǔ)了三百多張圖效果才立竿見影。做算法這件事數(shù)據(jù)永遠(yuǎn)比調(diào)參值錢。希望這篇筆記能幫你把這份數(shù)據(jù)集真正用起來少走我走過的那些彎路。本文還有配套的精品資源點(diǎn)擊獲取