news 2026/9/12 22:36:50

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
车牌检测实战:基于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 为什么车牌检测要单独做预处理而不是直接 resize

yolov5plate_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.yamldetect.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/trainimages/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 model=yolov8s.pt data=plate.yaml batch=32 imgsz=640 epochs=100。YOLOv8 的默认优化器是 AdamW,YOLOv5 默认是 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_lossmAP@0.5mAP@0.5:0.95
100.0870.0320.620.35
300.0560.0180.840.58
600.0420.0120.940.76
1000.0350.0090.9950.87

如果你的 mAP@0.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% 左右的模型体积。导出后必须用onnxruntimeonnx2trt验证一下输出维度。车牌检测模型的输出形状应该是(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_cast<int>(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_xpad_yratio必须与预处理阶段完全一致,否则检测框会偏移。预处理阶段我们用的 letterbox 填充,如果后处理直接除以 ratio 而没有减去 pad,框会整体向右下角偏移。很多部署项目精度下降不是模型问题,而是这种几何还原写错。建议在 main.cpp 里先对单张图片做可视化验证,画框后与原图叠加比对,确认坐标对齐后再接入视频流。

整个 C++ 工程依赖 OpenCV、TensorRT、CUDA。用 CMake 构建时,记得把 TensorRT 的includelib路径指对,并链接nvinfernvonnxparsercudart这几个库。在 Jetson 设备上,需要额外把 CUDA 架构设置成对应的算力值,比如 Xavier 是-gencode arch=compute_72,code=sm_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 中按层名禁止 FP16:

for (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 文件一一对应,配合我上面提到的训练参数,第一次复现就可以复现出接近标称的准确率。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 22:36:47

AI写作工具如何解决文科写作三大痛点

1. 文科写作的痛点与AI解决方案作为一名长期与文字打交道的文科研究者&#xff0c;我深刻理解学术写作中的三大困境&#xff1a;论证单薄、案例匮乏、表达平庸。传统写作模式下&#xff0c;我们需要花费大量时间在图书馆翻阅资料&#xff0c;手动整理文献卡片&#xff0c;这种低…

作者头像 李华
网站建设 2026/9/12 22:35:46

无人机图像数据集训练实战:YOLOv8小目标检测与切图调优指南

简介&#xff1a;面向无人机目标检测这一细分方向&#xff0c;这份数据集围绕四千余张业余无人机拍摄的实景照片构建&#xff0c;素材常见于大疆精灵等消费级无人机&#xff0c;并特意纳入非无人机及外观相似的干扰目标&#xff0c;适合训练能够区分真假无人机的检测模型。压缩…

作者头像 李华
网站建设 2026/9/12 22:34:49

C#调用PaddleOCR实现验证码识别的端到端方案

简介&#xff1a;这是一份面向C#开发者与OCR技术学习者的PaddleInference实战Demo资源&#xff0c;聚焦于轻量级验证码图像识别场景&#xff0c;适用于需在Windows桌面端集成OCR能力的工程实践或课程设计。资源基于VS2022.NET 4.8开发&#xff0c;整合OpenCvSharp4与Sdcb.Paddl…

作者头像 李华
网站建设 2026/9/12 22:32:08

基于ORL数据集与PCA特征脸的Matlab人脸识别系统实现

简介&#xff1a;这是一套基于Matlab实现人脸识别系统并配备GUI操作界面的毕业设计资料包&#xff0c;面向计算机、电子信息工程、数学等专业学生&#xff0c;适合作为课程设计、期末大作业或毕业设计的参考资料。压缩包内共423个文件&#xff0c;以402张bmp格式人脸图像为主体…

作者头像 李华
网站建设 2026/9/12 22:30:47

知识图谱驱动林业法规问答:建模、查询与混合检索实战

简介&#xff1a;一份以林业法律法规问答为实战场景的源码学习包&#xff0c;通过知识图谱将法规实体与关系结构化&#xff0c;面向计算机专业毕业生、高校研究者及林业信息化开发者&#xff0c;用于解决法规条文检索难、问答系统落地难的问题。压缩包共135个文件&#xff0c;体…

作者头像 李华