
简介面向车牌检测与识别任务的高质量图像数据集共包含1695张车辆图片每张均配有YOLO格式的边界框标注文件适合作为对象检测模型的训练与评估数据。数据覆盖不同拍摄环境和光照条件可直接用于训练YOLO系列、卷积神经网络等深度学习模型适用于智能交通监控、停车场管理、安防追踪等车牌定位与识别场景。压缩包内共有2000个文件其中jpg图像303张、txt标注文件1696个另有1个yaml配置文件用于说明类别和路径信息整体大小147.47MB。已有132人学习下载。使用者可省去手动标注成本直接加载数据开展算法实验也可基于标注信息进一步扩展其他检测框架结合预处理步骤可有效提升模型在实际场景中的识别精度与速度。1. 车牌识别用 1695 张图能落地吗先谈数据集规模再谈模型又一个车牌图像识别项目手里是 1695 张图片、YOLO 格式标注、目标是对象检测。先说结论车牌是刚性目标结构固定、纹理单一这个数据量足以训练出一个能在大多数路况下框出车牌的检测器但前提是别随机初始化训练也别把期望定成一张图里同时识别蓝牌绿牌双层牌再加字符识别。1695 张图的合适定位是验证从标注到部署的完整链路、支撑原型 demo 和中小场景的第一版车牌定位模型。适合正在做课程设计、毕业设计或停车场出入口方案的工程师配合预训练权重和放大样本的数据增强能把自采数据的时间省掉一大半。下面从拿到数据集那一刻开始讲怎么做。2. 拿到 YOLO 车牌数据集先别急着训练核对目录、标签与坏图直接开训是新手最容易犯的错跑一半发现 loss 不降回头查才知道标签字段错位或者图片和 txt 对不上。这一章用十分钟把数据集从“能跑”变成“能信”。2.1 目录结构与 YOLO 标签格式先看懂五个数字YOLO 训练要用的目录结构绝大多数情况下长这样plate_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml有些数据集只给一个 images 和一个 labels 目录train 和 val 没划分这种情况要自己 split。1695 张图按 8:2 或 7:3 划分都行验证集越大评估分数越有参考价值如果场景本身有类别不均衡先按类别比例分层抽样再做划分别直接随机切。images/train 里每张 jpg 在 labels/train 下有一个同名 txt内容长这样0 0.532814 0.437500 0.144742 0.048148五个字段分别是类别 id、归一化中心点 x、归一化中心点 y、归一化宽度 w、归一化高度 h。所有坐标都除过图片原始宽高所以理论上都在 0 到 1 之间。两个常见错误来源一是标注工具把中心点格式导出成了左上角加宽高的格式二是多类别任务里类别 id 没从 0 开始导致 names 错位。LabelImg 这类工具“打标完 yolo 格式的标”后自动生成的文件一般不会出大问题但数据集一旦经过人工改 txt 或从别的项目重组就必须用脚本过一遍。2.2 用脚本检查标签完整性、数值合法性与坏图我每次拿到新数据集都先跑一个校验脚本遇到过字段数不对、框完全出界、文件名大小写不匹配、图片解码失败等问题。下面这个脚本同时检查文件对应关系、坐标数值范围和图片可读性import os from pathlib import Path from PIL import Image def validate_dataset(img_dir, label_dir): errors [] imgs sorted(Path(img_dir).glob(*.jpg)) sorted(Path(img_dir).glob(*.png)) for img_path in imgs: label_path Path(label_dir) / (img_path.stem .txt) if not label_path.exists(): errors.append(f缺标签: {img_path.name}) continue for line_no, line in enumerate(label_path.read_text().splitlines(), 1): parts line.split() if len(parts) ! 5: errors.append(f{img_path.name} 第{line_no}行字段数错误: {line}) continue cls, x, y, w, h parts[0], *map(float, parts[1:]) if not cls.isdigit(): errors.append(f{img_path.name} 类别id不是数字: {cls}) if w 0 or h 0 or x 0 or x 1 or y 0 or y 1: errors.append(f{img_path.name} 坐标越界: {line}) try: with Image.open(img_path) as im: im.verify() except Exception: errors.append(f图片损坏: {img_path.name}) return errors errors validate_dataset(images/train, labels/train) print(f发现问题 {len(errors)} 项) for e in errors[:30]: print(e)脚本逻辑分三段先确认每张图都有同名 txt避免训练时部分图片没有监督信号再逐行解析五字段类别 id 必须是整数w 和 h 必须为正中心和宽高归一化后必须在 0 到 1 之间最后用 PIL 的 verify 检查图片文件是否损坏verify 只读文件头速度快不会把所有图片解码进内存。如果在真实数据集上跑出越界框先别急着删。看是不是所有框都整体偏移那通常是标注工具坐标系统的问题重新转换一遍就好只有少量框越界才逐张手工修或直接剔除。文件名大小写不匹配最隐蔽Linux 下严格区分大小写图片是 JPG 后缀、标签按小写匹配就会整批失败。2.3 把标注画回图片上做一次人工抽样巡检脚本只能查数值问题查不出语义问题——比如框把“京A12345”切掉一半、绿牌标成了蓝牌、双层黄牌只框了下层。数值合法并不代表标得对。我用 OpenCV 把框画出来按序号拼成大图人眼扫一遍import cv2 from pathlib import Path def draw_boxes(img_dir, label_dir, out_dir, sample50): Path(out_dir).mkdir(parentsTrue, exist_okTrue) imgs sorted(Path(img_dir).glob(*.jpg))[:sample] for i, img_path in enumerate(imgs): img cv2.imread(str(img_path)) h, w img.shape[:2] label_path Path(label_dir) / (img_path.stem .txt) for line in label_path.read_text().splitlines(): cls, x, y, bw, bh line.split() x, y, bw, bh map(float, (x, y, bw, bh)) x1 int((x - bw / 2) * w) y1 int((y - bh / 2) * h) x2 int((x bw / 2) * w) y2 int((y bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, cls, (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imwrite(str(Path(out_dir) / fvis_{i}.jpg), img) draw_boxes(images/train, labels/train, visual_check)归一化坐标还原成像素坐标的公式是 x1(x-w/2)*w先乘以图片尺寸再算左上角。因为车牌框贴边y1 可能小于 0显示文字时用 max(0, y1-8) 防止文字画到图片外。这里直接按 cls 的字符串画上去巡检时能顺便确认类别 id 和真实车牌类型是否对应。抽样巡检重点看三类图车辆占画面比例小的远景图、大片阴影遮挡的图、有绿化带或路牌干扰的图。这三种图决定了模型对真实场景的鲁棒性。如果 50 张里出现超过 3 处框的位置明显偏了别继续往下训回到标注阶段修正否则后面的训练只是在放大错误。2.4 统计类别与尺寸分布提前排查“风格偏科”最后一步统计 train 和 val 的类别数量与框尺寸分布。这个脚本每次做新数据集我都会跑它能直接暴露训练集和验证集的分布差异from collections import Counter from pathlib import Path def analyze(labels_dir): cls_count Counter() size_buckets Counter() for label_file in Path(labels_dir).glob(*.txt): for line in label_file.read_text().splitlines(): cls, x, y, w, h line.split() cls_count[cls] 1 area_ratio float(w) * float(h) if area_ratio 0.005: size_buckets[小目标(0.5%面积)] 1 elif area_ratio 0.03: size_buckets[中目标] 1 else: size_buckets[大目标] 1 return cls_count, size_buckets for split in [train, val]: cc, sb analyze(flabels/{split}) print(split, 类别:, cc, 尺寸:, sb)框面积占比如果大量集中在 0.005 以下说明车牌在画面里普遍很小imgsz640 可能不够提前上 960 或者考虑训练时做切图。类别分布如果 train 和 val 差异超过 10 个百分点评估分数不可信要做分层重划分。这个脚本跑完对数据集的“性格”基本心中有数后续训练很少再被数据问题打回重来。3. 用 YOLO 在这个数据集上训练一个能用的车牌检测模型从选型到命令3.1 版本选型和预训练权重迁移学习是这个小数据集的命根子YOLO 从 v5 到现在的多个版本迭代选型主要看两件事生态成熟度和部署平台支持。Ultralytics YOLOv8 是当前环境配置最省心的选择一条 pip 命令装完训练验证导出 ONNX 的命令风格统一。如果后续部署平台是 RK3588 这类边缘设备公司内部多半也已经支持 v8 的导出选它最稳。然后是预训练权重。yolov8n.pt 在 COCO 上见过百万级图像底层特征已经把边缘、纹理和局部形状的表达学好。加载这个权重后只需让检测头学着在已有特征基础上定位车牌数据需求从万级降到千级这是 1695 张图能跑通的真正底气。相反如果从零随机初始化训练几十个 epoch 后模型大概率把训练集背景背下来验证集 mAP 上不去。注意一个误区加载预训练权重后把学习率调得过高会在几十个 epoch 内破坏主干网络还不如不加载。我一般保持默认初始学习率或者给主干单独设低一点的学习率让检测头充分更新。这里还涉及一个理解点yolo 的损失函数由 box_loss、cls_loss 和 dfl_loss 三部分组成迁移学习阶段改的主要是 cls 分支box 和 dfl 基于预训练特征快速收敛所以前期 loss 掉得很快是正常的不要因此觉得模型已经学好了。3.2 data.yaml 与增强参数让 1695 张图变成“看起来有一万张”训练配置的第一步是写好 data.yaml# 车牌数据集配置 path: /home/user/plate_dataset # 改成你的实际路径 train: images/train val: images/val nc: 1 names: 0: platepath 写绝对路径最稳train 和 val 相对于 path 写。如果数据集分两类比如蓝牌 plate_blue、绿牌 plate_greennc 写 2names 按标签数字顺序排列。names 必须和标签 id 一一对应id 从 0 开始一旦标签里出现大于等于 nc 的 idyolo 会报错或静默吞掉这行两者都很麻烦。数据增强是这个小数据集的核心竞争力。yolo 训练时的增强参数可以通过命令行覆盖我常用的车牌检测参数组如下参数推荐值说明hsv_h0.015色调变化小别把车牌颜色改没了hsv_s0.4饱和度可变适应阴天和夜晚hsv_v0.4亮度变化适应逆光fliplr0.0车牌字符镜像后语义错误必须关rotate8小角度旋转模拟斜停车位scale0.3尺度缩放模拟远近距离translate0.15平移扰动防止位置过拟合mosaic0.8四图拼接大幅增加背景多样性fliplr0.0 是车牌任务的特有要求。左右翻转会把“京A12345”变成镜像检测阶段也许还能框住但后续接 OCR 时收到的就是错误字符序列所以一开始就关掉。rotate 控制在 8 度以内过大的旋转会让车牌变成近梯形给框回归增加噪声。mosaic 对过拟合抑制作用明显小数据集建议开到 0.8 以上。3.3 环境配置与训练命令一次跑通的完整步骤环境配置是高频卡壳点网上搜“yolo v8 anaconda环境配置要求”的提问特别多。我一般按下面的顺序做conda create -n yolo python3.10 -y conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralyticsPyTorch 安装源根据本机 CUDA 版本选先 nvidia-smi 查驱动支持的 CUDA 版本再决定 cu118 还是 cu121。CPU 机器删掉 --index-url 装 CPU 版即可。ultralytics 会自动带起 opencv、pandas 等依赖不需要手动逐个装。环境就绪后训练命令如下yolo detect train \ data/home/user/plate_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ device0 \ patience15 \ project./runs \ nameplate_v1参数逐个说epochs100 配合 patience15验证指标连续 15 轮不涨就早停避免无效耗时batch16 在 8G 显存上基本能跑显存不够就降到 4但 batch 太小时 BN 统计不准评估结果会有波动workers4 是数据加载线程数Windows 下偶发报错就改成 2 或 0Linux 保持 4device0 表示用第一张 GPU没 GPU 写 cpu但训练速度会慢到让人失去耐心。3.4 训练过程中的监控loss 曲线之外还要看什么训练日志每轮会打印 box_loss、cls_loss、dfl_loss 以及 precision、recall、mAP50 等指标。很多人只盯 loss但 loss 下降是训练集上的记忆程度mAP50 才是验证集上的泛化能力。我训练时会在旁边开着验证集预测图看框贴不贴车牌边缘、有没有把两个紧挨的车牌框成一个。best.pt 按验证集 mAP 确定last.pt 是最后一个 epoch绝大多数情况下用 best.pt 做推理和部署。这环节一个隐蔽的坑改完增强参数或标签后忘记清理缓存。yolo 会在数据集根目录生成 cache 文件改动标签后不删 cache 会继续加载旧标签等于白改。删掉 labels 目录下的 *.cache 文件再训或者用 force_rebuildTrue 强制重建。4. 测试、评估与置信度门限让车牌检测在真实场景下用起来4.1 读懂验证输出里的四个数字验证命令只有一行yolo detect val \ modelruns/plate_v1/weights/best.pt \ data/home/user/plate_dataset/data.yaml \ splitval \ conf0.25 \ iou0.5mAP50 是把 IoU 阈值固定在 0.5 时模型在所有置信度下做 PR 曲线得到的平均精度。车牌检测只要框把车牌完整框住就算对IoU 到 0.5 已经足够所以 mAP50 达不到 0.9 以上基本说明模型没学好。mAP50-95 在 0.6 到 0.8 之间都正常它要求框的位置非常精细车牌场景里框偏几个像素IoU 就会从 0.9 跌到 0.7不必为这个数字焦虑。precision 和 recall 要对比看。precision 明显低于 recall说明模型把很多非车牌框成了车牌典型如蓝底路牌误检recall 低则是漏检。两者结合 mAP 一起看比单独盯一个数更能定位问题方向。4.2 置信度门限怎么调用扫描代替拍脑袋训练和评估时的 conf0.25 只是通用默认值实际部署时门限怎么定才是关键。我常用的方法把 conf 从 0.05 到 0.7 按步长扫一遍记录每个门限下的 precision 和 recall按业务选工作点from ultralytics import YOLO model YOLO(runs/plate_v1/weights/best.pt) for conf in [round(x * 0.05, 2) for x in range(1, 15)]: metrics model.val(dataplate_dataset/data.yaml, confconf, iou0.5, splitval, verboseFalse) print(fconf{conf:.2f} P{metrics.box.mp:.3f} fR{metrics.box.mr:.3f} mAP50{metrics.box.map50:.3f})典型输出里你会看到conf 较低时 recall 高但 precision 低假阳性多conf 升高后 precision 升高但 recall 下降漏检增加。停车场道闸场景要的是不漏拍工作点选在 recall 下跌之前执法取证场景要的是不误报工作点选在 precision 稳定之后。没有“最好”的 conf只有和业务匹配的 conf。4.3 推理脚本与 NMS 参数测试图片和视频怎么批量跑模型验证通过后用真实场景采集的图片做反馈测试是上线前必要的一步。习惯把现场照片放进一个目录批量推理from ultralytics import YOLO model YOLO(runs/plate_v1/weights/best.pt) results model.predict( sourcefield_images, # 放现场照片的目录 conf0.3, # 按4.2扫描结果确定 iou0.5, # NMS的IoU阈值 imgsz640, saveTrue, # 保存带框结果图 projectfield_result, namerun1 ) for r in results: if len(r.boxes) 0: print(f{r.path}: 未检测到车牌)source 既可以是目录也可以是单个视频文件。现场测试图不参与任何训练和验证是彻底的外部数据。这里的 iou0.5 是 NMS 的 IoU 阈值和评估时的 IoU 阈值是两回事。NMS 用来在相邻框之间去重车牌场景目标稀疏默认 0.5 就够iou 设太高会把几个相邻车牌的框合并到一起设太低又会在同一车牌上保留多个重叠框这个参数值得在测试时来回试几次。4.4 部署前的导出验证不只跑通还要卡在帧率上模型在测试图片上表现稳定后常见做法是导出 ONNX 再做一轮部署验证。导出命令yolo export modelruns/plate_v1/weights/best.pt formatonnx imgsz640 opset12导出后可以用 onnxruntime 在 CPU 上跑一遍确认推理结果和 PyTorch 下一致。边缘设备上部署时车牌检测通常要求单帧处理在几十毫秒内ONNX 配合 FP16 往往能比 PyTorch 快一截。如果导出的 ONNX 在设备上结果异常优先检查预处理是否有差异——yolo 的推理默认做 letterbox 填充ONNX Runtime 里没有自动这一步需要自己在预处理中补上。部署版本的 conf 和 iou 要和 4.2、4.3 扫描出来的值保持一致很多人导出新模型后忘了同步阈值导致线上表现和测试结果两模两样。4.5 用失败案例反向定位问题跑完现场素材后挑出三类失败图漏检、误检、框偏。漏检去看车牌在画面里的尺寸宽度小于 80 像素说明输入分辨率不够测试时 imgsz 提到 960 试试不行就训练时提高分辨率误检看误检目标的颜色和边缘蓝底白字路牌经常被当蓝牌这时要增加训练集中负样本比例框偏看车牌倾斜角度旋转增强没开的模型对斜停车框不准开启 rotate±10 度重训一次。失败案例一定要归档。我在项目目录下建一个 fail_cases 文件夹每次迭代后重新跑一遍看模型是否解决了旧问题、有没有引入新问题。这是衡量模型迭代是否有效的唯一标尺比盯着 mAP 数字可靠得多。5. 车牌数据集训练避坑1695 张图最容易翻车的 5 个地方小数据集容错空间小一个坑就能让前面所有工作打水漂。这里按“现象原因解决”整理成踩坑记录每条都是一次真实翻车经历。5.1 现象训练 loss 持续下降验证 mAP 却纹丝不动原因增强没开起来。1695 张图本就不过量模型死记硬背很快训练集表现好验证集一换环境就漏检。另一个常见原因是验证集和训练集有重叠很多划分脚本重命名图片时把同一张图的正副本分进两个集合数据泄漏会让 mAP 虚高部署立刻掉点。解决按 3.2 的增强参数打开mosaic 和 scale 优先。同时检查两个集合是否有重复文件最简单的判断方法是随机抽 20 张验证图去训练集里比对文件 hash有重复就重新划分。5.2 现象预测时把蓝底路牌误检为车牌误检率居高不下原因数据集全都是车头正面照模型没学过“像车牌但不是车牌”的负样本。户外蓝底白字路牌、蓝色广告牌在外观上和车牌高度相似模型学到的特征分不清两者边界。解决从现场或网上采集几百张无车的道路环境图不加标注直接放进训练目录。ultralytics 训练流程会自动把无标签图片当作负样本参与背景 loss 计算这一步往往能让误检率下降一半。5.3 现象斜停车辆的车牌检不出来框总是斜着框不住原因训练数据大多来自正面规整角度旋转增强没打开模型见不到角度变化推理时遇到斜停车位就崩。解决开启 rotate±10 度配合 shear±5 度模拟透视。训练后验证集上专门看斜车牌如果还框不住就到标注层面补几十张斜车牌。rotate 开太大会让细长车牌变成近梯形反而干扰框回归角度宁可小一点靠扩样本补。5.4 现象绿牌和蓝牌混为一类或者只检出蓝牌原因类别不均衡。以蓝牌为主的数据集里绿牌样本占比太低模型容易把绿牌当成蓝牌的光照变体推理时偏向蓝色特征。解决先按 2.4 统计确认类别比例再对绿牌样本做复制和增强重采样。如果业务不需要区分车牌颜色干脆把所有车牌合成一个 plate 类单一类别设置在这种数据量下更抗造。需要区分时再补数据不要硬上。5.5 现象mAP 很高但摄像头实拍视频里总是错过低速驶过的车原因推理速度不够或车牌目标太小。mAP 在静态图片上算视频场景里车牌出现可能只有几帧错一帧就等于整个漏掉。连续帧之间目标框抖动太厉害也会让后处理跟不上。解决优先提推理速度小模型加 FP16 推理往往能把单帧时间降一半瓶颈在车牌尺寸过小就做 ROI 截取先用运动检测圈出车辆区域再对 ROI 做车牌检测。上线前把“连续十次经过能检到几次”当硬指标而不是只看 mAP。6. 让车牌检测更稳的进阶两招先截车脸再识别车牌以及用现场测试集反哺迭代6.1 两阶段管线检测完车牌再上 OCR很多业务做完车牌定位还不够要读出车牌号。常见做法是检测框裁剪出来送给车牌识别模型或 OCR 引擎读字符这就是“检测加识别”的两阶段方案。yolo 检测模型专注定位把车牌区域交给下游字符识别只处理裁剪后的图不关心车在画面什么位置。两阶段还有个好处OCR 读错时能快速判断错误来自检测框还是字符识别不用在两个任务之间互相猜。部署时两阶段可以分开跑检测模型在 GPU 或边缘盒子上做裁剪后的车牌小图交给 CPU 侧 OCR成本可控、可维护性好。对比端到端方案两阶段在小数据集上更容易达到预期精度每个子任务的数据需求都比一个大而全的任务低。6.2 用现场测试集反哺迭代而不是靠验证集的一串数字验证集是模型选择的路标但代替不了现场真实测试。我每次迭代都做这样一件事从实际部署点位收集一批带标注的现场图单独存放、不进训练集每次训练完跑一遍记录精度和召回再对照 fail_cases 文件夹里的老问题确认哪些被修好、哪些还挂着针对还挂着的补数据或调增强进入下一轮。我自己踩过一次大坑用验证集把 mAP 调到了 0.97很有成就感结果拿到现场一跑早上逆光、晚上灯箱、雨天水渍导致大面积漏检全部推倒重来。从那以后一直保持“验证集选模型、现场集定结论”的习惯。基于 1695 张图起步的车牌检测把数据集核对、预训练迁移、增强策略和置信度门限做扎实跑通并交付一个可用 demo 是完全可行的现场反馈和失败案例归档才是后续迭代的真正引擎。希望帮到你。本文还有配套的精品资源点击获取