ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLO车牌检测实战:1695张图从训练到边缘部署全链路

YOLO车牌检测实战:1695张图从训练到边缘部署全链路 简介这份车牌图像数据集面向计算机视觉研究者、深度学习开发者及智能交通相关项目实践者用于车牌检测与识别等对象检测任务的模型训练与测试。数据集共1695张车牌图像每张均配有YOLO文本格式的边界框标注包含类别标签与坐标信息可直接接入YOLO系列检测框架。压缩包为zip格式内含2000个文件其中1696个txt标注文件、303个jpg图像文件及1个yaml配置文件整体约147.47MByaml文件便于快速配置训练参数。目前已有132人学习下载。图像覆盖多种拍摄环境与车辆场景标注精度较高适合作为CNN等深度学习模型的训练素材也可用于验证小目标与密集目标检测算法的性能。使用者可基于该数据集完成从数据预处理、模型训练到评估优化的完整流程并需注意车牌图像的隐私与版权合规问题。1. 车牌图像数据集与 YOLO 对象检测1695 张图能跑出什么结果手上只有 1695 张车牌图片标注是 YOLO 格式想直接训一个能用的车牌检测器这事到底靠不靠谱我先把结论放前面能跑而且能跑出可用的结果但前提是你得把数据切分、增强策略和评估口径这三件事做对。车牌图像识别这个方向很多人第一反应是上 OCR 做端到端字符识别但实际工程里更稳的路径是两段式——先用 YOLO 把车牌框出来再对裁剪区域做字符识别。1695 张图对于「检测车牌位置」这个单一类别任务来说属于小样本但不算贫瘠YOLOv8n 或 YOLOv8s 这种轻量模型在合理增强下 mAP50 做到 0.9 以上是常见结果。这篇文章面向的是手里已经拿到这份 YOLO 格式车牌数据集、准备自己训练和部署的从业者。我会把从数据体检、格式校验、训练配置到边缘部署的完整链路拆开讲参数怎么设、坑在哪、失败时看什么指标都落到可复现的命令和代码上。如果你还在纠结「yolo 是什么」或者「yolo 算法讲解 ppt」那种层面这篇可能节奏偏快但如果你已经准备动手下面每一步都能直接抄。需要提前说清楚一个边界1695 张图训出来的模型泛化能力高度依赖你的采集场景。如果训练集全是白天正脸车牌拿去跑夜间、逆光、大角度倾斜的监控画面翻车是必然的。所以后面我会专门讲怎么用增强和验证集设计来提前暴露这个问题而不是等部署上线才发现误检率高。2. 先做数据体检1695 张 YOLO 标注到底能不能直接开训拿到一份 YOLO 格式数据集最忌讳的就是不看一眼直接yolo train。YOLO 格式本身很简单——每张图对应一个同名.txt每行是class x_center y_center width height坐标全部归一化到 0~1。但「格式对」不等于「数据对」1695 张图里常见的暗病包括标注框越界、宽高为 0、类别索引不连续、图片和标签对不上号。这些问题在训练时不会报错只会让你的 loss 曲线看起来「玄学」——震荡、不收敛、mAP 卡在某个值上不去。2.1 YOLO 标注格式的三个硬性约束先把格式规则钉死后面所有校验都围绕这三条约束项要求违反后果坐标范围归一化后必须在 (0, 1) 区间内越界框被静默裁剪目标丢失数值类型浮点数建议保留 6 位小数精度损失导致小目标框偏移类别索引从 0 开始连续整数索引跳号导致类别数统计错误车牌检测通常是单类别所以classes.txt里应该只有一行license_plate对应索引 0。如果你拿到的是多类别比如还分了蓝牌、黄牌、新能源绿牌那类别数就是 3data.yaml里的nc要跟着改。这里有个高频踩坑点很多人把颜色分类当成类别结果模型学的是「颜色」而不是「车牌」遇到褪色或夜间偏色的车牌就废了。我的建议是检测阶段只保留一个类颜色和字符交给下游处理。2.2 用一段脚本把数据集体检做完下面这段脚本做四件事统计图片与标签配对情况、检查坐标越界、检查零面积框、输出类别分布。直接放在数据集根目录跑。import os import glob from collections import Counter IMG_EXTS (.jpg, .jpeg, .png, .bmp) root ./dataset # 数据集根目录按实际路径改 img_dir os.path.join(root, images) lbl_dir os.path.join(root, labels) imgs [f for f in glob.glob(os.path.join(img_dir, *)) if f.lower().endswith(IMG_EXTS)] missing_label, bad_coord, zero_area [], [], [] cls_counter Counter() for img_path in imgs: stem os.path.splitext(os.path.basename(img_path))[0] lbl_path os.path.join(lbl_dir, stem .txt) if not os.path.exists(lbl_path): missing_label.append(stem) continue with open(lbl_path, r) as f: for line_no, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: bad_coord.append((stem, line_no, field_count)) continue c, x, y, w, h int(parts[0]), *map(float, parts[1:]) cls_counter[c] 1 # 坐标越界检查中心点加减半宽高不能超出 [0,1] if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_coord.append((stem, line_no, out_of_range)) if w * h 1e-6: zero_area.append((stem, line_no)) print(f图片总数: {len(imgs)}) print(f缺失标签: {len(missing_label)} - {missing_label[:5]}) print(f坐标异常: {len(bad_coord)} - {bad_coord[:5]}) print(f零面积框: {len(zero_area)} - {zero_area[:5]}) print(f类别分布: {dict(cls_counter)})逻辑说明脚本先建立图片和标签的一一对应关系缺失标签单独记录因为 YOLO 训练时会把没有标签的图当负样本如果你本意是漏标那模型就被喂了错误信号。坐标检查用的是归一化后的中心点加宽高w*h小于 1e-6 基本就是标注时手滑画了个点。类别分布能帮你确认是不是真的单类别——如果cls_counter里冒出个 5、6说明标注工具导出时带了别的类。参数说明root改成你自己的数据集路径IMG_EXTS按实际图片格式增减。跑完如果missing_label和bad_coord都是 0类别分布只有{0: N}那这份数据可以直接进下一步。如果有异常先修数据再谈训练别抱侥幸心理。2.3 训练集/验证集切分别用随机切分糊弄自己1695 张图常见切法是 8:1:1 或 8:2。但车牌数据有个特点同一段视频抽帧出来的图片高度相似。如果你随机切分验证集里很可能混进了和训练集几乎一样的帧导致验证 mAP 虚高部署时打脸。正确做法是按「采集批次」或「视频来源」切分——同一段视频的帧要么全进训练集要么全进验证集。我一般会先按文件名前缀或时间戳分组再整组分配。如果数据来源单一没法分组至少用sklearn的分层切分并固定随机种子保证可复现import os, shutil, random from sklearn.model_selection import train_test_split random.seed(42) root ./dataset imgs sorted(os.listdir(os.path.join(root, images))) train, val train_test_split(imgs, test_size0.2, random_state42) for split, files in [(train, train), (val, val)]: for sub in (images, labels): os.makedirs(os.path.join(root, split, sub), exist_okTrue) for f in files: stem os.path.splitext(f)[0] shutil.copy(os.path.join(root, images, f), os.path.join(root, split, images, f)) shutil.copy(os.path.join(root, labels, stem .txt), os.path.join(root, split, labels, stem .txt)) print(ftrain{len(train)}, val{len(val)})切分完记得再跑一遍 2.2 的体检脚本分别对 train 和 val 目录跑确认两边类别分布一致。如果 val 里某个类别一个都没有那这个类别的评估指标就是假的。3. 训练配置怎么定从 data.yaml 到 YOLOv8 超参的实操清单数据没问题了接下来是训练。这一步的核心不是「把命令敲对」而是理解每个参数在车牌这个小目标场景下意味着什么。车牌在整图里占比通常很小尤其是远距离监控画面所以输入分辨率、anchor 匹配和增强策略都得围绕「小目标」来调。3.1 data.yaml 与模型选型先写data.yaml这是 YOLO 训练的入口配置path: ./dataset train: train/images val: val/images nc: 1 names: [license_plate]path是数据集根目录train和val是相对路径。nc是类别数车牌检测填 1。names顺序必须和标注里的类别索引一致索引 0 对应license_plate。模型选型上1695 张图属于小数据集直接上 YOLOv8l 或 x 会严重过拟合。我的经验是YOLOv8n 起步mAP 不够再换 YOLOv8s。n 版本参数量约 3M在 1695 张图上训练一轮只要几十秒单卡 V100 或同级迭代快适合先跑通链路。如果你后续要部署到 RK3588 这类边缘设备n 或 s 也是唯一现实的选择——大模型转 RKNN 后推理延迟很难压到实时。预训练权重直接用 COCO 上训好的yolov8n.pt。有人会问「车牌和 COCO 类别又不重叠预训练有用吗」——有用底层卷积学的是边缘、纹理、角点这些通用特征迁移到车牌上能显著加快收敛。从零训在小数据集上几乎必然欠拟合。3.2 训练命令与关键参数逐条解释yolo detect train \ data./dataset/data.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience50 \ mosaic1.0 \ scale0.5 \ degrees10.0 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ projectruns/plate \ nameexp1逐条说清楚epochs200小数据集收敛快但 200 轮是给足余量。配合patience5050 轮验证指标不涨就早停不会白跑。imgsz640车牌是小目标640 是精度和速度的平衡点。如果远距离车牌特别多可以提到 960 或 1280但显存和推理耗时同步上涨。batch16按显存调。V100 16G 跑 640 分辨率batch 开到 32 也没问题消费级卡就降到 8 或 16。lr00.01/lrf0.01初始学习率和最终学习率因子余弦退火从 0.01 降到 0.0001。小数据集别用太大学习率否则 loss 直接飞。mosaic1.0Mosaic 增强四图拼接。对车牌这种小目标特别有效能显著增加单张图里的目标数量和背景多样性。但注意——训练最后 10 轮建议关掉 mosaic让模型在真实分布上收尾否则框的定位会偏。scale0.5/degrees10.0随机缩放和旋转。车牌有角度变化适度旋转增强合理但别超过 15 度否则字符方向都反了检测框也跟着歪。hsv_h/s/v色调、饱和度、亮度扰动。车牌颜色受光照影响大这三个参数是提升夜间和逆光鲁棒性的关键。hsv_v0.4让模型见过暗图和过曝图。训练启动后重点盯三个东西box_loss是否稳定下降、mAP50是否在涨、验证集的precision和recall是否平衡。如果box_loss震荡剧烈先降lr0到 0.005如果mAP50早早卡住检查验证集是不是和训练集分布差太远。3.3 训练中 BN 崩溃和 loss 不收敛的排查顺序热词里「yolo 训练中 bn 崩溃」是个高频问题车牌训练同样会遇到。BN 崩溃的典型现象是 loss 突然变成 NaN或者某一轮之后所有预测框都堆在图像中心。排查顺序如下先看 batch size。BN 层依赖 batch 内统计量batch 太小比如 2 或 4时统计量噪声大容易崩。把 batch 提到 16 以上或者改用yolov8n.pt自带的bn动量设置。再看学习率。lr00.01对某些数据偏大降到 0.005 或 0.001 试。检查数据里有没有脏样本。坐标越界、零面积框在训练时会产生极端梯度直接触发 NaN。回到第 2 章的体检脚本把异常样本清掉。最后看 AMP。混合精度训练在部分显卡上会溢出加ampFalse关掉试试。这四步走完90% 的 BN 崩溃能定位。剩下的 10% 多半是数据本身有系统性问题比如标注框普遍偏大或偏小导致模型学不到稳定特征。4. 评估与验证mAP 之外你更该看混淆矩阵和 PR 曲线训练跑完runs/plate/exp1/下会生成一堆曲线图。很多人只看results.png里的 mAP50 就下结论这是不够的。车牌检测的评估要回答两个问题漏检多还是误检多以及在什么尺度上失效。4.1 混淆矩阵怎么读才不误导YOLO 输出的混淆矩阵是归一化后的横轴是预测类别纵轴是真实类别。单类别车牌检测的矩阵是 2x2license_plate和background。重点看两个数真实车牌被预测为 background 的比例 漏检率。这个数高说明模型对某些车牌「视而不见」常见于小目标或遮挡。background 被预测为车牌的比例 误检率。这个数高说明模型把类似车牌的纹理比如广告牌、车灯当成了车牌。热词里提到「yolo 混淆矩阵总合不唯一」这通常是因为多类别任务里归一化方式理解错了。单类别任务不存在这个问题矩阵每行加起来是 1。如果你看到行和不为 1检查是不是把conf阈值设得太低导致大量低置信预测被计入。4.2 用验证集跑一次带置信度阈值的评估默认评估用的是conf0.001这个阈值极低会把大量噪声框算进来导致 recall 虚高、precision 虚低。实际部署时你不可能用这么低的阈值所以要按真实阈值重新评估yolo detect val \ modelruns/plate/exp1/weights/best.pt \ data./dataset/data.yaml \ imgsz640 \ conf0.25 \ iou0.5 \ plotsTrueconf0.25是部署常用起点iou0.5是 COCO 标准。跑完看val_batch0_pred.jpg这是模型在验证集第一批图上的实际预测可视化。这张图比任何数字都直观——漏检的车牌、误检的框、框得歪不歪一眼就能看出来。我习惯把这张图和 PR 曲线对照着看PR 曲线告诉你阈值取多少时 precision 和 recall 平衡最好可视化告诉你错误具体长什么样。4.3 小目标漏检的定位方法如果验证发现远距离小车牌漏检严重别急着换模型。先把验证集里所有车牌按面积分档大96x96、中32x32~96x96、小32x32分别统计各档的 recall。YOLO 的评估输出里没有直接分档但你可以用标注文件自己算import os, glob lbl_dir ./dataset/val/labels buckets {small: [0, 0], medium: [0, 0], large: [0, 0]} # [命中, 总数] for lbl in glob.glob(os.path.join(lbl_dir, *.txt)): with open(lbl) as f: for line in f: _, _, _, w, h map(float, line.split()) area w * h # 归一化面积 if area 0.01: buckets[small][1] 1 elif area 0.09: buckets[medium][1] 1 else: buckets[large][1] 1 print(buckets)这个脚本只统计了标注分布要算命中率还得把预测结果和标注做 IoU 匹配。但先看分布就有价值如果验证集里 70% 都是 small那模型在小目标上表现差就是数据分布问题解法是提高imgsz或增加小目标样本而不是调模型结构。5. 避坑与排查车牌 YOLO 训练里最容易翻车的五件事这一章是我自己踩过和帮别人排查过的真实问题按「现象 → 原因 → 解决」写。每一条都对应一个具体的失败模式不是泛泛而谈。5.1 训练 loss 正常但验证 mAP 始终为 0现象训练日志里box_loss和cls_loss都在降但验证mAP50一直是 0 或接近 0。原因最常见的是data.yaml里的names顺序和标注类别索引对不上。比如标注里车牌是索引 1但names里license_plate写在第一个索引 0模型学的是「索引 0 是背景」验证时自然全错。另一个原因是验证集路径写错YOLO 实际在验证一个空目录。解决用第 2 章的体检脚本确认标注里的类别索引然后逐字核对data.yaml的names列表。路径问题就打印val/images下的文件数确认不是 0。5.2 模型把车牌框得特别大几乎覆盖整张图现象预测可视化里框比真实车牌大好几倍甚至框住整个车头。原因标注时框画大了或者数据增强里的scale参数过大导致模型学到「大框更安全」。还有一种情况是mosaic增强在训练末期没关四图拼接后目标尺度混乱。解决先抽查 20 张标注可视化确认框贴合车牌。然后训练最后 10 轮加close_mosaic10让模型在真实尺度上收尾。scale从 0.5 降到 0.3 试。5.3 夜间或逆光图片漏检率飙升现象白天验证集 recall 0.95夜间图片 recall 掉到 0.5 以下。原因训练集里夜间样本太少模型没见过低照度下的车牌纹理。HSV 增强的hsv_v虽然能模拟亮度变化但模拟不出夜间特有的噪声和光晕。解决如果拿不到更多夜间数据把hsv_v提到 0.6并加入随机伽马变换。更根本的办法是采集夜间样本哪怕只有 100 张混进训练集也能明显改善。部署侧可以在预处理加直方图均衡但这是补救不是根治。5.4 训练到一半 loss 突然变 NaN现象前 50 轮正常第 51 轮box_lossnan之后所有指标失效。原因按第 3.3 节的顺序排查。车牌数据里最常见的是标注框宽高为 0 或极小产生无穷大梯度。其次是学习率在某个 batch 上遇到脏样本后爆炸。解决先跑体检脚本清掉零面积框。然后加ampFalse排除混合精度溢出。如果还崩lr0降到 0.001并加grad_clip10.0做梯度裁剪。5.5 部署到边缘设备后误检率比验证集高很多现象验证集 precision 0.92部署到 RK3588 或 Jetson 后实际画面里误检明显增多。原因验证集和真实场景的分布差异。验证集是精选图片真实场景有运动模糊、压缩噪声、极端光照。另外边缘部署时输入分辨率可能被压缩比如从 640 降到 416小目标特征丢失。解决部署前用真实场景的视频抽帧做一次「影子验证」不参与训练只做评估。如果误检集中在某类背景比如广告牌针对性采集负样本加入训练。分辨率尽量保持和训练一致实在要降先验证降分辨率后的 mAP 损失是否可接受。6. 从 1695 张图到可用模型我的迭代节奏和一个提点技巧最后聊点实操层面的节奏感。1695 张图训车牌检测我的习惯是分三轮走而不是一次调到位。第一轮用yolov8n.pt 默认增强 epochs100目的是跑通链路、拿到基线。这一轮不看绝对指标只看流程有没有问题数据加载对不对、验证可视化正不正常、导出 ONNX 能不能推理。基线 mAP50 大概在 0.85 左右。第二轮针对第一轮暴露的弱点调增强和分辨率。如果小目标漏检多imgsz从 640 提到 960如果误检多加负样本不含车牌的背景图标签为空文件如果夜间差加大 HSV 扰动。这一轮epochs200patience50通常能把 mAP50 推到 0.92 以上。第三轮做模型蒸馏或换更大的yolov8s但只在第二轮仍不满足要求时才做。多数车牌检测场景n 版本在 640 分辨率下已经够用盲目上大模型只会让部署更难。这里给一个提点技巧用检测框的置信度做二次过滤。车牌检测的误检往往集中在低置信区间0.25~0.4而真实车牌通常置信度在 0.6 以上。部署时把阈值从 0.25 提到 0.4precision 能明显提升recall 损失很小。这个阈值不要拍脑袋定用验证集的 PR 曲线找 precision 和 recall 的拐点那个拐点对应的 conf 就是你的部署阈值。我自己的习惯是每次训练完把best.pt、data.yaml、训练命令和验证集 PR 曲线截图存进一个exp_record文件夹命名带日期和关键参数。这样三个月后回头看能立刻复现当时的环境不用靠记忆猜「那次到底用的什么学习率」。车牌检测这个任务不难难的是把每一次迭代的变量控制住别让「玄学」背锅。希望帮到你。本文还有配套的精品资源点击获取
返回列表