基于YOLOv5与CNN的车牌检测识别项目实战:从CCPD数据集到模型复现
简介这份资源面向计算机、人工智能及相关专业的本科生与初学者提供一套基于CNN与YOLOv5的车牌检测与识别完整工程数据集采用CCPD官方数据集可用于毕业设计、课程设计、大作业、工程实训及学科竞赛等场景帮助读者快速搭建并复现车牌识别流程。压缩包共10个文件约44.33MB包含Python脚本、模型权重文件、YAML配置、Keras与TensorFlow模型文件、Jupyter Notebook及README说明文档覆盖训练、检测、识别与模型加载等环节结构清晰便于按模块查阅。目前已有78人学习下载。资源经过测试运行功能完整读者可参照说明文件复现项目也可在此基础上修改扩展实现其他检测识别功能适合作为学习练手与项目立项的参考。1. 车牌检测识别项目拆包CNN 加 YOLOv5 这套组合拳到底能不能打车牌识别这个方向每年毕业设计和课程设计都有人做但真正能跑通、能复现、答辩不被问穿的并不多。我拿到这个包的第一反应是看目录结构——plate_rec.py、plate.yaml、best.pt、train.ipynb、plateRec_model、plate_dec.py、README.md外加 Keras 的saved_model.pb和keras_metadata.pb。这个组合说明它不是单纯丢一个 YOLOv5 权重让你自己猜而是把检测和识别拆成了两段YOLOv5 负责从整图里框出车牌位置CNN 分类网络负责把框出来的车牌字符逐个认出来。数据集用的是 CCPD 官方数据集国内车牌场景覆盖比较全蓝牌、绿牌、黄牌都有光照和倾斜角度也够杂。适合谁手上有毕设或课设任务、需要一套能跑通的全流程代码、又不想从零标注数据的人。不适合谁想直接拿去做商业级车牌识别、要求毫秒级响应和极端天气鲁棒性的人——这套代码的定位是教学和复现不是产线部署。2. 环境搭建与 CCPD 数据集处理从 conda 到 YOLO 格式的完整链路2.1 环境依赖与版本选择这个包用的是 YOLOv5 做检测所以环境配置绕不开 PyTorch 和 ultralytics 那套依赖。我一般会先建一个干净的 conda 环境避免和系统里已有的 torch 版本打架。常见做法是 Python 3.8 或 3.9PyTorch 选 1.10 到 1.13 之间的版本太新的版本有时候和 YOLOv5 的某些算子不兼容。conda create -n plate_rec python3.9 -y conda activate plate_rec pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install -r requirements.txt这里requirements.txt里通常包含opencv-python、numpy、matplotlib、pyyaml、tqdm这些。如果你的机器没有 GPU把cu117换成cpu就行但训练速度会差很多。plate.yaml是 YOLOv5 的数据配置文件里面定义了训练集、验证集路径和类别数。这个文件不要随便改路径除非你确定自己的数据集放在哪。2.2 CCPD 数据集的结构与转换CCPD 官方数据集下载下来是一堆以文件名编码信息的图片比如025-95_113-226469_448522-444524_232521_228469_447472-0_0_22_27_27_33_16-64-88.jpg。文件名里包含了车牌框坐标、四个角点、车牌号码等信息。YOLOv5 需要的是每张图对应一个.txt标注文件格式是class x_center y_center width height归一化到 0 到 1。import os import cv2 import numpy as np def ccpd_to_yolo(img_dir, label_dir): os.makedirs(label_dir, exist_okTrue) for fname in os.listdir(img_dir): if not fname.endswith(.jpg): continue parts fname.split(-) # 车牌框坐标在第三段格式为 x1y1_x2y2 bbox parts[2].split(_) x1, y1 map(int, bbox[0].split()) x2, y2 map(int, bbox[1].split()) img_path os.path.join(img_dir, fname) img cv2.imread(img_path) h, w img.shape[:2] x_center (x1 x2) / 2.0 / w y_center (y1 y2) / 2.0 / 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 {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}\n)这段代码的逻辑是从文件名里解析出车牌框的左上角和右下角坐标然后转成 YOLO 需要的中心点加宽高格式。参数说明img_dir是 CCPD 图片目录label_dir是输出标注目录。注意 CCPD 有些图片文件名格式不完全一致比如新能源车牌长度不同解析的时候要加异常处理不然会翻车。转换完之后在plate.yaml里把train和val指向对应的图片目录nc设为 1因为只有车牌一个类别。2.3 训练配置与启动train.ipynb里一般会调用 YOLOv5 的训练脚本。如果你习惯命令行也可以直接跑python train.py --img 640 --batch 16 --epochs 100 --data plate.yaml --weights yolov5s.pt --project runs/train --name plate_exp--img 640是输入分辨率CCPD 图片普遍偏大640 够用--batch 16看显存8G 显存跑 16 差不多不够就降到 8--epochs 100是训练轮数一般 50 到 100 轮就能收敛--weights yolov5s.pt是预训练权重用官方在 COCO 上训过的模型做迁移学习比从零训快得多。训练过程中重点看mAP0.5和box_loss如果mAP一直不涨先检查标注文件有没有写错尤其是归一化坐标有没有超过 1。3. CNN 识别网络与检测后处理从车牌框到字符序列3.1 plateRec_model 的结构与加载plateRec_model目录下是 Keras 保存的模型文件saved_model.pb是模型结构keras_metadata.pb是元数据variables里是权重。这个 CNN 网络的作用是输入一张裁剪好的车牌图输出每个字符的概率分布。国内蓝牌是 7 个字符新能源是 8 个所以输出层通常是 7 或 8 个 softmax 分支每个分支对应一个字符位置。import tensorflow as tf model tf.keras.models.load_model(plateRec_model) print(model.summary())加载之后先看summary()确认输入尺寸和输出维度。常见输入是(80, 240, 3)或者(64, 128, 3)输出是(7, 65)这种形状65 表示字符类别数数字、字母、省份简称加起来。如果加载报错大概率是 TensorFlow 版本不匹配这个包一般用 TF 2.x别用 TF 1.x 去加载。3.2 plate_rec.py 的推理流程plate_rec.py是整个项目的入口脚本逻辑一般是读图 → YOLOv5 检测车牌框 → 裁剪车牌区域 → 送进 CNN 识别 → 输出车牌号。我拆过类似的代码核心步骤大概是这样import cv2 import numpy as np import torch # 加载 YOLOv5 模型 yolo_model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) yolo_model.conf 0.5 # 置信度阈值 def detect_and_recognize(img_path): img cv2.imread(img_path) results yolo_model(img) boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls box plate_img img[int(y1):int(y2), int(x1):int(x2)] plate_img cv2.resize(plate_img, (240, 80)) plate_img plate_img.astype(float32) / 255.0 plate_img np.expand_dims(plate_img, axis0) preds cnn_model.predict(plate_img) plate_number decode_preds(preds) print(f车牌号: {plate_number}, 置信度: {conf:.2f})conf 0.5是检测置信度阈值调高会漏检调低会误检CCPD 场景下 0.5 到 0.6 比较稳。cv2.resize的尺寸要和 CNN 训练时的输入尺寸一致不一致会报维度错误。decode_preds是自定义的解码函数把 softmax 输出映射回字符这个函数一般在plate_dec.py里。3.3 plate_dec.py 的解码逻辑plate_dec.py负责把 CNN 输出的概率矩阵转成可读的车牌字符串。常见做法是每个位置取 argmax然后查字符表。字符表一般包含省份简称、数字 0-9、字母 A-Z去掉容易混淆的 I 和 O。CHARS [京, 沪, 粤, 津, 冀, 晋, 蒙, 辽, 吉, 黑, 苏, 浙, 皖, 闽, 赣, 鲁, 豫, 鄂, 湘, 桂, 琼, 渝, 川, 贵, 云, 藏, 陕, 甘, 青, 宁, 新, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V, W, X, Y, Z] def decode_preds(preds): indices np.argmax(preds[0], axis-1) return .join([CHARS[i] for i in indices])这里preds[0]是 batch 里第一张图的输出形状是(7, 65)。np.argmax取每个位置概率最大的索引然后查CHARS表。注意字符表的顺序必须和训练时一致不然识别结果会全错。这个坑我踩过当时字符表顺序换了结果车牌号全是乱码。4. 避坑与常见问题排查那些让你怀疑人生的报错4.1 现象训练 loss 不下降mAP 一直是 0原因标注文件格式不对最常见的是坐标没有归一化或者类别索引写成了 1 而不是 0。CCPD 转换脚本里如果没除以宽高YOLOv5 读进去就是越界坐标直接忽略。解决打开一个.txt标注文件确认四个值都在 0 到 1 之间且类别是 0。4.2 现象CNN 识别结果全是同一个字符原因输入图片没有做归一化或者归一化方式不对。训练时如果用了rescale1./255推理时也要除以 255。另外如果裁剪的车牌区域是空的YOLO 没检测到送进去的是一张全黑或全白的图CNN 会输出一个固定结果。解决在裁剪后加一个判断如果plate_img.size 0就跳过并检查 YOLO 的置信度阈值是不是太高。4.3 现象加载 best.pt 报错 RuntimeError: Attempting to deserialize object on a CUDA device原因模型是在 GPU 上保存的当前环境没有 GPU 或者 CUDA 不可用。解决在加载时加map_locationcpuyolo_model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, map_locationcpu)或者把模型转到 CPU 再推理。这个报错在换机器跑的时候特别常见血泪经验是先在本地 CPU 上跑通再上 GPU。4.4 现象TensorFlow 加载 saved_model 报版本不兼容原因saved_model.pb是用特定 TF 版本保存的TF 2.3 和 TF 2.10 之间都有差异。解决先看README.md里有没有写 TF 版本没有的话试 2.5 到 2.8 之间的版本。如果还是不行用tf.saved_model.load()而不是tf.keras.models.load_model()前者兼容性更好。4.5 现象推理速度特别慢一张图要好几秒原因YOLOv5 默认输入尺寸是 640如果图片分辨率是 4K预处理会花很多时间。另外如果每次推理都重新加载模型那更慢。解决把模型加载放在循环外面只加载一次推理前把图片缩放到 1280 宽以内如果还是慢把 YOLOv5 换成yolov5n或yolov5s别用m或l。5. 进阶技巧用 train.ipynb 微调与验证模型效果5.1 在 CCPD 子集上做微调train.ipynb不只是给你跑一遍训练它其实是一个可以改的模板。如果你想在特定场景下提升效果比如只识别蓝牌可以把 CCPD 里蓝牌的图片单独抽出来重新跑一遍训练。常见做法是先用完整数据集训一个基线然后针对误检多的场景做增量训练。# 在 train.ipynb 里修改 data 配置 data: train: /path/to/ccpd_blue/train/images val: /path/to/ccpd_blue/val/images nc: 1 names: [plate]把plate.yaml里的路径改成子集路径epochs设 30 到 50lr0调小到 0.001这样不会把预训练权重带偏。微调完之后用val.py跑一遍验证看mAP0.5有没有提升。5.2 用混淆矩阵定位识别错误CNN 识别部分最容易出错的是相似字符比如0和O、1和I、8和B。可以在plate_dec.py里加一个混淆矩阵统计把预测结果和真实标签对比看看哪些字符经常被认错。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt # 假设 y_true 和 y_pred 是展平后的字符索引列表 cm confusion_matrix(y_true, y_pred) sns.heatmap(cm, annotFalse, cmapBlues) plt.savefig(confusion_matrix.png)跑完之后看热力图如果0和O交叉位置颜色很深说明这两个字符混淆严重。解决办法是在训练时增加这两个字符的样本权重或者在解码时加规则车牌第二位不可能是O只能是字母这样可以直接排除掉。5.3 验证模型是否真的复现成功复现的标准不是“能跑”而是“结果可重复”。我一般会做三件事第一用同一张图跑三次看输出是否完全一致第二用best.pt在验证集上跑一遍看mAP是否和 README 里写的一致第三把plate_rec.py里的推理结果和人工标注对比随机抽 20 张图看准确率有没有超过 90%。如果这三步都过了说明这个包是真的能复现不是那种只给你一个空壳的。从那以后我每次拿到这种检测加识别的包都强制走一遍“单图三次一致性 验证集 mAP 随机抽样人工比对”的流程少一步都不敢说它跑通了。希望帮到你。本文还有配套的精品资源点击获取