简介:基于Python与CNN模型识别的停车场智能车牌识别系统源码,是一套面向计算机视觉课程设计、期末大作业及智能停车场项目开发者的完整工程。项目以CenterNet完成车牌目标检测,结合最优CNN模型对字符进行识别,并通过Pygame模块搭建图形交互界面,从图像预处理、车牌定位到字符输出均有对应实现,代码结构完整、可直接运行,适合作为毕设或课设的参考基座。压缩包总计2000个文件,约124.68MB,其中999个py文件覆盖算法核心与业务逻辑,h/json/xml等文件用于模型配置与数据组织,md/txt文档提供使用说明与设计思路,另含部分Java、C、JavaScript等扩展辅助文件,目录划分清晰,便于按模块研读、调试与二次开发。该资源已有475人学习,读者可从中获取目标检测与车牌识别的完整融合思路、界面交互设计方案及工程化组织方式,也能直接复用其中模型与脚本,快速搭建可演示的智能停车场识别原型,支撑项目答辩与学习报告撰写。
1. 基于Python和CNN的停车场车牌识别:这套源码到底能落地多少
停车场出入口的车牌抓拍,看着是个简单任务,实际跑过才知道:白天强光、夜间反光、雨雪泥污、弯道来车,任何一项都能让传统OCR闹脾气。用基于Python和CNN模型识别的停车场智能车牌识别系统源码,就是把“定位、分割、识别”这条流水线用卷积神经网络串起来,对一个固定场景做到接近可用的识别率。这套东西适合两类人:一是做毕业设计需要可跑通demo的在校生,二是想给自助洗车、园区道闸加一个轻量识别模块的嵌入式开发者。它解决的不是“识别一个车牌”,而是“在停车场这种非理想光照下,怎么稳定输出车牌号码”。这篇文章从模型选型、数据集准备、训练推理命令、核心模块再到部署踩坑,把整条链路拆开讲清楚。
2. 系统架构与选型:为什么停车场场景首选CNN而不是传统OCR
2.1 CNN在车牌识别中的角色:从检测到分类的流水线
一套完整的车牌识别系统,通常拆成三步:第一步在画面里找到车牌位置,第二步把车牌区域里的字符一个个切开,第三步把每个字符识别成汉字、字母或数字。传统做法用颜色阈值、边缘检测、模板匹配,在固定卡口上能跑,但一遇倾斜、模糊、逆光就崩。CNN卷积神经网络的好处在于,它用一堆可训练的卷积核自己学习“像车牌”的特征,对光照和形变的容忍度高得多。
在停车场场景里,我一般会把定位用目标检测网络或OpenCV传统定位,字符识别用轻量CNN。这套源码里,重点就在“识别”这一步:设计一个输入固定尺寸、输出字符类别的CNN模型,让它在已经切好的字符图片上做分类。如果只是靠CNN做端到端检测和识别,对训练数据量和算力的要求会高很多,作为一套源码包反而不好维护。
2.2 模型选型:轻量CNN和YOLO系列的取舍
很多人拿到源码第一反应是“我要端到端检测车牌”,其实没必要。停车场摄像头位置固定,车牌宽高比、字体分布相对稳定,把问题拆开反而好调。字符识别模型可以做到很小:三个卷积层加两个全连接层,参数量几十万,CPU就能实时。如果用YOLO直接回归车牌的四个角点和类别,训练需要更多标注数据,且在小目标上容易漏检。我见过不少源码包只给一个字符分类模型,自己不带定位,这时候需要配合一个前置定位模块。
| 方案 | 训练数据量 | 推理速度 | 适用场景 |
|---|---|---|---|
| YOLO端到端检测车牌 | 需要几千张带框标注 | GPU实时,CPU较慢 | 车牌位置不固定的移动巡检 |
| 传统定位 + 轻量CNN识别 | 只要字符图几百张 | CPU也能实时 | 固定相机停车场出入口 |
建议:如果源码里自带YOLO权重,直接用;如果只有字符识别模型,自己用OpenCV做定位,先灰度化、边缘检测、找轮廓,再切字符。这样即使没有高端显卡,也能在树莓派上跑。
2.3 数据集准备与标注格式:别小看字符类别定义和转换
无论是训练还是二次训练,都需要先吃透数据集结构。停车场车牌识别源码常见的数据组织是:一张车牌字符图片一个文件,标签是字符的类别序号。中文省份简称算一类,字母和数字各算一类。注意字符类别数不是10加24,因为省份简称有31个,汉字和字母、数字混在一起,要统一做成分类索引。
如果源码给的是VOC XML标注,需要先转成YOLO训练用的txt格式。常见做法是用脚本把xml里的<xmin> <ymin> <xmax> <ymax>归一化到图片宽高,再写成一行“类别编号 cx cy w h”。下面是一个转写片段:
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() w_img = int(root.find('size/width').text) h_img = int(root.find('size/height').text) lines = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_map: continue bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) cx = (xmin + xmax) / 2 / w_img cy = (ymin + ymax) / 2 / h_img w = (xmax - xmin) / w_img h = (ymax - ymin) / h_img lines.append(f"{class_map[cls]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write('\n'.join(lines))参数说明:class_map是一个字符串到数字的字典,比如{'blue_plate': 0, 'green_plate': 1}。归一化坐标是YOLO训练的必要输入,直接喂像素坐标会训练发散。注意有些车牌还有新能源绿色牌照,长度从7位变成8位,类别定义里要留出位置,否则后处理切字符时索引会错位。
我之前处理过一个数据集,里面有一千张“苏”字车牌,但“藏”字只有几张,这种不均衡会让模型对低频字符几乎不识别。解决思路在后文避坑章节专门说。总之,先把数据集准备好,训练才不玄学。
3. 从源码跑通:Python环境、依赖安装与最小复现命令
拿到一个“停车场智能车牌识别系统源码.zip”,别急着双击train.py。先理清里面的文件结构:一般会有README、requirements.txt、weights目录、data目录、train.py、detect.py。缺依赖是最常见的卡点,所以下面按最小顺序复现。
3.1 环境准备:Python版本与依赖清单
建议用Python 3.8到3.10,太新的版本容易遇到OpenCV和PyTorch版本兼容问题。先创建一个虚拟环境:
python -m venv venv source venv/bin/activate # Windows 下改为 venv\Scripts\activate pip install --upgrade pip pip install -r requirements.txt如果源码里没有requirements.txt,手动安装核心依赖:
pip install opencv-python numpy torch torchvision matplotlib注意:只做CPU推理的机器可以装CPU版torch,去PyTorch官网复制对应命令;要训练的机器有N卡再装CUDA版。依赖装完后,用python -c "import cv2; print(cv2.__version__)"验证。很多源码包里的OpenCV代码用了较新的API,版本太旧会直接报模块找不到。这里的关键不是版本号越新越好,而是和你系统的CUDA、Python三者兼容。
3.2 运行训练脚本:关键参数与默认值
假设train.py有CLI参数。常见参数包括data、epochs、batch-size、img-size、device。最小训练命令:
python train.py --data data/plate.yaml --epochs 100 --batch-size 8 --img-size 416 --device cpu参数含义:
--data指向一个yaml文件,里面写入train、val图片路径和类别数量、类别名列表。--epochs100是常用起点,真实停车场数据量小(几千张)的情况下,50到100轮足够收敛。--batch-size由显存决定,8比较保守,如果OOM就降到4。--img-size是输入到网络的图像边长,416对车牌检测合适,太大训练慢。--device cpu强制用CPU训练,跑起来慢,但能验证流程。
训练过程中观察loss曲线,如果直线不降,后文有排错。如果你只想先看效果,跳过训练,直接用源码自带的weights/best.pt或model.h5跑推理。
3.3 运行推理脚本:检测单张图片和视频流
训练完或直接用源码自带权重,推理命令一般是:
python detect.py --source test.jpg --weights weights/best.pt --conf-thres 0.5 --save-txt参数说明:
--source可以是图片路径,也可以是摄像头编号0。停车场部署时用这个连USB摄像头。--conf-thres是置信度阈值,建议先设0.5观察;如果漏检很多,降到0.3,但会引入误检。--save-txt把识别结果(包括车牌号)写到txt文件,方便对接道闸逻辑。
跑通这一步,你就能看到一帧图里画出车牌框,并输出识别文本。此时再去调内部算法,才是有效率的路径。
3.4 拿到源码后先做三步,能省一晚上
第一,确认依赖清单是否完整,缺什么补什么。第二,跑一次推理,哪怕用测试图片,确认模型权重能加载。第三,看输出结果里是否带置信度和坐标,只有文本没有框的源码往往少了定位模块。我见过不少下载的免费python源码包,直接打印“车牌号”不画框,看起来很省事,但一旦画面里有多辆车就分不清。所以拿到源码先别追求端到端,先确认它能稳定读出一张单车牌图。
另一个容易忽略的点是路径分隔符。Windows下源码里写的是正斜杠,放在Linux上没问题;反过来,如果代码用了\拼接路径,Windows上跑得通,部署到Linux就开场爆错。我一般会先全局搜一遍os.path.join,确认没有硬编码。
4. 核心模块拆解:车牌定位、字符分割与CNN识别
4.1 预处理:灰度化、去噪与边缘检测
在车牌识别源码里,第一步通常不是直接丢给CNN,而是先做一个传统视觉预处理。好处是能缩小候选区域,降低CNN工作量。下面是我常用的定位预处理段:
import cv2 import numpy as np def preprocess(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯滤波,核大小5,抑制噪声但保留边缘 blur = cv2.GaussianBlur(gray, (5, 5), 0) # Sobel边缘检测,只取x方向,因为车牌字符边缘竖直方向多 sobel = cv2.Sobel(blur, cv2.CV_16S, 1, 0, ksize=3) abs_sobel = cv2.convertScaleAbs(sobel) # 二值化,高于阈值设为255 _, binary = cv2.threshold(abs_sobel, 150, 255, cv2.THRESH_BINARY) # 形态学闭操作,把字符之间的空隙连接成块 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 3)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return closed说明:Sobel取x方向是因为车牌字符的竖直笔画多,但中国车牌有汉字,横竖都有,实际可以两个方向都试。闭操作的核宽度17、高度3,是为了把单个字符连成一个车牌整体。如果车牌子被光线截断,可以把核宽度加大到25。注意这段代码产生的是二值轮廓图,后面还要找轮廓筛选。
很多开源源码在这个地方只用颜色阈值,遇到绿色车牌或黄色车牌就得改参数。用边缘检测的好处是颜色无关,鲁棒性更高。不好的地方是会引入车灯、保险杠边缘等噪声,后面必须按宽高比过滤。
4.2 字符分割:投影法与连通域分析
拿到车牌定位框后,需要把“京A12345”切成单独字符。最常见的做法是垂直投影:把二值图像每一列的白色像素数统计出来,投影值为零的列就是字符间隔。示例:
def split_chars(plate_gray): # 先二值化,文字为白色 _, binary = cv2.threshold(plate_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 统计每列白色像素数 h, w = binary.shape col_sum = np.sum(binary > 0, axis=0) # 找到连续非零区间,宽度大于阈值的视为字符 chars = [] in_char = False start = 0 for i in range(w): if col_sum[i] > 0 and not in_char: start = i in_char = True elif col_sum[i] == 0 and in_char: end = i if end - start > 10: # 过滤掉噪声点 chars.append((start, end)) in_char = False return chars这个方法的坑在于,车牌中间的圆点或分隔符也会产生投影缝隙,需要在切片时过滤掉宽度过短或位置异常的片段。有些源码会直接用固定比例把车牌切成7份,省去投影,但遇到新能源8位车牌就出错,所以还是投影法更通用。因为中国车牌字符间距不是完全均匀的,固定比例只适合标准蓝牌。
4.3 CNN识别:构建模型与训练配置
字符图片通常归一化到相同尺寸,比如32x64。下面是一个轻量CNN分类模型,用PyTorch实现,这就是标题里“CNN模型识别”的核心部分:
import torch import torch.nn as nn class PlateCharCNN(nn.Module): def __init__(self, num_classes=65): super().__init__() self.features = nn.Sequential( # 输入: 3x64x32(RGB字符图) nn.Conv2d(3, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 32x32x16 nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 64x16x8 nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d((1, 1)) # 128x1x1 ) self.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, x): x = self.features(x) x = torch.flatten(x, 1) return self.classifier(x)参数说明:输入尺寸选了64高、32宽,因为车牌字符高大于宽。第一个卷积层输出32通道,第二个输出64,第三个输出128。全连接前用了AdaptiveAvgPool2d,这样无论输入图片稍微变形,最后都能变成128维特征。分类数按你的数据集设定,比如中国蓝牌常用65类,其中31个汉字省份简称、24个字母、10个数字。训练时用交叉熵损失、Adam优化器、学习率0.001是稳定起点。要注意类别不均衡,比如“O”和“0”容易混淆,可以在损失函数里给难分类别加权重。
这个CNN模型很小,字符图片单张推理在普通CPU上只要几毫秒。如果不想自己训练,也可以用源码里训练好的char_model.pth。我建议拿到源码先看这个模型输入尺寸,很多坑都出在预处理尺寸和模型不匹配上。
5. 避坑与常见问题排查:从数据集标注到模型收敛的5个典型坑
这一章不直接贴代码,写的是调试经验。车牌识别的难点普遍不在模型结构,而在工程边界。
5.1 坑1:训练loss不下降,准确率卡在60%左右
现象:训练几十轮后loss在1.0附近波动,验证集准确率上不去。
原因:最常见的是学习率过大,优化器震荡;其次是数据类别不均衡,低频字符主导梯度方向。我见过一个源码自带数据集里,“皖”字样本占了一半,模型就变得只认“皖”。
解决:把学习率从0.01降到0.001,加一个余弦退火调度器。统计每类样本数量,对低频类用加权采样器。一个简单做法是在损失函数里给每个类别权重,频率越低的类别权重越高。如果时序上loss下降很慢,先跑10轮看趋势,不降就先调学习率,别急着加数据。
5.2 坑2:识别结果“0”和“O”、“1”和“I”混淆严重
现象:识别“京A01O12”这种车牌,输出“京A0O12”,置信度还不低。
原因:这两类字符在归一化后形状几乎一样,尤其是中文字体下,模型天然难以区分。纯靠CNN模型很难从像素层面找到稳定差异。
解决:从后处理下手。因为中国车牌的车牌号中字母“I”和“O”不允许出现,可以在输出层直接把这两个类别屏蔽,或设置规则:如果预测为“O”且概率略低于某个阈值,强制映射为“0”。在训练时也可以做字符级数据增强,例如随机细微旋转、仿射变换,让模型更关注笔画局部差异。
5.3 坑3:车牌定位把车灯、广告牌误检成车牌
现象:在停车场夜间场景,推理输出一堆红色框,框住车灯或反光物。
原因:训练数据里负样本太少,模型只知道“像车牌”的纹理,不知道“不是车牌”的边界。灯的边缘和车牌边缘在Sobel输出上非常像。
解决:收集一批不含车牌的夜间停车场截图,打上空白标签加入训练。同时调整NMS的IoU阈值到0.4,并限制检测框宽高比在2到5之间。这个坑在停车场俯拍角度特别常见,因为灯光和车牌在视觉上都接近矩形高亮块。
5.4 坑4:摄像头角度导致倾斜车牌识别率下降
现象:道闸摄像头安装在侧面,拍出来的车牌是斜的,字符分割后边界不齐,CNN识别错字。
原因:字符分割依赖垂直投影,倾斜让每列白色像素叠加,分割线错位。
解决:在车牌定位后增加一个透视变换校正步骤,根据定位的四个角点做仿射变换,把车牌转正。或者在训练数据增强里加入随机旋转正负15度,让CNN能适应斜字符。这个坑在现场施工时特别明显,因为安装高度和角度不标准,理想中的水平车牌很少见。
5.5 坑5:夜间反光、曝光过度导致漏检
现象:晚上强光灯直射,车牌区域一片白,定位直接找不到。
原因:摄像头自动曝光把车牌过曝了,边缘信息丢失。
解决:硬件端调整曝光时间或启用宽动态。算法端在推理前做直方图均衡化,把过亮区域拉伸回正常范围。训练时也要对训练集做亮度扰动,乘以随机系数0.6到1.4,让模型对曝光变化不那么敏感。这里有个玄学点:很多人只调算法参数,忽略摄像头本身的曝光设置,导致同样的模型在另一个停车场就失效。
6. 进阶落地:从单张图片到停车场出入口实时识别系统的关键一步
单张图片跑通只是起点,停车场出入口要的是实时。如果你的源码只有PyTorch推理脚本,我建议先把它导出成ONNX,再用ONNX Runtime做CPU/GPU推理,去掉PyTorch框架的开销。常见做法是在源码里加一个导出脚本:
import torch from model import PlateCharCNN model = PlateCharCNN(num_classes=65) model.load_state_dict(torch.load("weights/best.pth", map_location="cpu")) model.eval() dummy = torch.randn(1, 3, 64, 32) torch.onnx.export(model, dummy, "plate_char.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}})导出后,再用Flask封装一个HTTP识别接口,供门禁系统调用。代码示意:
import flask import cv2 import numpy as np import onnxruntime as ort app = flask.Flask(__name__) sess = ort.InferenceSession("plate_char.onnx") @app.route("/recognize", methods=["POST"]) def recognize(): file = flask.request.files["file"] img = cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR) # 这里接车牌定位和字符分割结果,把字符图list送入模型 # 输出为各类别置信度,取argmax后再做规则过滤 ...验证方法:准备白天、夜间、雨天各200张停车场真实截图,计算“整串车牌完全正确率”,而不是只算字符级准确率。我自己的习惯是每天夜里检查一次运行日志,把识别失败的车牌截图汇总成新训练集,隔一周微调一次模型。最早做这个项目我也翻过车,以为CNN模型越大越准,结果在停车场上被一张侧停的黑色SUV教做人。后来明白了:与其盲目换大模型,不如先把定位和分割调稳,再谈优化。希望帮到你。
本文还有配套的精品资源,点击获取