
简介一套完整的车辆与车牌多属性识别系统基于PyTorch深度学习框架融合CCPD、CRPD等公开数据集支持车辆检测、车牌检测识别、车身颜色识别和车牌颜色识别识别准确率达98.5%并兼容国内多种车牌样式。适用于计算机、人工智能、自动化等专业学生用于课程设计、毕业设计或项目演示也适合初学者入门进阶和开发者二次开发。资源包共154个文件包含43个Python源码、模型权重文件、YAML配置、测试图片及少量Shell脚本等整体约39.03MB结构清晰既可直接推理测试也支持CPU或GPU环境自行训练模型。压缩包内目录划分明确测试图片与结果保存逻辑完整。目前已有1181人学习下载项目代码经测试运行稳定可作为深度学习目标检测与识别方向的学习范本。1. 为什么车牌识别是个看着简单、落地烦人的方向晚上九点停车场的出闸机刚抬杆又落下屏幕上是皖A·D123 请通行副驾的人脸不清晰但车牌与车身颜色被识别得清清楚楚。这个场景背后是一套典型的基于深度学习与CCPD、CRPD数据集的车牌识别系统它不只输出那串车牌字符还要同时给出车牌颜色蓝/黄/白/绿/黑和车辆颜色红/黑/白/银/灰……。很多人以为拿一个YOLO模型跑通检测就完了真正接项目才会发现三个任务混在一个pipeline里有多考验数据处理和工程排错。这篇笔记就是拆这样一个打包好的Python实践项目源码、CCPD与CRPD数据集、模型权重一应俱全但再完整的zip也比不上一张能讲清楚标注怎么转、损失怎么设、阈值怎么调的作战地图。适合正在跟着源码复现的训练者也适合评估多任务车牌识别技术选型的部署工程师。2. 核心识别链路与数据集选型CCPD与CRPD在车牌识别里的分工2.1 两个数据集怎么看一热一冷互相补短板CCPD全称Chinese City Parking Dataset中文常叫中国城市停车场数据集视角高度统一停车场出入口的枪机车头正对镜头一张图一辆车、一个车牌车牌区域大且清晰。CCPD的强项是检测和字符识别因为样本干净模型很容易把基础精度拉上来。它最大的弱项是车牌颜色极度偏科蓝牌占比高得离谱新能源绿牌只是点缀黄牌、白牌几乎见不到。CRPD是冲着复杂场景去的全称常被写作Chinese Plate Recognition Dataset。夜间、雨雪、倾斜、污损、模糊、低分辨率CRPD里全都有车牌颜色也比CCPD丰富得多网约车、公交车的黄牌特种车白牌货车黑牌都占了合理比例。两个数据集一冷一热合在一起正好互补CCPD把场景分布打底CRPD把泛化能力逼出来。这是标题里不单独放某个数据集、而是同时放CCPD和CRPD的根本原因。我常用下面这个对比辅助选型也建议你先按行核对一遍再动手维度CCPDCRPD场景分布停车场出入口车头正对目标大夜间/雨雪/遮挡/倾斜难样本多车牌颜色蓝牌为主少量绿牌蓝/黄/白/绿/黑多色混合标注形式JSON含车辆框与车牌四点坐标矩形框字符级标注格式以txt/csv为主适合用途训练检测与字符识别的干净基线给模型加抗干扰能力和颜色多样性需要泼一盆冷水如果你拿到的是未经清洗的zip先把CRPD里分辨率过低、人眼都看不清的图挑出来别直接塞进训练集。那些图在验证集里当负样本可以当正样本只会把loss曲线抖成心电图。2.2 为什么做成检测分类而不是端到端序列识别车牌识别这个需求有两种主流实现路径。第一种是Detection OCR先检测车牌再用CRNN或者Transformer把车牌字符挨个识别出来适合需要精细输出省份简称字母数字的场景。第二种是Detection 分类头检测到车牌后不打字符而是分类出蓝牌/黄牌/绿牌同时检测车辆区域并分类车辆颜色。标题里这套.zip显然指向第二种因为它的输出物不是字符序列而是两个颜色标签加一个定位框。选择第二种的工程理由是明显的字符级OCR中文车牌维护成本高省份简称几十类、字体形状多变要求训练数据逐字符标注CCPD和CRPD的标注格式对字符识别并不友好。更现实的是停车场和路侧监控的核心需求往往是这辆车的颜色是什么、挂什么颜色的牌而不是把字符一个一个打出来。先检测再分类检测用YOLO系列分类接管出的区域用ResNet或者MobileNet之类的小分类头训练时互不干扰排错时边界清晰。有人问为什么不用HALCON那种传统模板匹配来做如果你之前在HALCON里用字符模板跑过车牌项目就会知道它在一张曝光均匀、角度端正的图上确实能跑通但一到倾斜、反光、夜间就立刻变脆。这也是这些年大家都在往深度学习迁移的原因。2.3 多任务输出设计一个头部还是拆成三个模型设计输出时常见做法有两种。第一种是共享一个backbone在Detection层上直接挂多个分类分支一次前向同时输出目标框、车牌颜色和车辆颜色。第二种是YOLOv5只负责检测车辆与车牌车牌颜色和车辆颜色各接一个独立的小网络。从源码包的交付习惯来看训练时通常合并成一个多任务loss推理时再拆出三类结果。我偏向于在YOLOv5的Detect头后面接自定义分支而不是纯三个独立模型原因只有一个推理耗时。三个串行模型在树莓派或者Jetson上可能拖到几百毫秒而一次前向把三个结果全部带出来典型的输出通道数大约是这样的前景目标类别2类车辆、车牌车牌颜色5类车辆颜色8类再加上坐标与置信度结果是41258Detect输出的特征图通道数会改到20左右。多任务loss的常规写法是一个加权和坐标回归用CIoU Loss负责把矩形框框准目标类别用BCE Loss负责区分前景目标和背景车牌颜色、车辆颜色用CrossEntropy Loss某个小分类的loss配一个系数。这里有个血泪经验一开始把三个任务权重全设成1你会发现蓝牌大样本把黄牌和绿牌完全淹没模型永远在输出蓝牌。正确做法是给样本量少的颜色类提高权重或者用类平衡采样把每个车牌颜色的batch占比尽量对齐。这是后面训练参数那一章的重点。3. 跑通代码包的第一步从CCPD标注到YOLO训练集的最小闭环3.1 环境准备不要照搬Python教程里的默认安装法先把工程目录结构捋顺。拿到zip解压后我一般先按下面的规划重建目录而不是直接在解压根目录下训练plate_recognition/ ├── data/ccpd/ # CCPD原始图json标注 ├── data/crpd/ # CRPD原始图标注 ├── data/ccpd/labels/ # 转换后YOLO标签 ├── data/crpd/labels/ ├── models/yolov5s.pt # 预训练权重 ├── train.py ├── detect.py ├── convert_ccpd.py └── config/ccpd_crpd.yaml环境配置是第一个翻车点。很多入门教程直接让你装最新的Python和PyTorch但YOLOv5这类仓库对版本其实很敏感。我一般用Python 3.8加PyTorch 1.10CUDA 11.3这一组合在大部分显卡驱动上都能稳定跑。用虚拟环境隔离别污染系统Python# 创建并激活虚拟环境注意指定Python版本 python3.8 -m venv venv source venv/bin/activate # 安装CUDA 11.3对应的PyTorch不要装成CPU版 pip install torch1.10.0cu113 torchvision0.11.0cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装yolov5仓库的依赖 pip install -r yolov5/requirements.txt第一行命令里的python3.8是刻意指定的。如果直接用系统默认的python3可能指向3.10以上版本YOLOv5的一些老接口在3.10上会报奇怪的类型错误。第三行指定CUDA版本是为了规避驱动新但PyTorch旧导致的算子不匹配。装完跑python -c import torch; print(torch.cuda.is_available())输出True再往下走。3.2 把CCPD的JSON标注转成YOLO格式转换脚本与参数说明CCPD的标注是JSON里面同时含车辆框和车牌四点坐标结构大体是bbox给出车体矩形points给出车牌四边形的四个角点。YOLO训练标签要求是类别 中心x 中心y 宽 高而且全部归一化到0到1所以转换脚本至少要解决两件事把四点坐标折算成外接矩形把矩形归一化。# convert_ccpd.py import json, os from pathlib import Path import numpy as np SRC Path(data/ccpd/images) DST Path(data/ccpd/labels) PAD 1.05 # 车牌框外扩系数防止裁剪时切掉字符边缘 def poly_to_xywh(points, img_w, img_h, pad1.0): 把四点坐标转成YOLO格式的归一化xywh pts np.array(points, dtypenp.float32) x_min, y_min pts.min(axis0) x_max, y_max pts.max(axis0) cx ((x_min x_max) / 2.0) / img_w cy ((y_min y_max) / 2.0) / img_h w (x_max - x_min) * pad / img_w h (y_max - y_min) * pad / img_h return cx, cy, w, h for img_path in SRC.glob(*.jpg): json_path img_path.with_suffix(.json) if not json_path.exists(): continue # 从文件名或JSON头部取图像宽高CCPD的JSON里一般直接给 anno json.loads(json_path.read_text(encodingutf-8)) img_w, img_h anno[width], anno[height] lines [] # 类目0车辆1车牌 vehicle anno.get(cars, [{}])[0].get(bbox, [0,0,100,100]) v_x, v_y, v_w, v_h vehicle lines.append(f0 {((v_xv_w)/2)/img_w} {((v_yv_h)/2)/img_h} {v_w/img_w} {v_h/img_h}) # 车牌四点坐标存于cars[*].plate.points plate_pts anno[cars][0][plate][points] cx, cy, w, h poly_to_xywh(plate_pts, img_w, img_h, PAD) lines.append(f1 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) DST.mkdir(parentsTrue, exist_okTrue) DST.joinpath(img_path.stem .txt).write_text(\n.join(lines), encodingutf-8)这段脚本的关键参数是PAD 1.05。四点坐标直接取min/max得到的是紧贴字符的外接矩形训练时如果数据增强做了随机裁剪车牌边缘的字容易被切掉。外扩5%给增强留余量这是复制粘贴时最容易被忽略的细节。img_w和img_h必须从标注文件读不能自己猜因为CCPD原图尺寸并不完全一致一旦宽高反了所有标注坐标都会错位到图像对角。转换完务必抽验。用YOLOv5自带的train.py --cache跑一个epoch或者写几行脚本把标签画回图上看矩形框是否贴合车牌。这一步省掉后面训练出来的框偏上偏下你都不知道问题出在数据还是模型。3.3 训练主命令三个任务怎么配进一条命令数据都准备好后把两类数据集的路径写进一个YAML。注意CRPD如果已经自带txt格式标注你需要先确认它的坐标是否归一化、类别ID是否与CCPD一致不一致就在转换脚本里做一次映射。数据集配置文件的写法# config/ccpd_crpd.yaml train: data/ccpd/images data/crpd/images val: data/ccpd/images nc: 2 names: [vehicle, plate]nc这里先设成2因为YOLOv5的原生训练只负责检测车辆和车牌车牌颜色与车辆颜色走自定义分类分支。接下来启动训练python train.py \ --data config/ccpd_crpd.yaml \ --weights models/yolov5s.pt \ --img 640 \ --batch 32 \ --epochs 200 \ --hyp data/hyps/hyp.scratch-low.yaml \ --multi-scale--multi-scale是给车牌检测模型用的它让模型每隔几次迭代随机缩放输入尺寸增强对不同距离车牌的适应力代价是训练时间变长。--hyp选了hyp.scratch-low.yaml而不是高增强配置原因是车牌字符这类细节目标对过强的HSV扰动很敏感。如果你发现验证集上字符区域被过度扭曲优先把hsv_h、hsv_s这些增强参数往低调。3.4 多任务loss的改造位置与权重初始化YOLOv5原版只输出框和目标类别要把车牌颜色、车辆颜色也塞进同一次前向需要在Detect层改输出通道数。常见做法是在model/yolo.py的Detect类里把输出维度从5nc改成5nc_color_platenc_color_car然后在损失函数里分别计算。# 在训练循环的loss计算位置新增分支 from torch.nn import CrossEntropyLoss # 假设头部特征图已经拆出分类结果 plate_color_logits outputs[:, 5:10] # 车牌颜色共5类 car_color_logits outputs[:, 10:18] # 车辆颜色共8类 plate_color_targets targets_plate_color # 从标注中读取的标签 # 加权交叉熵避免蓝牌样本淹没其他颜色 class_weights torch.tensor([1.0, 2.0, 2.5, 1.5, 3.0]) # 蓝/黄/白/绿/黑 loss_plate_color CrossEntropyLoss(weightclass_weights)(plate_color_logits, plate_color_targets) loss_car_color CrossEntropyLoss()(car_color_logits, targets_car_color) loss loss_detect 0.5 * loss_plate_color 0.3 * loss_car_color这里class_weights不是拍脑袋写的而是先统计CCPD和CRPD里五个颜色类别的样本数取中位数类别为基准计算逆频率得到。车辆颜色的8类权重同理。两个辅助loss的系数设为0.5和0.3是让主检测任务的梯度占主导辅助分类只在骨干网络共享部分产生影响防止分类任务把检测特征带偏。4. 把模型部署到真实画面推理脚本、阈值与三类识别结果怎么读4.1 推理脚本一次前向输出全部标签训练完成后你手里的best.pt已经能同时产出三个信息。推理时不建议重新写一套复杂管线直接用YOLOv5的推理封装再对输出做后处理# detect.py 核心片段 import torch model torch.hub.load(yolov5, custom, pathruns/train/exp/weights/best.pt, sourcelocal) model.conf 0.35 # 检测置信度阈值 model.iou 0.45 # NMS的IoU阈值 results model(test_imgs/night_vehicle.jpg) boxes results.xyxy[0].cpu().numpy() # x1,y1,x2,y2,conf,cls for box in boxes: x1, y1, x2, y2, conf, cls box cls int(cls) if cls 0: # 车辆区域 car_crop frame[int(y1):int(y2), int(x1):int(x2)] # 车辆颜色分类由单独的轻量分类头完成 car_color car_color_head(car_crop) # 输出8类softmax elif cls 1: # 车牌区域 plate_crop frame[int(y1):int(y2), int(x1):int(x2)] plate_color plate_color_head(plate_crop) # 输出5类softmax print(f车牌位置x1y1x2y2: {x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}, conf{conf:.2f}) print(f车牌颜色: {plate_color}, 车辆颜色: {car_color})model.conf和model.iou是关键取值。白天光线好conf设0.45能滤掉大量误检夜间置信度普遍下降我习惯动态调低到0.3否则会漏掉真实车牌。NMS的iou设0.45是平衡值设太大会把同一辆车上的车灯和车牌框合并成一个框设太小则同一个目标多个框同时输出。类别ID映射一定要对照训练时的namesID顺序一变整批输出含义就全反了。4.2 验证指标mAP和颜色准确率分开看多任务系统最容易犯的错误是只看mAP。YOLOv5的val.py只评估检测框车牌颜色和车辆颜色的精度在它里面看不到需要单独写评估脚本python val.py \ --data config/ccpd_crpd.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --iou 0.5--iou 0.5对应mAP0.5即预测框与真实框的IoU超过0.5就算检测成功车牌检测这个任务看mAP0.5就够了。mAP0.5:0.95是更严苛的多尺度IoU平均COCO类通用目标检测才更看重车牌目标小、场景固定不要被这个数字劝退。颜色准确率则是两个分类头的Top-1正确率。单独跑评估集统计每个类别的混淆矩阵尤其是蓝牌和黄牌之间的混淆——夜间光照下这两个颜色在RGB空间非常接近。评估脚本的要点是按颜色类别分别统计而不是只算总准确率否则蓝牌占比高总准确率会被它抬到虚高。5. 车牌识别系统实测避坑六条高频踩坑与排错实录5.1 车牌框外扩太大把相邻车灯框进裁剪区域现象检测框贴合车牌但车牌颜色识别明显不准蓝牌经常识别成绿牌黄牌识别成白牌。原因训练时PAD设成了1.12外扩12%的框在车灯、镀铬饰条附近时把环境颜色一起包进了分类头的输入区域。解决把PAD收回1.05如果车牌有倾斜角先用仿射变换把车牌对齐到水平再做颜色分类而不是依赖矩形框裁剪。5.2 夜间图像上黄牌几乎全军覆没现象白天黄牌识别准确率90%以上一到夜间掉到50%以下大量被误判成白牌。原因黄色在低照度下饱和度下降通道统计上和白牌的高光区域极其接近。解决训练时单独给车牌颜色分类分支增加夜间增强包括亮度抖动、饱和度降低、随机加高斯噪点或者把夜间样本按CRPD做一次伪影调色扩充同时把黄牌类别的loss权重调到2.5以上。5.3 训练loss下降但val曲线在30轮后开始剧烈震荡现象前30个epoch mAP一路向上之后训练集loss继续降验证mAP却上下乱跳。原因--multi-scale配合过高的基础学习率在训练后期让已经收敛的权重反复跳出不稳定的解。解决第60轮后把学习率降到初始值的十分之一或者关闭--multi-scale如果用的是hyp.scratch-high.yaml先切回low配置。5.4 Windows下训练直接CUDA out of memory现象python train.py启动后不到10秒就报错显存被拉满。原因默认batch在Windows上没有自动按显存缩放8GB显存跑batch 32必然爆显存。解决先按显存折减8GB显存从batch 16起步6GB就到8同时把--workers从默认的8降到2Windows的DataLoader进程启动开销比Linux大得多一样会吃内存。5.5 ONNX导出后输出张量维度错位现象PyTorch里推理正常导出ONNX后用TensorRT加载车牌颜色输出全部变成乱标签。原因YOLOv5的Detect层在导出时会做一次维度重组自定义颜色分支的输出位置被打乱。解决导出前固定模型的输出shape打开--dynamic时不要开全轴动态只固定batch维导出后用onnxruntime跑同一张图逐维度和PyTorch输出对比确认类别维顺序一致再交给TensorRT。5.6 验证集mAP很高但停车场现场新场景漏检一半现象测试了自己的测试图感觉不错一到新停车场闸机就漏检。原因CCPD的拍摄视角高度统一模型把停车场出入口的枪机视角当成了隐含特征换了低角度或者侧向安装的相机特征分布整体漂移。解决最少引入100到200张现场角度的图做微调把新旧数据按7比3混合训练不要全量重训加载best.pt继续训30个epoch就能拉回现场分布。6. 临近交付时的检查清单用硬案例找系统边界再谈阈值是否可信系统交付前我习惯做一个硬案例筛选。把验证集和现场测试图全部跑一遍凡是检测置信度低于0.4、颜色分类结果与标注不一致的图单独复制到一个hard_examples目录人工翻一遍。这一翻往往能发现标注错误和类别边界问题比空调阈值管用得多。import glob, shutil from pathlib import Path hard_dir Path(hard_examples) hard_dir.mkdir(exist_okTrue) for img_path in glob.glob(test_imgs/*.jpg): results model(img_path) boxes results.xyxy[0] max_conf boxes[:, 4].max().item() if len(boxes) else 0.0 if max_conf 0.4: shutil.copy(img_path, hard_dir / Path(img_path).name)硬案例看完之后再对阈值做分时策略。白天光线稳定conf设0.45到0.5宁缺毋滥夜间或者逆光时段降到0.3再多跑一层车牌纹理校验防止车灯反光区域被误判成车牌。黄牌和绿牌因为样本量少单独再拉低阈值会比较有效。这套系统的边界其实很清楚清晰蓝牌车95%以上的检测车色识别率黄绿白牌在良好光照下可用夜间只保证框得准、颜色七成正确需要百分百颜色识别就得加补光灯。我的习惯是任何车牌识别项目上线前先固定一个本地验证集把mAP和颜色Top-1做成一张表格每次调整都拿表格说话不然所有调参都成了玄学。希望这套方法能帮你少走两步冤枉路也祝你的车牌识别系统一次落地就能过验收。本文还有配套的精品资源点击获取