尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

车牌识别停车计费系统实战:从Python算法到exe一键部署

车牌识别停车计费系统实战:从Python算法到exe一键部署 简介车牌识别技术是智慧停车与智能交通的基础依托图像处理和深度学习模型自动完成车辆身份获取被广泛应用于停车场收费、无人值守等场景。文章围绕一套基于PythonOpenCVYOLOv5的车牌识别计费系统从车牌定位、透视矫正、字符识别到计费引擎与SQLite数据存储完整展示了全流程实现。系统具备离线运行、可配置计费规则、跨天封顶等特点支持免费时段、异常车辆处理并通过PyInstaller打包为独立exe方便现场部署。相比人工登记该方案提升了通行效率减少了漏费纠纷且数据本地保存安全可控。对于有意构建低成本停车管理系统的开发者而言这套从算法到工程落地的实践过程提供了有价值的参考。1. 项目缘起为什么我要自己做一套车牌识别计费系统去年一个开停车场的朋友找到我说他的场子不大二百来个车位高峰时段车辆进出频繁之前一直靠保安拿小票登记、人工核对时间收费。这种方式至少有三个问题一是高峰期容易排长队出口算完金额还要找零效率很低二是偶尔会有熟人不登记直接放行月底账对不上三是一到交接班当班数据全在纸面上漏记错记说不清楚。他问我能不能搞一套低成本、能离线运行的车牌识别计费系统不需要云端服务能在一台普通办公电脑上跑起来入口摄像头识别车牌出口再识别一次系统自动算时长和费用。交付的时候最好给一份源代码、一个能直接双击运行的可执行程序再加一份使用说明让现场保安也能快速上手。这就是这个项目的起点。整个项目基于Python实现核心包含车牌识别、停车时长统计、按时计费、进出场记录管理四个模块。最终我交付的是一个压缩包里面分了三个目录src源码目录、dist可执行程序目录、docs使用说明目录。用这种方式打包一方面方便懂技术的朋友二次开发另一方面也让不懂代码的现场人员拿到就能部署不需要装Python环境。需要说明的是这套系统并没有使用太重的框架仍然是纯Python OpenCV 深度学习推理库的组合。技术选型上我放弃了云端API方案因为现场要求必须断网可用而且车牌照片属于敏感信息也不适合全量上传到外部服务。后面章节我会把车牌识别和计费引擎的实现细节、打包成exe的坑、以及部署现场的实测数据完整写出来。2. 系统总体设计与功能边界2.1 模块划分从摄像头采集到计费结算整个系统我拆成了五个相对独立的模块这样测试和后续维护都方便。入口摄像头负责抓拍车辆前方画面程序通过OpenCV读取视频流对每一帧做车牌检测检测到车牌后截取车牌区域再进入字符识别流程。识别出的车牌号会写入进库记录表同时记录入场时间、入口抓拍的图片路径。出口模块的逻辑与入口不同。出口不需要每次都成功识别才放行因为偶尔会有车牌被遮挡或者夜间反光严重的情况。我设计了一个兜底流程如果出口识别置信度低于设定阈值系统会把抓拍图和识别结果推到人工确认界面由收费员手动修改车牌再进入计费流程。这样虽然多一步操作但避免了误识别导致计费错误。计费模块是整个系统的核心它接收进出场匹配后的记录根据停留时长和费率规则计算金额。这里我强调了一个原则计费规则必须是可配置的不能写死在代码里。因为不同停车场的免费时长、首小时价格、后续每小时价格、每日封顶价格都不一样写死意味着每次改价都要改代码重新打包太不现实。数据存储我选了SQLite而不是MySQL或PostgreSQL。原因很直接这是一个单机版系统没有多终端并发写库的需求SQLite零配置、单文件备份、性能足够。如果未来要升级成多入口多出口的网络版再把数据层替换成MySQL也不难因为我在dao层做了隔离。2.2 数据表结构与计费规则配置数据库我设计了四张表车辆入场记录表、车辆出场记录表、收费记录表、计费规则表。停车时长和费用都通过SQL查询计算不在内存里保留关键状态这样程序意外重启也不会丢账。计费规则表是这四张表里最关键的我用一个JSON字符串存储规则明细例如{ free_minutes: 15, first_hour_price: 5, hourly_price: 3, daily_cap: 30, enable_overlap_free: true, billing_interval_minutes: 60 }这里几个字段的含义我在使用说明里写得很清楚free_minutes进场后多少分钟内离场免费超过后从入场时间开始计算总时长。first_hour_price不足一小时按一小时计的金额。hourly_price超出首小时后每个计费周期多少钱。daily_cap单日最高收费超过后按封顶价计算。计费算法我单独抽成了一个函数逻辑是先判断总时长是否小于等于免费分钟数如果免费则金额为0否则先扣除免费时段再按整小时向上取整计算费用最后判断是否超过单日封顶值。2.3 工作流程入场、出场、免费时段、异常车辆正常情况下车辆入场时识别到车牌系统在入场记录表插入一条记录状态为“在场”。出口再次识别到同一车牌时系统查找该车牌最新一条“在场”记录计算时间差生成收费记录并把入场记录状态更新为“已离场”。免费时段处理容易出错。比如进场15分钟内免费如果入口记录是09:00出口识别是09:14计费金额必须是0但系统里还是要保留一条完整的进出场记录方便月底核对流量而不是直接把这条记录删掉。这一点我在代码注释里专门标了出来。异常车辆包括三类无牌车、识别结果置信度低的模糊车、出场时查不到入场记录的车辆。无牌车我采用发放临时凭证号的方式把凭证号作为“虚拟车牌”写入入场记录模糊车走人工确认查不到入场记录的则按当前时间补录一条入场记录后正常计费。这些边界情况如果不提前处理现场使用时会非常痛苦。3. 车牌识别核心定位、矫正、字符分割与识别3.1 车牌定位为什么纯边缘检测不够用车牌识别是整个系统里技术含量最高、也最容易出问题的一环。网上很多demo只用颜色阈值加边缘检测在单一背景下能跑通但换到真实停车场画质差、逆光、车灯直射、地面反光误检率会直线上升。我最终采用了双路检测策略。第一路是用YOLOv5微调出来的车牌检测模型模型输入640x640的图像输出车牌边界框和置信度第二路是传统OpenCV的颜色特征提取专门用来兜底。车牌本身就是高饱和度的蓝色或绿色区域颜色特征在远距离时往往比深度学习模型更稳定。两路结果做融合判断如果YOLO检测到车牌且置信度超过0.6直接用如果置信度在0.3到0.6之间则结合颜色区域做位置校验如果置信度低于0.3但颜色特征区域面积足够大也保留候选框。这个策略实现了很高的召回率代价是偶尔会有误检但误检框在后续字符识别阶段会被过滤掉。3.2 透视矫正与字符分割的细节检测到车牌区域后不能直接送识别模型。因为摄像头安装角度和车辆位置的关系车牌在画面中常常是倾斜的、带有透视形变的直接切出来送去识别准确率会大打折扣。我做的第一步是找车牌的四个角点。在小尺寸车牌图上用Canny算子提取边缘再用概率霍夫变换检测直线段通过直线相交确定四边形的四个顶点最后做透视变换把车牌区域矫正成水平方向的矩形。这里有一个容易忽略的细节车牌边框的宽度不是固定的有些经过打磨或加装边框的车牌边缘检测会拿到不规则的线条。所以我在提取角点之前会先对二值化图像做一次形态学闭运算把断开的边缘连接起来。闭运算的核大小我设为5x5太小连不上太大容易把临近字符也融合进去。矫正完成后车牌图像被统一缩放到宽240像素、高80像素。字符分割我用了垂直投影法把每列像素值累加波谷位置就是字符之间的间隙。对于普通蓝牌和新能源绿牌字符排列规律不同我在分割时先判断车牌颜色再决定是否按8个字符还是7个字符的标准来校验。3.3 字符识别方案从浅层特征到深度学习模型字符识别这块我对比过几种方案。最早试过Tesseract OCR直接对车牌区域做OCR效果很差因为车牌字符是定制的字体而且有汉字、字母、数字混合Tesseract容易把“鲁B·12345”这种车牌识别成乱码。后面换成自己训练的CNN分类器字符集包括31个省份汉字、24个大写字母避免O和I、10个数字总共65个类别准确率能做到98%左右但训练数据要求很高。我整理了上万张真实车牌字符样本才勉强够用。最后我选择了PaddleOCR的车牌识别模型做兜底因为PaddleOCR内置了针对车牌的优化模型对模糊、倾斜、低光照的鲁棒性更好。实际运行时我先用自己训练的CNN分类器去做主识别同时用PaddleOCR做二次验证如果两者结果一致直接通过如果不一致取置信度更高的一方作为最终结果并把低置信度的那条记录扔进人工确认队列。用双模型交叉验证识别耗时增加了大概80毫秒但准确率提升了约1.5%。对于计费系统来说这1.5%的准确率意味着每天少几十笔人工干预现场体验差别很大。3.4 夜间、逆光、雨雾场景的预处理策略真实停车场里环境光线变化是最大的坑。白天大太阳、傍晚逆光、夜间只有昏暗灯光摄像头拍出来的车牌质量天差地别。我做了三件事来应对。第一在抓拍前连续读取三帧画面计算帧间亮度方差取亮度最均衡的那一帧作为识别输入这一步能有效避免车辆大灯闪到摄像头瞬间的过曝帧。第二对输入图像先做直方图均衡化再用自适应阈值二值化提高字符与背景的对比度。第三在夜间场景下如果检测到画面平均亮度低于某个阈值就把图像整体乘以一个增益系数再做识别。这些预处理不是越高大上越好关键是稳定。我试过引入图像去噪模型和超分辨率模型确实能让车牌更清晰但推理时间增加了三倍在普通i5电脑上跑会导致视频流卡顿最终放弃只保留了计算量较小的传统方法。4. 计费引擎与系统联动的实现细节4.1 计费规则模块的可配置化设计计费引擎我写了一个独立的类叫BillingEngine对外只暴露一个接口calculate_fee(entry_time, exit_time, rule_config)。入场时间和出场时间都是datetime对象rule_config就是从数据库读取出来的计费规则字典。计算逻辑要注意一点跨天场景。比如车子停了一天半daily_cap按自然日封顶还是按24小时滚动封顶两个口径计算结果是不同的。我在规则里加了一个配置项daily_cap_type取值“calendar_day”表示按自然日封顶“rolling_24h”表示按入场时间起算的24小时滚动封顶。默认推荐calendar_day因为对车主来说更容易理解也更容易对上账单。另外免费时段的判定时机也很关键。如果车辆入场后免费时段还没结束但系统已经生成了计费记录会不会出现重复计算我的处理方式是计费记录在出场时才生成不预先生成占位数据。这样既避免免费时段内的中间状态也减少数据库里的垃圾数据。4.2 数据库读写与并发处理机制单机版系统也会遇到并发问题入口相机和出口相机同时抓拍两个进程或两个线程同时向SQLite写数据有可能出现“database is locked”。我的解决办法是使用一个写入队列所有写操作都投递到队列里由单一线程串行处理。读操作不走队列直接查询SQLite的只读副本。更稳妥的方案是开启SQLite的WAL模式同时对数据库连接设置busy_timeout为3秒。WAL模式下读操作不会阻塞写操作写操作之间也不会互相阻塞大幅减少了锁冲突。这两个配置在连接后立刻执行conn.execute(PRAGMA journal_modeWAL) conn.execute(PRAGMA busy_timeout3000)实际压力测试表明单线程串行写入1000条记录耗时不到0.3秒完全满足现场需求。4.3 界面与交互给操作员一套能快速上手的UI图形界面我用了PyQt5。界面不需要多华丽但一定要直观。主窗口分为三个区域左侧是视频预览区实时显示摄像头画面并在画面上叠加车牌识别框中间是进出场记录列表按时间倒序排列支持按车牌搜索右侧是操作区包含入场登记、出场结算、人工修改车牌、费用确认四个按钮。人工修改车牌这个功能很关键。识别错的车牌如果直接进入计费车主会投诉。界面上我让收费员在结算弹窗里可以看到系统识别结果和抓拍图对比如果识别不一致可以直接修改为正确车牌再重新计算费用。这个交互流程虽然多了一步但杜绝了绝大多数计费纠纷。历史数据我看重了两点一是所有操作都记录操作员ID方便追责二是支持导出Excel报表方便月度对账。这两点现场管理员非常看重反而是花哨的图标动画没人关心。5. 打包成独立可执行程序的完整路线5.1 PyInstaller打包时最容易被忽略的隐藏依赖项目开发完成后编译成可执行程序是最容易翻车的一步。很多Python项目在开发环境跑得好好的一打包就各种报错问题大多出在动态链接库和隐藏导入上。我的打包命令是这样写的pyinstaller -F -w -n ParkingSystem --hidden-importPyQt5.sip --hidden-importcv2 --hidden-importpaddleocr --add-data models:models --add-data config.ini:. main.py-F参数表示打包成单个exe文件-w表示不显示控制台窗口。但单文件模式有一个缺点程序启动时需要临时解压到系统临时目录启动速度会慢一些打包体积也会偏大。如果现场电脑性能一般建议改用--onedir模式也就是生成一个文件夹里面包含exe和依赖dll启动速度快很多。隐藏导入这一块PyQt5的sip模块是网上报错的重灾区。如果不手动hidden-import打包后的程序经常在import PyQt5时崩溃但又没有明确报错排查起来非常痛苦。OpenCV在PyInstaller里有自己的hook但从opencv-python升级到新版本后hook偶尔失效我习惯还是手动加一次。PaddleOCR的模型文件比较大如果直接放进exe里单文件体积会飙到2GB以上而且每次启动都要重新解压模型性能很差。我的做法是把模型目录外置到exe同级目录下的models文件夹用--add-data把模型引用路径指过去。这样做的好处是以后升级模型只需要替换models目录里的文件不用重新打包exe。5.2 模型文件与资源文件的外置策略外置资源文件后程序运行时要能正确找到这些文件的路径。开发环境下路径是../models打包后exe在dist目录模型在exe旁边的models目录路径不同所以我在代码里写了一个动态路径解析函数def resource_path(relative_path): if hasattr(sys, _MEIPASS): # PyInstaller打包后临时解压目录 base_path sys._MEIPASS else: # 开发环境 base_path os.path.dirname(os.path.abspath(__file__)) return os.path.join(base_path, relative_path)但这里有个细节如果模型文件外置exe同级目录是运行时的工作目录sys._MEIPASS是临时目录并不等于exe所在目录。我最终是把models目录单独复制到dist目录下程序启动时先检查exe同级目录的models是否存在如果存在就直接用否则再回退到sys._MEIPASS里的内置模型。5.3 打包后的典型报错与解决办法打包完成后我整理了一份FAQ列表直接写进了程序使用说明。第一个高频问题是“打开程序提示缺少VCRUNTIME140.dll”。这个不是PyInstaller能解决的需要安装Microsoft Visual C Redistributable。现场电脑如果是精简版Windows很容易缺这个运行库我在说明文档里附上了微软官方下载地址。第二个高频问题是“摄像头打不开提示cv2.VideoCapture失败”。多半不是代码问题而是摄像头被其他程序占用或者USB摄像头驱动没装好。我在程序里加了摄像头检查逻辑启动时枚举所有可用摄像头编号如果指定的编号打开失败会在界面上提示可用的摄像头列表。第三个问题是杀毒软件误杀。PyInstaller打包出来的exe偶尔会被Windows Defender或第三方杀毒软件标记为木马因为Python运行机制里有一些动态加载的特征。遇到这种情况我建议在首次使用前加入白名单同时用--key参数对exe进行简单加密虽然不能完全规避误杀但能减少特征命中的概率。6. 从开发机到现场部署、使用说明与实测效果6.1 程序使用说明里必须写清楚的内容一份好的使用说明不是简单写两句“双击exe运行”就完事。我写的使用说明分了八个部分内容覆盖环境要求、安装步骤、首次配置、日常操作、计费规则修改、数据备份、常见问题、故障排查。环境要求里我明确写了Windows 10/11 64位系统内存不低于4GBCPU推荐i5以上摄像头支持USB接口或RTSP协议。这些要求是我实测后的底线内存低于4GB跑起来明显卡顿CPU太老则车牌识别延迟会拉到2秒以上。首次配置部分我要求管理员修改config.ini里的入口摄像头编号、出口摄像头编号、车牌识别置信度阈值、计费规则参数。这些参数都配了注释说明例如[camera] # 入口摄像头编号0表示第一个摄像头 entry_camera_id 0 # 出口摄像头编号 exit_camera_id 1 [billing] # 免费时长分钟 free_minutes 15 # 首小时价格元 first_hour_price 5 # 后续每小时价格元 hourly_price 3 # 每日封顶价格元 daily_cap 30数据备份我专门强调要每周执行一次直接把整个data目录拷贝到其他磁盘即可因为SQLite数据库文件就在data目录下。恢复时只要停掉程序把备份文件覆盖回去再重新启动。6.2 现场安装与摄像头角度调整经验摄像头安装角度决定了整个系统的识别成功率这部分我在使用说明里用了很大篇幅。摄像头距离车头的距离我推荐是1到2米。太近车牌会超出画面边缘太远车牌区域像素太少字符识别容易错。安装高度一般离地0.6到1.2米角度要略微向下倾斜避免直接对着天空或车灯。在正式固定摄像头之前我建议先用手机连着摄像头预览画面手动调整位置确保车辆停在闸机前时车牌在画面中间偏下的位置且水平方向占画面宽度的三分之一左右。另一个容易忽略的点是灯光补偿。夜间场景如果停车场出入口照明不足摄像头抓拍到的车牌会整体偏暗识别率急剧下降。我试过两种方案一种是在识别程序里做亮度增强效果有限另一种是加装补光灯效果立竿见影。所以我在说明文档里建议现场务必保证出入口有基本照明如果能加装窄带补光灯识别率可以稳定在99%以上。6.3 实测数据识别准确率、速度与计费准确性系统在朋友那个停车场跑了两个月我拉了几组统计数据。总进场记录15683条出场记录15208条车牌识别准确率不需要人工干预的比例为98.6%剩余1.4%基本集中在夜间强逆光、车牌被泥水遮挡、极端角度这三种情况。平均识别速度单次拍照到结果显示为650毫秒左右其中车牌检测约150毫秒字符识别约300毫秒透视矫正和预处理约200毫秒。这个速度在单车道出入口是够用的车辆在闸机前停留1到2秒完全没压力。计费准确性我做了随机抽查手工抽取200条记录逐条用手机秒表验证时长再对照费率表人工算费发现全部与系统计算结果一致。唯一一次异常是我自己测试时修改了系统时间导致计费时长变成负数程序界面上直接弹了错误提示但数据库没有写入脏数据。这个问题我在代码里加了保护出场时间小于入场时间时直接拒绝生成计费记录并抛出一条明确错误信息。7. 踩坑记录与后续可扩展方向7.1 我对几个典型坑的复盘先说车牌粘连问题。某些车牌字符间距比较小垂直投影法分割时相邻字符的投影连在一起导致分割失败。我的解决办法是引入先验知识普通蓝牌固定7个字符新能源绿牌固定8个字符。如果分割结果不等于标准数量就按等宽字符重新切分把车牌宽度均匀切成标准字符数加一个间隔区。这个方法对少数粘连严重的样本仍然会失败但整体成功率提高了三个百分点。再说一次识别错误导致的连锁反应。入口识别成“鲁A12345”出口识别成“鲁A12346”系统会自动判定这是两辆车导致一辆“幽灵车”永远在库内。我设计了定期清理功能每天晚上12点扫描所有入场超过48小时且没有出场记录的车辆标记为可疑记录推送到界面由管理员人工确认为“已离场”还是“异常滞留”。这样避免场内车辆数据无限膨胀。最后是系统时钟的问题。如果电脑时间被人为改错计费计算会跟着出错。我在程序启动时做了一个“启动前时间自检”检查数据库里最后一条入场记录的时间戳与当前系统时间的关系如果发现当前时间比记录时间还早超过5分钟说明系统时钟异常程序会拒绝正常计费只允许进入只读模式。这个设计让我朋友避免了一次因为电脑时间跳变导致的计费纠纷。7.2 后续可扩展的方向这套系统目前是单机版但架构上已经预留了扩展空间。如果同一个停车场有多个入口和多个出口可以把SQLite换成MySQL或PostgreSQL把识别服务端的视频处理模块独立出来部署成局域网内的识别服务。这样入口相机识别后直接把结果推送到计费中心出口相机再调用识别服务查询车辆信息。如果想加月卡功能只需要在车辆表里增加一个is_monthly字段和到期时间出场计费时先判断是否在有效月卡期内是就直接放行不生成收费记录。这个改动不涉及识别逻辑半天就能改完。无牌车目前用的是临时凭证号方案比较原始。后续想支持无牌车扫码入场可以在入口处生成一个二维码内含临时车辆ID车主出场时扫码系统根据二维码ID匹配入场时间再调支付接口完成扣费。这块我还在设计等做完再单独写一篇分享。如果停车场想接微信支付或支付宝支付建议走扫码枪方案。出场时系统生成一个订单二维码车主扫码支付支付回调后自动抬杆放行。要注意的是计费系统必须能够在支付状态回查接口不通时仍能正常抬杆本地一定要保留一份支付成功记录的缓存防止依赖外部服务导致出口堵塞。最后再分享一个小技巧车牌识别模型不能一劳永逸建议每隔半年用现场积累的真实抓拍数据重新做一次微调。我在项目里写了一个简单的数据收集器每次识别成功的抓拍图都会留存到本地一个月下来就攒了上万张真实场景样本这批数据比网上随便找的测试集有价值得多。用它们做数据增强和模型微调识别率会随着时间不断提升而不是越用越陈旧。本文还有配套的精品资源点击获取
返回列表