简介:这套智能停车场车牌识别计费系统源码与可执行文件,基于Python3开发,融合OpenCV图像处理、机器学习与SQLite/MySQL数据库技术,覆盖车牌自动识别、停车计时计费、前端GUI交互等完整功能,适用于商场、写字楼等实际停车场景。项目结构清晰,压缩包内共2000个文件,以1777个py源码为核心,还包含pyc编译文件、txt配置说明、h头文件、xml参数、pdf文档及少量网页资源,整体约186.56MB,可直接运行也可二次开发。资源提供从图像预处理、车牌定位、字符分割到字符识别的完整代码,并附带程序使用说明文档,帮助理解计费规则、数据库管理以及不同计费策略的实现。目前已有67人学习浏览,整体而言,对搭建类似智能停车系统、开展课程设计或进行二次开发,均具有较高的实际参考价值。
1. 智能停车场车牌识别计费系统:拿到就能跑的车牌识别+自动计费完整项目
如果你正在找一个人工智能/计算机视觉方向的Python实战项目,或者需要一套能直接演示“摄像头拍车牌→自动识别→计算停车费→打印账单”全流程的课设、毕设底座,这个项目值得花一下午拆一遍。它不是那种只贴几个算法片段的教学Demo,而是一个把车牌识别(LPR)和停车计费业务串起来的完整系统:带GUI界面、带SQLite数据库、带可执行文件,源码解压后能跑,跑起来能看到真实的车牌识别和计费流程。
项目基于Python3实现,核心链路是OpenCV图像处理 + 车牌定位/字符分割/字符识别 + Tkinter界面 + SQLite存储。难能可贵的是它把“识别”和“业务”做了解耦:识别模块负责把车牌图片转成字符串,计费模块负责把字符串和入场时间换算成金额。对于想学图像处理工程化、或者想快速搭一个“识别+计费”闭环系统的开发者来说,这几乎是现成的参考实现。
2. 系统架构与目录结构:从源码包到可运行程序的整体拆解
2.1 技术选型:为什么是OpenCV + Tkinter + SQLite
整个项目的技术栈是典型的“够用、好跑、易改”组合。图像处理和字符识别部分基于OpenCV实现,而不是深度学习方案。这里有个很实在的考虑:车牌识别如果用YOLO或PaddleOCR那类模型,光是环境配置就可能劝退一半用户,而且CPU推理速度未必理想。项目选择传统图像处理路线——颜色特征定位车牌区域、轮廓分析做字符分割、模板匹配或特征匹配做字符识别,好处是依赖少、速度快、代码可读性强,对教学和二次开发都非常友好。
GUI层用的是Tkinter而不是PyQt。Tkinter是Python内置库,不需要额外安装,打包体积也小。对于停车场管理这种表单密集型界面(显示车牌、入场时间、缴费金额、查询记录),Tkinter完全够用。数据存储选SQLite更是明智,Python自带的sqlite3模块就能操作,免去了安装MySQL的麻烦,而且单文件数据库对项目分发特别有利——可执行文件拷到哪,数据库跟着走。
提示:如果你之前用过PyQt写界面,再看这个项目的Tkinter代码会不太习惯。Tkinter的布局是靠pack/grid逐层叠加,回调函数用command参数挂载,没有Qt那种信号槽机制,但胜在简单直接,跑起来你就懂了。
2.2 源码包目录结构逐层分析
项目压缩包解压后的结构大致是这样:
智能停车场车牌识别计费系统/ ├── 程序使用说明.doc ├── 可执行文件/ # 打包好的exe目录 ├── 源码/ │ ├── main.py # 程序入口,启动GUI │ ├── car_ui.py # 界面层:主窗口、停车管理、计费展示 │ ├── car_identify.py # 车牌识别核心:定位、分割、字符识别 │ ├── database.py # 数据库操作:建表、增删改查 │ ├── billing.py # 计费规则模块:按时计费逻辑 │ ├── train_model.py # 字符识别模型训练脚本 │ ├── templates/ # 字符模板图片目录 │ ├── temp/ # 临时图片存放(车牌裁剪图等) │ └── images/ # 测试图片这种分层方式很聪明,把“界面显示”和“业务逻辑”和“算法识别”各放一层。main.py只是启动入口,car_ui.py处理用户交互和状态展示,car_identify.py是纯算法模块,不依赖界面代码,你完全可以把它单独抽出来作为车牌识别工具类复用。
包内还有一堆看起来跟项目无关的文件:fortranobject.c、wrapmodule.c、gfortran_vs2003_hack.c、ndarraytypes.h。这些是NumPy/SciPy在Windows下用MinGW编译时产生的C语言辅助文件——说明作者用的科学计算库是通过pip装的预编译轮子,或者在某些环境下做过源码编译。实际运行项目用不到它们,但如果你打算用gfortran编译NumPy的某些扩展,会用到这些头文件。忽略即可,不影响项目运行。
2.3 可执行文件和源码的关系
包里有可执行文件目录,意味着作者用PyInstaller打包过。可执行文件适合没有Python环境的用户直接运行,或者现场演示时避免命令行翻车。打包配置一般是在项目根目录执行:
pyinstaller -F -w -i parking.ico main.py-F参数生成单文件,-w表示不显示控制台窗口。注意打包时要把templates字符模板目录和images测试图片一起带上,否则识别模块会找不到模板文件直接报错。源码里跑的是.py脚本,可执行文件跑的是打包后的产物,两者功能一致,但二次开发时当然改源码。
3. 核心模块拆解:一个车牌从图像到计费金额的完整链路
3.1 车牌识别模块:灰度化、二值化、定位、分割、识别五步走
car_identify.py是整个项目最核心的模块。整个识别流程可以分为五个步骤:图像预处理、车牌定位、字符分割、字符识别、结果输出。我们一步步来看关键代码。
首先是图像预处理和车牌定位:
import cv2 import numpy as np def locate_plate(img): # 转为灰度图,减少颜色干扰 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊去噪点,核大小5x5 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 垂直边缘检测:Sobel算子x方向求梯度 sobel_x = cv2.Sobel(blurred, cv2.CV_16S, 1, 0) abs_sobel = cv2.convertScaleAbs(sobel_x) # 二值化,将边缘图转为黑白 _, binary = cv2.threshold(abs_sobel, 80, 255, cv2.THRESH_BINARY) # 形态学闭运算,把断开的边缘连起来 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) # 查找轮廓,按面积筛选出可能是车牌的区域 contours, _ = cv2.findContours(closed, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) for contour in contours: x, y, w, h = cv2.boundingRect(contour) # 车牌宽高比通常在 2.5 ~ 5.5 之间 if 2.5 < w / h < 5.5 and 2000 < w * h < 50000: return x, y, w, h return None这个定位逻辑用的是最经典的边缘检测+轮廓筛选方案。Sobel算子找垂直边缘很重要——车牌字符密集,垂直边缘响应强;宽高比限制是核心筛选条件,普通车牌(蓝底白字)的宽高比约是3:1到4:1。面积范围参数要根据摄像头分辨率调整,如果识别不到车牌,第一个该检查的就是这个阈值区间。
定位到车牌区域后,接下来是字符分割:
def segment_chars(plate_img): # 转为灰度并二值化 gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 用OTSU自动阈值,比固定阈值适应性更好 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 找字符轮廓 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) char_list = [] for contour in contours: x, y, w, h = cv2.boundingRect(contour) # 过滤掉噪声:宽高比在0.3~1.2之间,面积不大不小 if 0.3 < w / h < 1.2 and 50 < w * h < 2000: char_list.append((x, y, w, h)) # 按x坐标从左到右排序 char_list.sort(key=lambda item: item[0]) # 去除最左边可能是车牌边框的干扰项 return char_list二值化用了OTSU大津法而不是固定阈值,这是对光照变化的自适应处理。字符分割的宽高比过滤可以参考:汉字字符比如“京”“苏”偏方正,宽高比接近1;字母数字偏窄,约0.5。API里返回的是排序后的坐标列表,后面的识别循环就基于这个顺序拼接。
字符识别采用模板匹配:
def recognize_char(char_img, template_dir='templates'): # 将字符图缩放到模板统一尺寸 20x40 resized = cv2.resize(char_img, (20, 40)) best_score = 0 best_char = '?' # 遍历模板目录下的所有模板图片 for template_name in os.listdir(template_dir): template = cv2.imread(os.path.join(template_dir, template_name), 0) template = cv2.resize(template, (20, 40)) # 计算归一化相关系数,值越接近1越相似 result = cv2.matchTemplate(resized, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, _ = cv2.minMaxLoc(result) if max_val > best_score: best_score = max_val best_char = template_name.split('.')[0] return best_char模板匹配的思路很直接:把待识别字符和模板库里的每一张图算相似度,取最高分。TM_CCOEFF_NORMED是归一化相关系数匹配,对光照和对比度有一定的鲁棒性。每个模板图片的文件名就是它代表的字符,相当于用文件系统做了标签管理。字符识别准确率依赖模板质量,如果你要适应新能源绿牌或不同省份字体,扩充templates目录是最直接的路径。
3.2 计费模块:按时计费的规则引擎
billing.py实现了停车费用计算,核心是“按时段+按小时”的计费模型:
from datetime import datetime def calculate_fee(entry_time, exit_time, rate_per_hour=5.0): """计算停车费用 参数: entry_time: 入场时间 datetime 类型 exit_time: 出场时间 datetime 类型 rate_per_hour: 每小时收费,默认5元 返回: (费用金额, 停车时长分钟数) 元组 """ if exit_time <= entry_time: return 0.0, 0 # 计算停车分钟数 minutes = int((exit_time - entry_time).total_seconds() / 60) # 不满一小时的部分按一小时算 hours = (minutes + 59) // 60 # 最低消费1小时 hours = max(hours, 1) fee = round(hours * rate_per_hour, 2) return fee, minutes这个计费函数的特点是不足一小时按一小时计算,最少计费一小时,符合大多数停车场规则。exit_time <= entry_time的防御性判断很有必要——实际运行中摄像头时间错乱或者手动录入出错都会导致时间倒挂,直接返回0比抛异常优雅得多。
数据库中存的是入场时间字符串,计算时要parse成datetime:
def parse_time(time_str): return datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')如果你的停车场有“首小时免费”“夜间半价”“会员折扣”之类的规则,直接改calculate_fee函数内部逻辑就行,没必要动识别模块。
3.3 GUI与业务流程串接
car_ui.py里定义了主窗口类,用户操作逻辑是:点击“入场抓拍”→摄像头或图片读取车牌→录入车牌和当前时间→点击“出场结算”→识别车牌→查数据库算费用→显示账单。
关键代码片段是入场、出场的处理:
def entry_vehicle(self): # 从摄像头读一帧 img = self.capture_frame() plate = car_identify.recognize_plate(img) if plate is None: messagebox.showwarning('识别失败', '未识别到车牌,请调整角度重拍') return now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') database.insert_record(plate, now, None, None) self.refresh_table() def exit_vehicle(self): plate = self.plate_var.get() if not plate: messagebox.showwarning('提示', '请先输入/识别车牌号') return record = database.find_entry_record(plate) if record is None: messagebox.showerror('错误', '找不到该车牌的入场记录') return now = datetime.now() fee, minutes = billing.calculate_fee(record[2], now) database.update_exit(plate, now.strftime('%Y-%m-%d %H:%M:%S'), fee) self.show_bill(plate, record[2], now, minutes, fee)这段代码体现了系统设计的核心思想:识别模块只负责出车牌字符串,数据库只负责存取记录,计费模块只负责算钱。每块各司其职,而且都有失败处理分支——识别失败、查无记录、时间异常都做了弹窗提示。你如果要做二次开发,比如加一个“月卡用户不收费”的功能,改动点就在exit_vehicle里加一个判断分支。
4. 数据库设计与运行部署:从建表语句到启动配置
4.1 数据库表结构分析
数据库模块database.py负责SQLite的初始化和增删改查。建表语句是理解业务数据流的最佳入口。默认表名是parking_records,字段包括:
CREATE TABLE IF NOT EXISTS parking_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate_number TEXT NOT NULL, entry_time TEXT NOT NULL, exit_time TEXT, fee REAL DEFAULT 0 );- id是自增主键,作为记录的唯一身份
- plate_number存储识别出的车牌字符串,如“京A12345”
- entry_time是入场时间,格式固定为“YYYY-MM-DD HH:MM:SS”
- exit_time初始为NULL,出场结算后UPDATE填入
- fee是应收费用,默认0
车牌的查询和插入用参数化SQL防止注入:
def insert_record(plate, entry_time, exit_time, fee): conn = sqlite3.connect('parking.db') cursor = conn.cursor() cursor.execute( 'INSERT INTO parking_records (plate_number, entry_time, exit_time, fee) VALUES (?, ?, ?, ?)', (plate, entry_time, exit_time, fee) ) conn.commit() conn.close()用?占位符而不是字符串拼接,这是防止SQL注入的标准姿势。虽然单机应用安全风险不高,但这习惯值得保留。
4.2 本地运行三步走
环境要求是Python 3.6以上版本,需要安装的依赖包如下:
pip install opencv-python numpy不需要装Tkinter,因为它是Python标准库的一部分。运行项目只需要一个命令:
python main.py启动后会弹出主窗口,界面分为三个区域:顶部是操作按钮(入场登记、出场结算、刷新列表),中间是当前识别结果展示,底部是停车记录表格。如果电脑没有摄像头,项目提供了打开本地图片测试的入口——选择images目录下的测试车牌图片,识别模块跑完会把结果显示在界面上。
打包成exe的命令如下:
pip install pyinstaller pyinstaller -F -w main.py --name 智能停车场管理系统-F是单文件模式,-w是去掉控制台窗口。运行后dist目录下会生成exe文件,注意把templates文件夹复制到exe同级目录下,否则识别时找不到模板报错。
4.3 参数配置的常见调整项
摄像头编号是识别模块里最常见的“玄学”参数。代码里默认用的是cam = cv2.VideoCapture(0),0表示系统第一个摄像头。如果你接的是USB外接摄像头,可能要用cv2.VideoCapture(1)。查一下设备编号的方法很简单:
import cv2 for i in range(3): cap = cv2.VideoCapture(i) if cap.isOpened(): print(f'摄像头 {i} 可用') cap.release()车牌定位的面积阈值也是要调的参数。上面定位代码里的2000 < w * h < 50000是针对距离摄像头3-5米的车牌设定的。如果你的摄像头装在道闸正上方、距离很近,车牌区域会非常大(比如8000-100000像素),这时候需要放大上限。识别不到车牌时,优先怀疑这个范围设置。
5. 避坑指南:运行和二次开发中常见的6个典型问题
5.1 报错“找不到templates路径”
现象:运行后程序报错FileNotFoundError,提示找不到模板文件。
原因:代码里用的是相对路径,也就是启动时所在目录下找templates。如果直接从别的目录执行py文件,或者打包后exe没带模板文件夹,就找不到了。
解决:把模板路径改成相对当前文件的绝对路径:
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')用__file__属性定位模块位置,再拼模板目录,这样不管从哪个目录启动都能找到。
5.2 摄像头打不开或者画面全黑
现象:界面能启动,但点“入场登记”时卡顿后提示识别失败,检查发现摄像头图标亮但画面全黑。
原因:摄像头被其他程序占用(比如微信视频通话、OBS推流),或者驱动权限没开(Windows下隐私设置拦截了摄像头)。
解决:先关掉占用摄像头的程序,在Windows设置里检查“隐私→相机→允许桌面应用访问摄像头”。代码里可以在打开摄像头后主动检查:
cap = cv2.VideoCapture(0) if not cap.isOpened(): print('摄像头0打开失败,尝试摄像头1') cap = cv2.VideoCapture(1) ret, frame = cap.read() if not ret: print('读取一帧失败,摄像头被占用或硬件异常')5.3 车牌识别率低,尤其是汉字识别错乱
现象:数字和字母识别基本对,但省份汉字频繁出错,比如把“京”识别成“示”,“鲁”识别成“鱼”。
原因:模板匹配对字体变体和图像噪声特别敏感。汉字的笔画结构复杂,稍微有倾斜、模糊、光照不均就会让相关系数掉下来。另一个常见是字符分割时把汉字切成了左右两半,导致匹配到的模板是错的。
解决:训练模板时多采集几个不同角度、不同亮度下的同款字符,每个字符存多张模板图。匹配时取最高分而不是第一个。更实用的办法是在识别后加一个“已知车牌前缀校验”——中国车牌第一个字符是省份简称,有一个固定集合,不在集合里就认定识别失败,重新识别或人工确认:
PROVINCE_CODES = ['京', '津', '冀', '晋', '蒙', '辽', '吉', '黑', '沪', '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '桂', '琼', '渝', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新']5.4 打包exe后闪退
现象:源码用python main.py能跑,但pyinstaller打包后的exe一启动就没反应。
原因:最常见的两个原因——模板文件路径不对(见5.1),或者依赖库没打进去。OpenCV在pyinstaller打包时需要带上它的数据文件(特别是haarcascade和模板文件)。
解决:用--add-data参数把模板目录加进去:
pyinstaller -F -w main.py --name 智能停车场 --add-data "templates;templates"Windows下分号分隔源和目标目录。加了这个参数后,exe运行时会自动把模板文件释放到临时目录,代码里要同时兼容这种路径:
if hasattr(sys, '_MEIPASS'): BASE_DIR = sys._MEIPASS else: BASE_DIR = os.path.dirname(os.path.abspath(__file__))这是PyInstaller打包后读取附带文件的固定套路。
5.5 计费金额算错,免费停车也收费
现象:车辆停了2分钟离场,系统却收了5块钱。
原因:计费逻辑里hours = max(hours, 1)这条规则把不足一小时统一按一小时算了。这不是bug,是计费规则如此。但如果你希望有免费时长或按分钟计费,需要修改这里。
解决:把最低消费改为15分钟免费:
if minutes <= 15: return 0.0, minutes hours = (minutes - 15 + 59) // 60注意改动计费逻辑后要同步更新界面上的“收费标准”文案,否则现场使用会引发纠纷。
5.6 多人同时用SQLite写数据导致database is locked
现象:程序跑了一天后偶尔报错sqlite3.OperationalError: database is locked。
原因:SQLite在高并发写入场景下会锁库。单机版停车场系统一般只有一个客户端操作,但如果你后面改成了双击入口同时打开多个窗口,就会触发这个错误。
解决:给数据库操作加一个连接超时参数:
conn = sqlite3.connect('parking.db', timeout=5)timeout设置等待锁释放的时间,超时还没拿到锁才真正报错。另一个做法是每次操作短连接(用完立刻close),项目里已经是这个模式,只要保持这个习惯就行。
6. 二次开发指南:三个实用改造方向与验证方法
6.1 改造一:加入车牌黑白名单管理
停车场最常用的二次开发需求是“月卡车辆自动放行、黑名单车辆报警”。实现思路是在数据库里加一张表:
CREATE TABLE IF NOT EXISTS vip_plates ( plate_number TEXT PRIMARY KEY, expiry_date TEXT NOT NULL, owner_name TEXT );然后在exit_vehicle函数里加一个查月卡有效期的分支:
def is_vip(plate): conn = sqlite3.connect('parking.db') c = conn.cursor() c.execute('SELECT expiry_date FROM vip_plates WHERE plate_number = ?', (plate,)) row = c.fetchone() conn.close() if row is None: return False expiry = datetime.strptime(row[0], '%Y-%m-%d') return expiry >= datetime.now()验证方法:手动往vip_plates表插入一条记录,设置expiry_date为明天,跑出场结算流程确认费用为0;再把expiry_date改成昨天,确认恢复正常收费。
6.2 改造二:增加“识别置信度”显示
模板匹配的best_score可以反映识别置信度。把recognize_char函数的返回值改成同时返回字符和得分,在界面上用不同颜色标记低置信度字符——比如分数低于0.8的字符标红,提示操作员人工核对。
def recognize_char_with_score(char_img): # ... 省略匹配逻辑 ... if best_score < 0.8: return best_char, best_score, 'low' return best_char, best_score, 'high'这个方法的价值在于:不是所有识别结果都可信,让用户在界面上看到置信度分级,比静默出错更实用。实际停车场系统中,低置信度时一般会触发二次识别或人工介入。
6.3 改造三:识别模块独立成命令行工具
如果不想启动GUI,可以把识别模块做成命令行工具:
# lpr_tool.py import sys import cv2 from car_identify import recognize_plate if __name__ == '__main__': img_path = sys.argv[1] img = cv2.imread(img_path) if img is None: print('图片读取失败') sys.exit(1) plate = recognize_plate(img) print(f'识别结果: {plate}')这个工具用于批量测试图片识别率,也可以嵌入到现有业务流程中。做法是把car_identify.py里依赖Tkinter的代码隔离干净,然后命令行脚本直接调用。
我从这个项目上学到的教训是:写识别类项目时,永远把“识别失败怎么办”当作正常分支来设计,而不是当异常处理。项目里每个识别环节都有“返回None→提示重新拍摄”的兜底逻辑,这在现场使用中救了无数次。后来我自己写的OCR项目,也都强制走一遍这个设计——先定义失败行为,再写成功路径。希望这篇拆解能帮到你,无论拿它跑课设还是做商业项目起步,都能少踩几个坑。
本文还有配套的精品资源,点击获取