據(jù)集:COCO標(biāo)注轉(zhuǎn)YOLO訓(xùn)練與避坑實(shí)踐)
簡介面向車牌識別與車牌檢測算法訓(xùn)練的數(shù)據(jù)集資源聚焦典型黃色車牌檢測場景。整套數(shù)據(jù)包含9324個(gè)文件壓縮包約257.79MB其中4627張jpg與35張jpeg圖片對應(yīng)4662個(gè)json標(biāo)注文件標(biāo)簽采用polygon多邊形目標(biāo)框每個(gè)車牌由四個(gè)頂點(diǎn)貼合定位能較好適配傾斜、透視等復(fù)雜拍攝角度為車牌字符識別與目標(biāo)檢測提供可直接使用的標(biāo)注信息。圖片經(jīng)過人工逐張篩選已剔除不清晰樣本并處理有歧義區(qū)域質(zhì)量較統(tǒng)一無需二次清洗即可用于模型訓(xùn)練。作者還提供標(biāo)簽格式轉(zhuǎn)換支持如需要rectangle矩形目標(biāo)框的json可私信溝通便于接入不同檢測框架。目前已有885人學(xué)習(xí)下載適合計(jì)算機(jī)視覺初學(xué)者、算法工程師以及智慧交通、停車場管理等項(xiàng)目開發(fā)者直接復(fù)用。1. 車牌檢測數(shù)據(jù)集黃牌樣本為什么比藍(lán)牌更容易翻車做車牌檢測的人大概率都經(jīng)歷過這種場景藍(lán)牌車跑得穩(wěn)穩(wěn)當(dāng)當(dāng)一到黃牌就漏檢、誤檢輪著來。黃牌多掛在貨車、客車和工程車輛上車牌顏色和車身漆面、車廂反光、泥污混在一起檢測框經(jīng)常畫偏甚至直接丟掉目標(biāo)。這份車牌檢測數(shù)據(jù)集以黃牌為主體圖像覆蓋白天、傍晚、陰天等多種光照每張圖都配好 JSON 標(biāo)簽類別、邊框、圖像尺寸齊全拿過來不用重新標(biāo)注直接能跑通數(shù)據(jù)加載 → 可視化 → 訓(xùn)練 → 評估的完整鏈路。適合正在做交通監(jiān)控、停車場出入口識別、車型分類預(yù)處理的開發(fā)者也適合想用現(xiàn)成數(shù)據(jù)快速驗(yàn)證 YOLO 系列模型、跑通圖像識別與車牌檢測完整流程的從業(yè)者。2. 數(shù)據(jù)集結(jié)構(gòu)與 JSON 標(biāo)注格式先搞懂標(biāo)簽再動(dòng)手2.1 目錄組織訓(xùn)練集、驗(yàn)證集與標(biāo)注文件的對應(yīng)關(guān)系拿到壓縮包解壓之后目錄組織比較直白圖像和標(biāo)注分離訓(xùn)練集與驗(yàn)證集各自獨(dú)立。常見做法是dataset/ ├── images/ │ ├── train/ │ │ ├── img_0001.jpg │ │ └── ... │ └── val/ │ ├── img_0100.jpg │ └── ... └── annotations/ ├── train.json └── val.jsonimages 目錄下是按 train / val 拆好的原始圖像annotations 目錄里放的是與之一一對應(yīng)的 JSON 標(biāo)注文件。train.json 只描述 images/train 下的圖像val.json 同理二者不會(huì)交叉引用。這種組織方式和 COCO 數(shù)據(jù)集的慣例一致后續(xù)接 Detectron2、MMDetection 或者自己寫數(shù)據(jù)加載器都比較省事。如果打算用 YOLO 系列訓(xùn)練只需要把 images 目錄原樣保留再把 JSON 轉(zhuǎn)成同名 txt 標(biāo)簽丟進(jìn) labels 目錄即可具體轉(zhuǎn)換腳本在下一章。我第一次拿到時(shí)踩過一個(gè)坑直接用 glob 去掃 images 目錄結(jié)果把 val 的圖像也掃進(jìn)來了訓(xùn)練集和驗(yàn)證集出現(xiàn)重疊訓(xùn)練時(shí) loss 一震蕩就開始懷疑模型最后查了兩小時(shí)才發(fā)現(xiàn)是數(shù)據(jù)加載的問題。后來我改成以 JSON 里的 file_name 為準(zhǔn)來構(gòu)建圖像路徑映射而不是反向掃描目錄問題就消失了。具體做法是給 JSON 里的 file_name 拼接上對應(yīng)的 train 或 val 前綴再檢查文件是否存在。提示以 JSON 的 file_name 為準(zhǔn)構(gòu)建圖像路徑映射訓(xùn)練前先檢查 train 和 val 的圖像集合是否有交集。2.2 JSON 字段逐項(xiàng)解析images、annotations、categories 三塊各管什么標(biāo)注文件用了 COCO 風(fēng)格的字段頂層分成三塊images 記錄圖像元信息annotations 記錄每個(gè)車牌框categories 定義類別。打開一個(gè)典型的 train.json結(jié)構(gòu)是這樣的{ images: [ { id: 1, file_name: img_0001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [862, 415, 210, 68], area: 14280, iscrowd: 0 } ], categories: [ { id: 1, name: yellow_plate } ] }三個(gè)塊各管各的事別混。images 里的 id 在全局唯一file_name 是相對于 images 目錄的文件名width 和 height 是標(biāo)注時(shí)記錄的原始分辨率。后續(xù)做歸一化坐標(biāo)換算必須用這兩個(gè)記錄值不能用腳本里讀到的實(shí)際尺寸代替因?yàn)閭€(gè)別圖像可能被預(yù)處理改過尺寸標(biāo)注框是基于原分辨率畫的混用會(huì)造成少量框偏移。annotations 里的 image_id 關(guān)聯(lián)到 images.idcategory_id 關(guān)聯(lián)到 categories.id本數(shù)據(jù)集目前只有 yellow_plate 一個(gè)類別bbox 是 [x, y, width, height] 四個(gè)數(shù)值表示車牌外接矩形左上角坐標(biāo)和寬高單位是像素area 等于 width 乘 height一些框架用它做小目標(biāo)篩選和評估加權(quán)iscrowd 為 0 表示普通目標(biāo)為 1 表示群體區(qū)域轉(zhuǎn)換格式時(shí)容易忽略這個(gè)字段后面避坑章節(jié)專門講。2.3 坐標(biāo)系換算COCO 絕對坐標(biāo)與 YOLO 歸一化坐標(biāo)的互轉(zhuǎn)COCO 的 bbox 是絕對像素坐標(biāo)原點(diǎn)在圖像左上角x 向右、y 向下。YOLO 系列訓(xùn)練時(shí)需要的是歸一化坐標(biāo)目標(biāo)中心點(diǎn) (cx, cy) 和寬高 (w, h) 都除以圖像寬高取值在 0 到 1 之間。換算公式不復(fù)雜但方向搞反的話訓(xùn)練出來的模型框中心點(diǎn)全在圖像外面損失函數(shù)根本收斂不了。def coco_to_yolo(bbox, img_w, img_h): x, y, w, h bbox cx x w / 2.0 cy y h / 2.0 return cx / img_w, cy / img_h, w / img_w, h / img_h參數(shù)含義bbox 是 COCO 格式的 [左上角 x, 左上角 y, 寬, 高]img_w 和 img_h 來自 JSON 里 images 塊的 width / height。返回的是歸一化后的中心點(diǎn) x、中心點(diǎn) y、歸一化寬、歸一化高。注意 x 和 w / 2 的單位都是像素要先加出中心點(diǎn)再除以圖像寬不能先分別歸一化再相加那樣算出來的中心點(diǎn)坐標(biāo)是錯(cuò)的。反過來從 YOLO 轉(zhuǎn) COCO 時(shí)用中心點(diǎn)減半寬得到左上角def yolo_to_coco(cx_norm, cy_norm, w_norm, h_norm, img_w, img_h): x (cx_norm * img_w) - (w_norm * img_w) / 2.0 y (cy_norm * img_h) - (h_norm * img_h) / 2.0 w w_norm * img_w h h_norm * img_h return [int(v) for v in (x, y, w, h)]yolo_to_coco 最后轉(zhuǎn) int 是因?yàn)楫嬁驎r(shí)需要整數(shù)像素坐標(biāo)而訓(xùn)練標(biāo)簽文件里一般保留 6 位小數(shù)不需要取整。這兩段函數(shù)建議直接放進(jìn)一個(gè) utils.py后面做格式轉(zhuǎn)換和可視化都要反復(fù)用到。如果數(shù)據(jù)集里有旋轉(zhuǎn)標(biāo)注或者多邊形標(biāo)注這套矩形換算就不夠用了需要額外處理角度不過本數(shù)據(jù)集的 JSON 里只有軸對齊矩形框用這兩個(gè)函數(shù)足夠。3. 把 JSON 標(biāo)簽用起來統(tǒng)計(jì)、可視化與格式轉(zhuǎn)換3.1 標(biāo)注統(tǒng)計(jì)先摸清數(shù)據(jù)量和分布再?zèng)Q定訓(xùn)練策略拿到數(shù)據(jù)集先別急著開訓(xùn)練花兩分鐘跑個(gè)統(tǒng)計(jì)腳本確認(rèn)圖像數(shù)量、標(biāo)注數(shù)量、每張圖平均車牌數(shù)以及 bbox 寬高比的分布。這些數(shù)字直接決定輸入分辨率、錨框設(shè)置和是否要做特殊增強(qiáng)。import json from collections import Counter with open(annotations/train.json, r, encodingutf-8) as f: data json.load(f) print(圖像數(shù)量:, len(data[images])) print(標(biāo)注數(shù)量:, len(data[annotations])) per_image Counter() wh_ratios [] for a in data[annotations]: per_image[a[image_id]] 1 x, y, w, h a[bbox] wh_ratios.append(round(w / h, 2)) print(單圖最多車牌數(shù):, max(per_image.values())) print(寬高比均值:, sum(wh_ratios) / len(wh_ratios)) print(寬高比范圍:, min(wh_ratios), -, max(wh_ratios))這段腳本從 JSON 里讀出標(biāo)注列表per_image 用 image_id 做鍵統(tǒng)計(jì)每張圖的車牌數(shù)量wh_ratios 收集所有框的寬高比。黃牌的標(biāo)準(zhǔn)寬高比接近 3:1如果統(tǒng)計(jì)出來大量框的比值明顯偏離這個(gè)范圍比如小于 2 或者大于 5說明標(biāo)注邊界可能有問題或者圖像里存在非標(biāo)準(zhǔn)尺寸的特殊車牌需要回看原始圖確認(rèn)。寬高比分布還會(huì)影響后面的訓(xùn)練配置YOLOv5 訓(xùn)練時(shí)有一個(gè)自動(dòng)錨框的步驟k-means 聚類結(jié)果直接受 bbox 寬高分布影響換 YOLOv8 這類 anchor-free 模型則不用關(guān)心錨框聚類但同樣要從輸入分辨率上解決小目標(biāo)問題。3.2 可視化檢測框檢查標(biāo)簽是否真的框住了車牌統(tǒng)計(jì)只能發(fā)現(xiàn)數(shù)值層面的異常標(biāo)簽是否真正貼著車牌邊緣必須把框畫出來看。用 OpenCV 疊加標(biāo)注框是檢查數(shù)據(jù)質(zhì)量最快的方式隨機(jī)抽一部分圖出來看即可。import cv2 import json data json.load(open(annotations/val.json, encodingutf-8)) for img_info in data[images][:10]: img cv2.imread(fimages/val/{img_info[file_name]}) annos [a for a in data[annotations] if a[image_id] img_info[id]] for a in annos: x, y, w, h [int(v) for v in a[bbox]] cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 255), 2) cv2.putText(img, plate, (x, y - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2) cv2.imwrite(fcheck_{img_info[file_name]}, img)cv2.rectangle 的四個(gè)參數(shù)分別是左上角坐標(biāo)和右下角坐標(biāo)注意不是中心點(diǎn)加寬高這里用 bbox 的 x、y 和 x w、y h 拼出右下角。顏色用了 BGR 的 (0, 255, 255)也就是黃色和黃牌本身顏色一致如果覺得看不清可以換成紅色 (0, 0, 255)。我一般會(huì)抽 20 到 30 張圖重點(diǎn)看三種情況框是否緊貼車牌邊緣、是否把車燈或車身裝飾條也框了進(jìn)去、車牌字符區(qū)域是否完整包含在框內(nèi)。抽樣最好覆蓋不同光線條件因?yàn)辄S牌在逆光和夜間反光時(shí)的視覺特征差別很大白天看著正常的標(biāo)簽到了強(qiáng)反光場景可能就偏了。3.3 轉(zhuǎn) YOLO 格式生成 txt 標(biāo)簽并整理訓(xùn)練目錄YOLO 系列訓(xùn)練需要每個(gè)圖像對應(yīng)一個(gè)同名 txt 文件每行格式是類別ID 中心點(diǎn)x 中心點(diǎn)y 寬 高全部歸一化。JSON 轉(zhuǎn) txt 的腳本可以一次性處理訓(xùn)練集和驗(yàn)證集import json import os def convert_json_to_yolo(json_path, out_dir): data json.load(open(json_path, encodingutf-8)) os.makedirs(out_dir, exist_okTrue) for img_info in data[images]: name os.path.splitext(img_info[file_name])[0] img_w, img_h img_info[width], img_info[height] lines [] for a in data[annotations]: if a[image_id] ! img_info[id]: continue if a.get(iscrowd, 0): continue cx, cy, w, h coco_to_yolo(a[bbox], img_w, img_h) lines.append(f0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, name .txt) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines) \n) convert_json_to_yolo(annotations/train.json, labels/train) convert_json_to_yolo(annotations/val.json, labels/val)參數(shù)說明json_path 是輸入的 COCO 風(fēng)格標(biāo)注文件out_dir 是輸出的 txt 目錄腳本會(huì)自動(dòng)創(chuàng)建。類別 ID 直接寫死為 0因?yàn)楫?dāng)前數(shù)據(jù)集只有 yellow_plate 一個(gè)類別如果后續(xù)自己擴(kuò)展成多類別必須建一個(gè)類別名到 ID 的映射字典用 name 查 ID不能繼續(xù)寫死。這里顯式跳過 iscrowd 為 1 的標(biāo)注避免群體區(qū)域被當(dāng)成單個(gè)車牌來訓(xùn)練。寫文件時(shí)每行末尾加了換行符YOLO 訓(xùn)練不受影響但用其他數(shù)據(jù)檢查腳本按行讀取時(shí)更省事。沒有標(biāo)注的圖片會(huì)生成空 txt 文件YOLO 訓(xùn)練會(huì)把它當(dāng)作背景樣本跳過可以保留也可以刪除取決于你需不需要負(fù)樣本。4. 黃牌檢測訓(xùn)練與評估四個(gè)避坑記錄和排查方法4.1 現(xiàn)象遠(yuǎn)距離黃牌漏檢嚴(yán)重模型只撿近處車牌模型在驗(yàn)證集上跑出來的 mAP 看起來不錯(cuò)但實(shí)際監(jiān)控畫面里遠(yuǎn)處貨車的小車牌基本檢測不到。原因是兩層一是輸入分辨率不夠YOLO 默認(rèn) 640 輸入下一個(gè) 30 像素寬的小車牌經(jīng)過幾次下采樣后特征圖里只剩一兩個(gè)像素分類頭很難給出置信度二是黃牌在遠(yuǎn)處本身對比度低和灰色路面、車廂陰影混在一起特征更弱。解決思路是先把訓(xùn)練輸入分辨率提到 960 甚至 1280同時(shí)開啟多尺度訓(xùn)練讓模型見過不同尺寸的目標(biāo)如果框架支持按 bbox 面積做采樣加權(quán)可以把小目標(biāo)樣本的權(quán)重調(diào)高。注意分辨率提高后顯存占用明顯上漲batch size 要相應(yīng)調(diào)小我試過從 640 換到 960batch 從 64 降到 32 才沒爆顯存。4.2 現(xiàn)象黃色誤檢到車燈、車身貼紙和道路標(biāo)線訓(xùn)練完可視化 bad case 發(fā)現(xiàn)模型經(jīng)常把貨車車燈、車身黃色貼紙、甚至路面黃色標(biāo)線框成車牌。原因在于黃牌的特征主要是顏色加矩形紋理而 HSV 空間里車燈反光和黃色貼紙與車牌顏色非常接近僅靠顏色分支很難區(qū)分。解決分三步訓(xùn)練階段做 HSV 顏色抖動(dòng)增強(qiáng)讓模型不只依賴顏色這一個(gè)維度數(shù)據(jù)層面把包含車燈的難例挑出來加入訓(xùn)練集或者做復(fù)制粘貼增強(qiáng)把這些區(qū)域作為正樣本讓模型學(xué)會(huì)區(qū)分推理階段可以配合后處理規(guī)則過濾掉寬高比不在合理范圍、置信度低于閾值的框。如果用的檢測框架支持設(shè)置忽略區(qū)域也可以把車燈位置標(biāo)成 ignore 區(qū)域讓模型在這些位置不計(jì)算損失實(shí)測對誤檢率下降很明顯。4.3 現(xiàn)象mAP 虛高但實(shí)際框偏標(biāo)注框邊界不齊有一次訓(xùn)練出來的模型 mAP0.5 到了 0.9 左右看起來很好但部署到現(xiàn)場發(fā)現(xiàn)檢測框總是往左偏半個(gè)字符的位置。后來查了幾張驗(yàn)證集圖像的標(biāo)注發(fā)現(xiàn)不少框的左邊沿壓到了字符邊緣甚至切進(jìn)字符里右邊卻多留了一個(gè)字符的空白。原因是標(biāo)注階段不同的人對車牌邊界理解不一致有的以字符區(qū)域?yàn)闇?zhǔn)有的以底板邊緣為準(zhǔn)。解決方法是先統(tǒng)一標(biāo)注規(guī)范黃牌框必須包含完整底板左右留 2 到 3 像素邊距上下留 1 到 2 像素然后寫腳本統(tǒng)計(jì)全部 bbox 的寬高比把偏離均值超過 30% 的框列出來人工復(fù)查。從那以后我每次拿到數(shù)據(jù)集都會(huì)先做一遍 bbox 寬高比分布統(tǒng)計(jì)再?zèng)Q定要不要修標(biāo)注。4.4 現(xiàn)象訓(xùn)練正常但驗(yàn)證時(shí) loss 震蕩JSON 和 txt 標(biāo)簽不一致訓(xùn)練日志里 loss curve 在某個(gè) epoch 后開始周期性震蕩排查后發(fā)現(xiàn)是因?yàn)閿?shù)據(jù)加載順序是隨機(jī)的但有的 txt 標(biāo)簽和圖像文件不是一一對應(yīng)。具體原因是前面提到的掃描目錄時(shí)把 val 圖像也掃進(jìn)了訓(xùn)練集導(dǎo)致同一張圖一會(huì)兒用訓(xùn)練標(biāo)簽、一會(huì)兒用驗(yàn)證標(biāo)簽標(biāo)簽內(nèi)容不一致loss 自然震蕩。解決方法是嚴(yán)格以 JSON 的 file_name 為準(zhǔn)構(gòu)建數(shù)據(jù)集禁止單獨(dú)掃描目錄同時(shí)訓(xùn)練前寫一個(gè)校驗(yàn)?zāi)_本檢查每個(gè) txt 標(biāo)注文件對應(yīng)的圖像是否存在于訓(xùn)練目錄、train 和 val 兩個(gè)集合是否完全不相交。這個(gè)校驗(yàn)?zāi)_本十幾行就能寫完但能避免后面浪費(fèi)好幾個(gè)小時(shí)的排錯(cuò)時(shí)間。另外一個(gè)容易忽略的點(diǎn)是如果 JSON 里存在 iscrowd1 的標(biāo)注部分框架會(huì)忽略部分框架不處理轉(zhuǎn)換時(shí)統(tǒng)一過濾掉才能保證結(jié)果一致。5. 進(jìn)階針對黃牌的數(shù)據(jù)增強(qiáng)與難例篩選把召回率再往上拉5.1 針對性增強(qiáng)別用通用增強(qiáng)按黃牌物理特征來設(shè)計(jì)通用增強(qiáng)里的隨機(jī)旋轉(zhuǎn) 90 度對車牌檢測是災(zāi)難因?yàn)檎鎸?shí)場景里車牌不會(huì)倒著出現(xiàn)。我一般用 Albumentations 做一組針對性增強(qiáng)核心是模擬光線變化和運(yùn)動(dòng)模糊import albumentations as A transform A.Compose([ A.HueSaturationValue(hue_shift_limit10, sat_shift_limit30, val_shift_limit20, p0.5), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.5), A.MotionBlur(blur_limit5, p0.3), A.RandomSizedBBoxSafeCrop(width960, height960, p0.5) ], bbox_paramsA.BboxParams(formatcoco, label_fields[labels]))參數(shù)說明HueSaturationValue 的 val_shift_limit 控制亮度偏移模擬不同時(shí)間段的光照sat_shift_limit 控制飽和度抖動(dòng)對應(yīng)晴天和陰天的顏色差異MotionBlur 模擬車輛行駛和相機(jī)抖動(dòng)產(chǎn)生的模糊RandomSizedBBoxSafeCrop 會(huì)在裁剪時(shí)保持 bbox 完整width 和 height 設(shè)為 960對應(yīng)訓(xùn)練分辨率。注意 hue 偏移不要超過 15否則車牌顏色會(huì)從黃色漂移到綠色或橙色反而制造錯(cuò)誤樣本?;貍髟鰪?qiáng)參數(shù)時(shí)bbox_params 的 format 必須寫 coco和原始標(biāo)注格式一致如果用 yolo 需要提前做歸一化。5.2 難例篩選用驗(yàn)證集錯(cuò)誤樣本反哺訓(xùn)練集增強(qiáng)做到位之后下一步是難例挖掘。把訓(xùn)練好的模型在驗(yàn)證集上推理收集所有漏檢和誤檢的樣本按置信度排序人工挑出典型的難例回填到訓(xùn)練集。這一步不需要全部重新標(biāo)注常見做法是只挑 50 到 100 張配合復(fù)制粘貼增強(qiáng)把難例復(fù)制到多張圖里模型很快就能學(xué)會(huì)這些特征。驗(yàn)證時(shí)不僅看整體 mAP還要單獨(dú)統(tǒng)計(jì)黃牌類別的召回率把驗(yàn)證集里所有包含黃牌的圖像挑出來按類別算 recall這個(gè)數(shù)字比整體 mAP 更能反映實(shí)際問題有沒有解決。如果召回率上去了但誤檢也變多再回頭看 PR 曲線的平衡點(diǎn)調(diào)整置信度閾值和 NMS 參數(shù)。我從那次踩坑之后養(yǎng)成了一個(gè)習(xí)慣每次拿到新數(shù)據(jù)集訓(xùn)練前強(qiáng)制走一遍標(biāo)注可視化復(fù)查 bbox 寬高比統(tǒng)計(jì) 訓(xùn)練驗(yàn)證集合交集檢查三步訓(xùn)練后再加一次黃牌類別召回率統(tǒng)計(jì)才敢部署。這套流程幫我攔下了不少數(shù)據(jù)集層面而非模型層面的問題。這份數(shù)據(jù)集配合前面的腳本從 JSON 解析、可視化到 YOLO 格式轉(zhuǎn)換整條鏈路可以直接復(fù)現(xiàn)希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取