news 2026/9/10 7:06:43

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5与CRNN的车牌识别实战:从数据训练到部署优化

简介: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 国内车牌数据集选型:单类还是多类

常见做法是直接使用公开的 CCPD(Chinese City Parking Dataset)数据集,它包含超过 20 万张国内停车场场景的车牌图片,覆盖不同角度、光照、模糊程度,并自带车牌的四个角点坐标和字符标签。CCPD 的图片按文件名编码了很多信息,比如025-95_113-154&383_386&473-386&473_177&454_154&383_363&402-0_0_22_27_27_33_16-37-15,其中第二段四个坐标就是车牌的四边形顶点,第三段是字符编号列表,对应省份汉字、字母和数字。

类别设计上有两种做法:

  • 单类:一个plate类,检测出车牌区域后另接识别模型。这样做的好处是训练简单,类别不均衡问题少,后续识别模型可以单独换更好的骨架。
  • 多类:按车牌颜色分bluegreenyellow等类别。检测和颜色分类一次完成,但会增加类别间混淆,特别是蓝色和绿色在低光照下不好区分。

我对真实项目的建议:先上单类,把检测精度做扎实,颜色分类放到识别后处理里去做。原因在于,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-154&383_386&473-... 第三段是以 - 分隔的角点: x1&y1_x2&y2... """ part = name.split('-')[2] # 取 '154&383_386&473_...' 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(f'0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n')

这段代码做了一件关键事:把 CCPD 的角点坐标按最小外接矩形的思路转成 YOLOv5 需要的中心点和宽高值。注意这里我用的是minmax直接取四边的边界,不是旋转框的minAreaRect。原因是 YOLOv5 原生不支持旋转框,直接取外接矩形会引入一点背景噪声,但训练时用数据增强里的随机旋转和透视变换,可以缓解这个问题。如果你追求更高精度,可以改用cv2.minAreaRect得到旋转矩形后再取外接框,但对小目标没有本质区别。

参数说明:IMG_DIRLABEL_DIR分别放原始图片和标注文件,fname.split('-')[2]这个索引一定要确认,CCPD 文件名第二段是车牌位置相关,第三段才是角点,不同版本的 CCPD 文件名格式会有细微差异,建议先打印前几条文件名肉眼核对。

3. YOLOv5 车牌检测模型训练:网络结构、超参数和命令

数据准备好之后,进入 YOLOv5 训练环节。很多人直接把yolov5s.pt预训练权重拿来微调,连配置文件都不改,这样在车牌这种小目标场景往往会遇到收敛慢、召回低的问题。

3.1 YOLOv5 网络结构里和车牌检测强相关的三个组件

YOLOv5 的网络结构包含 Backbone(CSPDarknet)、Neck(PANet)和 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 根目录下放imageslabels两个文件夹,各自拆分为trainval子目录。项目里常见的数据集 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_hhsv_shsv_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_cls=False, lang='ch') 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, cls=False) 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_cls=False表示不启用方向分类,因为 YOLOv5 检测出的车牌框已经做过透视矫正,方向基本端正;lang='ch'让 OCR 字典优先覆盖中文字符,如果不设这个参数,省份汉字很容易被识别成形状相近的数字或字母。

识别准确率方面,一个训练得当的 CRNN 模型在 CCPD 测试集上字符准确率在 95% 以上,整牌准确率在 85% 到 90% 之间。剩下的 10% 误差主要集中在"0/O"、"8/B"这类形近字符,以及污损和倾斜严重的样本。

5. 从模型到 Demo:ONNX 导出与三个落地优化技巧

模型在 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 个候选框,这个数量在车牌这种小目标场景是够用的。推理时记得关闭模型内部的 NMS,YOLOv5 的 PyTorch 模型默认带有 NMS 后处理,而导出的 ONNX 模型不包含,需要在代码里自行实现 NMS,否则会输出大量重叠框。

提示:如果只做 CPU 推理,providers不要同时加CUDAExecutionProvider,ONNX Runtime 会默认选择第一个可用的,一旦 CUDA 环境依赖缺失会直接抛异常而不是自动回退。

5.2 人脸车牌这类小目标场景的三个落地调优技巧

第一个技巧是二次检测。YOLOv5 在监控大图上检测车牌的召回率基本够用,但对远处的小目标会有漏检。如果检测出的框置信度低于 0.3,且图片原本来自视频流,可以尝试将原图两倍放大后重新检测一次。代价是推理时间翻倍,适合用在帧率要求不高的离线任务。

第二个技巧是视频流场景的跟踪辅助。给检测结果接一个简单的 IoU 跟踪器,比如 ByteTrack,如果当前帧某个区域没有检测到车牌,可以用上一帧的位置结果直接补上去。这在车辆进出闸机、夜间闪烁场景下能把帧级漏检率降一个量级。跟踪器带来的额外延迟每帧不到 1ms,比强行把检测模型做大更划算。

第三个技巧是透视矫正。YOLOv5 输出的是矩形框,但实际车牌是倾斜的平行四边形。识别阶段如果直接把倾斜区域交给 OCR,字符会有透视变形。可以在后处理里用车牌区域的四个角点做一次四点透视变换,映射到固定尺寸 240×80 的正视角图像再送识别。CCPD 数据集的文件名里本身就包含角点坐标,训练数据集里可以手动标注四个角点,用一个轻量的关键点回归头来预测它们。这套方案在识别率上能再提升 2 到 3 个百分点,代价是额外的标注和一个小模型训练。

性能和精度的最终平衡点需要在硬件上实测。Jetson 或 CPU 机器上,一张 1080p 的图用 YOLOv5s 做检测大约 30ms,CRNN 识别单个车牌约 10ms,整体可以在 50ms 以内完成一次完整识别,这已经满足绝大多数停车场、出入口的实时性要求。

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

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

2026团队编程管理工具免费版实测:7款主流工具深度对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 6:57:51

医院温湿度监控系统全流程解析:从需求到运维

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 6:57:26

TVBoxOSC 完全配置指南:从安装电视盒子播放器到日常使用

TVBoxOSC 完全配置指南&#xff1a;从安装电视盒子播放器到日常使用 【免费下载链接】TVBoxOSC TVBoxOSC - 一个基于第三方项目的代码库&#xff0c;用于电视盒子的控制和管理。 项目地址: https://gitcode.com/GitHub_Trending/tv/TVBoxOSC TVBoxOSC 是一款开源免费的电…

作者头像 李华
网站建设 2026/9/10 6:57:26

车载Android串口开发实战:从UART/RS485到Modbus协议解析

做车载 Android 开发这几年&#xff0c;串口这块踩过的坑比写的代码还多。从最初在调试板上拿 USB 转串口线测 UART&#xff0c;到后来在量产车机上调 RS485 多设备组网&#xff0c;中间经历过电平不匹配烧板子、SELinux 权限搞不定一直打不开设备、Modbus 帧解析各种乱码丢包&…

作者头像 李华