牌識(shí)別實(shí)戰(zhàn):從數(shù)據(jù)標(biāo)注到部署的完整指南)
簡(jiǎn)介基于YOLOv8實(shí)現(xiàn)車(chē)牌檢測(cè)與車(chē)牌識(shí)別的完整項(xiàng)目源碼及詳細(xì)流程教程面向計(jì)算機(jī)視覺(jué)開(kāi)發(fā)者、算法研究人員和智能交通項(xiàng)目從業(yè)者解決從車(chē)牌定位到字符識(shí)別的端到端落地問(wèn)題。資源包共55個(gè)文件壓縮包約32.93MB涵蓋jpg/png圖片樣本、ipynb交互式分析筆記、pt模型權(quán)重、mp4演示視頻、yaml配置與README說(shuō)明文檔等類(lèi)型豐富且目錄結(jié)構(gòu)清晰當(dāng)前已有298人學(xué)習(xí)下載。讀者可借助項(xiàng)目中的車(chē)牌與車(chē)輛檢測(cè)Notebook、預(yù)訓(xùn)練模型和演示視頻快速?gòu)?fù)現(xiàn)識(shí)別流程并通過(guò)教程全面了解數(shù)據(jù)集準(zhǔn)備、模型訓(xùn)練與性能評(píng)估等關(guān)鍵步驟。整體思路結(jié)合YOLOv8的多尺度特征融合與高效損失函數(shù)設(shè)計(jì)兼顧檢測(cè)速度與精度并對(duì)復(fù)雜光照、角度變化等場(chǎng)景進(jìn)行了針對(duì)性處理可作為畢業(yè)設(shè)計(jì)、競(jìng)賽實(shí)踐或工業(yè)級(jí)車(chē)牌識(shí)別系統(tǒng)開(kāi)發(fā)的直接參考。1. 車(chē)牌識(shí)別項(xiàng)目實(shí)戰(zhàn)YOLOv8檢測(cè)加識(shí)別這條線(xiàn)值不值得上手一個(gè) YOLOv8 車(chē)牌識(shí)別項(xiàng)目聽(tīng)上去是兩個(gè)任務(wù)先定位車(chē)牌再把車(chē)牌號(hào)讀出來(lái)。很多教程包喜歡把整套流程封裝成幾十個(gè) py 文件新手解壓后最容易卡在“demo 能跑換自己的圖片就翻車(chē)”。這篇文章只講一件事如何用 YOLOv8 把車(chē)牌檢測(cè)和車(chē)牌識(shí)別真正跑通。從環(huán)境搭建、數(shù)據(jù)標(biāo)注、訓(xùn)練、后處理到部署每一步都給可復(fù)現(xiàn)的命令和參數(shù)適合剛做完 YOLOv8 目標(biāo)檢測(cè)練習(xí)、想轉(zhuǎn)一個(gè)落地小項(xiàng)目的開(kāi)發(fā)者。你可以把它當(dāng)成第一個(gè)能放到邊緣盒子上演示的視覺(jué)方案也可以當(dāng)作理解兩段式識(shí)別架構(gòu)的起步案例。只要手里有一批帶車(chē)牌的圖片和一塊普通顯卡就能跟著做下來(lái)。2. 車(chē)牌識(shí)別架構(gòu)拆解YOLOv8做檢測(cè)、再做字符識(shí)別兩條路線(xiàn)怎么選2.1 兩段式才是落地主流檢測(cè)車(chē)牌和識(shí)別字符分開(kāi)做的原因拿到車(chē)牌識(shí)別項(xiàng)目先別急著訓(xùn)練一個(gè)模型同時(shí)輸出車(chē)牌位置和字符串。無(wú)論標(biāo)題里怎么寫(xiě)實(shí)際工程里更常見(jiàn)的做法是兩段式Y(jié)OLOv8 先檢測(cè)車(chē)牌再對(duì)裁剪后的車(chē)牌圖片做文字識(shí)別。原因有兩點(diǎn)。第一兩段的數(shù)據(jù)組織難度完全不同。車(chē)牌檢測(cè)只需要一類(lèi)plate幾百?gòu)垐D就能見(jiàn)效車(chē)牌識(shí)別要覆蓋 31 個(gè)漢字、24 個(gè)字母和 10 個(gè)數(shù)字還要應(yīng)對(duì)藍(lán)牌、綠牌、黃牌、白牌和雙層車(chē)牌字符級(jí)標(biāo)注工作量更大。第二錯(cuò)誤定位方便。如果識(shí)別結(jié)果亂碼我可以在檢測(cè)框輸出位置直接裁剪原圖判斷是檢測(cè)框偏了還是文字識(shí)別錯(cuò)了一段式模型一旦出錯(cuò)很難判斷問(wèn)題出在哪個(gè)環(huán)節(jié)。車(chē)牌識(shí)別算法本身也有多種實(shí)現(xiàn)。常見(jiàn)的是用 YOLOv8 做字符檢測(cè)即第二個(gè)模型在裁剪圖上檢測(cè)每個(gè)漢字、字母、數(shù)字的位置并同時(shí)給出類(lèi)別另一種是檢測(cè)車(chē)牌后接 CRNN 或 PaddleOCR 做整牌識(shí)別。標(biāo)題里“基于 YOLOv8 實(shí)現(xiàn)車(chē)牌檢測(cè)車(chē)牌識(shí)別算法”更貼近前一種兩個(gè)模型都在 Ultralytics 生態(tài)里訓(xùn)練、導(dǎo)出、部署的接口一致學(xué)習(xí)成本最低。還要說(shuō)清楚一個(gè)容易踩的誤區(qū)不要指望 YOLOv8 一個(gè)模型直接輸出字符串。YOLO 輸出的是框和類(lèi)別車(chē)牌識(shí)別本質(zhì)上是變長(zhǎng)序列識(shí)別字符之間有強(qiáng)順序關(guān)系。即使把每個(gè)字符當(dāng)成獨(dú)立目標(biāo)檢測(cè)出來(lái)也必須在后處理里做排序、去重和長(zhǎng)度校驗(yàn)。這部分排序邏輯就是整個(gè)項(xiàng)目里最容易寫(xiě)錯(cuò)、也最值得專(zhuān)門(mén)寫(xiě)測(cè)試的地方。2.2 最小可跑環(huán)境Ubuntu 20.04 CPU與GTX 1660 Ti的YOLOv8配置環(huán)境配置不用一步到位。常見(jiàn)做法是先在一臺(tái) Ubuntu 20.04 機(jī)器上把 CPU 版本跑通確認(rèn)代碼鏈路沒(méi)有斷再換到 NVIDIA 顯卡上訓(xùn)練。這里給兩套我常用的組合用途推薦環(huán)境說(shuō)明流程驗(yàn)證Ubuntu 20.04 CPU Python 3.10只跑推理和少量訓(xùn)練imgsz 降到 320模型訓(xùn)練GTX 1660 Ti 6GB CUDA 11.8適合 YOLOv8n、YOLOv8sbatch 控制在 8 左右安裝命令很直接# 推薦 Python 3.10Ultralytics 對(duì) 3.8~3.11 都支持3.10 兼容性最穩(wěn) conda create -n lpr python3.10 -y conda activate lpr pip install ultralytics opencv-python # 驗(yàn)證安裝能打印出模型結(jié)構(gòu)說(shuō)明環(huán)境正常 python -c from ultralytics import YOLO; model YOLO(yolov8n.pt)這里yolov8n.pt是 YOLOv8 官方預(yù)訓(xùn)練權(quán)重。如果安裝時(shí)網(wǎng)絡(luò)受限可以先把權(quán)重文件放到當(dāng)前目錄再執(zhí)行上面的驗(yàn)證命令。CPU 環(huán)境不需要安裝 CUDAUltralytics 會(huì)自動(dòng)退回 CPUExecutionProviderGPU 環(huán)境建議先執(zhí)行nvidia-smi確認(rèn)驅(qū)動(dòng)版本再裝對(duì)應(yīng) CUDA。GTX 1660 Ti 是 6GB 顯存訓(xùn)練 YOLOv8n 時(shí)可以開(kāi)batch8, imgsz640換到 YOLOv8s 后建議降到batch4。千萬(wàn)不要直接上 YOLOv8l顯存不夠會(huì)出現(xiàn)難排查的CUDA out of memory而且對(duì)車(chē)牌這類(lèi)小目標(biāo)提升有限。2.3 最小推理腳本解壓源碼后先確認(rèn)檢測(cè)環(huán)節(jié)是否通拿到項(xiàng)目源碼包后我一般不會(huì)立刻看完整教程而是先找一個(gè)測(cè)試圖片把檢測(cè)模型的最小推理跑通。這樣能快速排除環(huán)境問(wèn)題也能確認(rèn)best.pt是否真的存在。from ultralytics import YOLO # 換成你訓(xùn)練好的車(chē)牌檢測(cè)權(quán)重沒(méi)有就用官方y(tǒng)olov8n.pt做流程驗(yàn)證 model YOLO(best.pt) result model.predict( test.jpg, imgsz640, # 現(xiàn)場(chǎng)圖片車(chē)牌較小用640視頻流可降到416 conf0.4, # 漏檢多就降到0.25誤檢多就升到0.5 device0, # CPU環(huán)境改為 devicecpu verboseFalse ) # 拿到檢測(cè)框后自己打印不要只依賴(lài)可視化結(jié)果 boxes result[0].boxes.xyxy.cpu().numpy() for x1, y1, x2, y2 in boxes: print(fplate: {int(x1)}, {int(y1)}, {int(x2)}, {int(y2)})conf的調(diào)整需要現(xiàn)場(chǎng)判斷。車(chē)牌檢測(cè)的置信度通常不會(huì)像通用物體那么高逆光、模糊、夜間場(chǎng)景下 0.4 都可能漏檢但如果道閘場(chǎng)景里經(jīng)常把車(chē)燈當(dāng)車(chē)牌就要上調(diào)到 0.5 以上。第一版先不追求最優(yōu)只要框能落在車(chē)牌周?chē)竺娴淖R(shí)別流程才有意義。3. 車(chē)牌數(shù)據(jù)集與訓(xùn)練環(huán)節(jié)從LabelMe標(biāo)注到Y(jié)OLOv8訓(xùn)練自己的車(chē)牌模型3.1 數(shù)據(jù)從哪來(lái)開(kāi)源數(shù)據(jù)集、自采補(bǔ)拍與多場(chǎng)景擴(kuò)樣訓(xùn)練車(chē)牌檢測(cè)200 到 500 張圖就足夠出第一個(gè)可用版本。網(wǎng)上有公開(kāi)的中國(guó)車(chē)牌檢測(cè)數(shù)據(jù)集也有類(lèi)似 CCPD 這種偏停車(chē)場(chǎng)場(chǎng)景的藍(lán)牌數(shù)據(jù)集。它們適合做預(yù)訓(xùn)練但直接拿預(yù)訓(xùn)練權(quán)重到現(xiàn)場(chǎng)往往不夠因?yàn)椴蓸咏嵌瓤撮T(mén)禁、路側(cè)和停車(chē)場(chǎng)差別很大。我的做法是三步先用公開(kāi)數(shù)據(jù)集把模型結(jié)構(gòu)驗(yàn)證一遍再補(bǔ)拍自己項(xiàng)目里的真實(shí)角度照片最后從現(xiàn)場(chǎng)視頻里抽幀篩選。補(bǔ)拍時(shí)要注意一個(gè)容易被忽略的問(wèn)題同一輛車(chē)在過(guò)去幾秒內(nèi)會(huì)被連續(xù)抽到十幾幀這些幀高度相似。如果不做去重訓(xùn)練集和驗(yàn)證集里可能同時(shí)出現(xiàn)同一輛車(chē)mAP 虛高換新場(chǎng)景就翻車(chē)。擴(kuò)樣時(shí)不要只做隨機(jī)翻轉(zhuǎn)。車(chē)牌上的漢字、字母翻轉(zhuǎn)后會(huì)失去語(yǔ)義所以水平翻轉(zhuǎn)只能用于檢測(cè)階段且要謹(jǐn)慎。比較可靠的增強(qiáng)是輕微旋轉(zhuǎn)、亮度擾動(dòng)、運(yùn)動(dòng)模糊、仿射變換和隨機(jī)遮擋。把這些增強(qiáng)配置寫(xiě)進(jìn) Ultralytics 的augment參數(shù)比自己在數(shù)據(jù)預(yù)處理里手工拼更省事。3.2 LabelMe JSON轉(zhuǎn)YOLO格式轉(zhuǎn)換腳本與四個(gè)邊界坑LabelMe 是車(chē)牌標(biāo)注里很常用的工具但它保存的是 JSON 格式Y(jié)OLOv8 訓(xùn)練需要的是 YOLO txt 格式。每個(gè)標(biāo)注文件轉(zhuǎn)換邏輯不復(fù)雜問(wèn)題常出在細(xì)節(jié)上。import json from pathlib import Path def labelme2yolo(json_path, out_dir, class_names): 把LabelMe JSON轉(zhuǎn)成YOLO txt。 class_names: 訓(xùn)練YAML里names的列表順序必須一致。 with open(json_path, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] txt_path Path(out_dir) / (Path(json_path).stem .txt) lines [] for shape in data[shapes]: label shape[label] if label not in class_names: continue # 用所有標(biāo)注點(diǎn)的外接矩形而不是假設(shè)只有2個(gè)點(diǎn) pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) # YOLO格式類(lèi)別 中心x 中心y 寬 高全部歸一化到0~1 cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_names.index(label)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) Path(txt_path).write_text(\n.join(lines), encodingutf-8) # 用法示例假設(shè)當(dāng)前目錄下是LabelMe導(dǎo)出的JSON for f in Path(labelme_json).glob(*.json): labelme2yolo(f, labels, [plate])這個(gè)腳本里有四個(gè)邊界坑值得專(zhuān)門(mén)說(shuō)。第一LabelMe 里畫(huà)車(chē)牌時(shí)不要隨手畫(huà)成四點(diǎn)四邊形然后把外接矩形交給 YOLO。對(duì)于傾斜但角度不大的車(chē)牌外接矩形可以接受但一旦視角偏得厲害外接矩形會(huì)包進(jìn)大量車(chē)身邊緣影響檢測(cè)精度。建議標(biāo)注時(shí)保留四個(gè)角點(diǎn)信息另存一份points.json用于車(chē)牌識(shí)別階段的透視矯正。第二類(lèi)別順序一旦訓(xùn)練時(shí)改了之前生成的 txt 全部作廢。很多項(xiàng)目翻車(chē)就是因?yàn)閏lass_names是[blue, plate]后面又改成[plate]導(dǎo)致所有訓(xùn)練樣本的類(lèi)別標(biāo)簽錯(cuò)位。第三空標(biāo)注圖片不要?jiǎng)h除?,F(xiàn)場(chǎng)圖片里沒(méi)有車(chē)牌的情況很常見(jiàn)這類(lèi)圖沒(méi)有 txt 文件放進(jìn)images/train目錄即可YOLOv8 會(huì)當(dāng)負(fù)樣本處理。如果所有訓(xùn)練圖都有車(chē)牌模型很容易把背景里的其他矩形區(qū)域誤判成車(chē)牌。第四轉(zhuǎn)換后一定要做一次反向校驗(yàn)。最簡(jiǎn)單的辦法是隨機(jī)挑 20 張圖把 txt 讀出來(lái)畫(huà)框看框是否貼合車(chē)牌。這一步能省下后面大量排查時(shí)間。3.3 訓(xùn)練自己的車(chē)牌檢測(cè)模型train命令、YAML與關(guān)鍵參數(shù)數(shù)據(jù)集目錄建議按 Ultralytics 默認(rèn)風(fēng)格組織lpr_dataset/ images/ train/ val/ labels/ train/ val/對(duì)應(yīng)的 YAML 文件保持簡(jiǎn)潔# plate.yaml path: /home/user/lpr_dataset train: images/train val: images/val names: 0: plate訓(xùn)練命令如下# 基于官方預(yù)訓(xùn)練權(quán)重繼續(xù)訓(xùn)練收斂速度比從零開(kāi)始快很多 yolo detect train dataplate.yaml modelyolov8n.pt \ epochs80 imgsz640 batch8 device0 patience15 \ projectruns/lpr nameplate_v8nepochs80對(duì)車(chē)牌檢測(cè)足夠不需要一上來(lái)就訓(xùn) 300 輪。patience15表示連續(xù) 15 輪驗(yàn)證集沒(méi)有提升就提前停止避免無(wú)效等待。batch8對(duì)應(yīng) GTX 1660 Ti 6GB 顯存如果你的卡顯存更大可以提高到 16CPU 訓(xùn)練時(shí)把device0改成devicecpu同時(shí)把epochs降到 20只用來(lái)驗(yàn)證流程不要追求效果。訓(xùn)練結(jié)束后很多人想畫(huà)損失函數(shù)曲線(xiàn)圖不需要去翻黑匣子。Ultralytics 會(huì)把每次迭代的指標(biāo)寫(xiě)到runs/lpr/plate_v8n/results.csv直接用 pandas 讀出來(lái)畫(huà)圖即可import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/lpr/plate_v8n/results.csv) # csv列名首尾可能帶有空格先清理 df.columns [c.strip() for c in df.columns] plt.plot(df[epoch], df[train/box_loss], labeltrain box loss) plt.plot(df[epoch], df[val/box_loss], labelval box loss) plt.legend() plt.savefig(loss_curve.png, dpi150)看曲線(xiàn)時(shí)重點(diǎn)不是訓(xùn)練集 loss 降得多快而是驗(yàn)證集 loss 是否同步下降、收斂后有沒(méi)有明顯反彈。如果訓(xùn)練 loss 一直降、驗(yàn)證 loss 不動(dòng)先檢查數(shù)據(jù)劃分是否泄漏再考慮加正則或減小模型尺寸。4. 車(chē)牌識(shí)別算法實(shí)現(xiàn)把檢測(cè)框變成車(chē)牌號(hào)的三步后處理4.1 方案一用YOLOv8字符檢測(cè)加排序還原車(chē)牌訓(xùn)練第二個(gè) YOLOv8 模型時(shí)數(shù)據(jù)集不是車(chē)牌框而是每個(gè)字符一個(gè)框類(lèi)別直接用字符本身。比如藍(lán)牌常見(jiàn)類(lèi)別是省份漢字加字母數(shù)字訓(xùn)練時(shí)把“京”“A”“B”“0”各自作為獨(dú)立類(lèi)別。不要統(tǒng)一標(biāo)成char再額外接分類(lèi)網(wǎng)絡(luò)那樣等于把一個(gè)模型拆成兩個(gè)訓(xùn)練和推理都更麻煩。推理流程分四步先用車(chē)牌檢測(cè)模型得到best.pt的檢測(cè)框裁出車(chē)牌區(qū)域再把裁剪圖送入字符檢測(cè)模型拿到所有字符框后按位置排序最后把類(lèi)別 ID 映射成字符拼成車(chē)牌號(hào)。from ultralytics import YOLO import numpy as np # 類(lèi)別映射必須和訓(xùn)練字符檢測(cè)模型時(shí)的YAML完全一致 idx2char {0: 京, 1: 津, 2: A, 3: B, 4: 0, 5: 1} plate_model YOLO(plate.pt) char_model YOLO(char.pt) def recognize_plate(frame, box): x1, y1, x2, y2 [int(v) for v in box] crop frame[y1:y2, x1:x2] res char_model.predict(crop, imgsz320, conf0.3, verboseFalse)[0] if res.boxes is None or len(res.boxes) 0: return xyxy res.boxes.xyxy.cpu().numpy() cls res.boxes.cls.cpu().numpy().astype(int) # 用字符中心點(diǎn)做排序 cx (xyxy[:, 0] xyxy[:, 2]) / 2 cy (xyxy[:, 1] xyxy[:, 3]) / 2 # 雙層車(chē)牌先按行粗分再按列排序 sort_idx np.lexsort((cx, np.round(cy / 20))) return .join(idx2char[cls[i]] for i in sort_idx)np.lexsort((cx, np.round(cy / 20)))是關(guān)鍵。普通藍(lán)牌一行字符直接按cx排序就行雙層綠牌或大型車(chē)牌照要先把字符按cy分成兩行再在行內(nèi)按cx排序。20這個(gè)像素值根據(jù)裁剪圖高度調(diào)整裁剪圖越高分行的閾值也應(yīng)越大。字符檢測(cè)模型的置信度閾值不要固定得太高。字符尺寸小特征少推理時(shí)大量正確字符可能只有 0.3 左右的置信度。先設(shè) 0.3 看結(jié)果再根據(jù)誤檢情況調(diào)整。4.2 方案二整牌OCR識(shí)別省掉字符坐標(biāo)排序如果項(xiàng)目周期緊不想標(biāo)注字符級(jí)數(shù)據(jù)可以使用整牌 OCR 方案。車(chē)牌檢測(cè)仍然用 YOLOv8識(shí)別部分換成 PaddleOCR 或 CRNNCTC 這類(lèi)通用文字識(shí)別模型。這樣能很快看到效果。from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch) def recognize_plate_ocr(crop): result ocr.ocr(crop, clsTrue) if not result or not result[0]: return # 車(chē)牌號(hào)通常是識(shí)別結(jié)果里面積最大的第一個(gè)文本 text result[0][0][1][0] return text.replace( , )這個(gè)方案的優(yōu)點(diǎn)是省掉字符排序邏輯缺點(diǎn)也很明顯PaddleOCR 訓(xùn)練數(shù)據(jù)面向通用場(chǎng)景對(duì)車(chē)牌字體的針對(duì)性不夠0和O、1和I這種相似對(duì)容易混。把它作為快速原型可以生產(chǎn)環(huán)境最好用一批現(xiàn)場(chǎng)車(chē)牌圖微調(diào)識(shí)別模型或者切換到字符檢測(cè)方案。4.3 臟數(shù)據(jù)清洗漢字省份、字母與數(shù)字的相似對(duì)處理車(chē)牌識(shí)別結(jié)果里最讓人頭疼的不是模型太大而是幾個(gè)相似字符反復(fù)認(rèn)錯(cuò)。漢字里“京”和“津”輪廓接近字母里O和數(shù)字0幾乎一樣還有藍(lán)色車(chē)牌邊緣的鉚釘和白色邊框會(huì)被當(dāng)成字符框。處理辦法分兩層。第一層是輸入側(cè)盡量做透視矯正和邊緣裁剪。如果標(biāo)注階段保留了車(chē)牌四角點(diǎn)識(shí)別前先透視變換成固定尺寸能明顯減少傾斜帶來(lái)的字符粘連。import cv2 import numpy as np def four_point_warp(img, pts): # pts 是車(chē)牌四個(gè)角點(diǎn)順序?yàn)樽笊?、右上、右下、左?src np.array(pts, dtypenp.float32) dst np.array([[0, 0], [440, 0], [440, 140], [0, 140]], dtypenp.float32) M cv2.getPerspectiveTransform(src, dst) return cv2.warpPerspective(img, M, (440, 140))第二層是輸出側(cè)建立字符白名單。車(chē)牌第一位必須是漢字第二位必須是字母后面是字母和數(shù)字混合。后處理里把不符合規(guī)則的識(shí)別結(jié)果直接丟棄比硬猜更有價(jià)值。比如一位有效數(shù)字的置信度只有 0.4但白名單里不允許出現(xiàn)O那就強(qiáng)制輸出0這類(lèi)規(guī)則在真實(shí)場(chǎng)景里非常管用。5. 車(chē)牌識(shí)別項(xiàng)目避坑實(shí)錄現(xiàn)象、原因與處理辦法5.1 模型把進(jìn)風(fēng)口或車(chē)燈當(dāng)車(chē)牌現(xiàn)象訓(xùn)練時(shí) mAP 在 0.9 以上現(xiàn)場(chǎng)圖片卻把黑色進(jìn)風(fēng)口、車(chē)燈輪廓或遠(yuǎn)處廣告牌里的“車(chē)牌”框出來(lái)。原因負(fù)樣本太少。很多車(chē)牌檢測(cè)數(shù)據(jù)集只在有車(chē)的圖上標(biāo)注車(chē)牌沒(méi)有專(zhuān)門(mén)收集“很像車(chē)牌但不是車(chē)牌”的背景圖。解決從現(xiàn)場(chǎng)視頻里抽幀挑 50 到 100 張沒(méi)有車(chē)牌的背景圖放進(jìn)images/train和images/val不給 txt 文件。同時(shí)把容易誤檢的框保存下來(lái)作為難例負(fù)樣本加入下一輪訓(xùn)練。5.2 檢測(cè)框準(zhǔn)了OCR卻多一位或少一位現(xiàn)象車(chē)牌框很標(biāo)準(zhǔn)但識(shí)別結(jié)果總是第一二位多一個(gè)字符或者最后一位丟失。原因字符檢測(cè)模型把牌照邊框、鉚釘或漢字旁邊的小色塊也當(dāng)成了字符框另一個(gè)常見(jiàn)原因是字符框順序排序?qū)戝e(cuò)導(dǎo)致字符串錯(cuò)位。解決先用可視化把每個(gè)字符框畫(huà)在裁剪圖上看是不是邊框污染。如果是在裁剪時(shí)去掉上下各 5% 的邊緣像素如果排序錯(cuò)把字符框的中心點(diǎn)打印出來(lái)核對(duì)lexsort的鍵順序。5.3 CPU推理只有不到1幀/秒視頻卡到?jīng)]法用現(xiàn)象用官方權(quán)重在 Ubuntu 20.04 CPU 環(huán)境推視頻每幀耗時(shí)超過(guò) 1 秒完全沒(méi)有實(shí)時(shí)性。原因imgsz640對(duì) CPU 來(lái)說(shuō)負(fù)載太大而且很多人誤以為 YOLOv8 必須用 640 輸入。解決先把imgsz降到 416 或 320。車(chē)牌在視頻里通常已經(jīng)占了幾十像素320 輸入對(duì)檢測(cè)影響沒(méi)有想象中大。再檢查predict里的device是否真的設(shè)成了cpu避免它跑去調(diào)用不存在的 CUDA。CPU 部署還想更快就把模型導(dǎo)出成 OpenVINO 格式推理速度能比原始 PyTorch 快一倍以上。5.4 TensorRT或RK3588部署后精度下降現(xiàn)象PC 上 PyTorch 推理效果正常導(dǎo)出成 TensorRT 或部署到 RK3588 后檢測(cè)框整體偏移置信度普遍下降。原因模型導(dǎo)出時(shí)預(yù)處理沒(méi)統(tǒng)一。YOLOv8 訓(xùn)練時(shí)默認(rèn)用 letterbox 填充灰邊部署端如果直接用resize改變寬高比檢測(cè)框坐標(biāo)自然就偏了。另外 INT8 量化用的校準(zhǔn)圖片太少也會(huì)讓精度掉得厲害。解決部署端前處理嚴(yán)格復(fù)刻訓(xùn)練時(shí)的 letterbox 邏輯記錄填充比例推理輸出坐標(biāo)后反算回原圖坐標(biāo)。INT8 量化至少準(zhǔn)備 200 張覆蓋不同光線(xiàn)和車(chē)色的代表圖做校準(zhǔn)不要用訓(xùn)練集里明顯重復(fù)的幀。5.5 訓(xùn)練Loss下降但驗(yàn)證集mAP不漲現(xiàn)象訓(xùn)練日志里 loss 一直掉驗(yàn)證集 mAP 卻在十幾輪后停在 0.8 附近換模型結(jié)構(gòu)也沒(méi)用。原因最常見(jiàn)的是數(shù)據(jù)劃分泄漏。同一輛車(chē)的連續(xù)幀同時(shí)出現(xiàn)在 train 和 val 里模型記住的是車(chē)而不是車(chē)牌其次是類(lèi)別 ID 不一致訓(xùn)練時(shí)0是藍(lán)牌驗(yàn)證時(shí)0是車(chē)牌導(dǎo)致評(píng)估完全錯(cuò)亂。解決按車(chē)輛而不是按幀劃分?jǐn)?shù)據(jù)集確認(rèn)同一輛車(chē)只出現(xiàn)在 train 或 val 其中一邊。訓(xùn)練前做一次標(biāo)簽自檢遍歷 labels 目錄檢查每個(gè)類(lèi) ID 是否都在 YAML 的names范圍內(nèi)。6. 導(dǎo)出與驗(yàn)證讓YOLOv8車(chē)牌識(shí)別從訓(xùn)練機(jī)走到生產(chǎn)6.1 用ONNX導(dǎo)出檢測(cè)模型并用ONNX Runtime推理訓(xùn)練完plate.pt后把它導(dǎo)出成 ONNX 是在道閘設(shè)備、RK3588 這類(lèi)平臺(tái)部署的關(guān)鍵一步。Ultralytics 已經(jīng)把導(dǎo)出封裝得很簡(jiǎn)單from ultralytics import YOLO model YOLO(runs/lpr/plate_v8n/weights/best.pt) model.export(formatonnx, imgsz640, dynamicFalse, opset12)dynamicFalse表示輸入尺寸固定為 640x640大多數(shù)視頻檢測(cè)場(chǎng)景不需要?jiǎng)討B(tài)尺寸固定尺寸還能減少 ONNX Runtime 的推理波動(dòng)。如果需要在 Java 服務(wù)里調(diào)用車(chē)牌識(shí)別我一般把檢測(cè)模型導(dǎo)出成 ONNX再用 onnxruntime-java 加載識(shí)別部分仍然用 Python 服務(wù)或單獨(dú)導(dǎo)出字符檢測(cè)模型。導(dǎo)出后先不要急著部署用同一張測(cè)試圖分別跑 PyTorch 和 ONNX 輸出對(duì)比檢測(cè)框坐標(biāo)誤差。誤差在 0.5 像素以?xún)?nèi)才算正常。很多部署端翻車(chē)都出在 letterbox 填充沒(méi)有對(duì)齊這一步能提前攔住大部分問(wèn)題。6.2 生產(chǎn)環(huán)境的驗(yàn)收清單最后留一份簡(jiǎn)短的驗(yàn)收清單項(xiàng)目值不值得上線(xiàn)跑完這幾點(diǎn)心里就有底檢查項(xiàng)目標(biāo)不合理時(shí)的處理近景車(chē)牌檢測(cè)10 米內(nèi)漏檢率低于 5%補(bǔ)訓(xùn)練夜間和逆光樣本遠(yuǎn)景車(chē)牌檢測(cè)能框住但不強(qiáng)求識(shí)別降低置信度閾值或分兩路模型單字符識(shí)別字母數(shù)字錯(cuò)誤率低于 2%檢查相似對(duì)替換規(guī)則CPU 推理耗時(shí)單幀低于 200 毫秒換模型為 YOLOv8n、降 imgsz、導(dǎo)出 OpenVINO部署端精度與 PC 端相差不超過(guò) 2%統(tǒng)一預(yù)處理、增加量化校準(zhǔn)集如果你現(xiàn)在正要在自己的項(xiàng)目里接車(chē)牌識(shí)別我的習(xí)慣是先不急著寫(xiě)界面而是把檢測(cè)、裁剪、識(shí)別三個(gè)動(dòng)作拆成獨(dú)立函數(shù)在控制臺(tái)上用 20 張現(xiàn)場(chǎng)圖片跑一遍記錄每個(gè)環(huán)節(jié)的耗時(shí)和錯(cuò)誤樣本再談后續(xù)優(yōu)化。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取