尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLOv8车牌识别实战:数据集准备、模型训练与踩坑解析

YOLOv8车牌识别实战:数据集准备、模型训练与踩坑解析 简介中国车辆车牌号识别数据集基于真实场景图像整理聚焦车牌数字与字母的识别任务面向计算机视觉初学者、算法工程师及YOLOv8目标检测实践者。压缩包内共2000个文件其中542张标注图片与1457个配套txt标签文件一一对应标签采用YOLOv8格式可直接用于模型训练、验证与评估另含1个yaml配置文件便于快速搭建数据路径与类别映射。数据集围绕中国车牌标准样式构建覆盖常见蓝牌、黄牌及新能源车牌等场景适合开展车牌检测、字符识别等方向的模型调参与效果验证。压缩包整体仅17.6MB轻量易下载目前已有751人学习使用是入门目标检测或完善车牌识别项目的实用训练素材。1. 1458张车牌图加上YOLOv8标注够不够训练一个能用的车牌识别模型车牌识别项目卡住的往往不是模型而是数据。YOLOv8官方权重没见过中国车牌网上零散扒来的图片不是标注格式对不上就是样本量太少训两轮就过拟合。这套1458张已标记图片的数据集把最磨人的数据环节提前做好了标注文件直接是yolov8需要的txt格式打开压缩包就能开始训练。它覆盖的是车牌检测与字符识别最常见的落地需求——先用YOLOv8把车牌位置框出来再识别框里的数字和字母适合一周内要跑通demo、或者正在准备自己车牌数据集的工程师。1458张的规模不算大但足够走通模型选型、调参和部署验证整条链路一张GTX 1660 Ti甚至纯CPU都能完成训练和推理。2. 看懂数据集结构images、labels和data.yaml缺一不可2.1 解压后先看目录images和labels是YOLO的硬规定拿到压缩包第一步不是急着写训练代码而是把目录结构看清楚。YOLOv8数据集有两条不成文的规矩图片和标注文件必须分开放图片在images目录同名txt在labels目录划分后的训练集和验证集也要各放各的子目录。用tree看一遍最快unzip 中国车辆车牌号识别数据集.zip cd 中国车辆车牌号识别数据集 tree -L 2 .预期大概是这样的层级. ├── images │ ├── train │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ │ └── ... │ └── val │ ├── ... │ └── ... ├── labels │ ├── train │ │ ├── 0001.txt │ │ ├── 0002.txt │ │ └── ... │ └── val │ └── ... └── data.yaml不同打包人习惯不一样有的会把test也带上有的只分train和val还有的连data.yaml都要自己补。但images和labels的同名对应关系逃不掉0001.jpg对0001.txt文件名不一致训练时直接报找不到标签。我一般动手前先跑一行命令核对别让文件名这种低级问题浪费半天。for f in images/train/*.jpg; do base$(basename $f .jpg) [ -f labels/train/$base.txt ] || echo missing: $base done这段脚本干的事很简单遍历train里每张jpg检查labels/train下有没有同名txt缺哪个打印哪个。跑完没有输出说明图片和标注一一对应可以进入下一步。注意脚本里中括号两侧的空格不能少少了在bash里就是语法错误图片扩展名不统一时把jpg改成实际用到的后缀。YOLO格式对标注文件的存放位置很敏感默认相对路径是labels目录下的同名txt。如果压缩包里是别的目录名比如annotations、mark训练前要么改脚本要么重命名目录。记住一个原则与其改训练代码去适配奇怪结构不如花两分钟把目录拉回YOLO规范后面换模型、换框架都省事。2.2 一行标注里的五个数字归一化坐标和class id打开一个txt标注文件你会看到一行或多行数字每行五个数。这是YOLO格式的核心也是新手最容易误解的地方。拿一个例子说明0 0.5234375 0.3729167 0.1414062 0.0715278五个数字依次是类别id、框中心点的x坐标、框中心点的y坐标、框宽度、框高度。坐标全部是归一化的也就是除以图片宽高之后的比例值范围在0到1之间不是像素值。比如图片宽640中心点x的像素是335归一化后就是335/6400.5234。训练的时候YOLO拿这个比例乘以输入尺寸还原出实际框。判断这份标注是「整张车牌一个框」还是「字符级逐个标注」看class id的范围就行class id范围标注粒度适用任务只有0整张车牌一个框车牌定位检测0-9数字0-9单独成框字符识别0-35字母A-Z和数字0-9字符识别0-64汉字字母数字混合完整车牌识别如果txt里全是0说明只框了车牌整体后面要识别数字和字母得另外接OCR如果class id有变化说明标注已经细到每个字符那一行就是一个字符的框。中国车牌常用字符集是31个省份汉字加24个字母I和O通常不用加10个数字总字符集在65个左右所以看到class id到64也别慌那是完整字符级标注。这里还要顺手检查一个隐藏问题标注坐标是否越界。框的中心点可能落在图片边缘宽高加起来也可能超过1.0训练时会产生无效anchor导致loss抖动。一行awk就能扫出来awk { if ($30 || $31 || $40 || $41 || $50 || $51 || $60 || $61) print FILENAME: $0 } labels/train/*.txtawk按空格把一行拆成字段第3到第6个字段对应中心点x、y和宽高任何一个超出0到1范围就把文件名和整行打印出来。YOLO训练时对越界框会自动clip但标注本身错得离谱clip只能兜底不能纠错早发现早清理。2.3 批量校验用脚本拦下坏样本和类别不平衡坐标越界检查完还要统计类别分布和空标注这两件事直接决定训练能不能收敛。1458张图说多不多但手动翻一遍不现实交给脚本最稳from collections import Counter from pathlib import Path label_dir Path(labels/train) cnt Counter() empty_files [] for txt in label_dir.glob(*.txt): lines [line.strip() for line in txt.read_text().splitlines() if line.strip()] if not lines: empty_files.append(txt.name) continue for line in lines: cnt[int(line.split()[0])] 1 print(类别分布:, sorted(cnt.items())) print(空标注文件:, empty_files)这段代码遍历所有txt去掉空行后统计每个class id出现的次数同时把没有任何标注的txt文件名记下来。空标注意味着训练时这张图会直接跳过白白占着样本名额类别分布则能看出蓝牌、绿牌、黄牌之类的样本比例。如果某个class出现次数只有几十次后面训练时它的mAP大概率起不来这一条在车牌这种强颜色特征的任务里特别灵验。跑完这一步数据集的底细就清楚了多少张图、几个类别、有没有空标注。带着这份体检报告进入训练后面出了问题也知道是数据问题还是模型问题而不是靠瞎试。2.4 写data.yaml类别、路径和划分三样缺一不可目录看明白了标注也验过了接着就是数据集配置文件data.yaml。YOLOv8训练时不管命令行传什么参数数据集路径、类别列表都是从这个文件读的。常见的最小配置长这样path: /home/user/plate_dataset train: images/train val: images/val nc: 1 names: 0: platepath是数据集根目录的绝对路径train和val是相对path的图片目录nc是类别数names是类别名列表。如果你手里的是字符级标注nc和names要对应改成实际类别数。YAML里names既可以是列表也可以是字典Ultralytics两种都认我个人习惯用字典因为类别多了以后一眼能看到id对应的字符。如果压缩包里没带data.yaml可以用一小段Python生成的变量换成你自己的路径import yaml config { path: /home/user/plate_dataset, # 改成压缩包解压后的实际路径 train: images/train, val: images/val, nc: 1, names: {0: plate}, } with open(data.yaml, w, encodingutf-8) as f: yaml.dump(config, f, allow_unicodeTrue, sort_keysFalse)两个细节注意一是allow_unicodeTrue因为names里如果有中文汉字不设置这个参数会被转义成\uXXXX还能用但排查起来很别扭二是path一定写绝对路径相对路径在跨目录运行训练命令时经常踩坑训练进程的工作目录和数据集目录不一致就找不到图片。写完data.yaml自己cat一遍再跑训练比debug一整天划算。3. 把YOLOv8跑起来CPU和GTX 1660 Ti都能完成的第一次训练3.1 装环境ultralytics一条命令CUDA检查别跳过训练环境这一步没什么玄学只要Python版本和PyTorch配对就能装。我常用的组合是Python 3.9或3.10配ultralytics最新版conda建个干净环境避免和系统Python打架conda create -n yolov8 python3.9 -y conda activate yolov8 pip install ultralyticspip install ultralytics会自动把torch、torchvision、opencv这些依赖一起装上。如果你的机器没有NVIDIA显卡pip会退而求其次装CPU版torch也能跑就是慢。Ubuntu 20.04上搭建yolov8环境跑CPU版本一个epoch大概几分钟到十几分钟100个epoch挂一晚上也够用。装完先验证环境别急着训练python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU)能打印出带CUDA的版本号且cuda.is_available()是True说明GPU可用如果是False就是CPU环境后面训练把device参数设成cpu。最容易翻车的不是CUDA而是pip装完torch后import报libGL.so.1找不到那是opencv缺系统库一条sudo apt install libgl1 -y就解决。这类问题跟模型无关却能让新手卡一下午所以先把环境验证命令跑通再进下一步。3.2 第一次训练最小命令和参数含义环境就绪直接开训。最基本的一条命令yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20 device0逐一说参数data指定yaml路径model指定预训练权重yolov8n.pt是nano版参数量最小训练最快epochs是迭代轮数100轮足够看趋势imgsz是输入分辨率640是YOLOv8默认值batch是每次迭代喂进去的图片数16在6G显存的GTX 1660 Ti上跑nano绰绰有余patience是早停耐心值连续20轮验证集指标没提升就自动停不用干等100轮跑完device0表示用第一块GPUCPU则改成devicecpu。显存不够就把batch降到8或4再不行换yolov8n.pt配上imgsz480。显存溢出有个很明确的报错——CUDA out of memory看到它先降batch不要一上来就换小模型。batch影响的是收敛稳定性模型影响的是精度上限两者解决的不是同一个问题。训练过程中会在当前目录生成runs/detect/train/里面存的东西才是这趟训练的全部产出weights/best.pt是最优权重weights/last.pt是最后一轮权重results.png是一张包含loss曲线、精确率、召回率、mAP的汇总图args.yaml记录了你这次训练的所有参数。终端每轮会打印一行指标GFLOPs旁边那几个字母分别代表不同含义P是精确率R是召回率mAP50是IoU阈值0.5下的平均精度mAP50-95是0.5到0.95多阈值平均。loss栏里的box_loss是边界框回归损失cls_loss是分类损失dfl_loss是分布焦点损失这三条曲线都下降说明模型在正常学习。YOLOv8自己就会画损失函数曲线图不需要额外写脚本。想看更细的曲线训练目录里有results.csv用pandas读出来自己画也行我一般直接看生成的png训练结束第一件事就是打开它曲线正常下降说明数据没问题、训练在收敛。3.3 验证和推理确认模型真的找到了车牌训练结束或者早停后第一步是回验用验证集看指标yolo detect val modelruns/detect/train/weights/best.pt datadata.yaml输出会给出precision、recall、mAP50、mAP50-95几个关键指标。对车牌检测这个任务mAP50一般要跑到0.95以上才算能用的模型mAP50-95在0.7到0.8之间是正常水平。这两个数低优先怀疑标注质量和类别不平衡而不是急着换模型。验证集指标好看只是第一步真正检验模型的是真实场景推理。拿一张没见过的车牌照片试一下yolo detect predict modelruns/detect/train/weights/best.pt source/path/to/test.jpg saveTruepredict会在图片上画出检测框和置信度保存到runs/detect/predict/。这一步要盯的是检测框有没有把整个车牌包住、有没有多框、有没有把车灯或装饰条当车牌。我自己的经验是验证集mAP再高真实照片上一框错位就说明泛化有问题别急着进入字符识别先回头调数据。到这一步「数据集→训练→验证→推理」整条链路已经走通后面才是真正烧时间的调参环节。4. 1458张图的调参路线epoch、数据增强和预训练权重的正确组合4.1 epoch、batch、imgsz怎么配合才算合理1458张图不是大数据集训练策略要按小数据集的逻辑来。epoch设多少不能拍脑袋看两个信号训练loss是不是还明显下降、验证集mAP是不是还在涨。如果50轮mAP还在往上走100轮不算多如果30轮就平台了加epoch也是空跑。训练loss一路降、验证loss升高那就是过拟合信号得靠数据增强或早停来治硬加epoch只会更糟。batch的调节逻辑更直接显存不满就尽量大batch大梯度更平滑收敛更稳定。6G显存跑nano用16跑small降到8跑medium就是4这是一个可复制的经验值。但batch也不是越大越好超过32之后收益递减明显还有可能让模型困在尖锐极小值泛化反而不如中等batch。imgsz值得多说几句。车牌相对整张图片是小目标640分辨率下一个车牌可能只有几十个像素宽特征很弱。把imgsz提到960甚至1280小目标特征会清晰很多mAP通常能涨几个点。代价是训练时间变长、显存占用变大。我一般先在imgsz640上把链路验证通确认能收敛再用imgsz960跑一轮对比mAP哪边高就用哪边而不是一上来就上大分辨率烧时间。推理时也可以单独指定更高的imgsz训练和推理的输入尺寸不需要完全一致宽高比接近就行。4.2 数据增强小数据集最好的后悔药小数据集最大的问题是过拟合增强参数调得好等于免费把数据集翻几倍。YOLOv8的增强参数可以直接写在训练命令里。对车牌场景我常用的组合是yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 \ hsv_h0.015 hsv_s0.5 hsv_v0.4 translate0.1 scale0.4 \ fliplr0.5 flipud0.0 mosaic1.0逐个说hsv_h、hsv_s、hsv_v是对色调、饱和度、亮度的随机扰动。车牌颜色是重要特征——蓝底白字、绿底黑字、黄底黑字——增强幅度太大容易把颜色语义冲掉hsv_h压到0.015左右只做微调hsv_s和hsv_v放到0.4到0.5模拟不同光照条件。translate是随机平移0.1够用scale是随机缩放比例0.4表示原尺寸的60%到140%之间缩放模拟远近变化。两个容易踩的坑先说清楚。一是flipud垂直翻转。普通物体检测可以用但车牌是文字上下翻转后字符顺序全部颠倒模型学到的是错误特征所以车牌场景必须flipud0.0。二是mosaic马赛克增强把四张图拼成一张YOLOv8默认开启且效果很好但小数据集要留意mosaic合成的图片里车牌子图可能被严重压缩变形。如果训练中recall上不去把mosaic关掉或者降一版试试这个坑在车牌这种极度依赖小目标的任务里概率很高。mosaic在最后10个epoch会自动关闭这是YOLOv8的默认行为不用自己操心。数据增强不是开得越猛越好验证集指标掉了就回退。每改一个参数跑一版对比别一次性改五六个参数到时候模型变差了都不知道是哪个参数害的。4.3 预训练权重nano起步small收尾freeze是双刃剑YOLOv8的预训练权重从COCO来COCO里没有车牌类别但它的通用特征提取能力——边缘、纹理、形状——对车牌检测依然有用。1458张图的规模从头训练基本不现实必须在预训练权重上继续训这也是这个数据量下最稳的路线。三种权重怎么选给一个可以直接照搬的判断权重参数量训练速度精度上限适用场景yolov8n.pt约3.2M最快中等链路验证、CPU训练yolov8s.pt约11.2M中等较高6G以上显存追求精度yolov8m.pt约25.9M慢高显存充足mAP优先GTX 1660 Ti这个级别从nano开始跑通确认数据没毛病再换small做最终训练。medium在6G显存上batch只能压到4训练时间拉长两三倍对1458张图来说精度提升往往不值这个时间成本性价比不高。freeze参数在训练小数据集时经常拿出来用作用是冻结模型前N层权重只训练后面的层。对小数据集这是双刃剑冻结前几层能防止预训练特征被小样本破坏训练更快更稳但冻结得太多模型能力不够任务特有特征学不出来精度反降。我的经验是freeze10跑一轮对比全量训练哪个验证集mAP高用哪个yolo detect train datadata.yaml modelyolov8s.pt epochs100 imgsz640 freeze101458张图还有个实用技巧先用nano训练并验证数据没有低级错误确认一切正常后把nano训练出来的best.pt作为small的初始权重继续训。这种做法比每次都从COCO预训练开始要快因为nano已经在车牌数据上见过足够多的样本损失函数已经处在合理区间。实际项目里我经常这么干收敛速度明显更快。5. 车牌识别踩坑记录5个让人翻车的问题和对应解法5.1 现象训练loss正常下降验证mAP也不低真实图片却一个车牌都框不出来最让新手心态崩的场景。训练日志漂亮得很损失函数曲线也是正常收敛一测真实照片检测框要么乱飞要么干脆没有。原因大概率是训练集和真实场景不在一个分布里注意三个最常见的分布偏移拍摄角度——数据集里全是平视正面拿俯视停车场照片去测框不出来很正常光照条件——全是白天晴天夜晚霓虹灯下的车牌特征被光照毁掉图片尺寸——训练时imgsz640推理时原图分辨率特别大小目标被resize后特征丢失。按这三个方向排查先复现问题而不是瞎调参数把真实图片缩到640左右再看有没有框没有就补一些俯视、夜晚的样本重新训练。1458张图不算大拿手机在停车场补拍五六十张用labelme标注车牌框转成YOLO格式丢进训练集效果往往立竿见影。5.2 现象蓝牌检出率很高绿牌几乎全军覆没新能源绿牌在数据集里占比不足5%时非常常见。中国车牌颜色和字符颜色差异很大蓝底白字、绿底黑字、黄底黑字、白底黑字。YOLO学到的主要是颜色和纹理特征蓝牌样本太多模型就偏向蓝牌。验证集mAP看着还行一拆分颜色统计蓝牌0.97、绿牌0.3问题暴露无遗。解决分两步先统计类别样本数量分布确认绿牌占比再针对性补数据。不用重新采集先把手头绿牌图片做增强hsv_v调大模拟不同亮度、轻微旋转、缩放把增强后的绿牌样本加进训练集把绿牌比例拉到20%以上。再训一轮绿牌mAP基本能回到0.9上下。这个问题的本质是样本不平衡靠增强只能缓解真正要做得补更多真实绿牌图。5.3 现象字符识别阶段7位车牌号码总是缺第1位或第7位这个坑出现在两段式方案里——检测模型框出的是车牌裁剪后送给OCR识别结果首尾字符经常丢。原因几乎总是同一处检测框没有完整包住车牌。YOLO输出的框是按训练标注学的如果标注时把车牌留白或者紧了框就会把第一个汉字或最后一个字母切掉一部分OCR拿到残缺字符自然认不出来。解法是给检测框做外扩用代码控制不用重新训练。检测模型输出后把框的宽度沿中心向左右各扩展5%同时上下扩展一点把边缘字符完整包进来。在推理代码里处理比重新标注重新训练便宜得多。还有一个习惯字符识别完之后核对识别结果长度正常车牌是7位或8位长度不对就标记为低置信度交给人工复核别直接让错误结果进业务系统。5.4 现象汉字省份简称识别准确率极低字母数字倒还行先确认数据集里有没有标注汉字。如果标注类别里本来就没有汉字那这个现象不是bug是数据边界。中国车牌是汉字字母数字的组合比如「京A12345」文献里常说的蓝牌字符集是31个汉字加24个字母加10个数字完全省略汉字就意味着模型无法完成「完整识别」这个动作只能在字母数字段上工作。应对方案按业务需求分叉如果只要识别号码把车牌检测后裁剪的图在字符识别层直接忽略汉字段如果要完整识别汉字类别必须补齐。汉字不能混在字母数字里一起训汉字外形和字母数字差异太大单独开一个分类模型识别汉字准确率会高很多。现在工程上更常见的做法是不单独训汉字分类直接接现成OCR引擎识别裁剪后的车牌图省去维护字符集模型的成本。5.5 现象训练时GPU上mAP90部署到RK3588等边缘设备后掉到70边缘设备跑YOLOv8几乎都要做模型转换常见路径是导出ONNX再转RKNN或TensorRT。精度掉点集中在两个环节一是量化int8量化对车牌这种小目标任务非常敏感边界框回归的精度在低比特下损失大二是输入尺寸训练用的imgsz和onnx导出时固定的shape不一致resize带来的形变直接掉点。排查按两条走先把导出的ONNX在PC上用onnxruntime跑一遍如果精度已经掉了问题在导出环节检查输入输出节点和dynamic shape如果ONNX精度正常、RKNN/TensorRT掉点优先试float16而不是int8。float16几乎无损int8换来的速度提升对小目标检测任务往往不值。混合精度推理是边缘部署车牌模型比较稳的折中方案精度损失控制在1%以内速度也比float32快不少。6. 从检测到识别两段式方案和端到端验证6.1 两段式识别检测框外扩之后再送OCR数据集训练出来的YOLOv8模型解决的是「车牌在哪」的问题「车牌是什么」要靠识别模块。如果标注是整框模式最可靠的落地路径是检测OCR两段式YOLOv8输出车牌框裁剪出车牌区域再交给OCR识别数字和字母。这里给一个可以直接套的推理脚本框架from ultralytics import YOLO from paddleocr import PaddleOCR model YOLO(runs/detect/train/weights/best.pt) ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) results model.predict(test.jpg, imgsz640, conf0.5) for r in results: for box in r.boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 [int(v) for v in box] w x2 - x1 x1 max(0, x1 - int(w * 0.05)) # 左右各外扩5% x2 min(r.orig_shape[1], x2 int(w * 0.05)) crop r.orig_img[y1:y2, x1:x2] # 裁剪车牌区域 result ocr.ocr(crop, clsTrue) if result and result[0]: print(.join([line[1][0] for line in result[0]]))流程很直观YOLO检测出车牌框之后代码先把框向左右各外扩5%再把裁剪区域交给PaddleOCR识别。外扩的原因来自5.3节那个坑——检测框边缘总爱切掉字符外扩是低成本高回报的一步。PaddleOCR的langch表示用中文模型对汉字和字母数字混合场景识别率更好。ocr.ocr返回的是嵌套结构result[0]里每个元素包含文本框坐标和识别文本line[1][0]取的就是文本内容最后join拼接成完整车牌号。6.2 端到端验证别只看mAP看整串号码识别率如果是字符级标注检测模型直接输出每个字符的框不需要OCR把所有字符框按x坐标从左到右排序拼接字符串就行还能通过字符框个数判断是7位还是8位车牌。但字符级的标注成本高实际项目里整框检测OCR是性价比更优的选择。最后说验证。训练完别只盯着mAP拿50张没参与训练的真实场景图做端到端测试统计完整识别率检测框得对、字符识别得全、号码得连续三个条件同时成立才算一次正确识别。我习惯把识别结果和人工标注导成表格逐行对比错的归档分类——是检测框的问题还是OCR的问题两类问题修的方向完全不一样。这套「检测OCR端到端验证」的流程做下来基本不会出现模型看着指标不错、业务上一塌糊涂的情况。希望帮到你。本文还有配套的精品资源点击获取
返回列表