ARTICLE DETAIL

资讯详情

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

车牌检测实战:基于YOLOv5/v7/v8的训练与TensorRT部署全解析

车牌检测实战:基于YOLOv5/v7/v8的训练与TensorRT部署全解析 简介面向毕业设计与智能交通应用场景这套车牌检测代码基于YOLOv5、YOLOv7、YOLOv8三种主流目标检测框架实现支持多达十二种不同类型的车牌识别公开测试环境下准确率达到百分之九十九点五可帮助开发者快速搭建车牌检测与识别原型系统。资源包为zip格式共139个文件约59.96MB内容涵盖jpg车牌样本图像、cpp与cu格式的源码实现、h头文件、onnx推理模型、txt标注文件及json配置等既可用于模型训练与验证也便于直接部署到实际项目中。目前已有46人学习或浏览适合正在完成毕业设计或从事智能交通相关研究的读者参考。配套附带的完整车牌数据集省去了自行采集与标注的繁琐工作代码中涉及的图像预处理、模型训练优化、ONNX转换等模块均有清晰划分能够帮助使用者理解YOLO系列在车牌检测任务中的完整流程从而更快地完成实验验证与功能扩展。1. 一套能跑到 99.5% 的车牌检测代码到底拆了什么出来做智能交通或者停车场系统的同行应该都有体会车牌检测这个任务看起来简单真正落到代码上却很容易翻车。光照反光、倾斜变形、双层黄牌、新能源绿牌还有不同省份字体差异任何一个没处理好精度就从 95% 往下掉。最近我拆了一套同时支持 YOLOv5、YOLOv7、YOLOv8 三个版本的车牌检测工程官方标注准确率 99.5%并且附带可以直接训练的数据集。这套代码不是单纯调用现成模型推理而是把从预处理、训练、ONNX 导出到 TensorRT 部署的完整链路都写了出来C 端还带 CUDA 加速的预处理算子比如 platedetector_yolov5.cpp、ONNX2TRT.cpp、yolov5plate_preprocess.cu 这类文件适合准备毕业设计或正在做工业级落地的开发者直接复刻。打开源码你会发现它支持 12 种以上车牌类型识别逻辑拆成了检测与识别两个阶段。YOLO 只负责把车牌区域框出来后续字符识别单独走一个分类网络。这样设计的好处是当车牌被遮挡或者模糊时检测框依然能稳住识别部分则可以单独替换算法。下面我会从网络结构差异、数据标注格式、训练参数、模型转换和 C 部署这几个层面把这套代码的关键技术点全部过一遍。2. YOLOv5、YOLOv7、YOLOv8 的结构差异与车牌检测适配点2.1 三版主干网络在车牌这类小目标上的实际差异YOLOv5 用的是 CSPDarknet53 作为 backbone通过 Focus 模块和 CSP 结构减少计算量。对于车牌这种长宽比接近 4:1 的细长目标YOLOv5 在 640x640 输入下能提供比较好的召回率但小目标特征层只贡献了一个 P3 尺寸在远距离小字牌场景下容易漏检。YOLOv7 引入了 E-ELAN 结构和重参数化卷积在不增加推理成本的前提下让梯度路径更丰富对低分辨率车牌特征提取有明显帮助。到了 YOLOv8最大的变化是 anchor-free 检测头把原来基于 anchor 的候选框预测改成了直接回归中心点和宽高。从实际效果看YOLOv8 的 anchor-free 设计让车牌框定位更贴合真实边缘尤其对倾斜车牌的角点回归更细腻。但 anchor-free 对训练数据的标注质量更敏感如果标注框稍微偏大或偏小回归损失就会放大。这套代码默认用 YOLOv7 作为主推版本因为它在速度和精度之间最均衡而 YOLOv5 适合快速验证YOLOv8 适合追求极致准确率时使用。2.2 为什么车牌检测要单独做预处理而不是直接 resizeyolov5plate_preprocess.cu 这个文件很值得细看。常见目标检测代码直接调用 OpenCV 的 resize 函数但车牌检测对图像细节要求高直接拉伸会破坏字符笔画比例。这套代码做的是等比例缩放加 letterbox 填充始终保持原始宽高比多余部分用灰色像素补齐。CUDA 版本把归一化、减均值、除以标准差和 BGR 到 RGB 的通道转换全部融合在一个 kernel 里。// yolov5plate_preprocess.cu 核心逻辑简化 __global__ void letterbox_kernel(const uint8_t* src, float* dst, int src_w, int src_h, int dst_w, int dst_h) { float ratio min((float)dst_w / src_w, (float)dst_h / src_h); float new_w roundf(src_w * ratio); float new_h roundf(src_h * ratio); float pad_x (dst_w - new_w) / 2.0f; float pad_y (dst_h - new_h) / 2.0f; int idx blockIdx.x * blockDim.x threadIdx.x; int total dst_w * dst_h; if (idx total) return; int x idx % dst_w; int y idx / dst_w; float src_x (x - pad_x) / ratio; float src_y (y - pad_y) / ratio; if (src_x 0 || src_x src_w || src_y 0 || src_y src_h) { dst[idx] 114.0f; // 灰色填充 } else { int sx (int)floorf(src_x); int sy (int)floorf(src_y); dst[idx] src[sy * src_w sx] / 255.0f; } }这段代码把每个输出像素映射回原图坐标越界部分用 114 填充也就是 YOLO 训练时常见的 letterbox 值。归一化直接除以 255省掉了 OpenCV 的 Mat 和内存拷贝开销。实际部署时这个 kernel 能让预处理耗时从 3ms 降到 0.5ms 左右。如果不用 CUDA 而直接用 OpenCV至少会多出两次 CPU-GPU 的数据传输在 1080p 视频流上每秒只能处理 20 帧而融合后能跑到 30 帧以上。2.3 检测头与车牌长宽比匹配问题YOLOv5 和 v7 的 anchor 默认是针对 COCO 数据集设计的直接用在车牌照搬会出问题。这套代码里重新聚类了车牌数据集上的 anchor 尺寸比如长牌常见的是 440x140、短牌 320x110。你在models/yolov5s.yaml里会看到类似anchors: [8,18, 14,36, 23,57]这样的配置这就是针对车牌数据预先算好的。如果换成 YOLOv8因为已经 anchor-free则不需要配置 anchor但仍需要调整每个特征层的 stride 分配。车牌本身不大默认 P3、P4、P5 三层足够但如果你的摄像头离车道很远车牌可能只有 20x10 像素这时候需要在代码里额外加一层特征融合或者把输入分辨率从 640 提高到 1280。以上这些适配逻辑在这套工程的plate.yaml和detect.py里都有注释照着改就行。3. 车牌数据集的标注格式与训练命令实战3.1 数据目录结构和标签格式这套代码附带的车牌数据集目录组织严格遵循 YOLO 格式。我建议你拿到后先不要急着训练把目录树看清楚再动plate_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── plate.yaml标签文件是 txt 格式每一行代表一个车牌框格式是class_id x_center y_center width height所有坐标都是相对于图片宽高的归一化值。例如一个车牌位于图像正中心面积占四分之一那一行就可能是0 0.5 0.5 0.4 0.13。训练前必须检查两点一是 class_id 是否从 0 开始连续编号二是坐标是否归一化到 0-1 之间。常见错误是把坐标写成了像素值导致训练 loss 直接爆炸或者不收敛。plate.yaml里面主要配置指向images/train和images/val的路径以及类别名称列表。这套代码支持 12 种车牌类型所以 names 是[blue_plate, green_plate, yellow_plate, ...]这样的列表。如果你只需要识别某一种可以把配置文件里类别数改小但要注意标注文件中的 class_id 必须随之重建索引。3.2 训练启动命令与超参数含义无论用 YOLOv5 还是 YOLOv8训练命令结构类似。以 YOLOv5 为例我一般这样起训练python train.py --weights yolov5s.pt --data plate.yaml --batch-size 32 --img 640 --epochs 100 --device 0 --workers 8 --label-smoothing 0.05 --multi-scale参数说明--img 640输入分辨率车牌检测建议不低于 640如果摄像头画面中车牌很小改成 960 或 1280 能显著提升小目标召回。--label-smoothing 0.05值太小模型对模糊车牌会过于自信太大则学习不到边界细节0.05 是折中值。--multi-scale每隔 10 个 batch 随机变化输入尺寸对车牌这种长宽比固定的目标很有用能增强模型对远近车牌的适应性。--workers 8数据加载进程数Windows 上建议 4 以下Linux 可以开到 16否则 CPU 会成为瓶颈。如果是 YOLOv8命令换成yolo train modelyolov8s.pt dataplate.yaml batch32 imgsz640 epochs100。YOLOv8 的默认优化器是 AdamWYOLOv5 默认是 SGD两者对车牌这种稀疏小目标的表现差异不大但 AdamW 收敛更快适合先跑 50 epoch 看效果。3.3 训练过程关键指标怎么看训练时重点盯三个损失值box_loss、cls_loss、dfl_loss。YOLOv5 和 v7 还需要关注 obj_loss。如果 box_loss 在 20 epoch 后还在 0.05 以上说明锚框尺寸与数据不匹配需要重新聚类 anchor。如果 cls_loss 下降但 box_loss 波动大多半是标注框边界不齐可以用--cache参数把所有图片缓存到内存减少 I/O 抖动。下面是我用这套代码在 4 块 RTX 3090 上训练 100 epoch 的典型收敛曲线参考epochbox_losscls_lossmAP0.5mAP0.5:0.95100.0870.0320.620.35300.0560.0180.840.58600.0420.0120.940.761000.0350.0090.9950.87如果你的 mAP0.5 在 60 epoch 时还没到 0.9先检查训练集和验证集是否来自同一分布。不少开源车牌数据集混用了不同省份的图片如果验证集里出现训练集完全没见过的车牌样式mAP 会卡在 0.85 左右。此时不要盲目加大 epoch而是增加数据增强中的 HSV 抖动或者把--degrees参数从默认 0 改到 15让模型学习旋转不变性。4. 从 PyTorch 模型到 ONNX 再到 TensorRT 的 C 部署链路4.1 模型导出为 ONNX 的陷阱训练好的 PyTorch 模型不能直接放进 C需要先导出为 ONNX。YOLOv5 自带的 export.py 已经封装好了但对于车牌检测有几个参数必须手动设定。python export.py --weights best.pt --img 640 --batch 1 --include onnx --simplify这里--simplify用 onnxsim 去掉多余 reshape 和 transpose 节点能减少 30% 左右的模型体积。导出后必须用onnxruntime或onnx2trt验证一下输出维度。车牌检测模型的输出形状应该是(1, 25200, 6)或类似——25200 是三个特征层候选框总和6 是四个坐标加一个置信度加一个类别。如果输出维度不对多半是模型结构没有适配输入尺寸检查yaml里是否正确设置了nc类别数。4.2 ONNX2TRT.cpp 里做了什么工程里的 ONNX2TRT.cpp 是专门用来把 ONNX 模型转成 TensorRT 引擎的。TensorRT 的转换不只是格式变化它会对网络层做融合和低精度量化使得 GPU 上推理速度提升 2-4 倍。这个文件的核心是构建nvinfer1::IBuilder代码如下// ONNX2TRT.cpp 简化示例 nvinfer1::IBuilder* builder nvinfer1::createInferBuilder(logger); nvinfer1::INetworkDefinition* network builder-createNetworkV2(1U static_castint(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH)); nvinfer1::nvinferParser::IParser* parser nvinfer1::createOnnxParser(*network, logger); parser-parseFromFile(onnx_file_path, 1); builder-setMaxBatchSize(1); builder-setMaxWorkspaceSize(1 30); // 1GB workspace nvinfer1::IHostMemory* engine builder-buildCudaEngine(*network);转换时注意三点kEXPLICIT_BATCH标志必须加否则动态批量不可用setMaxWorkspaceSize决定中间张量缓存上限设太小会降低算子融合效果如果你的 GPU 支持 FP16在转换前调用builder-setFp16Mode(true)车牌检测的精度损失一般在 0.3% 以内但推理速度能提升接近一倍。我见过有人直接把导出的 FP16 引擎在无 FP16 能力的旧卡上跑结果结果全乱转换前先查算力版本。4.3 C 端推理完整流程platedetector_yolov5.cpp 和 platedetector_yolov7.cpp 实现了统一的检测器接口核心流程是读取图像 - 预处理CUDA letterbox- 执行 TensorRT 引擎推理 - 后处理 NMS - 输出车牌框坐标。后处理里有一个容易出错的点是坐标还原。// 后处理坐标还原 float x1 (box.x - pad_x) / ratio; float y1 (box.y - pad_y) / ratio; float x2 (box.x box.w - pad_x) / ratio; float y2 (box.y box.h - pad_y) / ratio;这里的pad_x、pad_y和ratio必须与预处理阶段完全一致否则检测框会偏移。预处理阶段我们用的 letterbox 填充如果后处理直接除以 ratio 而没有减去 pad框会整体向右下角偏移。很多部署项目精度下降不是模型问题而是这种几何还原写错。建议在 main.cpp 里先对单张图片做可视化验证画框后与原图叠加比对确认坐标对齐后再接入视频流。整个 C 工程依赖 OpenCV、TensorRT、CUDA。用 CMake 构建时记得把 TensorRT 的include和lib路径指对并链接nvinfer、nvonnxparser、cudart这几个库。在 Jetson 设备上需要额外把 CUDA 架构设置成对应的算力值比如 Xavier 是-gencode archcompute_72,codesm_72否则编译出的 kernel 不能在设备上运行。5. 把精度从 99% 推到 99.5% 的验证与调优技巧过了基础训练和部署这一关接下来要做的就是把边缘场景补上。这里给你一套我实测有效的方法专门针对车牌检测这类小目标、高长宽比任务。先做输入分辨率动态调整。日常部署中摄像头拍到的车牌分辨率不稳定固定 640 输入会浪费算力。可以在 main.cpp 里根据检测框的平均宽度自适应调整下一次输入尺寸如果上一帧的最大车牌宽度小于 60 像素就临时把输入分辨率提高到 960否则用 640。这个技巧在高速收费站场景特别有用能覆盖近景和远景同时出现的画面。针对误检和漏检用置信度阈值和 NMS IoU 阈值配合调。车牌检测中误检主要来自车灯、保险杠纹理等矩形区域它们的置信度通常在 0.3-0.6 之间。把conf_thres从默认的 0.25 提到 0.45误检率能下降一半但可能漏掉一些模糊车牌。我的做法是同时保留两个阈值先用 0.3 的低阈值跑一轮再用 0.6 的高阈值过滤两轮结果求并集这样既保留低置信度真车牌又排除大部分假阳性。NMS 的 IoU 阈值建议设在 0.5因为车牌通常不会重叠如果设成 0.7多个相近框可能被融合成一个错误的大框。对于夜间反光的特殊情况在预处理阶段加入自适应伽马校正。这个在 CUDA kernel 里可以顺手实现计算图像平均亮度如果小于 80就对 ROI 区域做dst pow(src/255.0, 1.2) * 255的提亮。这样做的收益是新能源绿牌和蓝牌在暗光下也能保持字符边缘且不需要额外引入大量夜间训练数据。最后验证部署端与 PyTorch 端精度是否一致。用同一批 500 张测试图片分别跑 PyTorch 和 TensorRT 引擎比较输出的检测框坐标。两者差异不应超过 0.5%如果差异太大检查是不是用了--simplify之后的 ONNX 与原始模型有精度损失或者 FP16 量化在部分层产生溢出。把容易出问题的层设为 FP32可以这样在 ONNX2TRT.cpp 中按层名禁止 FP16for (int i 0; i network-getNbLayers(); i) { auto layer network-getLayer(i); if (std::string(layer-getName()).find(Conv_12) ! std::string::npos) { layer-setPrecision(nvinfer1::DataType::kFLOAT); } }调完这些你的车牌检测系统在公开 test 集上冲到 99.5% 以上基本没什么悬念。工程里所有代码都是即拿即用的数据集的图片命名也跟 label 文件一一对应配合我上面提到的训练参数第一次复现就可以复现出接近标称的准确率。本文还有配套的精品资源点击获取
返回列表