
简介本资源面向计算机视觉方向的毕业设计、课程设计及学科竞赛参赛者提供一套基于CNN与YOLOv5的车牌检测与识别完整工程数据集采用CCPD官方数据集可帮助读者快速搭建车牌识别实验环境并完成项目复现。压缩包共10个文件约44.33MB包含Python脚本、模型权重文件、YAML配置、Keras模型文件、Jupyter Notebook及README说明文档覆盖训练、检测、识别全流程便于按模块查阅与二次开发。目前已有79人学习关注。资源内代码经过测试运行功能完整可直接复现出相同项目效果设计报告亦可借鉴参考。读者可据此掌握YOLOv5目标检测与CNN字符识别的联合应用思路理解车牌定位、字符分割与识别的关键环节并在此基础上扩展出更多功能适合作为项目立项、实训或学习练手的优质参考。1. 车牌识别这套 CNN YOLOv5 组合为什么值得拿来跑一遍如果你手头正好有一个车牌检测识别的毕设或课设任务大概率绕不开两个选择要么用 OpenCV 传统图像处理硬怼要么直接上深度学习。前者在光照均匀、角度端正的图上还能看一旦遇到倾斜、模糊、夜间反光就原形毕露。这套资源走的是第二条路——YOLOv5 负责把车牌框出来CNN 负责把框里的字符读出来数据集用的是 CCPD 官方中文车牌数据集。整个包里有plate_rec.py、plate_dec.py、plate.yaml、best.pt、train.ipynb还有 Keras 的saved_model.pb和variables目录说明检测和识别是分开的两套模型不是端到端一把梭。适合谁如果你正在做车辆检测、智能交通、停车场管理这类题目或者想找一个「检测 识别」完整链路来练手这套东西能让你少走很多弯路。它不要求你从零标注数据CCPD 已经覆盖了多种天气、角度和光照条件训练脚本和推理脚本都给了权重文件也在理论上解压完配好环境就能跑通推理。但「理论上」这三个字在深度学习项目里往往意味着还有一堆细节要处理下面我按实际拆包和复现的顺序把每一步该看什么、改什么、防什么讲清楚。2. 拆包先看结构YOLOv5 检测与 CNN 识别是怎么分工的2.1 检测和识别为什么不做成一个模型很多人第一反应是YOLOv5 不是也能直接输出车牌字符吗理论上可以但实际做起来很别扭。车牌字符有 7 位直接让 YOLOv5 回归字符位置和类别相当于把检测和分类揉在一个头里训练难度大而且字符之间的间距很小框容易重叠。这套资源采用的是两阶段方案第一阶段 YOLOv5 只负责输出车牌的边界框不关心框里是什么字第二阶段把框出来的车牌区域裁剪、矫正送进一个 CNN 分类网络逐字符识别。这样做的好处是每个模型的任务单一训练收敛快出问题也容易定位——检测不准就调 YOLOv5识别错字就查 CNN。从文件结构也能看出来plate.yaml是 YOLOv5 的数据集配置文件里面定义了训练集、验证集的路径和类别数这里只有一类plate。best.pt是训练好的 YOLOv5 权重。plate_rec.py和plate_dec.py是识别阶段的脚本一个负责加载 CNN 模型做前向推理一个负责把输出的字符索引解码成实际车牌字符串。saved_model.pb和variables是 TensorFlow/Keras 格式的模型文件说明识别网络是用 Keras 训练和保存的。train.ipynb是训练过程的 notebook里面应该包含了数据加载、模型定义、训练循环和评估。2.2 环境依赖和版本对齐拿到包之后第一件事不是急着跑plate_rec.py而是先把环境配好。YOLOv5 对 PyTorch 和 torchvision 的版本比较敏感尤其是 5.x 版本用太新的 PyTorch 可能会遇到torch.load的兼容性问题。我一般会先建一个干净的 conda 环境Python 用 3.8 或 3.9然后按下面这个顺序装conda create -n plate_rec python3.8 -y conda activate plate_rec pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install opencv-python numpy pandas matplotlib seaborn pyyaml tqdm requests pip install tensorflow2.11.0这里把 TensorFlow 单独列出来是因为识别模型是 Keras 格式的需要 TensorFlow 来加载saved_model.pb。如果你只用 PyTorch 跑 YOLOv5 检测不跑识别那 TensorFlow 可以不装。但既然资源里给了识别模型建议还是装上否则plate_rec.py会直接报ModuleNotFoundError。提示如果你的显卡驱动比较新PyTorch 版本可以适当往上调但不要超过 2.0否则 YOLOv5 的某些自定义算子可能会报错。没有 GPU 也能跑只是推理速度会慢很多CPU 下每张图大概 0.5 到 1 秒。2.3 数据集配置文件的字段含义plate.yaml这个文件虽然小但决定了 YOLOv5 去哪里找数据。典型的 YOLOv5 数据配置文件长这样path: ./CCPD2019 train: images/train val: images/val nc: 1 names: [plate]path是数据集根目录train和val是相对于根目录的训练集和验证集图片路径。nc是类别数这里只有车牌一类所以是 1。names是类别名称列表顺序要和标注文件里的类别索引对应。如果你自己重新划分了数据集只需要改这三个路径和nc就行。注意 CCPD 原始数据集的目录结构和 YOLOv5 要求的格式不一样CCPD 是按省份和类型分文件夹的图片文件名里编码了车牌位置和字符信息。所以直接用 CCPD 原始包是跑不了 YOLOv5 训练的需要先做格式转换——把车牌区域裁剪出来生成 YOLO 格式的images和labels文件夹。这一步资源里没有现成的转换脚本但train.ipynb里可能有相关代码或者你可以按 CCPD 文件名规则自己写一个。3. 从 CCPD 原始数据到 YOLO 格式转换脚本与四个边界坑3.1 CCPD 文件名里藏着什么信息CCPD 数据集的一个特点是每张图片的文件名就是它的标注。比如025-95_113-184185_271279-271279_184279_184185_271185-0_0_22_27_27_33_16-66-88.jpg这一长串用-分隔成几个字段。第一个字段025-95_113是车牌在整图中的位置信息第二个字段184185_271279是车牌四个角点的坐标第三个字段是车牌号码的字符索引第四个字段是亮度、模糊度等属性。具体解析规则在 CCPD 的 README 里有说明但很多人第一次看会懵。要做 YOLOv5 训练你需要把每张图里的车牌区域裁剪出来同时生成对应的 YOLO 标注文件。YOLO 标注格式是class_id x_center y_center width height所有坐标都要归一化到 0 到 1 之间。下面这个脚本是我常用的转换逻辑import os import cv2 import numpy as np def parse_ccpd_filename(filename): 从 CCPD 文件名解析车牌角点坐标和字符 parts filename.split(-) # 第二个字段是四个角点格式如 184185_271279_... points parts[2].split(_) # 取左上和右下两个点估算边界框 x1, y1 map(int, points[0].split()) x2, y2 map(int, points[2].split()) return x1, y1, x2, y2 def convert_to_yolo(image_dir, output_img_dir, output_label_dir): os.makedirs(output_img_dir, exist_okTrue) os.makedirs(output_label_dir, exist_okTrue) for fname in os.listdir(image_dir): if not fname.endswith(.jpg): continue img_path os.path.join(image_dir, fname) img cv2.imread(img_path) h, w img.shape[:2] x1, y1, x2, y2 parse_ccpd_filename(fname) # 裁剪车牌区域 plate_img img[y1:y2, x1:x2] cv2.imwrite(os.path.join(output_img_dir, fname), plate_img) # 生成 YOLO 标注 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(output_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)这段代码的逻辑是遍历 CCPD 图片目录从文件名解析出车牌角点裁剪出车牌区域保存到output_img_dir同时计算归一化后的中心点和宽高写入output_label_dir。class_id固定为 0因为只有车牌一类。注意这里只用了两个角点估算边界框如果车牌倾斜严重裁剪出来的图会有多余背景但 YOLOv5 训练时对边界框的容忍度比较高影响不大。3.2 四个容易翻车的地方第一个坑是文件名解析。CCPD 的文件名格式在不同子集里可能有细微差异比如有些图片没有第四个字段直接split(-)后取parts[2]可能会越界。稳妥的做法是先判断len(parts) 3再解析角点。第二个坑是坐标越界。有些图片的车牌角点坐标可能超出图片实际尺寸裁剪时img[y1:y2, x1:x2]会得到空数组保存出来是 0 字节文件。解决方法是加一个max(0, x1)和min(w, x2)的钳制。第三个坑是字符编码。CCPD 文件名里包含中文省份简称在 Windows 下用os.listdir可能会乱码建议在 Linux 下做转换或者用os.listdir时指定encodingutf-8。第四个坑是训练集和验证集的划分。CCPD 有 20 多万张图如果全部用来训练验证集就得从里面切一部分出来。我一般按 8:2 随机划分但要注意同一个车牌的不同角度图片不能同时出现在训练集和验证集里否则验证准确率会虚高。3.3 训练参数怎么设train.ipynb里应该包含了 YOLOv5 的训练命令核心参数无非是--img、--batch、--epochs、--data、--weights。对于车牌检测这种单类目标输入尺寸 640 足够batch size 根据显存来8G 显存可以开到 1612G 可以开到 32。epochs 一般 100 到 300 之间看损失曲线什么时候平了就可以停。--weights建议用yolov5s.pt预训练权重比从头训练收敛快很多。如果你要复现best.pt的效果最好把随机种子固定住否则每次训练结果会有波动。python train.py --img 640 --batch 16 --epochs 200 --data plate.yaml --weights yolov5s.pt --project runs/train --name plate_exp训练完成后权重会保存在runs/train/plate_exp/weights/best.pt。把这个文件替换掉资源包里的best.pt就可以用自己训练的模型做推理了。注意 YOLOv5 的版本要和训练时一致用 5.x 训练的权重不要拿 7.x 的代码去加载否则会报KeyError或者RuntimeError。4. 推理链路排查plate_rec.py 和 plate_dec.py 怎么串起来4.1 检测输出到识别输入的衔接plate_rec.py的职责是加载 YOLOv5 模型对输入图片做前向推理拿到车牌边界框然后把框出来的区域裁剪出来送进 CNN 识别模型。这里有一个容易被忽略的细节YOLOv5 输出的框是归一化坐标需要乘回原图宽高才能得到像素坐标。另外YOLOv5 默认会做 NMS非极大值抑制如果一张图里有多个车牌plate_rec.py需要遍历所有检测框逐个识别。下面是一个简化的推理流程import torch import cv2 import numpy as np from plate_dec import decode_plate # 加载 YOLOv5 模型 model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) model.conf 0.5 # 置信度阈值 model.iou 0.45 # NMS IoU 阈值 def detect_and_recognize(img_path): img cv2.imread(img_path) results model(img) boxes results.xyxy[0].cpu().numpy() # x1, y1, x2, y2, conf, cls plates [] for box in boxes: x1, y1, x2, y2 map(int, box[:4]) plate_img img[y1:y2, x1:x2] # 调用识别模型 plate_text decode_plate(plate_img) plates.append((plate_text, (x1, y1, x2, y2))) return platesmodel.conf和model.iou是两个关键参数。conf太低会检出很多假框太高会漏掉模糊车牌。车牌检测一般设 0.4 到 0.6 之间比较合适。iou控制 NMS 的合并阈值如果同一车牌被检出多个框调低iou可以让 NMS 更激进地合并。4.2 识别模型的输入预处理plate_dec.py里的decode_plate函数负责把裁剪出来的车牌图送进 CNN得到字符序列。这里最大的坑是预处理不一致。训练 CNN 时用的输入尺寸、归一化方式、通道顺序推理时必须完全一致否则识别结果会乱码。常见做法是把车牌图 resize 到固定尺寸比如 94x24 或 128x32然后除以 255 归一化再转成 CHW 格式。如果你发现识别出来的字符全是同一个或者完全不对先检查预处理是不是和训练时一样。def preprocess_plate(plate_img, target_size(94, 24)): 将车牌图预处理为 CNN 输入格式 img cv2.resize(plate_img, target_size) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) # HWC - CHW img np.expand_dims(img, axis0) # 增加 batch 维度 return imgtarget_size必须和训练时一致。如果你不知道训练时用的尺寸可以打开train.ipynb看数据加载部分的resize参数或者看 CNN 模型第一层的输入 shape。saved_model.pb可以用saved_model_cli show命令查看输入输出节点信息。4.3 字符解码的映射表plate_dec.py里应该有一个字符映射表把 CNN 输出的索引转成实际字符。中国车牌字符包括省份简称京、沪、粤等、字母A-Z去掉 I 和 O、数字0-9。映射表的顺序必须和训练时一致否则会出现「索引对上了但字符错了」的情况。如果你自己重新训练了 CNN记得同步更新这个映射表。一个常见的做法是把映射表存成 JSON 文件训练和推理都读同一个文件避免硬编码不一致。5. 避坑与排查从环境到推理的五个血泪经验5.1 现象运行 plate_rec.py 报 No module named models原因YOLOv5 的代码没有正确安装或者路径不对。torch.hub.load会尝试从本地缓存或远程拉取 YOLOv5 仓库如果网络不通或者缓存损坏就会找不到models模块。解决把 YOLOv5 的源码单独 clone 下来放在项目根目录然后用sys.path.append把路径加进去。或者直接用torch.hub.load的sourcelocal参数指定本地路径。如果之前下载过 YOLOv5缓存一般在~/.cache/torch/hub/下删掉重新拉一次。5.2 现象检测框位置偏移严重框到了背景原因YOLOv5 输出的坐标是相对于输入图片的如果你在推理前对图片做了 resize但没有把坐标映射回原图尺寸框就会偏。解决要么推理时不 resize直接送原图要么记录 resize 的缩放比例把输出框坐标除以缩放比例再映射回原图。YOLOv5 的results.xyxy[0]返回的坐标已经是相对于原图的但如果你手动做了 letterbox 填充需要把填充的偏移量减掉。5.3 现象识别结果全是「京A88888」或者固定字符串原因CNN 模型没有正确加载或者输入预处理完全错了导致模型输出恒定。也有可能是plate_dec.py里的解码逻辑写死了默认值。解决先单独测试 CNN 模型用一张已知车牌图做推理打印原始输出 logits看是否有变化。如果 logits 全一样说明模型权重没加载上检查saved_model.pb的路径和 TensorFlow 版本。如果 logits 有变化但解码不对检查字符映射表。5.4 现象训练时 loss 不下降mAP 一直是 0原因标注文件格式不对或者图片路径和标注路径不匹配。YOLOv5 要求每张图片对应一个同名的.txt标注文件且标注文件里的坐标必须是归一化的。解决用yolo官方提供的check_dataset.py脚本检查数据集它会告诉你哪些图片没有标注、哪些标注越界。另外确认plate.yaml里的nc和标注文件里的class_id最大值一致如果标注里出现了class_id1但nc1训练会直接报错。5.5 现象GPU 显存溢出batch size 调到 1 还是 OOM原因YOLOv5 训练时默认会加载整个模型和优化器状态如果输入尺寸太大或者模型太复杂显存占用会很高。另外如果之前有残留的进程占用显存也会导致新进程 OOM。解决先用nvidia-smi查看是否有僵尸进程有的话kill -9掉。然后降低--img尺寸从 640 降到 416 或 320。如果还不行用--batch 1加--accumulate梯度累积来模拟大 batch。实在不行就换 CPU 训练只是慢但不会 OOM。6. 进阶技巧用 TensorFlow Serving 把识别模型部署成 API如果你做完课设还想再往前走一步可以把 Keras 识别模型用 TensorFlow Serving 部署成一个 HTTP 接口这样前端或者其他服务就能直接调用不用每次都在 Python 脚本里加载模型。saved_model.pb和variables目录本身就是 TensorFlow SavedModel 格式可以直接被 Serving 加载。先确认模型目录结构saved_model/ ├── saved_model.pb └── variables/ ├── variables.data-00000-of-00001 └── variables.index然后用 Docker 起一个 TensorFlow Serving 容器docker run -p 8501:8501 --name plate_serving \ --mount typebind,source$(pwd)/saved_model,target/models/plate \ -e MODEL_NAMEplate -t tensorflow/serving8501是 HTTP 端口8500是 gRPC 端口。启动后可以用 curl 测试curl -X POST http://localhost:8501/v1/models/plate:predict \ -d {instances: [[[[0.1, 0.2, ...]]]]}instances里的数据就是预处理后的车牌图形状要和模型输入一致。返回的 JSON 里包含每个字符位置的 logits取 argmax 再查映射表就能得到车牌号。这个方案的好处是模型只加载一次多个请求共享推理延迟比每次启动 Python 脚本低很多。坑在于 TensorFlow Serving 对输入 shape 的要求很严格如果预处理后的 shape 和模型签名不一致会直接返回 400 错误。我一般会先用saved_model_cli show --dir saved_model --all查看模型的输入输出签名确认input_shape和dtype后再写请求体。从那以后我每次部署模型前都强制走一遍saved_model_cli show确认签名和预处理逻辑对得上再往下做。希望帮到你。本文还有配套的精品资源点击获取