ARTICLE DETAIL

资讯详情

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

YOLOv5车牌检测与识别实战:工程结构、推理训练与避坑指南

YOLOv5车牌检测与识别实战:工程结构、推理训练与避坑指南 简介这是一份面向计算机视觉开发者与智能交通应用学习者的YOLOv5车牌检测与识别项目资源源自CSDN作者zhiqingAI的配套工程包可帮助读者快速搭建从车牌定位到字符识别的完整流程。压缩包共84个文件约78.16MB涵盖Python脚本、YOLOv5模型配置与训练权重pt/pth、多种车牌样例图片jpg/jpeg/png以及标注XML文件还包含Docker部署与模型推理相关配置结构清晰便于直接测试与二次开发。资源中已提供LPRNet等识别网络实现并配有tutorial.ipynb教程降低上手门槛。目前已有2057人学习下载适合希望实践目标检测与OCR结合、理解YOLOv5工程落地的中高级学习者。借助该资源读者可复现车牌检测、字符分割与识别全流程掌握数据增强、NMS、模型轻量化等关键处理手段为智能交通、安防监控等场景提供可参考的解决方案。1. YOLOv5车牌检测和识别下载后第一步该做什么YOLOv5车牌检测和识别是最常被搜索的实战项目之一。它装的不是零散代码而是一套能直接出结果的完整工程YOLOv5负责把街景、停车场照片里的车牌框出来后面的识别网络负责读字符最终输出完整车牌号。下载解压、配好环境、跑通detect.py你的测试图很快就能看到效果。它和网上只有训练代码的仓库差别在于weights里放好了权重根目录摆了十几张不同场景的测试图models里同时有YOLOv5的yaml配置和LPRNet识别网络requirements和Dockerfile也都齐了不用从头训练就能验证全流程。适合三类人被毕设要结果的学生、想把车牌识别落进智能交通项目的工程师、手里有YOLOv5基础笔记但一直缺一次完整实战的人。下面按我拆这个项目实际走的路把结构、推理、训练、避坑和串联方法一次讲清。2. 先搞清工程结构再跑代码YOLOv5源码、LPRNet和权重的关系2.1 压缩包里的核心文件怎么分工拿到压缩包先别急着开训花十分钟看一遍文件结构能少走不少弯路。这个工程可以按职责分成三组第一组是YOLOv5官方骨架models下的common.py、yolo.py、experimental.py定义了CSP网络模块和检测头train.py和detect.py分别是训练与推理入口utils下的datasets.py负责数据加载torch_utils.py处理EMA、AMP这些训练细节。第二组是车牌识别部分models/LPRNet.py就是识别网络它输出字符序列而不是检测框和YOLOv5是两套独立的模型。第三组是配套资源weights里是训练产物根目录下的jpg、png是测试图tutorial.ipynb是作者留的逐步演示。工程文件职责需要关注的点detect.py / train.pyYOLOv5检测的推理和训练入口保留官方逻辑数据路径要改成自己的yamlmodels/LPRNet.py端到端车牌字符识别网络CTC输出序列和YOLOv5检测器是两个模型独立加载独立推理models/yolov5s.yaml 等四个yaml检测网络结构配置s最快x最准差异看depth_multiple和width_multipleweights/YOLOv5和LPRNet的权重文件先ls看一眼文件名别在命令里猜simsun.ttc中文字体文件合成数据和中文可视化缺了它必出乱码read_yaml.py / train1.py作者加的配置读取和训练脚本和官方train.py的diff值得先看一遍tutorial.ipynb逐步演示脚本新手按cell跑一遍比直接读代码快注意这个工程里同时存在YOLOv5官方仓库自带的models/yolo.py和作者加的models/LPRNet.py所以它和普通YOLO仓库不一样你不能只改一个检测器的配置文件就期望它输出车牌字符串识别部分必须单独走一套前向。文件列表里还有个spinenet.py属于备用网络结构不影响主流程可以先放着不管。2.2 多尺度特征图怎么把车牌框出来YOLOv5在车牌检测部分做的事情可以拆成三件边界框预测、类别概率预测、对象中心位置预测。具体到网络结构上输入图被resize到640×640之后backbone会输出三个尺度的特征图步长分别是8、16、32。小步长对应高分辨率特征图负责找回目标大步长对应低分辨率特征图负责看全局。车牌在街景里通常只占画面几十分之一主要靠前两个尺度召回但太小的远处车牌也得靠大尺度特征的上下文协助判断所以三个尺度一个都不能少。yolov5s.yaml里的depth_multiple和width_multiple控制的是整个网络在每个stage的层数和通道数缩放。s、m、l、x四个规格的差异全部来自这两个值。对这个车牌工程我建议先用s规格跑通确认数据、权重、输出都正常之后再考虑换m来提高小车牌召回。width_multiple0.50意味着通道数砍半显存占用和推理速度都友好很多换成x规格能提升精度但显存和耗时翻倍要有心理准备。锚框anchor也是后处理的关键部分。YOLOv5对每层特征图的每个网格预置三组不同长宽比的anchor训练时算的是anchor和真实框之间的CIoU损失推理时在这基础上做NMS。车牌的长宽比很固定大约2:1到4:1之间这和通用目标检测的锚框比例差异很大。所以如果用自己数据训练建议让YOLOv5自适应计算适合车牌的anchor训练开始前它会自动跑一遍k-means把结果写进模型配置的anchors字段这一步几乎不花钱但能实打实提升框的贴合度。如果以后要部署到嵌入式设备YOLOv5s的权重通常在14MB左右导出成ONNX再量化到int8能压到4MB上下车牌这种单类任务在树莓派级别的主板上也能跑到十几帧。摘要里提到的模型轻量化处理落地方案就是这条。2.3 识别侧为什么选LPRNet以及怎么确认它加载的是哪个权重车牌识别那块老方案是先分割字符再逐个识别麻烦之处在于字符间距不固定倾斜、反光、污损时分割极易出错一步错后面全错。LPRNet的思路是放弃显式分割把整张车牌灰度图送进CNN得到一组时序特征再用CTC损失对齐到字符序列。它没有RNN模型很小中文省份缩写、字母、数字混排的车牌恰好适合这种变长序列输出。CRNN也能做这件事但CRNN依赖序列标注和足够的横向感受野在竖排黄牌或倾斜车牌的鲁棒性上不如LPRNet直接。怎么快速确认weights里哪个是YOLOv5权重、哪个是LPRNet权重直接torch.load看一眼键名这是最土但最有效的判断方式import torch ckpt torch.load(weights/xxx.pt, map_locationcpu) print(keys:, list(ckpt.keys())) # YOLOv5训练产生的checkpoint通常包含model与ema # 只有model没有ema的要么是推理版权重要么是识别模型 if model in ckpt: m ckpt[model] if isinstance(m, dict): print(该文件是YOLOv5训练检查点字段数, len(m)) else: print(模型实例类型, type(m)) else: print(没有model字段可能是LPRNet权重或优化器状态)逻辑说明YOLOv5官方train.py保存的.pt里一定有model字段而且通常带ema、updates、optimizer这些键如果只看到一堆不认识的键名大概率是识别模型的state_dict那就得用LPRNet.py里的网络定义去load_state_dict。参数说明map_locationcpu保证没有GPU的机器也能读权重排查阶段完全够用代价是大模型读取会慢一点。这个脚本跑完再回去看weights目录就不会把两个模型的文件搞混了。3. 环境配置与直接推理让detect.py在test1.jpg上跑出第一个车牌框3.1 yolov5环境配置依赖清单与版本玄学这个工程自带requirements.txt依赖是典型的YOLOv5全家桶torch、torchvision、numpy、opencv-python、matplotlib、pyyaml、tqdm、pandas这些。我拆的时候默认大家用conda管理Python环境这样装依赖不会污染系统Python翻了车也能一键重建。常见做法是先建干净环境再装conda create -n plate python3.8 -y conda activate plate cd yolov5-detect_car_plate pip install -r requirements.txt逻辑说明第一行创建名为plate的Python 3.8环境第二行激活它第三行切到工程根目录第四行安装全部依赖。参数说明Python版本不要选太新的3.9、3.10装老版torch经常遇到CUDA版本对不上的问题3.8对这个年代的工程最稳如果你用RTX 40系显卡新torch对CUDA 12支持更好直接装requirements里的torch版本大概率也能跑实在不行就手动指定torch的CUDA版本重装一次。Dockerfile这条路线适合完全不想在自己机器上装包的人。工程给了Dockerfile说明作者是支持容器化运行的。构建命令是docker build -t yolov5-plate . docker run --gpus all -v $(pwd):/workspace -it yolov5-plate bash逻辑说明build根据Dockerfile生成镜像run把当前目录挂载到容器里并以交互模式进入容器里的改动不会丢。参数说明--gpus all在只有CPU的机器上去掉就行-v的路径写法在Windows下要改成具体盘符路径别直接用$(pwd)。我个人不推荐新手首选Dockerdocker和显卡驱动之间还有一层CUDA匹配关系跑起来容易出现容器里看不到GPU的玄学问题但如果你在给别人交付、怕污染生产环境这反而是最干净的一条路。3.2 跑detect.py前先看weights目录里到底有什么这一步很多人会翻车。这个工程的weights目录里不一定是best.pt可能有多个不同名字的权重文件有的对应YOLOv5检测器有的对应LPRNet识别器。第一次跑detect.py之前最推荐先执行这个命令确认ls -lah weights/如果看到best.pt或者yolov5s.pt这类文件检测器就有现成权重可用。如果只有一堆分不清用途的.pt和.pth把上一章那个torch.load脚本跑一遍确认哪个是检测模型再往下走省得跑完detect.py发现输出的是空框或乱码。3.3 detect.py推理命令的每个参数都在改什么确认好权重之后推理命令基本长这样python detect.py --weights weights/best.pt --source test1.jpg \ --img-size 640 --conf-thres 0.25 --iou-thres 0.45 --device 0参数说明--weights指向检测器权重--source可以填单张图片、图片目录、视频文件路径或摄像头设备号test1.jpg在工程根目录直接写文件名就行在其它目录就得写绝对路径--img-size是推理时的输入分辨率640是官方默认值直接抄就行太小会丢小目标太大只是增加计算量不提高精度--conf-thres是置信度阈值小于它的检测框全部丢弃0.25是很常规的起点--iou-thres是NMS的IoU阈值值越大保留的重叠框越多--device 0表示用第一块GPU没GPU就写cpuCPU推理一张640×640的图大概要两三秒不是不能用。跑完之后结果默认输出到runs/detect/exp1生成画着车牌框和置信度标签的结果图。如果同时传入多张图片YOLOv5会批量处理并生成对应数量的标注图。找结果时别盯着终端输出发呆直接去runs/detect/exp1看新生成的文件。第一次跑通后怎么判断结果好不好我只看三件事框有没有包住整个车牌含白边框中心点是不是在车牌几何中心附近偏离太多说明anchor或NMS设置有问题标签置信度是0.9以上还是刚过0.25。如果一堆0.3左右的框叠在一起多半是NMS阈值给太大没压干净如果所有框置信度都高但位置歪说明训练数据里GT框普遍不贴边。这个观感上的判断练出来后面调参会顺手很多。3.4 tutorial.ipynb怎么用跟着cell走一遍工程里带的tutorial.ipynb是作者留的教学演示。打开Jupyter前先确认环境和内核没问题conda activate plate jupyter notebook注意在Notebook里跑detect.py不要直接用!python加相对路径因为Jupyter的工作目录不一定在工程根目录。我一般的做法是在第一个cell里写os.chdir(绝对路径)把工作目录切到工程根目录后面再用%run或!python调detect.py这样至少不会遇到找不到test1.jpg这种低级错误。提示如果Jupyter报找不到test1.jpg先os.chdir到工程根目录再用%run执行detect.py。如果你只为了看结果完全可以把tutorial.ipynb放一边直接用3.3的命令。两条路效果一样差别只是你是否需要一步步看中间输出。4. 训练自己的车牌数据集超参数设置与断点续训的坑4.1 数据标注和目录结构从一张jpg到一行txtYOLOv5的车牌检测训练数据不是把图片扔进去就行得给每张图配一张对应的txt标签文件。txt每行一个目标五个数字依次是类别id、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。归一化就是除以图片宽高所以cx和cy在0到1之间。车牌是单类目标标签第一列固定写0。如果手头是PascalVOC格式的xml标注写个小脚本转一下最快。这个工程里没有现成的转换脚本我一般用下面这段短代码import xml.etree.ElementTree as ET def voc_to_txt(xml_path, out_path, class_id0): 把VOC格式的xml转成YOLO格式的txt按原图尺寸归一化 root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.iter(object): box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) # 从角点坐标换算成中心点加宽高的形式这是YOLO系标签的固定格式 cx ((xmin xmax) / 2) / w cy ((ymin ymax) / 2) / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 用法示例 voc_to_txt(data/annotations/test1.xml, data/labels/test1.txt)逻辑说明标注框转YOLO格式必须用归一化后的中心点加宽高直接套角点坐标会导致训练时框的位置全错。class_id按需改车牌单类写0就行如果同时检测车辆、行人就编成1、2但names顺序必须和data.yaml保持一致。参数说明归一化保留6位小数足够不要用整数否则框的位置会有几个像素的误差对小目标车牌来说这点误差很要命。标注阶段的工具选择最快的是LabelImg导出VOC格式再转如果你用CVAT或X-AnyLabeling这类工具直接选YOLO格式导出省掉转换这一步。数据目录建议按YOLO的通用约定组织images存jpg、labels存txtjpg和txt保持同名训练脚本才能把图和标签配对。这个工程里train1.py是作者改过的训练入口动手前先diff一下train1.py和官方train.py重点看数据加载部分——作者八成改过datasets.py或者标签读取逻辑如果不看用官方train.py去跑他整理的数据集可能一上来就报label格式错误。4.2 train.py训练超参数哪些值得改、哪些不要动训练入口是train.py指定数据配置yaml和初始权重就能开跑。数据配置yaml里最关键的三行是train指定训练集图片路径val指定验证集图片路径nc写类别数1names写成[plate]。注意val和train路径别写重否则验证指标会虚高参考价值为零。python train.py --data data/plate.yaml --weights yolov5s.pt \ --epochs 300 --batch-size 16 --img 640 \ --hyp data/hyps/hyp.scratch-low.yaml --device 0参数说明--weights建议填YOLOv5官方发布的yolov5s.pt预训练权重这是COCO上训过的迁移到车牌场景比从零开始收敛快得多不要填成weights目录里那个检测权重除非你想接着别人的训练继续跑--epochs 300对于车牌这种单类任务够用一般150轮以后mAP就基本平了--batch-size根据显存来定8G显存配16大概率OOM降到8就能跑--img 640是训练分辨率和推理保持一致。--hyp指向超参数yaml这是最值得花时间调的地方。超参数文件里我实际动过这几个lr0是初始学习率默认0.01小数据集上容易炸我会降到0.005开头hsv_s控制饱和度增强幅度车牌任务里蓝牌、绿牌、黄牌底色差异大我会把hsv_s从0.7提到0.9让模型见过更多种饱和度fliplr水平翻转默认0.5但车牌上的字母和数字翻转后会变难认真实场景几乎没有镜像车牌我建议改成0.3给degrees加个5左右模拟摄像头安装的轻微倾斜scale给0.5左右让车牌在画面里的大小有些变化。这些超参数就是网上说的yolov5超参数调优的核心改完存成新的hyp文件不要动原始的。4.3 best.pt、last.pt和resume别把训练跑了白跑train.py每轮结束都会往runs/train/expN/weights里写两个文件last.pt是最后一轮的权重best.pt是验证集指标最好的权重。训练被中断或者显存崩了用last.pt接着跑python train.py --resume runs/train/exp1/weights/last.pt逻辑说明--resume会自动读取上次训练的epoch、超参数、数据集路径不需要再写一遍。唯一的坑是它也会读opt.yaml里记录的数据集路径如果你中途移动了数据集目录不更新opt.yaml就直接resume会报数据集找不到或者更隐蔽地继续读旧路径。处理办法是删掉runs/train/exp1里的opt.yaml然后重新写全命令训练而不是硬resume。我见过有人为这事排查了一下午最后发现是路径没同步。还有一个容易忽略的点--weights传best.pt训练会从best状态继续而不是把它的结构迁移过来如果你想换一个更大或更小的模型要在命令行里同时改对应模型的yaml。我一般会先看一眼runs/train/exp1里的opt.yaml确认上次训练用的模型规格再决定续训还是重训吃这一粒后悔药比重新训三百轮便宜得多。5. 车牌检测识别避坑手册五个常见的翻车点和对应解法先记住一个诊断原则别让两个模型互相背锅。检测框正但识别错错在识别检测框本身烂识别自然跟着错。把检测结果里的ROI单独截出来存成图片人工看一眼问题在哪一侧立刻清楚。5.1 检测框截掉车牌半边识别结果全是错字现象置信度显示很高框也画出来了但框只包住车牌的一部分字符被切成两段识别字符串和真实车牌对不上。原因置信度阈值偏高时完整车牌的框因为置信度略低于阈值被滤掉只剩评分最高但可能只包住局部特征的框IOU阈值偏大则可能把几个候选框合并成一个错位框。解决先跑detect.py把--conf-thres从0.25降到0.15--iou-thres从0.45降到0.4对比同一张图的输出。如果框还是歪问题就在标注数据——训练时GT框没有包含完整车牌边缘模型学到的是内框。标注时把完整的车牌外边框连同白边一起框进去让模型学完整目标。这一步是后处理参数和数据质量共同决定的结果别只调一边。5.2 蓝牌识别得不错新能源绿牌一塌糊涂现象蓝牌和普通黄牌的检测识别都正常换成渐变底色的绿牌车要么漏检要么识别少一位字符。原因绿牌是渐变绿和道路、绿化带的颜色接近特征上更难区分同时训练集里绿牌样本占比太少模型对绿色不敏感。绿牌长度比蓝牌多一位识别网络如果没支持这个长度输出会直接截断。解决先看数据分布绿牌样本占比有没有到20%没有就补数据或做针对性增强。超参数里调大hsv_s、调低hsv_v可以模拟阴影下的绿色渐变效果LPRNet那边的输出字符数改为8同时确认字符集里包含新能源车牌会用的D、F等字母。别指望只调YOLO解决识别问题这是两个模型叠加后的问题。5.3 中文省份汉字识别成乱码现象检测框正常识别结果里的京、津、冀、鲁变成口口、乱码或者干脆输出空字符。原因车牌识别模型的字符集里没有中文字符类别或者标注、合成阶段缺中文字体导致汉字类样本太少。工程里带了simsun.ttc如果代码里没指定字体路径可视化时中文就变方框模型训练时也没见过正确的中文样本。解决先检查识别模型输出层的维度字符表里必须包含省份简写汉字输出维度等于字符表长度再看代码里加载字体的地方把simsun.ttc的完整路径指过去。如果是matplotlib可视化中文加一行plt.rcParams[font.sans-serif] [SimSun]就能解决显示问题但这只是显示层识别层的中文类别还得保证训练数据里真有。5.4 train.py一跑就CUDA out of memory现象训练命令和教程里一模一样还没跑到第一个轮次就报CUDA out of memory进程被系统杀掉。原因教程的batch-size和img-size是按高端显卡配置写的8G显存硬抗batch-size 16加img 640必然OOM。YOLOv5训练时显存占用大头在特征图和梯度诱因就是这两项。解决--batch-size减半到8甚至4--img从640降到512OOM基本消失。注意img降到512后小目标检测精度会掉如果数据里车牌普遍小建议保持640、把batch压到4。RTX卡开amp混合精度也能省显存train.py默认会开确认命令行里没有--noamp就行。先看自己显卡再定batch不要一上来抄别人参数。5.5 推理速度不慢但输出一堆空框现象对着没有车的道路图或者一张广告牌YOLOv5也框出好几个车牌置信度还都超过阈值。原因置信度阈值太低时任何长得像车牌的纹理都会触发误检比如栅栏格、广告牌上的数字、车尾的品牌字母。车牌检测只有一类模型缺乏背景类别的抑制能力。解决把--conf-thres提到0.45以上误检数量会明显下降。如果不想丢小目标又怕误检加上--agnostic-nms让NMS在所有类别里一起抑制重叠框而不是按类别分别抑制。还有一种做法是工业部署里常用的两段式过滤用识别模型的置信度二次过滤识别分数低的检测框直接丢弃。置信度阈值适用场景表现0.100.15离线排查样本召回最高误检多适合先看哪里漏了0.25日常测试均衡官方默认值0.450.50视频流/部署误检少但小尺寸车牌可能被滤掉这些坑我自己跑的时候基本都踩过一遍有些是参数问题有些是数据分布问题列出来当一份排查手册用。如果追查时发现规律不在上面这五条里把检测框和识别结果分别存出来看通常很快能找到方向。6. 把检测和识别串成流水线一张图输出完整车牌号6.1 先确认LPRNet的输入输出格式串联前我会先把LPRNet的输入尺寸确认一遍它通常在网络第一层卷积或预处理函数里写死。常见配置是把车牌ROI缩放到接近3:1的尺寸然后转灰度、归一化到01。如果尺寸和网络定义不一致前向会直接报维度错误这是串联时最常见的翻车点之一。6.2 一条端到端的推理脚本工程里有hubconf.py最省事的方式是用torch.hub加载本地工程里的检测模型然后对每个框剪出ROI喂给LPRNet。脚本框架如下import cv2 import torch # 通过hubconf.py从本地工程加载YOLOv5检测模型 model_detect torch.hub.load(./, custom, pathweights/best.pt, sourcelocal) model_detect.conf 0.35 # 检测置信度阈值 model_detect.iou 0.45 # NMS阈值 # 识别模型按LPRNet.py里的定义加载 # lprnet build_lprnet(...) # lprnet.load_state_dict(torch.load(weights/lprnet.pt, map_locationcpu)) img cv2.imread(test1.jpg) results model_detect(img, size640) for *box, conf, cls in results.xyxy[0]: x1, y1, x2, y2 [int(v) for v in box] roi img[y1:y2, x1:x2] # LPRNet系列输入是灰度小图先转灰度再resize到网络定义的输入尺寸 roi_gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) roi_resized cv2.resize(roi_gray, (94, 24), interpolationcv2.INTER_CUBIC) # 送进识别模型前记得转tensor、归一化并加batch维 # plate_text decode(lprnet(roi_resized_tensor))参数说明torch.hub.load第一个参数./表示从当前目录加载要求目录下有hubconf.py这个工程正好具备所以不需要把代码打包安装path指定检测权重相对路径sourcelocal表示不走官方远程仓库。resize的(94, 24)需要和你的LPRNet输入对齐不同版本可能不一样以LPRNet.py里的第一层卷积输入为准。解码那步通常用贪心搜索少数场景用beam search一般场景贪心就够。没下载这个工程的去原发布页把压缩包拿下来对着这份脚本改路径就能跑。整套逻辑就是检测、裁剪、识别三段式也是智能交通项目落地最常见的流水线形态。那次我在停车场实测检测框各个精准识别结果却全乱码排查了两小时才发现LPRNet加载的是没带汉字分支的旧权重。从那以后我每次拿到这类工程都会先跑通这条十几行的串联脚本确认检测和识别两个环节各自都能出结果再继续调绝不再跳过端到端的sanity check。希望这一点能帮到你让你在车牌检测和识别的路上少走一段弯路。本文还有配套的精品资源点击获取
返回列表