
简介这是一套面向Python初学者与计算机专业学生的智能停车场车牌识别计费系统完整源码基于百度AI开放平台图片识别接口实现车牌自动识别、车辆出入场判断、收入统计与车位满预警等核心业务适合用作课程设计、毕业设计或Python桌面应用练手项目。资源包共2003个文件以1777个py源码文件为主体辅以pyc编译文件、txt配置说明、h与c底层依赖、xml与json配置、html/css/js前端页面及pdf、doc使用文档压缩包约189.59MB目录结构完整可直接在PyCharm中运行调试。目前已有247人学习下载。读者可获得可运行的项目源码、详细程序使用说明文档以及百度AI Key申请与替换的配置指引同时能参考收入柱状图统计、满预警提示等模块的实现思路快速理解车牌识别与计费系统的整体架构与开发流程。1. 从一张出口抓拍图说起这套 Python 车牌识别计费系统到底能跑通什么很多做停车场项目的同行都遇到过这种场景甲方给一张出口相机抓拍的 JPG让你当场算清楚这辆车该收多少钱。人工比对车牌、查入场时间、套费率三分钟起步高峰期直接堵死。这套 Python 实现的车牌识别计费系统解决的正是这个闭环——从一张车辆图片出发自动定位车牌、识别字符、匹配入场记录、按规则算出费用。它适合两类人一是做智慧停车、门禁道闸的嵌入式或后端工程师想拿一套能读、能改、能接自己相机的参考实现二是学 Python 想找一个「有真实业务逻辑」的练手项目的人而不是再写一遍学生管理系统。源码包里带了使用说明意味着作者至少把环境依赖和启动方式交代清楚了这对复现来说比一堆散装脚本强太多。下面我按「识别怎么落地 → 计费怎么串起来 → 哪里会翻车」的顺序拆开讲。2. 车牌定位与字符识别OpenCV 预处理加 OCR 的完整链路2.1 为什么是「颜色分割 形态学」而不是直接上深度学习拿到一张抓拍图第一步不是识别是找到车牌在哪。常见做法有两类一类是 YOLO 这类目标检测模型直接回归车牌框另一类是传统图像处理——转 HSV、按蓝/绿/黄底色做颜色掩膜、形态学闭运算连成矩形、再按长宽比筛候选框。这套源码走的是后者原因很实际停车场相机机位固定、光照相对可控颜色分割的算力开销几乎为零树莓派或低配工控机都能实时跑而训练一个鲁棒的车牌检测模型标注成本和调参周期对一个小项目来说不划算。我一般会先确认源码里颜色阈值是不是写死的写死就意味着换个停车场、换批相机大概率要重调。import cv2 import numpy as np def locate_plate(image_path): img cv2.imread(image_path) # 转 HSV颜色分割比 RGB 更抗光照变化 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 蓝色车牌的经验阈值lower/upper 需按实际相机微调 lower np.array([100, 80, 80]) upper np.array([124, 255, 255]) mask cv2.inRange(hsv, lower, upper) # 闭运算把断裂的字符区域连成整块 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for c in contours: x, y, w, h cv2.boundingRect(c) ratio w / float(h) # 国内单层蓝牌长宽比大致在 2.5~5.5 之间 if 2.5 ratio 5.5 and w 80: candidates.append((x, y, w, h)) return img, candidates这段代码的逻辑是「先粗筛再精修」。inRange把蓝色区域抠出来morphologyEx用宽扁的核把车牌上被字符割裂的区域粘回一整块findContours找外轮廓后用长宽比过滤掉广告牌、车身反光这些干扰。参数上最敏感的是 HSV 的 lower/upper 和形态学核尺寸阈值偏窄会漏掉偏色车牌偏宽会把蓝色车身也算进来核太大会把相邻两块车牌粘一起太小则连不成矩形。失败时先看 mask 图——如果车牌区域根本没白就是颜色阈值问题如果白了但轮廓碎就是核尺寸问题。2.2 字符分割与 OCRTesseract 的配置和它的脾气定位到车牌框后裁剪出来做二值化再交给 OCR。源码里大概率用的是 Tesseract因为它免费、有 Python 封装pytesseract中文车牌识别需要指定--psm和字符白名单。这里有个血泪经验Tesseract 对中文车牌的「省份汉字 字母数字」混合串识别率很看预处理直接喂原图基本是玄学必须先灰度、二值化、去噪、必要时做倾斜校正。import cv2 import pytesseract def recognize_plate(plate_img): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 自适应阈值比固定阈值更抗局部阴影 binary cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # 只允许车牌可能出现的字符减少误识别 config --psm 7 -c tessedit_char_whitelist \ 京沪粤苏浙鲁川渝ABCDEFGHJKLMNPQRSTUVWXYZ0123456789 text pytesseract.image_to_string(binary, configconfig) return text.strip().replace( , )--psm 7表示把输入当成单行文本这对裁剪后的车牌条最合适白名单把易混的I/O和汉字范围收窄能明显压低乱码率。参数上adaptiveThreshold的 blockSize 必须是奇数11 是常用起点光照均匀的图可以调到 15 让结果更平滑。如果识别结果里频繁出现0和O互换那是字体本身的问题得在业务层做映射纠正别指望 OCR 一步到位。2.3 把识别结果落成结构化数据识别出字符串只是中间态真正要进计费的是「车牌号 时间戳 动作类型」。我一般会在这一层加一个校验车牌长度、汉字位置、字母数字分布是否符合规则不符合的直接打回重识别或标记人工复核。这一步不做后面计费就会拿一个错牌号去查记录查不到还算好的查到别人的车就是事故。import re from datetime import datetime PLATE_PATTERN re.compile( r^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼] r[A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$ ) def build_record(plate_text, actionentry): if not PLATE_PATTERN.match(plate_text): return {valid: False, raw: plate_text} return { valid: True, plate: plate_text, action: action, timestamp: datetime.now().strftime(%Y-%m-%d %H:%M:%S) }正则里排除了I和O因为国内车牌规范里不用这两个字母这能挡掉一批 OCR 误识。action区分入场和出场是后面计费匹配的关键字段。到这里识别链路就算闭环了接下来看计费怎么接。3. 计费逻辑与数据存储从入场记录到出场结算的串联3.1 计费模型按时长、分时段还是封顶停车计费看着简单实际规则能绕死人。常见的有三种纯时长计费每小时 X 元、分时段计费白天贵晚上便宜、含免费时长和封顶价。这套源码作为教学实现通常会给一个可配置的费率表而不是把公式写死。我建议复现时先确认费率是硬编码还是从配置读——硬编码的改起来要动源码配置化的才能直接对接不同停车场。参数含义典型值调整影响free_minutes免费时长15调大减少短停收费纠纷rate_per_hour每小时费率5直接影响营收daily_cap单日封顶40防止天价账单night_rate夜间费率3分时段场景才生效这张表是我从常见实现里归纳的字段具体命名以源码为准。关键是理解每个参数在公式里的位置费用 min(ceil((出场-入场-free)/60) * rate, cap)其中ceil向上取整是行业惯例停 61 分钟按 2 小时算。3.2 用 SQLite 存进出记录并做匹配教学项目用 SQLite 最合适零配置、单文件、Python 标准库直接支持。核心是一张记录表入场插一条出场时按车牌查最近一条未结算的入场记录。import sqlite3 def init_db(pathparking.db): conn sqlite3.connect(path) conn.execute(CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate TEXT NOT NULL, entry_time TEXT, exit_time TEXT, fee REAL DEFAULT 0, settled INTEGER DEFAULT 0 )) # 车牌加索引出场查询才快 conn.execute(CREATE INDEX IF NOT EXISTS idx_plate ON records(plate)) conn.commit() return conn def on_exit(conn, plate, exit_time, calc_fee): cur conn.execute( SELECT id, entry_time FROM records WHERE plate? AND settled0 ORDER BY entry_time DESC LIMIT 1, (plate,)) row cur.fetchone() if not row: return None # 无入场记录需人工处理 fee calc_fee(row[1], exit_time) conn.execute(UPDATE records SET exit_time?, fee?, settled1 WHERE id?, (exit_time, fee, row[0])) conn.commit() return feesettled字段是防重复结算的关键没有它同一辆车出场两次会扣两次费。ORDER BY entry_time DESC LIMIT 1保证匹配最近一次入场避免历史记录干扰。索引加在plate上是因为出场查询是高频操作数据量上千条后没索引会明显变慢。失败排查时先看settled状态再看入场记录是否存在——「查无入场」是停车场系统最常见的异常通常是入场相机漏拍或识别错误导致。3.3 把识别和计费串成一条命令单张图片的完整流程应该是读图 → 定位 → 识别 → 校验 → 按动作类型入库或结算。我一般会写一个入口函数把这条链路串起来方便测试也方便接相机。def process_image(image_path, action, conn): img, candidates locate_plate(image_path) if not candidates: return {error: 未检测到车牌} x, y, w, h max(candidates, keylambda b: b[2] * b[3]) plate_img img[y:yh, x:xw] plate_text recognize_plate(plate_img) record build_record(plate_text, action) if not record[valid]: return {error: 车牌校验失败, raw: plate_text} if action entry: conn.execute(INSERT INTO records (plate, entry_time) VALUES (?, ?), (record[plate], record[timestamp])) conn.commit() return {plate: record[plate], action: entry} return {plate: record[plate], fee: on_exit(conn, record[plate], record[timestamp], calc_fee)}max(candidates, key...)取面积最大的候选框是因为车牌通常是画面里最显著的蓝色矩形但如果有多个候选这个策略会失效得靠位置先验车牌一般在画面下半部辅助。calc_fee是外部传入的计费函数这样费率逻辑和流程解耦换计费规则不用动主流程。4. 避坑与排查这套源码跑起来最容易翻车的五个地方4.1 现象识别结果全是乱码一个车牌都对不上原因通常是 Tesseract 没装中文语言包或者pytesseract找不到可执行文件路径。解决确认系统里装了tesseract-ocr和tesseract-ocr-chi-simWindows 下还要在代码里显式指定pytesseract.pytesseract.tesseract_cmd的绝对路径。这一步不解决后面所有逻辑都是空转。4.2 现象车牌定位框飘忽同一张图跑两次结果不一样原因是颜色阈值卡在边界上或者形态学核尺寸和图像分辨率不匹配。解决把 mask 中间结果存图看一眼确认车牌区域是否稳定为白色连通块如果边缘毛刺多先做一次高斯模糊再分割。分辨率差异大的图核尺寸要按图像宽度等比缩放别写死 17。4.3 现象出场结算时报「无入场记录」原因是入场时识别错了牌号或者入场记录被settled标记污染。解决先查数据库里该车牌的所有记录看是否有未结算的入场再核对入场图片的识别结果。工程上一般会加一个「模糊匹配」兜底——牌号只差一位时提示人工确认而不是直接拒绝。4.4 现象费用算出来是负数或大得离谱原因是时间格式解析失败entry_time和exit_time字符串比较或相减时出错。解决统一用datetime.strptime解析成对象再算差值别直接拿字符串做减法。跨天、跨月的时间差尤其要测这是计费逻辑最容易埋雷的地方。4.5 现象程序跑一会儿就卡死或内存涨原因是 OpenCV 的imread返回None时没判空后续操作直接抛异常或者循环里反复创建大数组没释放。解决每次读图后先if img is None: continue批量处理时用生成器逐张读别一次性全load进内存。5. 进阶技巧把识别准确率和计费健壮性再抬一档如果你已经跑通了基础流程想让这套系统更接近能上线的状态我建议从两个方向下手。第一是识别层加「多帧投票」停车场出口相机通常连拍同一辆车有多张图把每张图的识别结果收集起来取出现次数最多的牌号作为最终结果能显著压掉单帧误识。实现上就是维护一个Counter超过阈值才落库。from collections import Counter def vote_plate(results, min_votes2): counter Counter(r for r in results if r) if not counter: return None plate, count counter.most_common(1)[0] return plate if count min_votes else Nonemin_votes设 2 意味着至少两帧识别一致才采信单帧场景下这个函数会返回None所以要和单帧逻辑做兼容。第二是计费层加「幂等保护」出场结算用数据库事务包起来先SELECT确认未结算再UPDATE中间加行锁防止并发请求把同一辆车结算两次。SQLite 可以用BEGIN IMMEDIATE开启写事务。优化点手段收益代价识别准确率多帧投票误识率明显下降需要连拍图源结算安全事务 行锁杜绝重复扣费略增复杂度异常兜底模糊匹配 人工复核减少客诉需人工介入通道还有个容易被忽略的点把每次识别的原图、mask 图、识别结果、最终费用都落一份日志。上线后出问题这份日志就是唯一的后悔药——你能回放当时到底哪一步错了而不是靠猜。我一般会在process_image里加一个log_dir参数把中间产物按时间戳存下来跑一周就能看出哪些场景是薄弱环节。从那以后我每次接这类项目都强制先跑一遍「异常图集」——夜间逆光、车牌污损、角度倾斜各来几张确认系统在这些边界上的表现再谈上线。这套源码作为起点是够用的但真正决定它能不能扛住真实停车场的是你在这几个边界上补了多少。希望帮到你。本文还有配套的精品资源点击获取