简介面向停车场智能化管理场景的 Python 车牌识别计费系统完整工程适合计算机相关专业毕业设计、课程实训或对实时车牌识别感兴趣的开发者使用。系统基于 PyCharm 环境运行调用百度 AI 开放平台接口完成车牌识别覆盖车辆入场、出场判断、计时计费、收入柱状统计及车位满预警等功能模块。压缩包共包含 2003 个文件以 1777 个 Python 源码文件为主体另有 pyc 编译文件、txt 配置与密钥说明、h/c 等 C 扩展源码、xml/html/css/js 前端页面、pdf/doc 使用文档等整体约 189.59MB结构清晰便于按用途查阅。目前已吸引 247 人学习下载。资料内附详细使用说明文档按步骤配置 Key 后即可在 PyCharm 中运行体验完整呈现从图像识别、业务逻辑处理到数据统计的闭环流程能帮助读者快速理解车牌识别与计费系统的开发思路和界面交互实现。1. 智能停车场车牌识别计费系统先分清“识别”和“计费”谁才是主角停车场车牌识别计费系统拆开看就两件事让程序从摄像头画面里认出车牌再按进出时间算钱。很多第一次做的人把精力全砸在“识别”上结果系统跑起来后识别率挺高账却对不上——计费金额和手工记账差几十块。这个问题不在“照片识别”而在“识别结果怎么稳定地变成一条条可结算的停车记录”。这篇文章用 Python 从零搭一套可运行的智能停车场车牌识别计费系统覆盖环境准备、源码结构、计费流程和常见坑。适合要做课程设计、毕业设计或者想给小型停车场做一套低成本管理系统的人照着跑通后再按自己场地规则改费率就行。别指望装个开源库就完事真正的工程量在业务边界和异常处理上。2. 车牌识别与计费系统怎么搭先画清楚四条业务线和两个技术选型2.1 四条业务线入场、出场、缴费、记录查询做这套系统前先别急着装环境。动手前先把业务线画出来后面所有代码、数据表、坑都围着这四条线走。第一条线是入场道闸摄像头抓拍到车辆触发识别程序拿到车牌号、抓拍时间、图片路径往“在场车辆表”插一条记录状态标记为“在场”。第二条线是出场车开到出口摄像头再抓拍一次识别车牌程序根据车牌号查入场记录用当前时间减去入场时间得到停车时长。第三条线是缴费根据时长和费率规则算出应收金额人工确认收款或接虚拟支付接口状态变为“已缴费”后抬杆放行。第四条线是记录查询内部对账或车主扫码查记录都要能按车牌号、时间段筛出每一次进出和缴费明细。四条线里最容易被忽视的是第二条线里的“无入场记录”情况。比如摄像头漏拍或车牌识别失败车出不去人工放行后这笔记录在系统里是残缺的。常见做法是出场时查不到入场记录就弹一个“人工入场”补救入口让收费员补录入场时间再继续计费。建议把这条处理逻辑也写进主流程线上事故大多从这里来。我在给一个园区做方案时上千个车位、月卡车和临时车混跑不把业务线拆清楚后期加功能就会变成一团乱麻。2.2 识别引擎选型HyperLPR 和 PaddleOCR 的取舍车牌识别是这条链路里最像“黑匣子”的模块。做课程设计或小系统没必要自己训练模型直接用开源方案。常见两条路线一条是专用车牌识别库 HyperLPR另一条是通用 OCR 库 PaddleOCR 配车牌识别模型。HyperLPR 专为车牌设计对蓝牌、黄牌、绿牌和前后车牌都有针对性算法安装简单CPU 上就能跑实时视频流处理速度友好。PaddleOCR 识别泛化能力强如果场地里除了车牌还有其他字符识别需求比如识别停车位编号一套框架全干了但模型体积偏大第一次运行要加载权重文件树莓派这类低配设备上要留意内存。我的体验是纯停车场系统用 HyperLPR 起步更快。它返回识别结果里带车牌坐标、车牌字符串和置信度方便把“识别到哪个车牌”和“画面哪个位置”对应起来后面做防误判很有用。PaddleOCR 适合你在同一个项目里还要识别两行牌、分段字符等更杂的场景。两者可以并行装哪个置信度高用哪个这算“双引擎融合”但初期别这么干先把一条链路跑通。对比项HyperLPRPaddleOCR车牌专项训练有针对中国车牌通用专项模型CPU 运行轻量较重返回信息车牌坐标置信度文本框文本置信度上手成本低中适合场景纯停车场识别需要顺带处理其他 OCR 任务2.3 数据库选型SQLite 为什么够用什么时候换 MySQL数据库选型在小型系统里没有玄学数据量决定一切。一个中等规模停车场一天进出车次撑死几千条一年也就百万级记录SQLite 完全扛得住。它就是个文件备份靠拷贝部署不用装服务对入门项目非常友好。什么时候换 MySQL当系统要支撑多个岗亭同时写、还要做实时报表时SQLite 的并发写锁开始拖后腿这时再迁 MySQL 或 PostgreSQL。我一般建议单机、单岗亭、数据量在几十万条以内用 SQLite要上云、要远程管理、要多客户端并发访问换 MySQL。建表至少三张入场记录表 in_records 存车牌号、入场时间、抓拍图片路径、入场通道结算记录表 settlements 存车牌号、入场时间、出场时间、时长分钟、应收金额、实收金额、缴费时间费率表 fee_rules 存规则名、免费时长、单价、单次封顶、生效时间段。下面这段建表 SQL 是我常用的骨架按自己场地改金额类型和索引即可。CREATE TABLE in_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate_no TEXT NOT NULL, enter_time DATETIME NOT NULL, image_path TEXT, entrance TEXT DEFAULT A1, status TEXT DEFAULT in ); CREATE TABLE settlements ( id INTEGER PRIMARY KEY AUTOINCREMENT, record_id INTEGER, plate_no TEXT NOT NULL, enter_time DATETIME, exit_time DATETIME, duration_minutes INTEGER, amount_due REAL, amount_paid REAL, pay_time DATETIME ); CREATE TABLE fee_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, rule_name TEXT, free_minutes INTEGER DEFAULT 0, unit_price REAL DEFAULT 2.0, daily_cap REAL DEFAULT 20.0, start_time TEXT, end_time TEXT ); CREATE INDEX idx_in_records_plate ON in_records(plate_no);in_records 里 status 字段很重要查询在场车辆就是WHERE statusin出场时先改成 paid等抬杆再置为 out避免车辆还没出闸就被当成新来车重新计费。fee_rules 把费率独立出来后续涨价或设免费时段只改表不碰代码。注意 DATETIME 统一存时间戳还是本地时间强烈建议全部用时间戳或统一时区字符串否则跨天计费会算错几分钟。索引建在 plate_no 上是因为按车牌查入场记录是最频繁的查询不加索引在数据量大了之后会明显变慢。3. 用 Python 跑通最小计费闭环源码结构与核心流程拆解3.1 源码目录怎么摆模型、配置、业务代码分离标题里带了“源码使用说明”源码结构一开始就要干净。我见过不少项目把识别、计费、UI 全塞进一个文件改一处动全身。推荐按职责拆成目录parking_system/ ├── config.py # 费率、数据库路径、相机参数 ├── db.py # SQLite 连接与表初始化 ├── recognizer.py # 车牌识别封装 ├── billing.py # 计费规则核心 ├── main.py # 入口流程 ├── models/ # 存放模型权重文件 ├── images/ # 抓拍图片存储 └── requirements.txtmodels 目录放模型权重放到代码目录外的好处是升级代码时不会误删模型。images 目录建议按日期建子目录比如images/2025-06-01/A1.jpg后续对账时能快速找到某一辆车某一时刻的抓拍图。requirements.txt 固定依赖包别人拿到源码后用pip install -r requirements.txt就能装齐环境这比在 README 里手写依赖清单靠谱得多。3.2 车牌识别最小代码读图、识别、返回车牌号和置信度车牌识别封装在 recognizer.py 里不要在业务代码里到处 import cv2。这里用 HyperLPR 的常见调用方式写一个最小封装API 名会随版本略有差异以你实际安装的版本为准。import cv2 from hyperlpr3 import PlateRecognizer # 加载识别器参数可调 recognizer PlateRecognizer(conf_threshold0.6, max_plates1) def recognize_plate(image_path): image cv2.imread(image_path) if image is None: return None, 0.0 result recognizer(image) if result and len(result.plates) 0: plate result.plates[0] # plate.text 是车牌字符串, plate.score 是置信度 return plate.text, plate.score return None, 0.0conf_threshold 是置信度阈值低于 0.6 直接丢弃避免低质量识别产生脏数据。max_plates 限制识别数量停车场出口通常只该有一辆车设为 1 能减少后续误判如果你希望多车同时识别可保留到 2 或 3但要配合 ROI 区域过滤。返回的置信度不要只用来打日志建议存进记录表后续排查“这笔费为什么算错”时置信度低的车牌就是第一怀疑对象。3.3 计费核心逻辑进场记录、出场结算、免费时长与封顶计费核心放在 billing.py纯函数最好测试。下面这段实现的是最常见规则免费时长内不收费超出部分不足一小时按一小时算单日封顶。参数都从费率表读取不写死在代码里。import math from datetime import datetime def calc_fee(enter_dt, exit_dt, rule): rule 需要包含: free_minutes: 免费分钟数 unit_price: 每小时单价(元) daily_cap: 单日封顶(元), 没有封顶传 None duration_minutes int((exit_dt - enter_dt).total_seconds() // 60) if duration_minutes 0: return 0, 0 billable_minutes max(0, duration_minutes - rule[free_minutes]) hours math.ceil(billable_minutes / 60) amount round(hours * rule[unit_price], 2) if rule.get(daily_cap) and amount rule[daily_cap]: amount rule[daily_cap] return duration_minutes, amount注意 total_seconds 除法后用//取整得到的是完整分钟数避免浮点误差。math.ceil 向上取整意味着停了 61 分钟按 2 小时算这是停车场行业最常见规则但也最容易被车主质疑所以一定要在费率说明里写清楚。daily_cap 封顶是日累计逻辑如果跨天就要按自然日分段计算不能只算总时长否则 23:00 进场次日 01:00 出场晚上 10 点后和早上 6 点前费率不同后面会在避坑章节展开。3.4 一条龙流程摄像头触发 - 识别 - 入表 - 结算main.py 里把识别结果和计费逻辑串起来模拟入口和出口两个 handler。入口相机识别到车牌后直接插入在场记录出口相机识别到车牌后先查入场记录没有就用人工流程兜底。from datetime import datetime from db import insert_in_record, find_in_record, insert_settlement, mark_record_paid from recognizer import recognize_plate from billing import calc_fee def process_entry(plate, image_path, entrance_id): now datetime.now() insert_in_record(plate, now, image_path, entrance_id) def process_exit(plate, image_path): record find_in_record(plate) if not record: return no_record enter_dt record[enter_time] exit_dt datetime.now() duration, amount calc_fee(enter_dt, exit_dt, load_fee_rules()) insert_settlement(record[id], plate, enter_dt, exit_dt, duration, amount) mark_record_paid(record[id]) return amountfind_in_record 里要带statusin条件否则查到历史出场记录会把时长算成几百小时。process_exit 返回 no_record 时不要直接抬杆走到人工补录流程让收费员确认车辆入场时间。mark_record_paid 只把状态从 in 改成 paid真正抬杆后由道闸回调或收费员确认再置为 out这里留一个状态机是有意的防止界面刷新和实际抬杆时间不一致导致误判车辆还在场内。4. 把识别和计费接起来参数配置与使用说明里的四个关键点4.1 相机角度与触发帧率识别率的天花板在这源码和使用说明里最容易忽略的是相机安装参数。识别引擎算法再强拍不到清晰车牌也是白搭。在真实场地摸爬滚打后我总结了几条硬指标参数建议值说明车牌最小宽度120~150px低于这个值识别率明显下降俯仰角小于 20 度角度越大字符变形越严重帧率5~10 FPS够用即可过高增加 CPU 负担ROI 区域只保留车道区域排除相邻车道干扰抓拍触发方式建议用地感线圈加视频检测结合不要每帧都跑识别。帧率设 5 FPS如果车停得歪一点连续几帧里总有一帧是清晰的。ROI 裁剪是提升准确率最便宜的方法入口摄像头固定位置车头进入画面下方的区域识别前直接用 OpenCV 把这块区域裁出来旁边车道的车就算入画也不会干扰结果。光照变化是玄学问题白天逆光和夜间补光要分别做参数配置不要一套参数打天下。4.2 车牌号格式化省份简称、新能源车牌和字母数字归一开源识别模型输出的字符串不一定干净比如多余空格、O 和 0 混淆、I 和 1 混淆。入库前必须做格式化否则同一个车牌在入场和出场时可能被标准化成两个字符串直接导致查不到入场记录。import re def normalize_plate(text): text re.sub(r[\s\-_\.], , text.upper()) # 车牌第二位是字母, 后面是字母数字混排 # 常见混淆: O-0, I-1, Z-2, 但只处理后 5 位 if len(text) 7: province text[0] head text[1:2] tail text[2:] tail tail.replace(O, 0).replace(I, 1) return province head tail return text省份简称是汉字不要对它做字母替换。车牌的字母和数字混排里实际上不使用 O 和 I 这两个字母所以如果识别结果出现了 O 或 I大胆替换成 0 或 1 问题不大。新能源车牌是 8 位第二位是字母 D 或 F格式化后要单独判断长度8 位的不做截断。这里要注意编码问题Windows 下终端默认 GBK读入带汉字的识别结果容易乱码建议在文件头声明 UTF-8数据库连接也指定编码。4.3 费率配置用 JSON 还是数据库表动态调价怎么办开发阶段费率放 JSON 配置文件最快改费率不用碰数据库。但正式运行后停车场管理员可能要自己调价把费率落到 fee_rules 表里更合适改价只更新表不用重启服务。两种方式可以并行默认从数据库读数据库没有记录时才读 JSON 兜底。下面是一个 JSON 费率模板体现了分时段费率的雏形{ free_minutes: 15, unit_price: 2.0, daily_cap: 20.0, night_unit_price: 1.0, night_start: 22:00, night_end: 06:00 }unit_price 是白天每小时单价night_unit_price 是夜间单价夜间时段跨天。真正实现分时段费率的逻辑在 billing.py 里按分钟切分而不是把整段停车时长丢进一个公式。JSON 配置适合做单元测试因为一段配置就是一个测试用例数据库费率适合生产环境因为能支持运营人员后台改价。建议把这两者的边界写在“使用说明”里避免使用者改了 JSON 却发现不生效。4.4 日志与调试识别结果和计费结果串起来排查没有日志的计费系统是没法排障的。车主说“我停了 40 分钟为什么收 2 小时的钱”你手里没有一条完整链路日志就只能认栽。我习惯用 logging 模块把每一步串起来格式固定方便 grep。import logging logging.basicConfig( filenameparking.log, levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, encodingutf-8 ) logger logging.getLogger(__name__) logger.info(识别到车牌 %s, 置信度 %.2f, 图片 %s, plate, score, image_path) logger.info(结算入场 %s 出场 %s, 时长 %d 分钟, 金额 %.2f, enter_dt, exit_dt, duration, amount)注意 basicConfig 里的 filename 指向固定路径多模块运行时确保 log 目录存在否则启动即报错。日志内容里车牌号、置信度、图片路径、时长、金额必须齐全这是排查计费纠纷的后悔药。遇到识别错误时把图片路径打出来事后抽看图片是人工校正模型结果的必要手段。不要只打 part 一条摘要关键中间量都要有。5. 车牌识别计费系统的 6 个踩坑记录从能识别到能收费的最后一公里5.1 重复入场覆盖了初始记录停车时长变成负数现象同一辆车在入口反复触发识别进场时间段内出现多条 in 记录出场时查到最新一条停车时长只有几秒甚至负数。原因入场 handler 没有判断“这辆车是否已经在场”每一次识别都无脑插一条记录。解决插入前先查是否已有statusin的同车牌记录有就跳过或更新原记录的抓拍时间不新建记录。SELECT id FROM in_records WHERE plate_no ? AND status in;查到结果就说明车还在场内重复触发大概率是摄像头抖动或地感误触。此时只更新 image_path保留最早的 enter_time这样时长计算才稳定。5.2 新能源车牌 8 位被截断最后一位识别错现象绿牌车出场结算时车牌号只有 7 位最后一位缺失或错误导致查不到入场记录无法自动结算。原因HyperLPR 等模型在部分版本里对新能源车牌训练不充分或输入图片分辨率不够最后一位小字符被忽略。解决识别后做长度校验。第二位是字母且长度为 8 的绿牌如果识别结果长度为 7先不要急着当作蓝牌处理。可以把 len7 且第二位为字母的边缘情况标记为“疑似新能源”打印日志并走人工确认。更务实的方法是入场和出场都识别两次都错同一位置的概率低用入场和出场的车牌做模糊匹配允许一位差异并记录告警减少人工介入。5.3 一张图里有多辆车识别到了旁边车的车牌现象出口摄像头拍到两辆车排队系统把前车车牌识别成了后车结算金额对不上。原因识别器返回多个车牌代码默认取第一个不一定是目标车。解决利用识别结果里的车牌位置信息取画面中心区域、面积最大的那个车牌。更可靠的是在识别前做 ROI 裁剪只保留道闸前一条车道的区域。ROI 的四个坐标要在地感触发后微调不能凭截图目测一次定死不同场地的相机高度不一样。roi image[y0:y1, x0:x1] plate, score recognize_plate_from_cv_image(roi)这里的 x0,y0,x1,y1 是车道区域在整张图中的坐标写进 config.py 方便现场调试。5.4 停车跨天、跨费率时段分钟数取整乱了现象22:30 进场次日 01:30 出场白天晚上单价不同。按总时长 3 小时算可能多收或少收。原因计费逻辑只算总时长与总金额没有把时长按费率时段切分。解决把费率时段定义成区间按分钟粒度拆开。比如 22:00-06:00 算夜间18:00-22:00 算白天先算出每个时段各有多少分钟分别套用单价再向上取整到小时。注意 22:00 进场和 06:00 出场两个边界分钟都要包含在夜间时段内用半开区间处理。def calc_period_fee(enter_dt, exit_dt, periods): minutes 0 while enter_dt exit_dt: period get_period(enter_dt) segment_end min(exit_dt, next_period_start(enter_dt)) minutes (segment_end - enter_dt).total_seconds() / 60 enter_dt segment_end return round(minutes / 60 * rate, 2)这是简化版每段时长不会超过 6 小时仍要逐段向上取整还是整体取整每个场地规则不同必须在费率表里注明“按每段不足一小时按一小时收取”。5.5 同一辆车一进一出数据库事务没处理好导致多扣费现象出场时识别触发两次或缴费接口被重试结算表插了两条金额车主被重复扣款。原因结算流程没有做幂等控制两次请求之间没有事务锁。解决给 in_records 增加一个 settled 状态结算时把“查询入场记录、检查未结算、插入结算记录、更新状态”放在一个事务里。SQLite 的默认事务隔离级别下用BEGIN IMMEDIATE再执行查询避免并发写。代码里最简单的是在 insert_settlement 前先查一次是否已有该 record_id 的结算记录有就返回已有金额。SELECT amount_due FROM settlements WHERE record_id ?;如果已经存在直接把这个金额返回给前端不重复插入。5.6 摄像头夜间补光太强车牌区域过曝现象白天识别率 98%晚上降到 60%车牌区域一片白。原因补光灯方向直射车牌或相机曝光参数固定夜间车牌反射反光导致过曝。解决调低补光亮度把相机曝光模式改成区域测光锁定在 ROI 区域。如果场地是双向车道还要区分正向和反向车牌反向车牌的补光角度会不同。夜间识别率提升最直接的办法不是换模型而是调相机。调试时在夜间抓几张原图看车牌区域是否可见纹理如果白成一片说明过曝如果黑成一片说明欠曝。把这个过程写进使用说明使用者会少走很多弯路。6. 从命令行到 HTTP 接口进阶用法与验证车牌计费系统的准确性命令行跑通只是第一步真正部署时通常要把计费能力做成接口让岗亭客户端、收费员平板、甚至是公众号查询都能调用。用 Flask 包一层 HTTP 是成本最低的进阶方案源码里加一个 server.py把 process_entry 和 process_exit 暴露成 POST 接口。from flask import Flask, request, jsonify from main import process_entry, process_exit app Flask(__name__) app.route(/api/entry, methods[POST]) def api_entry(): data request.get_json() process_entry(data[plate], data[image_path], data.get(entrance, A1)) return jsonify({status: ok}) app.route(/api/exit, methods[POST]) def api_exit(): data request.get_json() amount process_exit(data[plate], data.get(image_path, )) return jsonify({status: ok, amount: amount})接口返回尽量简单status 和 amount 就够前端拿到 amount 展示给收费员。如果要做真实线上系统还要加签名校验和请求超时处理停车场内部网络一般不会有高并发但出入口同时有车时瞬时 QPS 也会到 10 以上Flask 默认的配置够用不需要一上来就上异步框架。验证系统准确性有个笨但有效的办法用一段录制好的 10 分钟视频反复回放视频里包含正常车牌、低速通过、夜间场景跑完后把识别出的车牌、置信度、计算出的金额和人工标注表对比。重点关注三件事识别准确率、计费金额平均绝对误差、无入场记录比例。这三个指标就是这套系统能不能上线试运行的门槛。我吃过一次亏当时为了赶进度直接用本地时间字符串做时长比较结果跨天跨月全是坑计费金额差了几十块半夜被队长打电话叫起来处理。后来把所有时间统一成 datetime 对象再运算按分钟取整一个月没再出现账目对不上。车牌识别计费系统的成败不在模型多高级而在边界条件是否都考虑到了。希望帮到你按这套思路跑通再根据自己场地微调就能少走我走过的弯路。本文还有配套的精品资源点击获取