资讯动态

基于YOLOv5与CRNN的车牌识别实战:从数据训练到部署优化

发布时间:2026/9/10 7:06:50 来源:尧图企业网站定制
简介YOLOv5车牌检测与识别工程资源面向目标检测入门及车辆识别开发者覆盖从数据预处理、模型训练、验证到推理部署的完整流程兼顾实时性与准确率平衡。压缩包共84个文件约78.16MB包含Python源码、权重文件、配置YAML、多种格式图片jpg/jpeg/png、XML标注文件及Dockerfile等其中源码与配置便于二次开发图片与权重可立即运行体验。已有2052人学习下载适合希望快速上手车牌识别项目的工程师和学生也适合作为课程设计与毕业设计的参考实现。资源内附训练好的模型、车牌字符识别LPRNet相关代码、多种尺寸的YOLOv5配置、测试图片及推理脚本可帮助理解从目标检测到OCR字符识别的完整链路也能在本地直接运行验证通过阅读关键脚本可掌握YOLOv5核心模块的实现细节。其中涉及的边界框预测、多尺度特征图、NMS去重和轻量化部署思路对解决复杂场景下的车牌定位问题有直接参考价值还可迁移到其他目标检测任务中。1. 误区YOLOv5 做车牌识别核心其实不在识别不少第一次接触车牌识别的开发者都被YOLOv5车牌检测和识别这个标题带偏以为训练一个 YOLOv5 模型就能从输入图片里直接得到京A12345这样的字符串。实际跑一次就明白YOLOv5 是目标检测器它输出的是框和类别不是序列文本。完整的车牌识别流程里YOLOv5 只负责车牌的检测定位后面必须再接一个字符识别步骤才能拿到结构化车牌号。这个拆分的意义在真实场景里特别明显。一个 1080p 的监控画面里车牌往往只有几十个像素宽直接对全图做字符识别准确率会差到没法用先把车牌区域裁剪出来放大再做字符识别指标能拉开十几个点。另一个原因是国内车牌本身有格式逻辑蓝牌是单排 7 位新能源绿牌是 8 位字符识别必须结合正则和后处理而 YOLOv5 对这类序列约束帮不上忙。这篇文章按从业者做这个任务最常见的技术栈展开从数据准备到 YOLOv5 训练再到字符识别的两种路线对比最后落地到新手最容易翻车的部署调优环节。适合毕设选题、工业项目预研和刚入坑视觉的算法工程师目标是照着文档能在一周内跑出可演示的 Demo。2. 车牌检测该用什么数据YOLOv5 格式落地与 CCPD 实战车牌数据集的质量决定整个系统的上限。YOLOv5 的训练对标注格式有严格要求第一步就是把数据收拾成它认识的样子同时确认类别设计没有偏离实际推理场景。2.1 国内车牌数据集选型单类还是多类常见做法是直接使用公开的 CCPDChinese City Parking Dataset数据集它包含超过 20 万张国内停车场场景的车牌图片覆盖不同角度、光照、模糊程度并自带车牌的四个角点坐标和字符标签。CCPD 的图片按文件名编码了很多信息比如025-95_113-154383_386473-386473_177454_154383_363402-0_0_22_27_27_33_16-37-15其中第二段四个坐标就是车牌的四边形顶点第三段是字符编号列表对应省份汉字、字母和数字。类别设计上有两种做法单类一个plate类检测出车牌区域后另接识别模型。这样做的好处是训练简单类别不均衡问题少后续识别模型可以单独换更好的骨架。多类按车牌颜色分blue、green、yellow等类别。检测和颜色分类一次完成但会增加类别间混淆特别是蓝色和绿色在低光照下不好区分。我对真实项目的建议先上单类把检测精度做扎实颜色分类放到识别后处理里去做。原因在于YOLOv5 这个阶段最怕的是漏检多一个类别就多一份漏检概率而颜色信息只用图像处理也能拿个七八成准确率。2.2 用脚本把 CCPD 转换成 YOLOv5 标注格式YOLOv5 的标注是每个 txt 文件对应一张图片每行内容为类别编号 中心点x 中心点y 宽度 高度这些数值都是相对图片宽高的比例值。CCPD 给的是角点坐标需要做一个四边形到垂直矩形的转换。import os import cv2 IMG_DIR ccpd/images LABEL_DIR ccpd/labels def ccpd_filename_to_polygon(name: str): 从 CCPD 文件名中截取四个角点坐标并返回多边形。 文件名格式: 025-95_113-154383_386473-... 第三段是以 - 分隔的角点: x1y1_x2y2... part name.split(-)[2] # 取 154383_386473_... points [] for pair in part.split(_): x, y pair.split() points.append((int(x), int(y))) return points def polygon_to_min_rect(points): 角点取最小外接矩形注意这里直接用四个点算 bbox不做旋转矫正 xs [p[0] for p in points] ys [p[1] for p in points] return min(xs), min(ys), max(xs), max(ys) for fname in os.listdir(IMG_DIR): if not fname.endswith(.jpg): continue img cv2.imread(os.path.join(IMG_DIR, fname)) h, w img.shape[:2] points ccpd_filename_to_polygon(fname) x1, y1, x2, y2 polygon_to_min_rect(points) # 计算中心点坐标和宽高并归一化到 [0, 1] cx ((x1 x2) / 2) / w cy ((y1 y2) / 2) / h bw (x2 - x1) / w bh (y2 - y1) / h label_path os.path.join(LABEL_DIR, fname.replace(.jpg, .txt)) with open(label_path, w) as f: f.write(f0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)这段代码做了一件关键事把 CCPD 的角点坐标按最小外接矩形的思路转成 YOLOv5 需要的中心点和宽高值。注意这里我用的是min和max直接取四边的边界不是旋转框的minAreaRect。原因是 YOLOv5 原生不支持旋转框直接取外接矩形会引入一点背景噪声但训练时用数据增强里的随机旋转和透视变换可以缓解这个问题。如果你追求更高精度可以改用cv2.minAreaRect得到旋转矩形后再取外接框但对小目标没有本质区别。参数说明IMG_DIR和LABEL_DIR分别放原始图片和标注文件fname.split(-)[2]这个索引一定要确认CCPD 文件名第二段是车牌位置相关第三段才是角点不同版本的 CCPD 文件名格式会有细微差异建议先打印前几条文件名肉眼核对。3. YOLOv5 车牌检测模型训练网络结构、超参数和命令数据准备好之后进入 YOLOv5 训练环节。很多人直接把yolov5s.pt预训练权重拿来微调连配置文件都不改这样在车牌这种小目标场景往往会遇到收敛慢、召回低的问题。3.1 YOLOv5 网络结构里和车牌检测强相关的三个组件YOLOv5 的网络结构包含 BackboneCSPDarknet、NeckPANet和 Head。对车牌任务来说最值得关注的是三个细节Focus / 6×6 Conv 下采样早期版本用 Focus 模块把宽高各减半后来版本替换成普通卷积。车牌在大分辨率图中的像素占比通常小于 5%这意味着高层特征图对小目标的响应很弱。网络默认在 8×、16×、32× 三个尺度输出检测结果车牌目标一般在 16× 这一层被检测到。PANet 的特征融合Neck 部分把深层的语义信息和浅层的纹理信息做双向融合。这对车牌这类纹理复杂、语义简单的目标是友好的不需要修改网络就能工作。Anchor 设置YOLOv5 默认的 Anchor 是基于 COCO 的 80 类目标统计出来的车牌的长宽比普遍在 3:1 到 5:1 之间和 COCO 的通用 Anchor 并不完全匹配。训练时--autoanchor会自动计算一个适合当前数据集的新 Anchor建议全程开启。3.2 用自己的数据跑通 YOLOv5 训练的最小命令训练前先确认目录结构是 YOLOv5 认知的标准格式Dataset 根目录下放images和labels两个文件夹各自拆分为train和val子目录。项目里常见的数据集 YAML 写成下面这样# plate.yaml train: /data/ccpd/images/train # 训练集路径 val: /data/ccpd/images/val # 验证集路径 nc: 1 # 类别数量 names: [plate] # 类别名称类别号要从 0 开始否则训练会报错。这是 YOLOv5 的硬性约定和 COCO 数据集从 1 开始不一样新手经常在这里卡住。训练命令python train.py \ --data plate.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --workers 8 \ --project /output/yolov5-plate \ --name exp1命令参数的含义--weights yolov5s.pt使用 COCO 预训练权重做迁移学习尤其是训练数据量不足 5 万张时千万不要从--weights 开始训练收敛速度和精度差距明显。--img 640输入分辨率。视频监控里车牌本身偏小如果显存允许比如 11GB 以上显存建议设置--img 960。更高的输入分辨率对小目标检测的提升是立竿见影的代价是训练时间大约增加 2 倍。--batch按显存调。Batch Size 对训练结果的影响不如学习率大YOLOv5 默认就带了 cosine 学习率调度不必过度调参。--workers数据加载的进程数Windows 上建议设为 0 或 2否则偶尔会遇到 DataLoader 卡死的问题。训练时每跑完一个 epoch终端会输出 P、R、mAP50、mAP50-95 四组指标。车牌检测这种单类且目标明显的任务mAP50 一般能到 0.95 以上mAP50-95 也有 0.7 左右。如果训练到后半程 mAP50-95 提升不明显就是把--epochs加大到 150 也帮助有限这是正常现象。注意YOLOv5 官方仓库的 train.py 在 Windows 上需要单独处理--workers建议在 Linux 环境下训练。行业里做视觉训练基本都在 Linux 服务器本地 Windows 只做推理验证。3.3 四个容易忽略但实际影响精度的超参数训练时默认参数能跑通但想在测试集上稳定到 99% 以上这几个参数值得单独调。--hyp超参数文件里hsv_h、hsv_s、hsv_v控制颜色增强。真实车牌场景里蓝色和绿色车牌的 HSV 分布差异很大调高hsv_v到 0.5 可以模拟不同光照环境的明暗变化让模型对夜间和逆光的鲁棒性更好。--mosaic默认开启将四张图拼接成一张图训练对小目标效果显著。但训练最后 10 个 epoch 建议关闭 Mosaic因为拼接出的图片和真实场景差异大模型后期需要回到正常分布上精调。在 train.py 或超参里设mosaic: 0.0即可。--multi-scale开启后每 10 个 batch 随机切换输入尺寸。对车牌这类长宽比固定、尺寸变化大的目标多尺度训练能提升泛化性。代价是训练一块 GPU 要跑更久。--label-smoothing设为 0.1对单类检测的收敛稳定性有正面作用特别是训练数据里有少量标注出错的情况。python train.py \ --data plate.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --multi-scale --label-smoothing 0.1多尺度训练时--img 640仍然作为基准尺寸实际每轮会按 0.5~1.5 倍随机缩放。这里一个常见误区是很多人把--img 1280直接当多尺度这不是一回事--img是固定输入分辨率多尺度是训练期间的动态扰动。4. 车牌识别链路YOLOv5 检测之后接 CRNN 还是字符检测模型检测出车牌位置后下一步是把裁剪出的区域转成可读的车牌号。这一步的选型直接决定整个项目的方向和工作量。4.1 两类主流思路对比端到端 CRNN 与二次字符检测第一类思路是裁剪车牌区域后送入 CRNN 这类序列识别模型。CRNN 把图像按宽度切成一列列特征用 CTC 损失函数解码出字符序列。这种方式适合字符数不固定的场景比如新能源车牌 8 位、蓝牌 7 位网络自己学会输出可变长序列。缺点是国内车牌的首位是省份汉字像京、鲁、粤这种结构复杂、互相之间差异小的字需要单独训练一个分类头否则混在字符序列里会很吃力。第二类思路是训练第二个 YOLOv5 模型直接检测车牌里的每个字符。这等于把识别转化为检测每个字符是一个类别。好处是继承了 YOLOv5 在检测上的一切优势可以直观看到每个字符的置信度坏处是训练数据要多标注 7 个框而不是 1 个且字符间紧密排列时相邻框的 NMS 会误伤。实际项目中我一般推荐第一类做法。理由很简单序列识别模型对字符排列的语义建模更好车牌字符之间的顺序约束CTC 在学习时会自动利用而字符检测方案里每个字符独立分类两个相近的字比如0和O难以通过上下文纠正。4.2 一套可用的 CRNN 字符识别后处理示例给一段容易复现的识别与后处理代码。这里以 PaddleOCR 为例它的文本识别模型在国内车牌这类竖排、倾斜场景下表现稳定比自训练 CRNN 更省事import re from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsFalse, langch) PROVINCE_MAP 京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼 PLATE_PATTERN re.compile(r^[\u4e00-\u9fa5][A-Z][A-Z0-9]{5,6}$) def recognize_plate(crop_img): 输入 YOLOv5 裁剪出的车牌区域返回规范化车牌号 result ocr.ocr(crop_img, clsFalse) if not result or not result[0]: return None raw_text .join([line[1][0] for line in result[0]]) # 过滤掉非中文字符和干扰符号模型可能会输出括号或空格 raw_text re.sub(r[^\u4e00-\u9fa5A-Za-z0-9], , raw_text).upper() if len(raw_text) 7: return None # 首位必须是省份简称 if raw_text[0] not in PROVINCE_MAP: return None match PLATE_PATTERN.match(raw_text) return match.group() if match else None这段代码做了三个关键处理第一PaddleOCR可能把车牌区域识别出多余字符正则先过滤掉所有非法字符第二PLATE_PATTERN校验车牌格式首汉字、第二个字母、后面 5 到 6 位字母数字第三PROVINCE_MAP限制了首字必须是合法省份简称能拦掉不少误识别。参数说明use_angle_clsFalse表示不启用方向分类因为 YOLOv5 检测出的车牌框已经做过透视矫正方向基本端正langch让 OCR 字典优先覆盖中文字符如果不设这个参数省份汉字很容易被识别成形状相近的数字或字母。识别准确率方面一个训练得当的 CRNN 模型在 CCPD 测试集上字符准确率在 95% 以上整牌准确率在 85% 到 90% 之间。剩下的 10% 误差主要集中在0/O、8/B这类形近字符以及污损和倾斜严重的样本。5. 从模型到 DemoONNX 导出与三个落地优化技巧模型在 PyTorch 里跑得再准也要落成能对外提供服务的推理接口。这一章讲怎么把训练好的 YOLOv5 转成 ONNX并给出验证和调优的技巧。5.1 用 ONNX 导出并验证精度的标准操作YOLOv5 仓库自带导出脚本这一步做起来很快但导出前后的精度对比必须做。命令行如下python export.py \ --weights /output/yolov5-plate/exp1/weights/best.pt \ --include onnx \ --opset 12 \ --img 640导出成功后用 ONNX Runtime 做一次推理对比确认输出数组和 PyTorch 版本的输出差异在可接受范围内。常见做法是拿十张图分别跑 PyTorch 模型和 ONNX 模型比较检测框坐标和置信度的平均偏差。import onnxruntime as ort import numpy as np import cv2 def yolov5_onnx_infer(onnx_path, img_path): sess ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) img cv2.imread(img_path) img cv2.resize(img, (640, 640)) / 255.0 # YOLOv5 的输入是 NCHW不分 BGR/RGB模型内部自己处理 blob np.transpose(img, (2, 0, 1))[None].astype(np.float32) outputs sess.run([output], {images: blob})[0] # outputs shape 为 (1, 25200, 7) boxes outputs[0, :, :4] # cx, cy, w, h conf outputs[0, :, 4] # 目标置信度 cls outputs[0, :, 5:] # 类别概率 return boxes, conf, cls输出数组的第一维是输出 stride 的数量第二维是预测框总数。YOLOv5 在 640 输入下会在 80×80、40×40、20×20 三个尺度生成共 25200 个候选框这个数量在车牌这种小目标场景是够用的。推理时记得关闭模型内部的 NMSYOLOv5 的 PyTorch 模型默认带有 NMS 后处理而导出的 ONNX 模型不包含需要在代码里自行实现 NMS否则会输出大量重叠框。提示如果只做 CPU 推理providers不要同时加CUDAExecutionProviderONNX Runtime 会默认选择第一个可用的一旦 CUDA 环境依赖缺失会直接抛异常而不是自动回退。5.2 人脸车牌这类小目标场景的三个落地调优技巧第一个技巧是二次检测。YOLOv5 在监控大图上检测车牌的召回率基本够用但对远处的小目标会有漏检。如果检测出的框置信度低于 0.3且图片原本来自视频流可以尝试将原图两倍放大后重新检测一次。代价是推理时间翻倍适合用在帧率要求不高的离线任务。第二个技巧是视频流场景的跟踪辅助。给检测结果接一个简单的 IoU 跟踪器比如 ByteTrack如果当前帧某个区域没有检测到车牌可以用上一帧的位置结果直接补上去。这在车辆进出闸机、夜间闪烁场景下能把帧级漏检率降一个量级。跟踪器带来的额外延迟每帧不到 1ms比强行把检测模型做大更划算。第三个技巧是透视矫正。YOLOv5 输出的是矩形框但实际车牌是倾斜的平行四边形。识别阶段如果直接把倾斜区域交给 OCR字符会有透视变形。可以在后处理里用车牌区域的四个角点做一次四点透视变换映射到固定尺寸 240×80 的正视角图像再送识别。CCPD 数据集的文件名里本身就包含角点坐标训练数据集里可以手动标注四个角点用一个轻量的关键点回归头来预测它们。这套方案在识别率上能再提升 2 到 3 个百分点代价是额外的标注和一个小模型训练。性能和精度的最终平衡点需要在硬件上实测。Jetson 或 CPU 机器上一张 1080p 的图用 YOLOv5s 做检测大约 30msCRNN 识别单个车牌约 10ms整体可以在 50ms 以内完成一次完整识别这已经满足绝大多数停车场、出入口的实时性要求。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价