ARTICLE DETAIL

资讯详情

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

车牌YOLOv5检测数据集构建实战指南

车牌YOLOv5检测数据集构建实战指南 简介本资源是面向计算机视觉开发者与深度学习初学者的车牌检测专用数据集专为YOLOv5模型训练优化设计解决智能交通、安防监控等场景中车牌精确定位与识别的核心需求。压缩包共7808个文件含3825张已标注车牌图像jpg、4000份对应边界框标签txt及1个预配置yaml文件完整覆盖训练/验证所需数据结构与参数设置开箱即用。资源大小183.36MB组织规范、标注准确省去数据清洗与格式转换环节。目前已有372人学习下载用户可直接加载该数据集开展YOLOv5训练全流程实践快速验证模型收敛效果并基于真实蓝绿双色车牌样本提升泛化能力与鲁棒性显著缩短车牌识别系统开发周期。1. 车牌YOLOv5检测数据集不是“拿来就能训”的资源包而是需要你亲手校准的工业级视觉入口你搜“车牌YOLOv5检测数据集”大概率是刚跑通YOLOv5官方COCO demo想快速切入一个有明确业务出口的方向——比如停车场闸机识别、高速卡口初筛、园区车辆登记。但现实很骨感网上标着“车牌YOLOv5数据集”的压缩包90%以上是三类货一类是纯图片乱序txt标签没按YOLO格式重排、没划分train/val/test、一类是用OpenCV随便截的模糊车牌图光照畸变全无控制、还有一类干脆是百度OCR接口返回的JSON里硬抠出的坐标根本没做人工复核。这些不是数据集是“数据陷阱”。真正能进产线的车牌检测模型不靠数据量堆而靠小而精的标注质量、可控的场景覆盖、以及和YOLOv5 anchor匹配的尺度分布。本篇不讲理论推导只说一线工程师怎么从零构建一个可落地的车牌YOLOv5检测数据集怎么收图、怎么标、怎么验、怎么防翻车。适合正在做智能停车、物流园区车辆管理、或交管边缘设备开发的嵌入式/算法同学——你不需要从头写YOLO但必须亲手把数据这关焊死。2. 构建车牌YOLOv5检测数据集从原始图像采集到YOLO格式转换的完整链路2.1 为什么不能直接用公开数据集真实场景的三个硬约束很多新手一上来就去下CCPDChinese City Parking Dataset或PKU-Auto60K结果训练时mAP卡在58%不上不下。不是模型不行是数据和你的场景错配。我去年在某省会城市智慧停车项目踩过坑总结出三个必须现场验证的硬约束尺度约束YOLOv5默认anchorP3-P5对416×416输入最友好对应车牌物理尺寸在图像中应占32×16像素到128×64像素区间。CCPD里大量远距离小车牌20px宽会导致P3层漏检而近距大车牌200px宽又让P5层回归不稳定。我们最终砍掉所有宽度25px或180px的样本。光照约束夜间补光灯造成的高光反射、雨天玻璃反光、强逆光下的车牌褪色——这些在CCPD里占比不足15%但实际停车场夜间误检率70%来自这三类。我们强制要求数据集中夜间样本≥30%、雨雾样本≥10%、逆光样本≥15%。安装约束摄像头俯角30°~60°、水平偏移±15°、距离3~8米直接影响车牌形变。CCPD多为车载前视视角而我们的闸机摄像头是垂直向下45°安装。必须用OpenCV的cv2.warpPerspective对原始图做俯角矫正预处理再标注否则bbox回归永远学不准。提示别迷信“大数据”。我们最终上线的模型只用了2173张高质量图但覆盖了6种光照4种安装角度3种天气组合mAP0.5达92.3%比用10万张CCPD未清洗数据训出的模型高11.7个百分点。2.2 图像采集用手机也能搞定但必须守住这四条红线你不需要专业相机。一台iPhone 12主摄 三脚架 手动模式APP如Halide足够。关键在流程控制时间红线每天只在早7:00-9:00晨光、午12:00-14:00正午强光、晚18:00-20:00黄昏渐暗、21:00-23:00夜间补光四个时段采集每时段至少200张。避开阴天——云层漫射光会让车牌对比度暴跌这种图直接丢弃。距离红线用激光测距仪实测摄像头到地面基准点距离确保所有图拍摄距离在4.5±0.3米。超出范围的图即使清晰也剔除——因为YOLOv5的anchor是按固定距离标定的。角度红线三脚架云台调至俯角45°±2°用手机内置水平仪App校准水平方向左右偏移≤5°。每次换点位前必须重新校准。车牌类型红线只收蓝牌小型汽车、黄牌大型车/工程车、新能源绿牌。挂“使”“领”“警”“粤港”等特殊牌照的图全部剔除——这些在YOLOv5分类头里属于长尾类别会拖累主干检测精度。实操时我用Python写了个校验脚本拍完自动检查import cv2 import numpy as np def check_image_quality(img_path): img cv2.imread(img_path) h, w img.shape[:2] # 检查是否过曝白色像素占比35% white_ratio np.sum(img 240) / (h * w * 3) if white_ratio 0.35: return False, overexposed # 检查模糊度拉普拉斯方差80视为模糊) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var() if laplacian_var 80: return False, fblurry: {laplacian_var:.1f} # 检查车牌区域亮度ROI内均值40视为过暗 roi img[int(h*0.3):int(h*0.7), int(w*0.2):int(w*0.8)] brightness np.mean(cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)[:,:,2]) if brightness 40: return False, ftoo dark: {brightness:.1f} return True, ok # 批量校验 for img_file in os.listdir(raw_images/): ok, reason check_image_quality(fraw_images/{img_file}) if not ok: os.remove(fraw_images/{img_file}) print(fRemoved {img_file}: {reason})这段代码在采集现场用树莓派4B实时跑不合格图当场删省得后期清洗。2.3 标注规范YOLO格式不是简单转坐标而是定义检测边界的哲学YOLOv5要求每张图对应一个.txt文件每行class_id center_x center_y width height归一化到0~1。但很多人忽略center_x/y和width/height的计算方式直接决定模型对车牌形变的鲁棒性。错误做法用LabelImg直接框车牌四边形取左上右下算中心和宽高。问题在于——车牌是刚性矩形但图像中因透视会产生梯形畸变。若按四边形框YOLO回归的是“视觉矩形”不是“物理车牌”。正确做法先拟合车牌四点透视坐标再用单应性变换还原为正面矩形最后标注这个正面矩形。步骤如下用CVAT或SuperAnnotate标出车牌四个角点左上、右上、右下、左下生成points.json对每个四点组用OpenCV求单应性矩阵def get_frontal_bbox(points, img_shape): # points: [(x1,y1), (x2,y2), (x3,y3), (x4,y4)] 顺时针 pts_src np.array(points, dtypefloat32) # 设定正面车牌尺寸按中国标准440mm×140mm → 宽高比3.14 w, h 440, 140 pts_dst np.array([[0,0], [w,0], [w,h], [0,h]], dtypefloat32) M cv2.getPerspectiveTransform(pts_src, pts_dst) # 将正面矩形映射回原图取边界框 dst_pts cv2.perspectiveTransform(np.array([pts_dst]), M)[0] x_min, y_min dst_pts.min(axis0) x_max, y_max dst_pts.max(axis0) # 归一化 h_img, w_img img_shape[:2] cx ((x_min x_max) / 2) / w_img cy ((y_min y_max) / 2) / h_img bw (x_max - x_min) / w_img bh (y_max - y_min) / h_img return [0, cx, cy, bw, bh] # class_id0 for license plate # 示例对一张图的所有车牌执行 with open(labels_raw/001.json) as f: points_list json.load(f)[points] img cv2.imread(images/001.jpg) yolo_lines [] for points in points_list: yolo_line get_frontal_bbox(points, img.shape) yolo_lines.append( .join(map(str, yolo_line))) with open(labels/001.txt, w) as f: f.write(\n.join(yolo_lines))逻辑说明get_frontal_bbox函数核心是用单应性变换把透视畸变的车牌“拉直”再取其正面投影的包围盒。这样YOLOv5学到的是车牌的物理尺寸先验而非图像中的视觉变形——模型在测试时遇到新角度车牌回归更稳定。参数说明w/h440/140是中国蓝牌标准尺寸若你项目含新能源车480×140mm需同步改为w480。2.4 数据集划分不是按比例切分而是按场景组合切分YOLOv5默认按7:2:1分train/val/test但车牌检测必须打破这个惯性。原因验证集和测试集必须包含你最怕的bad case而不是随机抽样。我们采用场景驱动划分法Train set只含白天晴好、距离4.5~6米、俯角45°±1°的“理想样本”占比50%Val set强制包含所有夜间补光、雨天反光、逆光车牌、俯角55°、距离7.5米的困难样本占比30%Test set完全独立于训练采集日且包含从未见过的车牌字体如某地市新发的“粤A·X1234临”临时牌、从未见过的摄像头型号海康DS-2CD3T47G2-L 400万像素占比20%。这样划分后val loss曲线能真实反映模型在恶劣条件下的退化程度而不是被理想样本平均掉。代码实现用pandas按元数据筛选import pandas as pd import os # 元数据表 metadata.csv 包含filename, time_period, weather, light_condition, camera_model, distance, angle df pd.read_csv(metadata.csv) # Train: ideal conditions only train_mask (df[time_period] day) \ (df[weather] sunny) \ (df[distance].between(4.5, 6.0)) \ (df[angle].between(44, 46)) train_files df[train_mask][filename].tolist() # Val: all hard cases val_mask (df[light_condition].isin([night, backlight, rain])) | \ (df[distance] 7.0) | \ (df[angle] 50) val_files df[val_mask][filename].tolist() # Test: completely new day new camera test_mask (df[date] 2023-10-15) (df[camera_model] Hikvision_DS-2CD3T47G2-L) test_files df[test_mask][filename].tolist() # 写入split文件 for split_name, files in zip([train, val, test], [train_files, val_files, test_files]): with open(f{split_name}.txt, w) as f: for f_name in files: f.write(fimages/{f_name}\n)注意metadata.csv必须在采集时就人工填写不能事后补——这是数据治理的起点。3. YOLOv5训练前的数据集质检三个必跑脚本避免90%的训练翻车3.1 标签完整性检查缺失.txt或空文件是最大隐形杀手YOLOv5训练时若某张图没有对应.txt或.txt为空PyTorch Dataloader会静默跳过导致实际batch size波动、loss震荡。必须提前扫净#!/bin/bash # check_labels.sh IMAGE_DIRimages LABEL_DIRlabels echo Checking label completeness # 找出有图无标签的文件 find $IMAGE_DIR -name *.jpg -o -name *.png | \ sed s/images\/// | sed s/\.[^.]*$// | \ sort /tmp/img_names.txt find $LABEL_DIR -name *.txt | \ sed s/labels\/// | sed s/\.txt$// | \ sort /tmp/label_names.txt echo Missing labels: comm -23 /tmp/img_names.txt /tmp/label_names.txt # 找出标签为空的文件 echo -e \nEmpty labels: find $LABEL_DIR -name *.txt -size 0 # 找出标签含非法字符如中文路径导致的乱码 echo -e \nLabels with non-ASCII chars: find $LABEL_DIR -name *.txt -exec grep -l [^[:ascii:]] {} \;运行后Missing labels列表里的图必须补标或删除Empty labels里的文件要重标non-ASCII chars说明你用Windows记事本保存过txt需用VS Code以UTF-8无BOM重存。3.2 坐标合法性检查YOLO格式的五个死亡边界YOLO要求center_x, center_y, width, height全在[0,1]区间且width0,height0,center_x±width/2 ∈ [0,1]。越界坐标会导致训练时nan loss。写个Python脚本批量验import os import numpy as np def validate_yolo_labels(label_dir): invalid_files [] for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue try: with open(os.path.join(label_dir, txt_file)) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) 5: invalid_files.append(f{txt_file}:{i} - too few values) continue try: cx, cy, w, h map(float, parts[1:5]) except ValueError: invalid_files.append(f{txt_file}:{i} - non-float coords) continue # 检查五条边界 if not (0 cx 1 and 0 cy 1): invalid_files.append(f{txt_file}:{i} - center out of [0,1]) if not (0 w 1 and 0 h 1): invalid_files.append(f{txt_file}:{i} - width/height invalid) if not (0 cx - w/2 1 and 0 cx w/2 1): invalid_files.append(f{txt_file}:{i} - x-boundary invalid) if not (0 cy - h/2 1 and 0 cy h/2 1): invalid_files.append(f{txt_file}:{i} - y-boundary invalid) except Exception as e: invalid_files.append(f{txt_file} - read error: {e}) return invalid_files invalids validate_yolo_labels(labels/) if invalids: print(INVALID LABELS FOUND:) for err in invalids[:10]: # 只打前10个避免刷屏 print(err) print(f... and {len(invalids)-10} more) else: print(All labels valid.)常见报错及修复center out of [0,1]标注工具坐标系设错如设成像素而非归一化重导出x-boundary invalid车牌紧贴图像左/右边缘YOLO无法学习边界回归手动裁剪图像留10%边距non-float coordsLabelImg导出时用了逗号分隔符应为空格用sed -i s/,/ /g *.txt批量修。3.3 数据集分布可视化用直方图揪出anchor不匹配的根源YOLOv5的P3/P4/P5层anchor是按COCO数据集统计的而车牌宽高比集中在3.0~3.5蓝牌3.14绿牌3.43与COCO的1.0~2.0严重错配。必须看width/height分布import matplotlib.pyplot as plt import numpy as np from glob import glob def plot_wh_distribution(label_dir): widths, heights, ratios [], [], [] for txt_file in glob(f{label_dir}/*.txt): with open(txt_file) as f: for line in f: parts line.strip().split() if len(parts) 5: continue w, h float(parts[3]), float(parts[4]) widths.append(w) heights.append(h) ratios.append(w/h) fig, axes plt.subplots(1, 3, figsize(15,4)) axes[0].hist(widths, bins50, alpha0.7); axes[0].set_title(Width Distribution) axes[1].hist(heights, bins50, alpha0.7); axes[1].set_title(Height Distribution) axes[2].hist(ratios, bins50, alpha0.7); axes[2].set_title(Width/Height Ratio) plt.tight_layout() plt.savefig(wh_distribution.png, dpi200) plt.show() plot_wh_distribution(labels/)典型健康分布图应显示width峰值在0.15~0.35对应416×416图中65~145pxratio集中在3.0~3.5。若ratio峰值在1.8说明你标的是整个车身而非车牌——立刻返工。4. 避坑指南车牌YOLOv5数据集构建中5个血泪经验4.1 现象训练初期loss下降极慢100 epoch后仍3.0原因标注时用了LabelImg的“旋转矩形”模式Rotated Bounding Box导出的YOLO格式实际是[class, cx, cy, w, h, angle]六维但YOLOv5只认五维。第六维被当class_id解析导致所有标签被分到第100类超出num_classes1模型根本学不到车牌特征。解决LabelImg中务必关闭“旋转框”选项Settings → Enable Rotated Bounding Box → Uncheck所有标注用普通矩形框。4.2 现象验证集mAP0.5突然从85%暴跌到42%且只发生在夜间样本原因采集夜间图时手机自动启用夜景模式多帧合成导致车牌区域出现运动模糊。但标注时按清晰图框选YOLO学到的是“清晰车牌”的纹理特征遇到模糊图直接失效。解决夜间采集强制关闭手机夜景模式用Pro模式手动设ISO≤800、快门≥1/30s。对已采集的模糊图用cv2.GaussianBlur(img, (3,3), 0)轻微模糊全图再标注——让模型学“模糊车牌”的通用特征。4.3 现象模型在测试集上对黄牌检测率仅61%蓝牌达94%原因数据集中黄牌样本仅占8%且多为远距离小图30px宽而YOLOv5 P3层对小目标敏感度低。模型把黄牌当背景学了。解决对黄牌样本做定向增强——用Albumentations库对黄牌图做RandomScale(scale_limit0.3, p0.8)放大再随机裁剪到416×416保证黄牌在输入图中宽度≥40px。同时在data.yaml中给黄牌class加权重nc: 2蓝牌0黄牌1并在训练时用--cls_weights 1.0,2.5提升黄牌损失权重。4.4 现象部署到Jetson Nano后FPS从42骤降到8GPU占用率99%原因数据集中混入了12MP超清图4000×3000YOLOv5推理时自动resize到640×640导致显存暴涨。但Nano只有4GB GPU内存resize过程卡死。解决采集时手机设为“4K 30fps”而非“最高分辨率”或用脚本批量降质# 批量缩放至最大边≤1280px mogrify -resize 1280x1280 -quality 95 images/*.jpg并删除所有identify -format %wx%h image.jpg | awk -Fx {if($11280 || $21280) print}命中的图。4.5 现象同一辆车白天检测正常雨天总把雨滴当车牌框出一堆假阳性原因数据集中雨天样本全是“小雨”而上线时遇到“暴雨”雨滴密度高到形成连续水痕YOLO的anchor误判为车牌长条。解决在雨天样本中人工添加雨痕mask——用Photoshop画笔在车牌区域外沿画半透明灰条opacity30%模拟暴雨水痕。这样模型学会区分“真车牌边缘”和“假水痕边缘”。我们加了200张此类增强图雨天误检率从37%降至6%。5. 进阶技巧用数据集自身生成YOLOv5专用anchor让mAP再提3.2%YOLOv5默认anchor如yolov5s.yaml中的anchors: [10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326]是为COCO设计的而车牌宽高比恒定≈3.2用默认anchor就像用鱼竿钓鲸鱼——太细。必须用你的数据集重聚类anchor。5.1 K-means聚类不是直接聚而是先做物理尺度归一化YOLOv5作者推荐用kmeans.py脚本但直接跑会失败——因为车牌在不同距离下像素尺寸差异巨大4米处宽120px8米处仅60px。必须先将所有bbox按物理距离归一化import numpy as np from sklearn.cluster import KMeans import matplotlib.pyplot as plt def get_normalized_boxes(label_dir, metadata_csv): # 加载距离元数据 meta_df pd.read_csv(metadata_csv) boxes [] for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue fname txt_file.replace(.txt, ) # 获取该图拍摄距离 dist meta_df[meta_df[filename]fname][distance].iloc[0] # 距离越大物理尺寸相同则像素越小 → 需放大补偿 # 补偿因子 4.5 / dist 以4.5米为基准 comp_factor 4.5 / dist with open(os.path.join(label_dir, txt_file)) as f: for line in f: parts line.strip().split() if len(parts) 5: continue w, h float(parts[3]), float(parts[4]) # 归一化到4.5米基准下的像素尺寸假设416x416输入 w_norm w * comp_factor * 416 h_norm h * comp_factor * 416 boxes.append([w_norm, h_norm]) return np.array(boxes) # 聚类k6对应P3-P5三层各2个anchor boxes get_normalized_boxes(labels/, metadata.csv) kmeans KMeans(n_clusters6, random_state0).fit(boxes) anchors kmeans.cluster_centers_ print(New anchors (width, height):) for i, (w, h) in enumerate(anchors): print(f {int(w)}, {int(h)})输出示例New anchors (width, height): 42, 13 68, 21 105, 33 152, 47 218, 68 320, 100这些数字就是为你数据集定制的anchor——宽高比全部≈3.2且覆盖了从近距小车牌到远距大车牌的完整尺度链。5.2 在YOLOv5中替换anchor三步生效无需改源码修改models/yolov5s.yaml中的anchors字段替换成上面6组数按顺序填入每层2个# yolov5s.yaml ... anchors: - [42,13, 68,21] # P3/8 - [105,33, 152,47] # P4/16 - [218,68, 320,100] # P5/32 ...训练时加参数--cfg models/yolov5s.yaml指定配置关键一步必须用--rect参数训练。因为新anchor尺度跨度大42px到320px不用矩形推理rectangular inference会导致小图pad过多、大图裁剪失真。--rect让Dataloader按batch内最长边resize保细节。注意不要用--line线性插值resize必须用--rect矩形pad。我曾因漏掉这步新anchor带来的mAP提升全被pad噪声抵消。5.3 验证anchor有效性画出聚类中心与真实bbox的IOU热力图光看数字不够要可视化anchor是否真的“罩住”了你的车牌。写个热力图脚本def plot_anchor_iou_heatmap(anchors, boxes, save_pathanchor_iou.png): # anchors: (6,2), boxes: (N,2) ious np.zeros((len(anchors), len(boxes))) for i, (aw, ah) in enumerate(anchors): for j, (bw, bh) in enumerate(boxes): # 计算宽高IOU类似GIoU的宽高部分 inter_w min(aw, bw) inter_h min(ah, bh) union_w aw bw - inter_w union_h ah bh - inter_h ious[i,j] (inter_w * inter_h) / (union_w * union_h 1e-8) plt.figure(figsize(10,6)) plt.imshow(ious, cmapviridis, aspectauto) plt.colorbar(labelWidth-Height IOU) plt.xlabel(Box Index) plt.ylabel(Anchor Index) plt.title(Anchor-Box Width/Height IOU Heatmap) plt.savefig(save_path, dpi200) plt.show() # 用前面的boxes和聚类anchor绘图 plot_anchor_iou_heatmap(anchors, boxes)健康热力图应显示每行每个anchor都有明显亮斑IOU0.6且亮斑分散在不同列覆盖不同尺度。若某行全暗说明该anchor冗余可删若某列全暗说明该尺度box缺失要补采。我用这套方法在三个不同城市停车场项目中落地平均将YOLOv5s的mAP0.5从86.1%提升到89.3%最关键的是P3层小车牌召回率从73%升至91%——这才是夜间闸机不抬杆的根本保障。现在我的习惯是每启动一个新车牌项目第一周不碰代码只做数据——用激光测距仪量距离、用色卡测光照、用单应性变换标四点。数据焊死了模型只是时间问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表