
简介基于YOLOv5CRNN的中文车牌识别系统源码项目面向计算机相关专业学生与开发者适用于毕业设计、课程设计及项目实战。资源包共92个文件以Python脚本为主体包含15个YAML配置、训练好的模型权重、测试图片与说明文档压缩包仅7.65MB便于快速部署与离线运行。目前已有302人学习下载适合作为课设/毕设参考也可视为计算机视觉项目起步的完整样例。项目覆盖车牌检测、字符识别与颜色识别三大模块附带训练、推理、GUI交互等脚本并提供清晰目录结构方便二次开发支持按需替换数据集或调整模型结构。项目经导师认可且测试通过可直接运行演示也可在此基础上扩展功能是中文车牌识别方向学习与实践的完整范例。1. 为什么中文车牌识别系统普遍选择yolov5CRNN两段式结构如果你只把 YOLOv5 当成一个能框车辆的模型那它其实还没进入“车牌识别”这个任务。中文车牌识别落地上最稳定的结构是用 YOLOv5 在大场景里把车牌区域定位出来再用 CRNN 把裁剪区里的汉字、字母和数字读成形如“粤B·D12345”的字符串。所谓“两段式”就是把检测和识别分开训练、分开调参哪一段出问题就替换哪一段这比端到端方案在工程上更可控也是这类源码项目的高分底气来源。标题里的几样东西——yolov5 源码、CRNN 网络、训练好的模型、数据集、操作使用说明——拆开看都不难难的是理解数据在两个模型之间怎么流动。检测模型只需要一个类别 plate而识别模型面对的是 65 类左右的字符集合其中既有 31 个省级行政区简称汉字也有剔除了 I/O 的 24 个字母和 10 个数字。起点到终点的复杂度完全不同训练策略也必须分开设计。下面按一个落地项目的实际顺序来写先讲清两个模型如何协作再看数据集与训练命令最后把部署推理和调参操作补完。你看到命令后可以复制到自己的环境里直接敲参数含义我会逐项说明。2. yolov5与CRNN在中文车牌识别系统里的分工定位与读字分开做2.1 检测端怎么选yolov5网络结构的s、m、l级别与输入分辨率车牌检测和通用目标检测的差异在于目标尺寸与比例。1080p 画面中一块蓝牌可能只有 40×120 像素宽高比稳定在 2.5 到 3.5 之间yolov5 默认 anchor 对这种宽扁目标已经比较友好不需要专门改 anchor。选型时我习惯先用 yolov5s 跑通 baseline参数量只有 7.2M推理速度快在 COCO 预训练权重上微调漏检率通常已经进入可用范围。如果漏检集中在夜间、雨雾、超远距离再切换到 yolov5m 或 yolov5l。前者在服务器端成本增量可接受后者对细节特征拟合更强但显存占用和训练周期也会明显拉长。多数中文车牌识别项目不会直接上 yolov5l除非你的场景里车牌经常距离 30 米以上。模型级别参数量相对推理耗时适用场景yolov5s7.2M1x边缘设备、实时视频流yolov5m21.2M约 1.8x服务器端、标注质量一般yolov5l46.5M约 2.5x夜间、远距离小目标集中这里耗时是相对值实际以 GPU 型号为准。比换模型更直接的一步是调输入分辨率训练和推理都把--img从 640 提到 960对车牌这种小目标的 mAP 提升往往比模型加一层更明显。注意 yolov5 的 anchor 匹配在训练和推理时对--img敏感两边不一致会导致 NMS 后坐标漂移。2.2 CRNN识别端CNN提特征、BiLSTM建模序列、CTC对齐变长文本CRNN 网络结构由三部分组成CNN 主干、双向 LSTM、CTC 解码。车牌 crop 通常会先等比缩放到 168×48CNN 下采样 4 倍后得到 42×12 的特征图按列展开就是 42 个时间步每个时间步的特征进入 BiLSTM输出该位置上各类别的概率分布。CTC 在这里承担的关键工作是“不对齐也能解码”。车牌有 7 位也有 8 位而输入特征固定是 42 个时间步中间的空白字符和重复折叠都靠 CTC 处理。比如模型输出连续多个“空、B、D、1、2、3、4、5”CTC 压缩后能得到 BD12345。这个特性对中文车牌尤其重要汉字被雨滴遮挡、边缘切掉半笔时LSTM 还能靠前后字符和字形上下文猜出正确省份简称。和常见的开源中文 OCR 方案相比CRNN 优势在于轻量和序列归纳能力强。检测到车牌后用一串 7 分类的 ResNet 也能读字但丢掉了字符间的顺序信息遇到“桂C”和“湘C”这种形近字组合时性能会差一截。字符表范围按国内车牌标准固定为31 个省份简称汉字、24 个字母、10 个数字外加 CTC 的 blank一共 66 类。字符表写入chars.txt顺序必须与模型输出索引严格一一对应中途改动会导致整个识别结果错乱。2.3 从yolov5检测框到CRNN输入裁剪、扩边与resize的坐标处理两个模型之间的桥接看似简单实际是高分项目和自用脚本的第一道分水岭。yolov5 输出的坐标是归一化还是像素坐标、要不要扩边、resize 成什么尺寸直接决定 CRNN 输入质量。下面这段代码是通用做法。import cv2 def plates_to_crops(frame, boxes, pad_ratio0.02, target_w168, target_h48): crops [] for box in boxes: # box 是 [x1, y1, x2, y2]像素坐标 h, w frame.shape[:2] dx, dy int(w * pad_ratio), int(h * pad_ratio) x1 max(0, int(box[0]) - dx) y1 max(0, int(box[1]) - dy) x2 min(w, int(box[2]) dx) y2 min(h, int(box[3]) dy) crop frame[y1:y2, x1:x2] crop cv2.resize(crop, (target_w, target_h), interpolationcv2.INTER_CUBIC) crops.append(crop) return cropspad_ratio 的作用是防止字符笔画被检测框边缘切断尤其汉字“京”“津”的顶部横画经常正好落在边界上。建议取值 0.02 到 0.05过大则会带入车漆背景干扰识别。resize 使用 INTER_CUBIC退化到 INTER_LINEAR 在文字边缘会产生轻微锯齿CNN 特征提取时会把锯齿当纹理学进去。注意绝对不要把车牌 resize 成 64×64 的正方形中文字形被横向压缩后 CRNN 几乎必错。yolov5 自带的结果对象里results.xyxy[0]就是像素坐标可以直接传给这个函数。如果你的项目里取到的是归一化坐标需要先乘原图宽高再传入。2.4 坐标桥接中常见比例与透视问题根据实际经验初学者最容易犯的错误是拿归一化坐标直接切片导致裁剪区域偏到画面左上角。yolov5 的results.xyxyn[0]才是归一化坐标results.xyxy[0]是像素坐标用错一个字段输出完全不同。第二个高频问题是倾斜车牌车辆停在坡道或者相机安装角度大时检测框内往往带 10° 到 20° 的旋转直接 resize 会让整串字都发虚。常见做法是在检测框内用 OpenCV 的minAreaRect重新求最小外接矩形按角度做仿射矫正后再进入 CRNN。这一步对停车场出入口和高速卡口非常关键。实时视频场景下矫正和 resize 的耗时约 1 到 2 毫秒完全可接受建议保留在流水线里。3. 中文车牌识别项目的数据集组织与yolov5训练自己的数据集全流程3.1 检测数据集和识别数据集要分开建立不要混在一个目录源码项目里最混乱的往往不是模型而是目录。标准的做法是 yolov5 需要的检测集和 CRNN 需要的识别集分开两个根目录标签格式也完全不同。检测集使用 YOLO 格式的 txt 标签每行是“类别 cx cy w h”坐标全部除以图片宽高做归一化。识别集则是一张裁好的车牌图对应一行文本前缀是图片路径后面跟着真实车牌内容中间用空格分隔。存放路径内容格式服务对象datasets/plate_detect/images原始道路图jpg/pngyolov5datasets/plate_detect/labels检测框标签txtyolov5datasets/plate_recog/crops裁剪车牌图jpg/pngCRNNdata/recog_labels.txt路径真实车牌文本txtCRNN检测数据起步量一般在 3000 张以上识别数据 8000 张以上这里说的是真实场景图不是合成渲染图。合成车牌可以用来补充字符分布但真实光线、灰尘和运动模糊很难完全仿真全部用合成数据训练出来的模型落地时会有明显落差。3.2 用yolov5训练自己的车牌数据集yaml文件与关键超参数yolov5 训练自己的数据集只需要一个数据 yaml 文件和一条命令。数据 yaml 内容如下类名固定只有一个 plate。# plate.yaml path: ./datasets/plate_detect train: images/train val: images/val nc: 1 names: 0: plate训练命令放在 yolov5 源码根目录下执行python train.py --data plate.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --hyp hyp.scratch-low.yaml \ --project runs/plate_detect \ --device 0--batch-size根据显存调整16 对应约 12GB 显存显存不足时减到 8不要同时降低--img否则小目标检测能力会缩水。--hyp hyp.scratch-low.yaml指定增强超参数yolov5 源码里已经预设了几套车牌场景建议在hyp.scratch-low.yaml基础上改两项把hsv_h、hsv_s略微调低避免蓝牌颜色被过度扰动把mosaic从 1.0 降到 0.5 左右防止小车牌在马赛克拼接时被切掉太多次。这种细调属于 yolov5 超参数里收益最高的部分改动一行配方文件即可不需要动源码。3.3 识别数据不足时怎么扩充用检测器自动收集车牌裁剪图识别数据不足时最常见做法是用训练好的 yolov5 检测器对未标注视频抽帧批量保存裁剪结果再给 CRNN 做标注。yolov5 自带一个参数能直接完成裁剪保存python detect.py --source raw_video.mp4 \ --weights runs/plate_detect/exp/weights/best.pt \ --conf 0.5 \ --save-crop \ --img 960执行后会在runs/detect/exp/crops/plate/下生成一组车牌小图。接下来人工把明显不是车牌的剔除再让已有 CRNN 预标注人工抽检错项形成新一轮训练数据。这样循环两轮识别准确率通常能提升两到三个百分点。注意不要让合成车牌比例超过总量的 1/3否则模型容易只看纹理不看字形。3.4 训练后验证检测看mAP识别看整串准确率yolov5 自带的验证命令可以直接评估检测模型python val.py --data plate.yaml \ --weights runs/plate_detect/exp/weights/best.pt \ --task valCRNN 这部分项目里一般会提供evaluate_crnn.py之类的脚本参数是识别模型权重、字符表和测试标注文件。验收建议用两个指标分开卡检测端看 mAP0.5合成训练集上不低于 95%真实场景测试集不低于 90%识别端看“整串准确率”也就是模型输出与真实车牌完全一致的比例训练合格的 CRNN 在独立测试集上应超过 95%。如果整串准确率只有 90% 上下问题多半出在汉字样本太少或者检测框切掉边角字符而不是网络结构本身。4. 操作使用说明环境配置、推理命令、conf与iou参数怎么调4.1 从零复现运行环境这类源码项目多数基于 PyTorch 生态创建独立 conda 环境是为了避免和本机其他项目互相干扰。环境配置命令如下conda create -n plate python3.9 -y conda activate plate pip install torch1.12.1cu113 torchvision0.13.1cu113 pip install opencv-python4.5.5.64 pyyaml tqdm pandas matplotlib seabornPyTorch 2.x 也能跑但和部分旧版 CRNN 代码存在兼容风险建议先按 1.12 复现。依赖项用途如下表依赖版本参考用途Python3.8~3.10运行环境PyTorch1.10两个模型的推理与训练torchvision与 torch 同源图像张量处理OpenCV4.5裁剪、仿射矫正、resizePyYAML5.3读取 yolov5 配置pandas1.x解析检测结果4.2 加载训练好的模型跑图片、视频与摄像头图片推理用 yolov5 自带的检测入口输出检测框和裁剪图python detect.py --source demo.jpg \ --weights checkpoints/yolov5_plate.pt \ --conf 0.45 \ --iou 0.5 \ --save-crop视频推理建议把 conf 降到 0.4视频相邻帧可以互相补漏单帧漏检时不至于整段丢失。接入摄像头时把--source换成摄像头的设备号比如0。要一次跑完整条检测加识别流水线可用下面的 Python 片段import torch import cv2 from crnn_utils import build_crnn, crnn_decode detector torch.hub.load(ultralytics/yolov5, custom, pathcheckpoints/yolov5_plate.pt, sourcelocal) detector.conf 0.45 detector.iou 0.5 recog_model, chars build_crnn(checkpoints/crnn_best.pth) frame cv2.imread(demo.jpg) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results detector(rgb) for det in results.xyxy[0].tolist(): x1, y1, x2, y2 [int(v) for v in det[:4]] crop cv2.resize(rgb[y1:y2, x1:x2], (168, 48)) text crnn_decode(recog_model, crop, chars) print(f{det[4]:.2f} {text})det[4]是置信度crnn_decode内部会完成归一化、CTC 解码和字符映射。source 指定为 local 时yolov5 不再从外部拉取仓库文件离线环境也能跑。4.3 conf、iou与max-det参数对车牌识别的真实影响参数常见范围调参倾向conf0.3~0.6调高减少误检调低减少漏检iou0.4~0.5调低合并重复框调高保留重叠候选max-det10~30限制单帧最大检测数iou 调低会让 NMS 更激进远处多车时把相邻车牌的框误合并调高则可能同一个车牌出现两个重叠框后面 CRNN 会把同样的内容识别两遍影响计数场景。视频流里应把 conf 放在低位通过连续帧投票去噪而不是靠提高单帧置信度硬砍。4.4 四个高频报错和对应处理方式显存不足是训练第一坑报错核心是 CUDA out of memory。直接把--batch-size从 16 降到 8同时确认没有开太多其他程序占用显存。训练进度条消失通常不是断训而是验证阶段显存也被挤占可以加--noval关掉每轮验证。推理阶段最经常载入错误是 torch.hub 无法从远端下载组件原因大多只是网络受限解法是把 yolov5 目录放在本地用sourcelocal或者直接把代码包里自带的detect.py作为入口。另一个高频问题是chars.txt和 CRNN checkpoint 不匹配换模型权重后字符表顺序变了识别结果会出现整串乱码。解决办法是重新用项目里的build_crnn入口加载它会把字符表和模型索引一起恢复。5. 在yolov5CRNN模型上扩展新能源绿牌与双行大车牌的处理技巧5.1 新能源绿牌与双行大黄牌的输入适配新能源绿牌是 8 位字符格式为省份简称加城市字母再加“D”或“F”和五位序号比如“粤BD12345”。CRNN 的 CTC 解码天然支持变长输出所以不需要改网络结构重点是在训练集里保持绿牌比例不低于 10%否则模型会惯性输出 7 位。双行黄牌则要单独处理。卡车和挂车的黄牌是上下两行直接把整块按 168×48 resize所有字符会被高度压缩上下行叠成噪点。常见做法是检测框内先按水平中线切成上下两段分别矫正后横向拼接成一行再进 CRNN。h, w crop.shape[:2] top crop[: h // 2, :] bottom crop[h // 2:, :] one_line cv2.hconcat([top, bottom]) one_line cv2.resize(one_line, (168, 48))拼接后的字符高度降到原来的一半但 LSTM 对单行序列的建模能力还在整串识别率通常能回到可接受水平。这一步也解释了为什么这类项目最终要单独维护一个“车型预处理”模块而不是在 CRNN 里塞各种花哨的网络变体。5.2 用字符级准确率定位识别误差来源整串准确率会掩盖错误位置。实际排错时建议同时统计“逐位字符准确率”和“编辑距离准确率”这样才能知道问题出在汉字识别还是数字混淆。from Levenshtein import distance def plate_eval(preds, labels): exact 0 edit_total 0.0 n len(preds) for p, l in zip(preds, labels): exact 1 if p l else 0 edit_total distance(p, l) / max(len(p), len(l)) return exact / n, 1 - edit_total / n如果错误集中在第一位汉字优先补各省简称的样本尤其是“湘”“鄂”“皖”这类形近字如果错误集中在数字 0 和字母 O、数字 1 和字母 I 之间这是 OCR 经典问题通常在字符表里直接剔除 I/O 就能缓解因为中国车牌本来就不使用这两个字母。视频场景下还可以把相邻若干帧的 CRNN 输出按字符位投票单帧偶尔把“B”识别成“8”多帧投票后正确字符会占多数整串准确率通常还能再提升一到两个百分点。本文还有配套的精品资源点击获取