简介:这是一套面向车牌检测与识别场景的机器学习算法实现,项目整合YOLOv5目标检测、LPRNet/CRNN序列识别等主流方案,覆盖数据预处理、模型训练、推理演示与ONNX/OpenVINO导出链路。适合计算机、人工智能等专业学生在课程设计、期末大作业或毕业设计中作参考,需具备一定Python与深度学习基础。压缩包共68个文件,约19.59MB,以py源码为主,辅以yaml模型配置、pth/pt预训练权重、sh脚本及ttf字体等资源,目录区分project_code、yolov5、weights、fonts等模块,便于对照调试。目前已有254人学习下载,实用性经受验证。使用时可从demo.py或detect_plate.py快速上手,借助自带权重完成车牌定位、字符识别与结果可视化;若需重新训练,train.py与ccpd_process.py等脚本提供了数据准备与训练流程的完整范例。
1. 车牌检测不是新问题,但“拿到能用的源码”才是门槛
一个基于机器学习的车牌检测算法源码包,可能是 GitHub 上某项目的完整工程,也可能是某个技术博客随附的百度网盘压缩包。它听起来很简单:解压、装依赖、跑 demo、看到车牌上画出蓝框,任务就算完成了。但实际交付和论文 demo 之间隔着一条很深的沟——真实停车场的低照度画面、倾斜角度、新能源绿牌、泥污遮挡,都会让这个检测算法的精度从“演示可用”掉到“上线翻车”。这篇文章从拿到一个车牌检测源码包开始,顺着一套完整的落地链路往下讲:源码怎么跑通、数据怎么处理、模型怎么调、部署怎么稳定。适合手里已经有一个 zip、但不确定它能不能用于生产的开发者,也适合想从零搭一套车牌检测方案的从业者。目标只有一个:判断这个算法值不值得投入,以及怎么把它改造成能上线的东西。
2. 先搞清算法选型:为什么传统机器学习方案在车牌场景里会翻车
2.1 车牌检测里的两代技术路线:特征工程 vs 深度特征
“基于机器学习”这个词在不同年代的项目里有完全不同的含义。2015 年以前,车牌检测的主流做法是 HOG(方向梯度直方图)提取特征,再用 SVM(支持向量机)做分类;稍微讲究一点的方案会先用边缘检测、颜色分割、长宽比过滤筛出候选区域,再对候选区域做字符级识别。这套方案在固定卡口、固定机位、光照稳定的场景下能做到不错的准确率,因为车牌本身就是高对比度、固定长宽比的矩形目标,人工设计的特征够用。但它有一个致命问题:场景一变就失效。夜间眩光、运动模糊、车牌倾斜超过 30 度、泥水遮挡,都会让 HOG 特征质量大幅下降,而候选区域往往在第一步就漏掉了目标。
2016 年以后,深度学习方案把这条技术路线整个替换掉了。基于 CNN 的检测器(从 Faster R-CNN 到 SSD、YOLO 系列)把“找候选区域”和“分类”合进同一个网络,特征也是从数据里自动学出来的,不再依赖人工设计。对于车牌检测这个具体任务,深度学习方案还有两个天然优势:一是车牌颜色和字符纹理的组合特征非常强,网络很容易学到;二是检测器在训练时可以自己从大量数据里学出车牌在各种背景下的形态变化,不需要手动调特征参数。现在你拿到的“机器学习车牌检测源码”,大概率是 PyTorch 或 TensorFlow 实现的 YOLO 系列工程,标题里的“机器学习”是宽泛说法,严格讲属于深度学习分支。
讲清楚这段区别是有意义的,因为直接决定了你拿到源码后怎么评价它。如果解压出来看到的是 train_svm.py、hog.cpp 这类文件,这个包的技术路线已经落后了,除非你的场景只有单个固定摄像头、车牌角度和距离几乎不变,否则不建议投入。如果是基于 YOLO 的工程结构,那值得继续花时间。
2.2 一套标准车牌检测流水线里必须有哪几个环节
一个生产可用的车牌检测系统,不是“输入图片 → 输出带框图片”这么简单。拆开看至少包含三个环节:车牌定位、车牌识别、目标跟踪或视频帧过滤。
车牌定位就是检测算法本身——在图像中找出车牌矩形框。这一步的输入是完整画面,输出是若干个边界框和置信度。车牌识别是把定位框内的区域再送给 OCR 模型(通常是 CRNN + CTC 或 PaddleOCR 这类模型),输出车牌号字符串。检测和识别是两条独立的模型流水线,很多人把“车牌识别源码”当成“车牌检测源码”,但你的标题明确是检测算法,所以 OCR 环节可以后置。还有一个环节是目标跟踪,用在视频流场景里:停车场道闸视频连续几十帧,没必要每帧都跑一遍检测,用 ByteTrack 或 DeepSORT 把同一辆车绑定,遇上车牌漏检的帧就去上一帧的结果里取。
做技术选型时,车牌检测的主流落地框架集中在 YOLO 系列。YOLOv5 和 YOLOv8 是社区资料最丰富的两个版本,前者稳定、部署方案成熟,后者在训练便利性和模型导出上更顺滑。如果源码里用的是 YOLOv8,那网络结构本身不是瓶颈,决定最终效果的是数据和训练参数。如果用的是 Faster R-CNN,对一张 1920×1080 的卡口大图推理时间可能达到几百毫秒,达不到停车场道闸的实时要求。我一般会优先选择 YOLO 系源码,因为后续做 TensorRT 部署和 INT8 量化的资料最多,踩坑时能找到参考。
3. 拿到“算法源码.zip”先跑通最小推理:目录检查、环境安装与关键参数
3.1 解压后第一件事:先看目录结构而不是急着配环境
有一个很反直觉的经验:拿到 zip 后,不要先建环境、跑安装,而是先花十分钟看目录结构。因为车牌检测源码包质量参差不齐,你手里这个 zip 可能是完整工程,也可能是从某篇论文里扒下来的不完整实现。先看目录能避免后面所有步骤白做。
一个结构完整的检测工程,至少包含以下部分:weights 或 checkpoints 目录(存放模型权重文件)、config 或 cfgs 目录(模型结构配置和训练超参数)、data 或 datasets 目录(数据集描述文件)、推理脚本(detect.py / infer.py)、训练脚本(train.py)、requirements.txt。权重文件是最先要确认的——一个车牌检测权重文件通常有几十 MB(YOLOv8n 大约 6MB,YOLOv8s 大约 22MB,基本全是 .pt 格式),如果 weights 目录下只有几 KB 的文件,那个文件很可能只是 README 或下载链接说明,实际权重需要自己去别处下载。
检查完目录后,用命令行快速验证文件完整性:
unzip 车牌检测算法源码.zip -d plate_source cd plate_source ls -lh weights/ config/ data/ file weights/best.pt说明:unzip 解压到指定目录后,用 ls 查看关键目录的权限和大小。file 命令会输出文件的实际格式——best.pt 应该显示为“PyTorch model”或“data”,如果显示“ASCII text”说明这是个假权重。我见过不止一个源码包在 weights 目录里放了个 .txt 文件,内容是“请在公众号回复暗号获取权重”,这种包直接放弃,不值得投入时间。
3.2 最小推理命令跑通全流程
环境安装是第一个分水岭。车牌检测源码绝大多数基于 PyTorch 或 Ultralytics 框架,Python 版本和依赖版本不对会报各种奇怪错误。常见做法是先创建独立虚拟环境,再按 requirements.txt 安装依赖:
conda create -n plate python=3.10 -y conda activate plate pip install -r requirements.txt python detect.py --source test.jpg --weights weights/best.pt --imgsz 640 --conf 0.25参数说明:source 指输入图片路径,test.jpg 换成实际图片;weights 指向权重文件;imgsz 是输入分辨率,640 是 YOLO 系列默认值;conf 是置信度阈值,低于 0.25 的检测框会被过滤掉。如果 detect.py 脚本不接收这些参数,大概率是另一套代码风格,需要直接读脚本开头确认输入方式。跑通后输出目录下会生成带标注的 result.jpg,打开看看:检测框是否贴合车牌边缘、有没有漏检和误检。
如果 requirements.txt 里锁定的版本和你本机 Python 版本冲突,优先调整 Python 版本而不是强行装依赖。Ultralytics 在 Python 3.10 和 3.11 上兼容性最好,3.8 以下会有不少新版本依赖装不上。装 CUDA 版本时注意 torch 和 CUDA 的对应关系,CPU 版本 torch 也能推理,只是速度慢很多,不要装错。
3.3 推理脚本内部逻辑:输入预处理、推理、后处理
跑通之后要搞清楚这个模型是怎么从输入图片得到结果框的,理解了内部逻辑才谈得上调参。绝大多数 YOLO 系推理脚本都做三件事:预处理、推理、后处理。
预处理阶段,图像被 resize 到 imgsz×imgsz 的方形尺寸,然后做归一化。这里有个关键参数 letterbox,它保证图片等比缩放后剩余部分用灰边填充,避免车牌被拉伸变形。有些源码省略了 letterbox,直接把图片暴力拉伸到 640×640,车牌的长宽比会失真,检测精度下降明显。推理阶段,模型输出一个特征图张量,包含预测框的坐标、宽高、置信度和类别概率。后处理阶段,用置信度阈值过滤低质量框,再用 NMS(非极大值抑制)去掉重复框。
后处理里 NMS 的 IOU 阈值最值得关注,默认一般在 0.45 到 0.5。阈值偏低会保留更多重叠框,造成同一个车牌被框两次;偏高又容易把两个紧挨着的车牌的框合并成一个。车牌场景通常一辆车只有一个车牌,帧内基本不存在同类目标重叠问题,所以 0.5 的默认值基本不用动。最终输出格式可能是 txt 文件,也可能是直接画框的图片,格式无所谓,但框坐标是归一化的还是绝对像素值必须确认——这直接影响后续接 OCR 还是接自定义业务逻辑。
| 参数 | 默认值 | 作用 | 调整建议 |
|---|---|---|---|
| imgsz | 640 | 输入分辨率 | 车牌较小则调到 1280,推理变慢 |
| conf | 0.25 | 置信度阈值 | 漏检多就降到 0.15,误检多就升到 0.4 |
| iou | 0.45 | NMS 重叠阈值 | 车牌间无重叠,0.5 可用 |
| device | cpu/cuda | 推理设备 | 有 GPU 优先 cuda:0 |
4. 把车牌数据变成模型能学的东西:标注格式、合成数据与增强细节
4.1 车牌类型差异决定了标注框不是越准越好
检测模型的学习对象是“图像区域和标签之间的映射”,这个映射的质量取决于标注框的一致性和数据集覆盖度。车牌检测的标注框并不追求“像素级贴合边缘”,更关键的是框的边界一致——训练集中所有蓝色车牌框都精准框住车牌四角,而绿色车牌框却多包了一圈保险杠,模型就会在绿色车牌上产生不一致的回归目标。
国内路面上能见到的车牌类型至少有四种:蓝底白字(燃油车)、绿底黑字(新能源)、黄底黑字(大型车)、白底黑字(军警车)。每种车牌的长宽比、颜色对比度、反光特性都不一样。如果你的训练集里只有蓝牌,模型会把“蓝色”当成强先验——遇到绿牌时可能直接漏检,因为颜色特征和训练分布差得太远。标注时建议按“矩形框完全包含车牌字符区域且不超出车牌边缘 2 像素”来统一标准,同时保证每种车牌类型在数据集中都有足够样本。
另外一个常见问题是标注格式。版权规范的数据集(如 CCPD 中国城市车牌数据集)有自己的一套编码信息,文件名里包含车牌的坐标、角度、字符内容;而 YOLO 训练需要的是每张图对应一个 txt 文件,每行是“类别 cx cy w h(归一化)”。两者之间的格式转换是第一个坑:CCPD 的标注是绝对坐标,YOLO 要求归一化,转换时漏掉分母就会跑出 NaN 损失。建议写一个转换脚本统一处理,不要在训练过程中去猜数据格式。
4.2 用合成车牌补足真实场景覆盖:仿射变换与光照增强的代码细节
标注真实数据成本很高,但车牌数据有一个巨大优势:车牌本身的样式是可以程序化生成的。用合成车牌做训练增强是业界通行做法,公开的合成车牌生成器(如开源车牌生成库)能生成任意省份简称、任意字符组合的车牌,再叠加到任意背景图上。合理使用合成数据和真实数据 1:1 混合训练,能把准确率提升几个点,这是真实项目验证过的经验。
合成车牌的增强细节有几个关键点。一是透视变换,车牌在真实画面中是倾斜的,完全正视角度的合成车牌对模型没有帮助,建议随机旋转和透视扭曲;二是光照变化,车牌反光很强,需要在 HSV 空间随机调整亮度和饱和度,模拟白天强光和夜间暗光;三是背景融合,纯色背景合成的样板对模型是个负样本,要贴到复杂背景里。下面是一个用 OpenCV 做车牌增强的典型代码片段:
import cv2 import numpy as np def random_perspective(plate_img, max_angle=15): """对车牌做随机透视变换,模拟倾斜视角""" h, w = plate_img.shape[:2] # 在四个角点上加随机偏移,生成透视矩阵 pts1 = np.float32([[0, 0], [w-1, 0], [0, h-1], [w-1, h-1]]) pts2 = np.float32([ [np.random.uniform(-max_angle, max_angle), np.random.uniform(-max_angle, max_angle)], [w-1 + np.random.uniform(-max_angle, max_angle), np.random.uniform(-max_angle, max_angle)], [np.random.uniform(-max_angle, max_angle), h-1 + np.random.uniform(-max_angle, max_angle)], [w-1 + np.random.uniform(-max_angle, max_angle), h-1 + np.random.uniform(-max_angle, max_angle)] ]) matrix = cv2.getPerspectiveTransform(pts1, pts2) return cv2.warpPerspective(plate_img, matrix, (w, h)) def random_illumination(plate_img): """随机调整车牌亮度与饱和度,模拟不同光照""" hsv = cv2.cvtColor(plate_img, cv2.COLOR_BGR2HSV) brightness = np.random.uniform(0.5, 1.6) saturation = np.random.uniform(0.7, 1.4) hsv[:, :, 2] = np.clip(hsv[:, :, 2] * brightness, 0, 255) hsv[:, :, 1] = np.clip(hsv[:, :, 1] * saturation, 0, 255) return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)参数说明:random_perspective 里 max_angle 控制透视偏移强度,15 度足够覆盖大多数卡口画面中车牌的倾斜幅度,太大会生成不真实的畸形车牌。random_illumination 里亮度乘数 0.5 到 1.6 意味着最低压暗一半、最高提亮 60%,夜间车牌反光时模型反而要适应这种高动态范围。把生成的增强样本混入训练集时,建议增强样本占比在 20%-40% 之间,太多会让模型对真实图像过拟合到合成纹理上。
4.3 训练集和验证集划分的一条边界:同源性数据不能既训练又验证
数据集划分这个坑比想象中隐蔽。很多车牌数据集是从同一个停车场、同一个摄像头采集的连续视频中截帧得到的,相邻帧几乎一模一样。如果按“前 80% 帧训练、后 20% 帧验证”的随机方式划分,训练集里已经出现过验证集那些车的图像——模型实际上在“背题”,而不是在学泛化能力,验证集上的准确率会虚高到不可思议。
正确做法是按车辆或按时间段划分。同一个车牌(同一辆车)的所有帧只能出现在训练集或验证集中,不能两边都有。更容易操作的方式是按天划分:用 1 号到 15 号的数据训练,16 号到 20 号的数据验证,这样能保证验证集是模型没见过的车辆、没见过的光照时段。评估时看一眼验证集图片里有没有训练集里出现过的车牌号码,如果有,说明划分有问题。
数据量参考:一个可用到生产级别的车牌检测模型,至少需要 3000 到 5000 张带标注的车牌图像。如果真实标注数据凑不齐,用合成数据补——但合成数据和真实数据的比例要控制在 3:7 以下,并且验证集必须全部是真实数据,否则模型真实效果会被合成数据的高可辨识度带偏。
5. 训练阶段最容易翻车的四个坑:现象、原因与处理
5.1 训练 loss 在降,但验证集 mAP 一直卡在 0.0x
训练了十几个 epoch,loss 正常下降,验证集的 mAP 却始终在 0.01 到 0.05 之间跳,看起来等于没学。遇见过类似问题的人应该不少。这个现象首先要怀疑不是模型问题,而是评估脚本的标签格式不匹配。
原因:很多源码包的训练流程能跑通,但验证流程里用的评测脚本是直接从别的项目拷的,标签格式没转对。比如训练用的是 YOLO 的归一化坐标,评测脚本却按 COCO 的绝对像素坐标解析,或者类别编号从 0 和从 1 混用。模型输出和真实标签在坐标空间上对不上,mAP 永远算不出来。
解决:先把评估脚本的输入输出手动走一遍——打印出一条预测框和一条真实框的坐标,看看是不是同一个坐标系;再画一张验证图像的可视化结果,把预测框和 GT 直接画在同一张图上,如果框的位置完全错位,就可以确认是格式问题。还有一种情况是评估脚本用了错误的 IOU 阈值,车牌框比较小,0.5 的 IOU 阈值下框稍微偏一点就判为不匹配,可以降到 0.25 快速验证。
5.2 远距离小目标车牌全部漏检,大目标好好的
停车场场景里,车辆从远处驶来时只有几十像素高,车牌更小,模型在大分辨率图上能检到,在 640×640 输入下全丢。这不是模型坏了,而是目标尺寸和输入分辨率之间的匹配问题。
原因:车牌检测器默认输入是 640×640,一张 1920×1080 的卡口图缩放后,远处那辆车只剩下十几个像素,车牌的纹理细节在缩放过程中被破坏。模型在训练时如果没怎么见过小目标样本,等于没有学过小目标特征。
解决:有两个方向。第一个是把训练和推理的 imgsz 都调到 1280,小目标特征保留更多,代价是推理速度降到原来的四分之一左右;第二个是训练时在数据增强里加入随机裁剪,让车牌在画面中占比更小,主动制造小目标样本。如果推理设备性能够,1280 输入通常是最直接有效的解法。另外,YOLOv8 有几个尺度不同的版本,小目标多的情况下优先选 s 或 m 而不是 n,n 版本为了速度牺牲了一部分小目标能力。
5.3 batch size 调小后 loss 爆炸,训练直接废掉
显存不够是常见约束,很多人习惯直接把 batch size 从 16 降到 2,然后训练 loss 开始剧烈震荡,甚至前几个 batch 直接出现 NaN。这不是模型结构问题,是 batch size 改变后带动了两个连锁反应。
原因:一是批量归一化层(BN)在 batch size 太小时统计量不稳定,每个 batch 的均值和方差波动巨大,训练过程无法收敛;二是学习率没有跟着调——batch size 减小一半,梯度更新时更少的样本平均,原来能用的学习率现在过大。
解决:调 batch size 的同时必须按比例调学习率。经验公式是 new_lr = old_lr × new_batch / old_batch,比如从 batch=16 降到 batch=4,学习率要同步降到原来的四分之一。如果显存不足不想动学习率,可以用梯度累积来模拟大 batch——每累积 4 个 batch 更新一次梯度,等效 batch 保持 16。用 YOLO 系框架时还有一个隐性问题:训练脚本里如果开了自动学习率调度,它会根据 batch size 重新计算默认学习率,手动改的 lr 可能被覆盖,需要确认超参数实际生效值。
5.4 测试集只有蓝牌,新能源绿牌一个都检不出来
模型对训练集里出现频率最高的颜色特征形成了强先验,这是检测任务里典型的频率偏差问题。蓝牌在数据集中占 80% 以上时,模型会倾向于把“蓝色矩形区域”当成重要特征,绿牌因为颜色特征完全不在训练分布里,被判别为背景。
现象:单独测蓝牌时准确率很高,一遇到绿牌就漏检或者置信度极低(低于 0.1)。原因就是数据分布不均衡。解决分两步:一是扩充绿牌样本,把绿牌数据的数量从仅占 5% 提升到占 20% 以上,注意不要让绿牌占比超过蓝牌太多,否则蓝牌又会变差;二是对绿牌做专门的增强,因为绿牌通常是白色字符、底色更亮,反光更明显,针对性模拟强光下的绿牌图像可以提升模型鲁棒性。训练后专门用绿牌测试集做验证,不要只看总体 mAP——总体 mAP 会被蓝牌的高表现拉起来,掩盖绿牌的问题。
6. 从检测到交付:ONNX 导出与 INT8 量化上车
6.1 把 PyTorch 模型导出成 ONNX 再部署到边缘设备
检测模型最终要跑在边缘设备上,常见方案是 Jetson 系列或工业级 x86 工控机。PyTorch 模型不能直接跑在 TensorRT 上,要先导出成 ONNX 再转 TensorRT 引擎。Ultralytics 框架的导出比较省事:
from ultralytics import YOLO # 加载训练好的权重并导出 ONNX model = YOLO("weights/best.pt") model.export(format="onnx", imgsz=640, half=True, simplify=True)参数说明:format 指定导出格式,imgsz 必须和训练时的输入分辨率一致,不一致会直接影响精度;half=True 表示导出 FP16 半精度模型,显存占用减半、速度提升,如果部署设备不支持 FP16 就去掉;simplify=True 会简化计算图,消除一些冗余算子,转换 TensorRT 时不容易报错。导出后在 ONNX Runtime 或 TensorRT 上验证精度,输出和 PyTorch 推理结果做对比——两者数值差异有一个容忍范围,小于 0.01 属于正常,如果差异很大,优先检查预处理逻辑(归一化方式、letterbox 是否一致)。
6.2 INT8 量化的校准集选择:不是越多越好而是越全越好
INT8 量化是把模型权重从 FP16 压缩到 INT8,推理速度能再翻一倍,但精度必然有损失。车牌检测目标比较单一,量化后精度损失通常可接受,前提是校准集要选对。校准集是量化过程中用来统计激活值分布的一小批数据,它的质量和构成直接决定量化后模型的表现。
这里有一个容易翻车的经验:校准集不是越多越好,几百张精心挑选的图片就够用,关键要覆盖全部典型场景——白天强光、夜间暗光、雨天、车牌倾斜、绿牌和蓝牌各占一定比例。如果只用一百张白天停车场图片做校准,量化模型在夜间就有可能漏检,因为量化时模型对夜间特征的统计分布没有覆盖到。我自己最早做车牌检测部署时,第一次 INT8 量化后白天精度掉了 4 个点,夜间掉了 12 个点,后来换了覆盖全天时段的校准集才把夜间精度拉回来。这算是血泪经验:车牌检测的量化校准,场景多样性比图片数量更重要。
校准集选好后,输出 INT8 模型,在真实场景视频流上连续跑几个小时,统计每帧的检测置信度分布和漏检帧率。部署不是终点,测够才算完成。希望这些踩坑记录能帮你少走一段弯路,也希望你在做车牌检测落地时,每一步都能先看到边界,再做出取舍。
本文还有配套的精品资源,点击获取