數(shù)據(jù)集:json標(biāo)簽校驗(yàn)與YOLO/COCO轉(zhuǎn)換避坑)
簡(jiǎn)介這是一份面向車牌檢測(cè)與字符識(shí)別任務(wù)的黃牌圖像數(shù)據(jù)集適合計(jì)算機(jī)視覺(jué)學(xué)習(xí)者、算法開發(fā)者及課程設(shè)計(jì)、項(xiàng)目實(shí)訓(xùn)使用。數(shù)據(jù)覆蓋多種拍攝角度標(biāo)注采用polygon多邊形目標(biāo)框四個(gè)頂點(diǎn)分別對(duì)應(yīng)車牌四角可更貼合傾斜、透視場(chǎng)景下的車牌輪廓為檢測(cè)模型訓(xùn)練與字符識(shí)別預(yù)處理提供直接可用的標(biāo)注基礎(chǔ)。所有圖片經(jīng)人工逐張篩選剔除模糊與歧義區(qū)域減少后續(xù)清洗成本。壓縮包共9324個(gè)文件其中4627張jpg、35張jpeg圖像與4662個(gè)json標(biāo)注文件整體約257.79MB圖像與標(biāo)簽一一對(duì)應(yīng)便于按目錄組織訓(xùn)練集和驗(yàn)證集。已有890人學(xué)習(xí)下載。對(duì)需要矩形框json格式的讀者作者也提供轉(zhuǎn)換支持無(wú)論是做黃牌檢測(cè)、車牌定位還是字符識(shí)別流程驗(yàn)證都能從中獲得較完整的圖像與標(biāo)注素材。1. 黃牌車牌檢測(cè)數(shù)據(jù)集不是“有框就行”json標(biāo)簽先決定上限某開發(fā)者拿一批黃牌車牌檢測(cè)數(shù)據(jù)集做園區(qū)門口抓拍訓(xùn)練損失降得很順推理時(shí)卻總把黃色廣告牌、收費(fèi)亭反光條框成車牌。翻到數(shù)據(jù)層才發(fā)現(xiàn)json標(biāo)簽里的框是中心點(diǎn)加寬高轉(zhuǎn)換腳本卻按左上右下讀模型學(xué)的是一套偏移框。這個(gè)資源就是帶json標(biāo)簽的黃牌車牌檢測(cè)數(shù)據(jù)集落在圖像識(shí)別流程里負(fù)責(zé)車牌識(shí)別之前的定位環(huán)節(jié)適合卡口、停車場(chǎng)、園區(qū)門口這類黃牌車較多的場(chǎng)景也適合想練json轉(zhuǎn)YOLO、COCO標(biāo)注口徑的人。黃牌顏色是先驗(yàn)不是標(biāo)簽json字段沒(méi)讀透訓(xùn)練越久偏得越遠(yuǎn)。下面從字段校驗(yàn)開始把能復(fù)現(xiàn)的步驟和翻車點(diǎn)拆開。2. 把json標(biāo)簽讀透字段探測(cè)、坐標(biāo)校驗(yàn)與黃牌樣本分布2.1 先做字段探測(cè)不要上來(lái)就硬編碼鍵名json標(biāo)簽最怕“看起來(lái)一樣”。同樣叫bbox有的存[x_min, y_min, x_max, y_max]有的存[x, y, w, h]還有的存歸一化后的中心點(diǎn)寬高。黃牌車牌檢測(cè)數(shù)據(jù)集如果來(lái)自不同標(biāo)注批次字段名和層級(jí)都可能變。我一般不會(huì)先寫轉(zhuǎn)換腳本而是先跑一個(gè)字段探測(cè)把一份json里所有鍵路徑打印出來(lái)確認(rèn)圖像路徑、寬高、標(biāo)注列表、框字段、類別字段分別在哪一層。import json from pathlib import Path def walk_keys(obj, prefix): 遞歸打印json鍵路徑列表只采樣前3個(gè)元素避免大文件卡住 if isinstance(obj, dict): for k, v in obj.items(): yield prefix k, v yield from walk_keys(v, prefix k .) elif isinstance(obj, list): for i, v in enumerate(obj[:3]): yield from walk_keys(v, prefix f[{i}].) json_path Path(labels/one_sample.json) data json.loads(json_path.read_text(encodingutf-8)) for key, value in walk_keys(data): # 只打印鍵路徑和值類型不打印整段base64或大數(shù)組 print(f{key} - {type(value).__name__})這段代碼只做一件事把嵌套結(jié)構(gòu)攤平。walk_keys對(duì)字典逐層拼路徑對(duì)列表只取前三個(gè)元素因?yàn)闃?biāo)注文件里objects或annotations可能很長(zhǎng)全量遞歸會(huì)拖慢排查。prefix用來(lái)保留層級(jí)比如objects.[0].bbox能直接看出框在標(biāo)注對(duì)象里而不是在頂層。實(shí)際使用時(shí)先拿一份樣本跑確認(rèn)鍵路徑后再寫批量腳本。常見(jiàn)字段命名可以對(duì)照下表但不要把它當(dāng)標(biāo)準(zhǔn)。表里只是排查方向最終以實(shí)際json為準(zhǔn)。信息類型常見(jiàn)鍵名要確認(rèn)的點(diǎn)圖像路徑imagePath、image_path、file_name是相對(duì)路徑還是絕對(duì)路徑根目錄在哪圖像寬高width、height、imageWidth與真實(shí)讀圖尺寸是否一致標(biāo)注列表objects、annotations、shapes是列表還是按類別分組的字典框坐標(biāo)bbox、points、box是四個(gè)數(shù)還是兩個(gè)點(diǎn)是否歸一化類別label、category、class_name大小寫、中英文、是否有空格提示字段探測(cè)階段不要急著改json先把原始鍵路徑和值類型記錄下來(lái)。后面轉(zhuǎn)換腳本報(bào)錯(cuò)時(shí)這份記錄就是后悔藥。2.2 坐標(biāo)口徑與越界檢查四個(gè)必須落庫(kù)的校驗(yàn)確認(rèn)字段后下一步不是直接轉(zhuǎn)YOLO而是做坐標(biāo)校驗(yàn)。黃牌車牌在圖像里通常偏小框偏移幾個(gè)像素肉眼看不出來(lái)但歸一化之后會(huì)被放大。我一般要求四個(gè)校驗(yàn)必須落庫(kù)坐標(biāo)數(shù)值有限、右下大于左上、框不越界、json寬高與真實(shí)讀圖尺寸一致。前三個(gè)決定框能不能用第四個(gè)決定歸一化分母對(duì)不對(duì)。import math from PIL import Image def to_xyxy(bbox, fmt, w, h): 把不同口徑的框統(tǒng)一成絕對(duì)像素xyxy if fmt xyxy: x1, y1, x2, y2 bbox elif fmt xywh: x, y, bw, bh bbox x1, y1, x2, y2 x, y, x bw, y bh elif fmt cxcywh: cx, cy, bw, bh bbox x1, y1, x2, y2 cx - bw / 2, cy - bh / 2, cx bw / 2, cy bh / 2 elif fmt norm_xyxy: x1, y1, x2, y2 bbox[0] * w, bbox[1] * h, bbox[2] * w, bbox[3] * h elif fmt norm_cxcywh: cx, cy, bw, bh bbox x1, y1 (cx - bw / 2) * w, (cy - bh / 2) * h x2, y2 (cx bw / 2) * w, (cy bh / 2) * h else: raise ValueError(f未知坐標(biāo)口徑: {fmt}) return x1, y1, x2, y2 def check_bbox(bbox, fmt, img_path, tol2.0): img Image.open(img_path) w, h img.size x1, y1, x2, y2 to_xyxy(bbox, fmt, w, h) if not all(math.isfinite(v) for v in (x1, y1, x2, y2)): return non_finite if x2 x1 or y2 y1: return invalid_wh # 允許少量越界超過(guò)tol像素就記為異常不要靜默裁剪 if x1 -tol or y1 -tol or x2 w tol or y2 h tol: return out_of_bound return okto_xyxy把五種常見(jiàn)口徑統(tǒng)一成絕對(duì)像素左上右下。fmt必須由字段探測(cè)確認(rèn)不能猜。check_bbox里tol2.0是容忍少量像素越界因?yàn)闃?biāo)注工具縮放時(shí)可能產(chǎn)生1到2像素誤差超過(guò)這個(gè)范圍就記異常而不是直接裁剪。裁剪會(huì)把錯(cuò)誤藏起來(lái)后面評(píng)估時(shí)看不出問(wèn)題。實(shí)際落地時(shí)建議把原始框、統(tǒng)一后的框、異常類型一起寫進(jìn)檢查表方便回查。如果json里有width和height還要和Image.open(img_path).size比對(duì)。差異超過(guò)1像素就要查原因是圖像被重新縮放后沒(méi)更新json還是手機(jī)拍攝的EXIF方向?qū)е伦x圖尺寸旋轉(zhuǎn)。EXIF問(wèn)題可以用ImageOps.exif_transpose在讀圖時(shí)統(tǒng)一方向再以旋轉(zhuǎn)后的尺寸為準(zhǔn)。2.3 用統(tǒng)計(jì)和可視化找“黃牌偏科”黃牌車牌檢測(cè)數(shù)據(jù)集容易“偏科”白天多、夜間少近景多、小目標(biāo)少干凈車牌多、泥污遮擋少。只看總框數(shù)沒(méi)意義要看每圖框數(shù)、框面積占比、寬高比和黃色像素比例的分布??蛎娣e占比能判斷小目標(biāo)比例寬高比能發(fā)現(xiàn)異常扁框黃色像素比例能輔助看顏色先驗(yàn)是否穩(wěn)定但它不是標(biāo)簽不能拿來(lái)當(dāng)訓(xùn)練目標(biāo)。import numpy as np from PIL import Image from pathlib import Path def yellow_ratio(img_path): 粗略統(tǒng)計(jì)黃色像素比例只用于分布檢查不參與訓(xùn)練標(biāo)簽 img Image.open(img_path).convert(RGB) arr np.asarray(img).astype(np.float32) / 255.0 r, g, b arr[..., 0], arr[..., 1], arr[..., 2] # 黃色大致滿足紅綠高、藍(lán)低閾值只用于觀察不作為硬規(guī)則 mask (r 0.45) (g 0.35) (b 0.45) (r b 0.12) (g b 0.08) return float(mask.mean()) def bbox_area_ratio(bbox, fmt, w, h): x1, y1, x2, y2 to_xyxy(bbox, fmt, w, h) return max(0.0, (x2 - x1) * (y2 - y1)) / (w * h)yellow_ratio用RGB近似黃色閾值寫成比例而不是固定0到255方便不同曝光圖像比較。bbox_area_ratio算框面積占全圖比例輸出分位數(shù)比看均值可靠。實(shí)際排查時(shí)把面積占比分成0.1%、0.1%到1%、1%三檔看驗(yàn)證集和訓(xùn)練集比例是否接近。如果訓(xùn)練集小目標(biāo)多、驗(yàn)證集近景多mAP會(huì)虛高業(yè)務(wù)現(xiàn)場(chǎng)卻掉點(diǎn)。注意黃色像素比例只適合做分布觀察。夜間暖光、泥污、反光條都會(huì)讓黃色比例失真把它當(dāng)過(guò)濾條件會(huì)埋下漏檢。2.4 劃分訓(xùn)練驗(yàn)證集按場(chǎng)景分組別按文件隨機(jī)黃牌車牌檢測(cè)數(shù)據(jù)集常見(jiàn)連拍同一輛車在同一個(gè)門口連續(xù)幾張文件名只差序號(hào)。按文件隨機(jī)劃分訓(xùn)練集和驗(yàn)證集會(huì)同時(shí)出現(xiàn)同一輛車的相鄰幀指標(biāo)看起來(lái)很好換場(chǎng)景就崩。我一般按場(chǎng)景分組目錄名、文件名前綴、拍攝點(diǎn)位、時(shí)間段任選一個(gè)能代表“場(chǎng)景”的鍵保證同一組只進(jìn)一個(gè)split。from collections import defaultdict from pathlib import Path import random def group_key(json_path: Path): # 按父目錄名分組如果文件名前綴更能代表場(chǎng)景可改成 json_path.stem.split(_)[0] return json_path.parent.name json_files sorted(Path(labels).rglob(*.json)) groups defaultdict(list) for p in json_files: groups[group_key(p)].append(p) keys list(groups.keys()) random.Random(42).shuffle(keys) train, val, test [], [], [] for i, k in enumerate(keys): # 7:2:1 按組劃分組內(nèi)文件整體進(jìn)入同一個(gè)split if i % 10 7: train.extend(groups[k]) elif i % 10 9: val.extend(groups[k]) else: test.extend(groups[k])group_key是劃分的核心。父目錄名通常對(duì)應(yīng)一個(gè)拍攝批次或一個(gè)場(chǎng)景比文件名隨機(jī)穩(wěn)。random.Random(42)固定種子方便復(fù)現(xiàn)。i % 10實(shí)現(xiàn)7:2:1組數(shù)少時(shí)可以改成按組大小分層避免某個(gè)split沒(méi)有小目標(biāo)。劃分完要再跑一次統(tǒng)計(jì)確認(rèn)訓(xùn)練集和驗(yàn)證集的框面積分位數(shù)、黃色比例分位數(shù)沒(méi)有明顯斷層。斷層太大就先調(diào)分組鍵不要急著訓(xùn)練。3. 從json轉(zhuǎn)到Y(jié)OLO與COCO轉(zhuǎn)換腳本、參數(shù)口徑和回讀自檢3.1 選YOLO還是COCO看訓(xùn)練框架和后續(xù)標(biāo)注回流json標(biāo)簽轉(zhuǎn)格式先定目標(biāo)格式。YOLO的txt適合單階段檢測(cè)訓(xùn)練每行類別 id cx cy w h全部歸一化到0到1COCO的json適合多工具協(xié)作和標(biāo)注回流bbox是絕對(duì)像素[x, y, w, h]area是面積category_id從1開始更常見(jiàn)。黃牌車牌檢測(cè)數(shù)據(jù)集如果只做單類plateYOLO txt最輕如果團(tuán)隊(duì)還要用標(biāo)注平臺(tái)復(fù)標(biāo)、做數(shù)據(jù)版本管理COCO json更穩(wěn)。維度YOLO txtCOCO json坐標(biāo)口徑歸一化中心點(diǎn)寬高絕對(duì)像素左上寬高類別 id通常從0開始通常從1開始空標(biāo)簽生成空txt表示負(fù)樣本沒(méi)有annotation也要有image記錄適合場(chǎng)景快速訓(xùn)練、單類檢測(cè)多工具協(xié)作、標(biāo)注回流易錯(cuò)點(diǎn)歸一化分母、類別順序image_id、category_id、area選型不是技術(shù)高低是流程匹配。單類黃牌車牌檢測(cè)YOLO txt足夠如果后續(xù)要加顏色分類、車型分類COCO json的擴(kuò)展字段更從容。轉(zhuǎn)換前把class_map固定下來(lái)比如{plate: 0}不要在不同腳本里各寫一套。3.2 寫一個(gè)可復(fù)用的json轉(zhuǎn)YOLO腳本轉(zhuǎn)換腳本要處理三件事找到標(biāo)注列表、統(tǒng)一框口徑、把類別名映射成id。未知類別必須報(bào)錯(cuò)不能靜默跳過(guò)否則訓(xùn)練時(shí)類別數(shù)對(duì)不上。空標(biāo)注也要生成空txtYOLO訓(xùn)練需要負(fù)樣本。import json from pathlib import Path from PIL import Image CLASS_MAP {plate: 0} # 黃牌數(shù)據(jù)集單類時(shí)固定為0多類時(shí)在這里統(tǒng)一順序 def iter_objects(data): 兼容 objects / annotations / shapes 三種常見(jiàn)標(biāo)注列表 for key in (objects, annotations, shapes): value data.get(key) if isinstance(value, list): return value return [] def pick_bbox(obj): for key in (bbox, box, points): if key in obj: return obj[key] raise KeyError(f找不到框字段: {obj.keys()}) def pick_label(obj): for key in (label, category, class_name, name): if key in obj: return str(obj[key]).strip() raise KeyError(f找不到類別字段: {obj.keys()}) def convert_one(json_path, img_path, out_txt, fmtxyxy): data json.loads(Path(json_path).read_text(encodingutf-8)) w, h Image.open(img_path).size lines [] for obj in iter_objects(data): label pick_label(obj) if label not in CLASS_MAP: raise ValueError(f未知類別 {label}先補(bǔ) CLASS_MAP) x1, y1, x2, y2 to_xyxy(pick_bbox(obj), fmt, w, h) # 歸一化并保留6位小數(shù)減少txt體積 cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{CLASS_MAP[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) Path(out_txt).write_text(\n.join(lines), encodingutf-8)iter_objects按常見(jiàn)鍵名找標(biāo)注列表找到第一個(gè)列表就返回避免同一份json里多個(gè)字段重復(fù)。pick_bbox和pick_label把字段差異收斂到兩個(gè)函數(shù)后面換數(shù)據(jù)集只改這里。CLASS_MAP必須顯式定義未知類別直接拋錯(cuò)這是血淚經(jīng)驗(yàn)靜默跳過(guò)會(huì)讓某類樣本在訓(xùn)練集里消失評(píng)估時(shí)才發(fā)現(xiàn)召回上不去。fmt要和字段探測(cè)結(jié)果一致傳錯(cuò)就是框整體偏移??諛?biāo)注時(shí)lines為空仍然寫出空txt負(fù)樣本才有記錄。提示轉(zhuǎn)換前先用一份樣本打印前5個(gè)框的原始值和轉(zhuǎn)換后的歸一化值肉眼確認(rèn)中心點(diǎn)落在車牌上再跑批量。3.3 轉(zhuǎn)COCO時(shí)最容易錯(cuò)的兩個(gè)字段COCO json有兩個(gè)字段最容易翻車image_id和category_id。image_id是圖像記錄的唯一整數(shù)annotation里的image_id必須和images里的id一致category_id通常從1開始YOLO習(xí)慣從0開始直接混用會(huì)讓類別錯(cuò)位。bbox寫絕對(duì)像素[x, y, w, h]area寫w * h不要寫歸一化值。coco { images: [], annotations: [], categories: [{id: 1, name: plate}], } ann_id 1 for image_id, (json_path, img_path) in enumerate(pairs, start1): w, h Image.open(img_path).size coco[images].append({id: image_id, file_name: str(img_path), width: w, height: h}) data json.loads(Path(json_path).read_text(encodingutf-8)) for obj in iter_objects(data): x1, y1, x2, y2 to_xyxy(pick_bbox(obj), fmt, w, h) bw, bh x2 - x1, y2 - y1 coco[annotations].append({ id: ann_id, image_id: image_id, # 必須等于 images 里的 id category_id: 1, # 與 categories 里的 id 對(duì)齊 bbox: [x1, y1, bw, bh], # 絕對(duì)像素不是歸一化 area: bw * bh, iscrowd: 0, }) ann_id 1這段代碼里image_id用enumerate生成保證唯一category_id固定1和categories一致。bbox是[x1, y1, bw, bh]不是[x1, y1, x2, y2]這是COCO和YOLO最大的口徑差。area用于評(píng)估時(shí)區(qū)分小中大目標(biāo)黃牌車牌大多落在小目標(biāo)區(qū)間area寫錯(cuò)會(huì)影響分層指標(biāo)。iscrowd0表示普通實(shí)例黃牌檢測(cè)一般不用crowd標(biāo)注。3.4 轉(zhuǎn)換后回讀自檢三類斷言轉(zhuǎn)換腳本跑完不等于正確。我一般做三類斷言每張圖有對(duì)應(yīng)txt、每行五列且類別id在集合內(nèi)、反算像素框與原始json框的IoU大于0.999。IoU斷言能抓住中心點(diǎn)寬高和左上右下混用的問(wèn)題比肉眼看幾張圖可靠。def yolo_to_xyxy(line, w, h): cls_id, cx, cy, bw, bh line.split() cx, cy, bw, bh map(float, (cx, cy, bw, bh)) x1 (cx - bw / 2) * w y1 (cy - bh / 2) * h x2 (cx bw / 2) * w y2 (cy bh / 2) * h return int(cls_id), x1, y1, x2, y2 def iou(a, b): ax1, ay1, ax2, ay2 a bx1, by1, bx2, by2 b ix1, iy1 max(ax1, bx1), max(ay1, by1) ix2, iy2 min(ax2, bx2), min(ay2, by2) iw, ih max(0.0, ix2 - ix1), max(0.0, iy2 - iy1) inter iw * ih union (ax2 - ax1) * (ay2 - ay1) (bx2 - bx1) * (by2 - by1) - inter return inter / union if union 0 else 0.0回讀時(shí)對(duì)每張圖讀原始json框和txt反算框逐框算IoU。閾值設(shè)0.999因?yàn)橹徊罡↑c(diǎn)舍入低于這個(gè)值就打印原始框、反算框和fmt十有八九是口徑傳錯(cuò)。類別id也要斷言在set(CLASS_MAP.values())里防止把plate寫成Plate后映射失敗??誸xt要允許存在但要在檢查表里標(biāo)記為負(fù)樣本不要當(dāng)成轉(zhuǎn)換失敗。4. 黃牌檢測(cè)訓(xùn)練基線配置、增強(qiáng)邊界與誤檢分析4.1 單類檢測(cè)還是多類檢測(cè)先定標(biāo)簽語(yǔ)義黃牌車牌檢測(cè)數(shù)據(jù)集先定標(biāo)簽語(yǔ)義。如果json里只有plate就做單類檢測(cè)輸出一個(gè)框顏色信息交給后處理或單獨(dú)分類器。如果json里明確分了yellow_plate、blue_plate可以保留多類但類別順序必須和CLASS_MAP一致。不要因?yàn)閳D像是黃牌就把顏色塞進(jìn)類別名除非標(biāo)注本身這么定義。單類的好處是減少類間混淆黃牌顏色先驗(yàn)在推理階段做二次校驗(yàn)更靈活。單類訓(xùn)練時(shí)names只寫0: plate評(píng)估只看一類AP。多類訓(xùn)練時(shí)每個(gè)類別都要有足夠樣本否則小類別會(huì)把整體mAP拉低。黃牌場(chǎng)景里如果混入少量藍(lán)牌負(fù)樣本可以把藍(lán)牌車牌標(biāo)成背景不框或者單獨(dú)加一類取決于業(yè)務(wù)要不要拒絕藍(lán)牌。標(biāo)簽語(yǔ)義定不下來(lái)后面轉(zhuǎn)換、訓(xùn)練、評(píng)估都會(huì)反復(fù)。4.2 基線訓(xùn)練配置與黃牌小目標(biāo)參數(shù)基線配置先跑通不要一上來(lái)堆增強(qiáng)。黃牌車牌在卡口圖里往往只占幾十像素imgsz比模型大小更關(guān)鍵。我一般從960起步顯存不夠再降batch按顯存調(diào)lr0用常規(guī)值patience留早停。數(shù)據(jù)配置文件只寫路徑和類別不寫花哨參數(shù)。path: /data/plate_yellow train: images/train val: images/val names: 0: plateyolo detect train \ datadata.yaml \ model預(yù)訓(xùn)練權(quán)重 \ imgsz960 \ epochs100 \ batch16 \ lr00.01 \ patience20 \ workers8 \ projectruns/plate \ nameyellow_baselinepath是數(shù)據(jù)集根目錄train和val寫相對(duì)路徑避免換機(jī)器后失效。imgsz960是為了保住小目標(biāo)細(xì)節(jié)黃牌車牌在640下可能只剩十幾個(gè)像素。batch16不是固定值顯存不夠就降到8同時(shí)按比例調(diào)lr0。patience20表示驗(yàn)證指標(biāo)20輪不升就停省時(shí)間。workers8按CPU核數(shù)調(diào)太高反而拖慢數(shù)據(jù)加載。第一次訓(xùn)練先關(guān)掉強(qiáng)增強(qiáng)確認(rèn)loss正常下降再加。增強(qiáng)參數(shù)要克制。馬賽克增強(qiáng)會(huì)把多張圖拼在一起小目標(biāo)可能被切碎黃牌顏色對(duì)HSV增強(qiáng)敏感色相擾動(dòng)過(guò)大可能把黃色推到橙色。常見(jiàn)做法是先把馬賽克關(guān)小或關(guān)掉隨機(jī)縮放范圍不要壓得太狠HSV的飽和度和明度擾動(dòng)保留色相擾動(dòng)調(diào)低。具體值看驗(yàn)證集不要照搬。參數(shù)基線建議調(diào)整理由imgsz960起黃牌車牌小分辨率比模型深度更直接batch顯存允許的最大值小目標(biāo)梯度噪聲大batch太小不穩(wěn)定lr00.01附近預(yù)訓(xùn)練權(quán)重微調(diào)過(guò)大容易震蕩mosaic先低后高小目標(biāo)被切碎后反而學(xué)不到完整車牌hsv_h調(diào)低黃色色相范圍窄擾動(dòng)過(guò)大改變顏色語(yǔ)義patience20到30小目標(biāo)指標(biāo)波動(dòng)大早停太早會(huì)誤殺注意驗(yàn)證集指標(biāo)不升不一定是模型不行先回看標(biāo)注框是否偏移、驗(yàn)證集是否和訓(xùn)練集同場(chǎng)景。數(shù)據(jù)層的問(wèn)題調(diào)參救不回來(lái)。4.3 評(píng)估指標(biāo)怎么看mAP之外還要看召回分層評(píng)估命令把置信度閾值壓低先看PR曲線全貌再定業(yè)務(wù)閾值。黃牌檢測(cè)不能只看mAP50小目標(biāo)多的時(shí)候mAP50-95會(huì)明顯低這是正?,F(xiàn)象。要看召回按框面積分層小框召回夠不夠夜間子集召回掉多少誤檢集中在什么背景。yolo detect val \ modelruns/plate/yellow_baseline/weights/best.pt \ datadata.yaml \ imgsz960 \ conf0.001 \ iou0.6conf0.001是為了讓PR曲線覆蓋低置信度區(qū)間評(píng)估階段不怕框多怕漏。iou0.6是NMS和匹配的常用值黃牌車牌框比較規(guī)整0.5到0.6都可以試??唇Y(jié)果時(shí)先看召回再看精確率。業(yè)務(wù)現(xiàn)場(chǎng)漏檢比誤檢更麻煩閾值可以偏低誤檢交給二次校驗(yàn)。指標(biāo)關(guān)注點(diǎn)異常信號(hào)mAP50框大致重合時(shí)的綜合表現(xiàn)高但現(xiàn)場(chǎng)漏檢查驗(yàn)證集泄漏mAP50-95高IoU下的定位精度明顯低查小目標(biāo)和框偏移Precision誤檢比例低查黃色廣告牌、反光條Recall漏檢比例低查夜間、遮擋、小目標(biāo)分層AP按面積/場(chǎng)景拆開小框AP遠(yuǎn)低于大框調(diào)imgsz和增強(qiáng)評(píng)估完不要只看一個(gè)總分。把驗(yàn)證集按白天/夜間、近景/遠(yuǎn)景、干凈/遮擋拆開分別跑一遍。黃牌車牌檢測(cè)數(shù)據(jù)集如果夜間樣本少整體mAP會(huì)被白天拉高夜間召回可能很難看。拆開看才知道下一步是補(bǔ)數(shù)據(jù)還是調(diào)增強(qiáng)。4.4 誤檢分析黃色先驗(yàn)不能當(dāng)硬規(guī)則黃牌檢測(cè)的誤檢經(jīng)常來(lái)自黃色物體廣告牌、反光條、收費(fèi)亭、工程車車身。顏色先驗(yàn)可以做二次校驗(yàn)但不能在檢測(cè)前硬過(guò)濾。硬過(guò)濾會(huì)把夜間暖光下的黃牌、泥污車牌、偏色圖像直接刪掉漏檢比誤檢更難補(bǔ)。我一般把檢測(cè)框裁出來(lái)統(tǒng)計(jì)框內(nèi)黃色像素比例看誤檢和真檢的分布是否可分再?zèng)Q定閾值。import numpy as np from PIL import Image def crop_yellow_ratio(img_path, xyxy): img Image.open(img_path).convert(RGB) x1, y1, x2, y2 map(int, xyxy) crop np.asarray(img.crop((x1, y1, x2, y2))).astype(np.float32) / 255.0 if crop.size 0: return 0.0 r, g, b crop[..., 0], crop[..., 1], crop[..., 2] mask (r 0.45) (g 0.35) (b 0.45) (r b 0.12) (g b 0.08) return float(mask.mean())這個(gè)函數(shù)只做統(tǒng)計(jì)不直接過(guò)濾。把真檢和誤檢的黃色比例分別畫分布如果真檢集中在高分段、誤檢集中在低分段可以設(shè)一個(gè)保守閾值做二次排序而不是一刀切。閾值取分位數(shù)比如保留真檢95%召回對(duì)應(yīng)的下限再看誤檢能壓掉多少。夜間和逆光樣本要單獨(dú)看否則閾值會(huì)被白天樣本帶偏。5. 避坑與排查json標(biāo)簽落地時(shí)最容易翻車的五件事5.1 框整體偏移中心寬高與左上右下混用現(xiàn)象很典型訓(xùn)練loss正常下降驗(yàn)證集mAP也有零點(diǎn)幾但推理框整體往左上或右下偏車牌只框到一半。原因是json里存的是中心點(diǎn)寬高cx, cy, bw, bh轉(zhuǎn)換腳本按左上右下x1, y1, x2, y2讀中心點(diǎn)被當(dāng)成左上角框整體偏移半個(gè)寬高。黃牌車牌框本來(lái)就小偏一點(diǎn)就掉出車牌。解決不靠肉眼抽查靠回讀斷言。轉(zhuǎn)換后把txt反算成像素框和原始json框算IoU低于0.999就報(bào)錯(cuò)。to_xyxy里把fmt作為必填參數(shù)不允許默認(rèn)值逼著調(diào)用方確認(rèn)口徑。批量轉(zhuǎn)換前先拿一份樣本打印原始框和反算框確認(rèn)中心點(diǎn)落在車牌中心。字段探測(cè)階段看到bbox是四個(gè)數(shù)還不夠要看數(shù)值范圍如果數(shù)值都小于1可能是歸一化如果寬高明顯小于坐標(biāo)可能是寬高格式。5.2 圖像尺寸字段與實(shí)際讀圖不一致現(xiàn)象是歸一化后的框整體縮放錯(cuò)位或者同一批數(shù)據(jù)里部分圖正常、部分圖偏移。原因是json里的width、height是標(biāo)注時(shí)的尺寸圖像后來(lái)被重新縮放、裁剪或者手機(jī)EXIF方向?qū)е伦x圖尺寸旋轉(zhuǎn)json沒(méi)同步更新。用json尺寸做歸一化分母就會(huì)把錯(cuò)誤放大到所有框。解決是以真實(shí)讀圖尺寸為準(zhǔn)不用json寬高做分母。讀圖時(shí)先做EXIF方向校正再取size。如果json寬高和讀圖尺寸差異超過(guò)1像素記錄下來(lái)抽樣看是縮放還是旋轉(zhuǎn)。批量腳本里不要把json寬高當(dāng)唯一真值它只能做交叉校驗(yàn)。遇到旋轉(zhuǎn)圖先把圖像轉(zhuǎn)正再標(biāo)注或轉(zhuǎn)換不要一邊轉(zhuǎn)一邊用舊尺寸。from PIL import Image, ImageOps def read_size(img_path): img Image.open(img_path) img ImageOps.exif_transpose(img) # 先按EXIF轉(zhuǎn)正 return img.sizeImageOps.exif_transpose返回轉(zhuǎn)正后的圖像size才是視覺(jué)上的寬高。黃牌車牌如果橫豎方向不一致框的寬高比會(huì)異常統(tǒng)計(jì)時(shí)很容易發(fā)現(xiàn)。5.3 訓(xùn)練驗(yàn)證集連拍泄漏導(dǎo)致指標(biāo)虛高現(xiàn)象是驗(yàn)證集mAP很高換一個(gè)門口或換一天就掉得厲害。原因是同一輛車、同一個(gè)場(chǎng)景的連拍圖被隨機(jī)分到訓(xùn)練集和驗(yàn)證集模型記住了背景和車牌字符不是學(xué)會(huì)定位。黃牌車牌檢測(cè)數(shù)據(jù)集如果按文件隨機(jī)劃分相鄰幀幾乎一模一樣指標(biāo)虛高是必然的。解決是按場(chǎng)景分組劃分。用目錄名、文件名前綴、拍攝點(diǎn)位、時(shí)間段做group_key保證同一組只進(jìn)一個(gè)split。劃分后檢查訓(xùn)練集和驗(yàn)證集的框面積分位數(shù)、黃色比例分位數(shù)差異大就換分組鍵。連拍圖還可以用感知哈?;蛭募笮〗迫ブ氐灰豢课募蛱?hào)序號(hào)可能跨場(chǎng)景重復(fù)。5.4 類別名大小寫與中英文不一致現(xiàn)象是轉(zhuǎn)換時(shí)報(bào)未知類別或者訓(xùn)練時(shí)類別數(shù)對(duì)不上評(píng)估混淆矩陣?yán)锍霈F(xiàn)空類。原因是json里同一類寫成plate、Plate、車牌、車牌CLASS_MAP只寫了一個(gè)鍵。靜默跳過(guò)未知類別會(huì)讓樣本悄悄消失訓(xùn)練集里某類樣本變少召回上不去。解決是先統(tǒng)計(jì)所有類別名再定映射表。統(tǒng)計(jì)時(shí)用strip()去空格但不要自動(dòng)轉(zhuǎn)小寫先把原始值列出來(lái)人工確認(rèn)哪些是同一類。CLASS_MAP里未知類別必須拋錯(cuò)不能continue。映射表單獨(dú)存一份json或csv轉(zhuǎn)換腳本和評(píng)估腳本共用避免兩處各寫一套。from collections import Counter counter Counter() for obj in all_objects: counter[pick_label(obj).strip()] 1 print(counter)Counter輸出原始類別名和出現(xiàn)次數(shù)先看有沒(méi)有大小寫變體、中英文混寫、尾部空格。確認(rèn)后再寫映射比如{plate: 0, Plate: 0, 車牌: 0}但訓(xùn)練時(shí)只輸出一個(gè)id。映射表要版本化后面加類別時(shí)先改表再轉(zhuǎn)格式。5.5 顏色閾值硬過(guò)濾造成漏檢現(xiàn)象是白天效果不錯(cuò)夜間、逆光、泥污車牌漏檢明顯。原因是在檢測(cè)前用固定HSV閾值過(guò)濾黃色區(qū)域夜間暖光把黃牌照成橙色泥污把飽和度拉低閾值一卡就沒(méi)了。黃牌顏色先驗(yàn)有用但它是統(tǒng)計(jì)特征不是硬門禁。解決是把顏色放到檢測(cè)后做二次校驗(yàn)用分位數(shù)定閾值不用固定值。先統(tǒng)計(jì)真檢框的黃色比例分布取保留95%真檢的下限再看誤檢能壓掉多少。夜間樣本單獨(dú)統(tǒng)計(jì)閾值可以放寬。檢測(cè)器主輸出仍然保留顏色只做排序或復(fù)核不直接刪除框。這樣即使顏色判斷錯(cuò)了還有檢測(cè)框兜底。6. 進(jìn)階用法把黃牌檢測(cè)接進(jìn)業(yè)務(wù)流程的三個(gè)技巧6.1 切片推理處理小目標(biāo)黃牌車牌在廣角卡口圖里可能只占幾十像素整圖縮放到960再推理小目標(biāo)只剩幾個(gè)像素。我一般用切片推理把原圖按重疊窗口切成若干塊分別推理后再把框映射回原圖坐標(biāo)做一次NMS。切片大小和重疊率要看車牌像素分布太小會(huì)增加誤檢太大起不到放大作用。常見(jiàn)做法是切片邊長(zhǎng)取原圖短邊的三分之一到二分之一重疊20%左右先在一批驗(yàn)證圖上比較整圖推理和切片推理的小框召回再定參數(shù)。切片推理耗時(shí)高適合離線復(fù)核或重點(diǎn)點(diǎn)位不適合所有幀全量跑。6.2 顏色先驗(yàn)做二次校驗(yàn)而不是硬門禁顏色先驗(yàn)放在檢測(cè)后用法是給每個(gè)框算黃色比例再結(jié)合置信度做排序或復(fù)核。不要設(shè)一個(gè)固定閾值直接刪框而是看真檢和誤檢的分布重疊區(qū)。重疊區(qū)大的樣本送人工復(fù)核重疊區(qū)小的按置信度處理。夜間、逆光、泥污單獨(dú)分組統(tǒng)計(jì)閾值分組設(shè)置。這樣顏色是加分項(xiàng)不是一票否決。黃牌車牌檢測(cè)數(shù)據(jù)集的黃牌比例高顏色特征確實(shí)有用但業(yè)務(wù)現(xiàn)場(chǎng)的偏色和遮擋會(huì)讓固定閾值翻車。6.3 用json回寫做人工復(fù)核閉環(huán)模型跑完不要只保存txt把檢測(cè)結(jié)果回寫成和原始json同結(jié)構(gòu)的文件帶上圖像路徑、框、置信度、顏色比例人工復(fù)核時(shí)直接看json和裁圖。復(fù)核確認(rèn)的框再回流到訓(xùn)練集形成閉環(huán)?;貙憰r(shí)保留原始image_id或文件名不要打亂順序置信度低和顏色比例處于重疊區(qū)的樣本優(yōu)先送審。這樣每次復(fù)核都在補(bǔ)數(shù)據(jù)而不是一次性驗(yàn)收。從那以后我每次拿到帶json標(biāo)簽的數(shù)據(jù)集都先把字段探測(cè)、坐標(biāo)校驗(yàn)、回讀斷言跑一遍再談?dòng)?xùn)練。黃牌車牌檢測(cè)數(shù)據(jù)集的價(jià)值不在圖像多而在標(biāo)簽口徑能不能復(fù)現(xiàn)。需要拿這份資源練手時(shí)按資源頁(yè)的獲取方式取原始包先跑字段校驗(yàn)和轉(zhuǎn)換自檢再開訓(xùn)練別急著上大模型。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取