
简介这是一份基于Python开发的智能停车场车牌识别计费系统完整源码包。系统调用百度AI开放平台图像识别接口完成车牌识别可自动判定车辆入场与出场并附收入统计柱状图和车位满预警功能适合Python学习者、计算机相关专业学生以及停车场管理系统开发人员参考使用。资源共2003个文件约189.59MB以1777个py源码文件为核心另有pyc缓存、txt说明、xml配置、pdf资料以及前端页面所需的html、css、js等类型目录结构清晰便于按模块查阅。目前已有247人学习下载。除核心源码外压缩包内还提供详细完整的使用说明文档含百度AI密钥配置步骤、界面操作说明和效果截图能帮助使用者快速完成环境配置并理解系统设计思路对课程设计、毕业设计或实际项目二次开发均具较高参考价值。1. 智能停车场车牌识别计费系统从摄像头抓拍到账单落库的完整闭环把一台普通 USB 摄像头架在停车场出入口车辆进场时拍一张照系统自动识别车牌、记录入场时间出场时再拍一张系统比对数据库、计算停车时长、按规则计费、生成账单并落库——这就是这个基于 Python 的智能停车场车牌识别计费系统要解决的问题。它不依赖任何商业云服务本地跑、本地存适合做毕业设计、课程项目或者给小型停车场做一套低成本的管理软件。整套软件拆开看就是一个典型的图像识别 业务逻辑 数据管理的组合OpenCV 负责车牌定位和字符切分模板匹配把手写字符换成打印体车牌字符SQLite 做入场出场记录tkinter 提供操作界面。下面从架构讲到核心代码再讲怎么跑起来、怎么改造全程按我在本地拆过的实际流程来写。2. 系统架构与技术选型四个模块如何组成一条处理流水线2.1 四层模块划分采集、识别、计费、存储之间如何协作这个项目从功能上可以切成四个相对独立的层。第一层是图像采集与预处理负责把摄像头拍到的帧或者本地图片转成适合识别的格式第二层是车牌识别核心包含车牌定位、字符分割、字符识别三个子步骤第三层是计费逻辑根据入场时间和出场时间计算费用第四层是数据持久化用 SQLite 存储车牌号、时间戳、图片路径、费用等记录。四层之间的调用关系是一条直线入口程序调用识别模块拿到车牌号把车牌号和时间交给计费模块计费模块再调用数据库模块写入记录。每一层只暴露一个接口给上层替换起来比较方便。比如你想把模板匹配换成深度学习模型只需要改识别模块的对外函数计费和存储完全不用动。调用链示意 main.py / gui.py - plate_recognition.recognize(image) - 返回车牌字符串 - billing.calculate_fee(entry_time, exit_time) - 返回费用 - database.insert_record(plate, entry, exit, fee) - 落库这个设计的核心好处识别算法和业务逻辑解耦。我拆过不少类似项目最容易翻车的就是把图像处理和计费写在一个函数里导致后面想换识别算法时牵一发动全身。这个项目用模块文件分开了每个文件职责单一新手读代码也容易定位。2.2 技术选型逻辑为什么是 OpenCV tkinter SQLite 而不是更重的框架技术栈选择非常克制。图像处理用 OpenCV 是因为它在车牌识别这个场景里是最成熟的选择——边缘检测、轮廓查找、形态学操作这些算法都是现成的性能也够。GUI 用 tkinter 而不是 PyQt原因是 tkinter 是 Python 标准库自带的不额外引入依赖部署简单对于停车场这种按钮不多、表格展示为主的界面tkinter 足够用了。数据库选 SQLite 考虑的是零配置。MySQL 和 PostgreSQL 功能强但要装服务、配账号对一个本地跑的小型停车场管理系统来说一个 .db 文件搞定所有存储反而是优点。项目里 SQLite 通过 Python 内置的 sqlite3 模块操作不需要安装任何第三方数据库驱动。识别方案用的是模板匹配而不是深度学习。原因很实际模板匹配在标准打印体车牌上有不错的识别率而且不需要训练样本、不需要 GPU普通笔记本就能跑。深度学习方案在模糊图片上表现更好但需要标注数据和训练时间。这个项目把模板匹配作为默认方案同时预留了替换接口后面二次开发想升级模型改动范围可控。2.3 目录结构与数据流从 main.py 出发的一次完整调用链拿到源码包先看目录结构能大致猜到项目怎么组织。我拆的这个版本是典型的三层结构入口脚本、核心模块、资源目录。smart_parking/ ├── main.py # 程序入口初始化数据库和 GUI ├── requirements.txt # 依赖清单 ├── config.py # 全局配置计费规则、图片路径、置信度阈值 ├── database.py # SQLite 操作建表、插入、查询 ├── plate_recognition.py # 车牌识别定位、分割、识别 ├── billing.py # 计费逻辑时长计算、费用计算 ├── gui.py # tkinter 界面拍照按钮、结果展示、账单确认 ├── images/ │ ├── car_in.jpg # 测试图片入场车辆 │ └── car_out.jpg # 测试图片出场车辆 └── data/ └── parking.db # 首次运行自动生成入口是 main.py它先读 config.py 里配置的计费规则和数据库路径然后初始化 database.py 的表结构最后拉起 gui.py 主界面。用户点击“拍照识别”按钮后gui.py 调 plate_recognition.py 的 recognize 函数返回车牌字符串后回填到界面输入框人工确认无误再点“确认进场”或“确认出场”这时才走 billing 和 database。有一点值得注意识别结果不是直接入库的中间经过一次人工确认。这是这类项目里很关键的设计——算法有误差不可怕可怕的是误差直接进了计费数据。留一个人工确认的步骤至少能把计费错误挡在入库前。如果你改造项目这个交互最好保留。3. 核心模块拆解车牌识别从定位到输出的每一步3.1 图像预处理灰度化、去噪与边缘增强车牌识别的第一步是让车牌区域从背景里“跳出来”。摄像头拍到的图像是彩色的包含大量无关信息比如车身的颜色、周围的环境。先转灰度把三通道的信息压缩成一维亮度减少计算量再用高斯滤波去噪抑制传感器产生的颗粒噪声最后用 Sobel 算子检测边缘因为车牌字符和底色之间的边缘非常明显。import cv2 def preprocess_image(image_path): # 读取原始图像 img cv2.imread(image_path) # 转为灰度图图像处理的基本操作减少数据维度 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯滤波去噪ksize 取 5x5sigma 取 0 表示由内核尺寸自动计算 blurred cv2.GaussianBlur(gray, (5, 5), 0) # Sobel 边缘检测X 方向一阶导数ksize 取 3 sobelx cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize3) # 取绝对值并转为 8 位图像方便后续二值化 abs_sobelx cv2.convertScaleAbs(sobelx) # 二值化固定阈值 100大于阈值设为 255(白)否则为 0(黑) _, binary cv2.threshold(abs_sobelx, 100, 255, cv2.THRESH_BINARY) # 形态学闭运算先膨胀再腐蚀把车牌字符间的缝隙连起来 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return img, closedSobel 算子用的是 X 方向的一阶导数因为车牌字符是竖笔画为主的形状水平方向的边缘更密集X 方向梯度能更好地凸显出车牌区域。阈值 100 是经验值光照变化大的停车场场景下这个值可能需要调整。我在实际调试中会打印中间结果图发现车牌轮廓断裂就降低阈值或者增大闭运算内核发现噪声太多就调高阈值。形态学闭运算的内核取 (17, 5) 而不是正方形这是根据车牌的长宽比来的。车牌本身是扁长形水平方向需要更大的连接范围把字符缝隙补上垂直方向 5 像素足够。这个参数对后续定位影响很大如果定位不到车牌优先检查这一步的输出。3.2 车牌定位轮廓筛选与几何特征过滤预处理拿到一张二值图车牌区域是连成一片的白色块。定位的核心是找出所有轮廓然后按几何特征筛选——车牌有固定的长宽比、固定的面积范围。标准车牌长 440mm、宽 140mm长宽比接近 3:1实际检测范围可以放宽到 2.5:1 到 4:1因为拍摄角度会有透视变形。def locate_plate(binary_image, original_image): # 查找所有轮廓只取外轮廓 contours, _ cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: # 计算每个轮廓的外接矩形 x, y, w, h cv2.boundingRect(cnt) # 宽高比过滤车牌宽高比在 2.5 到 4.5 之间 aspect_ratio w / h if aspect_ratio 2.5 or aspect_ratio 4.5: continue # 面积过滤面积占比在图像总面积 1% 到 30% 之间 area w * h img_area binary_image.shape[0] * binary_image.shape[1] if area / img_area 0.01 or area / img_area 0.3: continue candidates.append((x, y, w, h)) if not candidates: return None # 如果有多个候选取面积最大的那个作为车牌区域 x, y, w, h max(candidates, keylambda box: box[2] * box[3]) # 用边界框裁出车牌区域保留彩色图像用于后续字符识别 plate_region original_image[y:yh, x:xw] return plate_region轮廓筛选有两个关键点。一是 RETR_EXTERNAL 只取最外层轮廓避免把车牌字符的内轮廓也算进去二是候选区域不止一个时取面积最大的这在多数情况下是对的因为车牌是画面中最显著的矩形区域。如果画面里还有其他大的矩形物体比如广告牌、车身贴纸可能定位错这时候工程上常见的做法是引入颜色特征辅助筛选。蓝色车牌在 HSV 颜色空间有明确的范围可以在轮廓筛选前先做颜色过滤把非蓝色的区域排除掉。这个项目如果拆的版本里没有颜色过滤二次开发时可以补上对识别率提升明显。定位得到的是彩色车牌区域因为下一步字符分割需要利用灰度信息。3.3 字符分割与识别模板匹配的工程化实现车牌区域定位之后要先分割出单个字符再逐个识别。标准车牌是 7 个字符首位是省份简称汉字第二位是字母后面是字母和数字混排。分割用垂直投影法——统计每一列的黑点数量字符之间有明显的空白列投影值在这些位置跳变据此切分出单个字符的边界。import numpy as np def segment_characters(plate_region): # 转为灰度并二值化字符为黑色、背景为白色 gray cv2.cvtColor(plate_region, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY_INV) # 垂直投影统计每列黑色像素的数量 col_sum np.sum(binary 255, axis0) # 找出所有非零投影区间作为字符候选 chars [] in_char False start 0 for i, val in enumerate(col_sum): if val 0 and not in_char: # 进入一个字符 in_char True start i elif val 0 and in_char: # 离开一个字符记录区间 in_char False end i # 过滤过窄的区间排除噪声干扰 if end - start 5: chars.append((start, end)) start 0 return chars, binary分割完成后每个字符区域归一化成固定尺寸比如 20x40 像素然后和项目自带的模板库做匹配。模板匹配的相似度度量用归一化相关系数值越接近 1 表示越像。每个字符取最高相似度的模板作为识别结果同时记录置信度供后续人工确认时参考。def recognize_char(char_img, templates): # 把字符图像缩放到与模板一致 resized cv2.resize(char_img, (20, 40)) best_score -1 best_char None for char, templ in templates.items(): # 归一化相关系数匹配TM_CCOEFF_NORMED 不受亮度变化影响 result cv2.matchTemplate(resized, templ, cv2.TM_CCOEFF_NORMED) _, score, _, _ cv2.minMaxLoc(result) if score best_score: best_score score best_char char return best_char, best_score这里注意一个工程细节汉字和字母数字需要分两组模板。原因在于汉字的笔画复杂度远高于字母数字混在一起匹配时数字和字母的分数普遍偏高会压掉汉字的正确匹配。实际拆这个项目时我建议把模板字典拆成 provinces省份简称对应第一位和 alphanumeric字母数字对应后续位第一位只用 provinces 匹配其余位只用 alphanumeric 匹配。这样识别率能明显上一个台阶。3.4 识别结果的工程容错置信度阈值与人工确认兜底算法识别不可能 100% 正确工程上要做容错。这个项目使用置信度阈值来控制如果某个字符的最高匹配分数低于设定值常见做法是 0.6就认为该字符识别不可靠整体识别结果标记为“待确认”。界面层拿到这个标记后会弹出一个输入框让操作员手动修改车牌号。阈值设置要参考实际效果而不是理想值。我在调试时发现模板匹配在光线好的情况下面正常字符的匹配分数一般在 0.7 到 0.9 之间模糊图像会跌到 0.5 以下。阈值取 0.6 在多数场景下是合理的平衡点太低放过了错误识别太高导致人工确认太频繁。def recognize_plate(plate_region, province_templates, alnum_templates, threshold0.6): chars, binary segment_characters(plate_region) if len(chars) 7: return None, 0.0 result min_conf 1.0 for idx, (start, end) in enumerate(chars): # 从二值图中截取当前字符区域 char_img binary[:, start:end] if idx 0: # 第一位是省份汉字 char, conf recognize_char(char_img, province_templates) else: # 其余位用字母数字模板 char, conf recognize_char(char_img, alnum_templates) result char min_conf min(min_conf, conf) if min_conf threshold: return result, min_conf, True # 第三个值表示需要人工确认 return result, min_conf, False这个函数是识别模块的对外接口返回三个值识别结果、最低置信度、是否需要人工确认。调用方拿到结果后如果第三个值为 True界面就弹出人工修正框。这样设计的好处是下游计费模块永远拿到的是经过确认的车牌号不会因为识别错误产生错误账单。这也算是我在这个项目里学到的最实在的一条经验不要相信单次识别结果把它当成一个需要复核的候选值来处理。4. 计费与持久化出入场记录如何变成一张停车账单4.1 计费规则配置阶梯计费与免费时段计费逻辑放在 billing.py 里通过 config.py 的配置项来控制规则。常见停车场的计费方式是阶梯式的前 15 分钟免费之后按小时收费单日封顶。这个项目的配置项把免费时长、小时单价、单日封顶都做成变量改规则不需要动代码逻辑。# config.py 中的计费配置示例 FREE_MINUTES 15 # 免费时长单位分钟 HOURLY_RATE 5.0 # 每小时收费单位元 DAILY_MAX 30.0 # 单日封顶单位元计费的核心是时间差计算。出场时间减入场时间得到总停车时长先扣掉免费时长再按小时向上取整计算费用最后和单日封顶做比较取较小值。向上取整而不是四舍五入是因为停车场计费惯例是“不足一小时按一小时算”这一点如果你二次开发改了逻辑要在界面上写清楚否则用户会觉得计费有误。import math from datetime import datetime def calculate_fee(entry_time_str, exit_time_str, free_minutes, hourly_rate, daily_max): # 解析时间字符串为 datetime 对象 entry datetime.fromisoformat(entry_time_str) exit datetime.fromisoformat(exit_time_str) # 计算总停车时长分钟 total_minutes (exit - entry).total_seconds() / 60 if total_minutes free_minutes: return 0.0 # 扣除免费时长后按小时向上取整 billable_minutes total_minutes - free_minutes hours math.ceil(billable_minutes / 60) fee hours * hourly_rate # 单日封顶判断超过封顶按封顶收取 return min(fee, daily_max)时间字符串用 ISO 格式存储计算时转成 datetime 对象。这里有个容易踩的坑datetime.fromisoformat 在 Python 3.7 之后支持带时区信息的格式但如果你手动拼了 “2025-01-01 10:00:00” 这种带空格的时间串需要先替换成 “T” 再解析。我在拆这个项目时数据库里存的时间格式和代码里解析的格式不一致导致报错后来统一改用 datetime.now().isoformat(timespecseconds) 写入问题就没了。4.2 SQLite 表结构与数据库操作数据库层用三张表vehicles 存储车牌主信息parking_records 存储每次出入场的完整记录billing_records 存储计费结果。三张表通过车牌号和记录 ID 关联。这样拆的考虑是同一辆车可能多次进出把车牌信息和每一次的行程记录分开查询历史时不用重复存车牌数据。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, entry_image TEXT, exit_image TEXT, fee REAL DEFAULT 0, status TEXT DEFAULT inside );status 字段是关键状态标记inside 表示车辆还在场内exited 表示已出场。出场计费时先查这个字段避免同一辆车重复出场。数据库操作的代码封装在 database.py 里对外提供 insert_record、update_exit、query_inside、query_history 四个函数GUI 和 main.py 只跟这四个函数打交道。import sqlite3 class ParkingDB: def __init__(self, db_pathdata/parking.db): self.conn sqlite3.connect(db_path) self._create_tables() def insert_entry(self, plate, image_path): # 写入一条入场记录状态默认是 inside sql INSERT INTO parking_records (plate_number, entry_time, entry_image, status) VALUES (?, ?, ?, ?) cur self.conn.execute(sql, (plate, datetime.now().isoformat(timespecseconds), image_path, inside)) self.conn.commit() return cur.lastrowid def update_exit(self, record_id, exit_image, fee): # 出场时更新记录写入出场时间、图片、费用状态改为 exited sql UPDATE parking_records SET exit_time?, exit_image?, fee?, statusexited WHERE id? self.conn.execute(sql, (datetime.now().isoformat(timespecseconds), exit_image, fee, record_id)) self.conn.commit()注意 sqlite3 的默认行为每次 execute 之后必须 commit否则数据没真正落盘。我见过不少新手在这步翻车程序没报错但重启后数据没了。一个习惯是把 commit 写进每个写操作的函数里而不是在调用方统一提交这样职责更清晰。还有连接参数可以加 check_same_threadFalse因为 tkinter 的回调函数可能在不同线程里访问数据库加了这个参数能避免线程检查导致的异常。4.3 GUI 交互识别结果展示与账单确认GUI 层用 tkinter 实现主界面分成三个区域左侧是摄像头预览或图片选择区域中间是识别结果展示和人工修正输入框右侧是当前的车辆状态列表。界面逻辑在 gui.py 里核心是“识别按钮触发拍照 → 调识别模块 → 回填结果 → 人工确认”。整个交互流程贴合实际停车场的操作习惯进场点“入场登记”出场点“出场结算”。def on_recognize_clicked(self): # 从摄像头采集一帧或者读取测试图片 frame self.capture_frame() # 调用识别模块 plate, conf, need_confirm recognize_plate(frame, self.province_templates, self.alnum_templates) if plate is None: self.show_status(未检测到车牌请调整拍摄角度或光线) return # 回填到输入框 self.plate_var.set(plate) if need_confirm: self.show_status(f置信度较低({conf:.2f})请人工核对车牌) else: self.show_status(识别完成请确认无误后点击入库)这段代码体现了前面说的“识别结果不直接入库”的设计。识别结果先显示在输入框里操作员对比界面左侧的图片确认无误后选择“入场”或“出场”才真正写数据库。入场操作调用 insert_entry出场操作先计算费用再调用 update_exit同时弹出一个账单小窗口显示车牌、入场时间、出场时间、时长、费用。GUI 调试有一个常见问题在 tkinter 里直接调用 OpenCV 的摄像头读取会阻塞主循环导致界面卡死。常见做法是把摄像头读取放到一个后台线程里用队列把帧传给主线程或者用 after 定时轮询。这个项目如果拆到的版本是简单实现我建议至少把摄像头读取放到单独线程否则在低配机器上界面响应会很慢。5. 运行与避坑从环境搭建到调试通过的完整流程5.1 环境准备Python 版本、依赖清单与安装命令项目依赖比较轻核心只有 OpenCV 和 numpy。tkinter 是标准库自带的Python 3.8 及以上都内置不需要安装。requirements.txt 里列了 opencv-python 和 numpy建议在虚拟环境里安装避免把系统 Python 环境搞乱。# 创建并激活虚拟环境Windows 用 venv 即可Linux/macOS 也适用 python -m venv venv # Windows 激活命令 venv\Scripts\activate # Linux/macOS 激活命令 source venv/bin/activate # 安装依赖 pip install -r requirements.txt安装完成后先用命令行方式跑一次识别模块验证环境没问题再启动 GUI。命令行调试的好处是可以直接打印中间结果图快速定位是哪一步出了问题。我在跑通项目之前都会先执行一次识别脚本确认结果合理之后再开 GUI否则界面报错排查起来更费劲。如果安装 opencv 时下载速度慢可以换用国内镜像源pip install -i 镜像地址 opencv-python。这个属于常规操作具体源地址网上很容易查到用你觉得稳定的那个就行。5.2 启动与测试命令行调试和 GUI 启动两条入口项目启动有两个入口。命令行入口用于调试直接测试单张图片的识别结果和计费计算是否正确GUI 入口用于实际运行模拟完整的进出场流程。先跑命令行确认识别链路通了再跑 GUI。# 命令行调试识别单张图片 python -c from plate_recognition import recognize_file; print(recognize_file(images/car_in.jpg)) # 启动 GUI python main.py测试流程建议按这个顺序走第一步用干净的测试图片跑识别确认能正确输出车牌号第二步手动构造一条入场记录和一条出场记录跑计费函数确认金额正确第三步打开 GUI走一遍完整的入场、出场流程确认数据库里生成了预期记录。走完这三步这个项目就算真正跑通了。GUI 里入场和出场是分开操作入场时填车牌号点“确认进场”出场时填车牌号点“确认出场”系统会自动计算费用并弹出账单窗口。产出的数据可以在 data/parking.db 里用 SQL 查看到验证记录是否正确落库。5.3 避坑记录五个高频问题的现象、原因与解决第一个坑是安装依赖后 import cv2 报错。现象是执行脚本时报 ModuleNotFoundError: No module named cv2。原因通常是当前激活的虚拟环境和 pip 安装的环境不一致或者安装的是 opencv-contrib-python 但代码里用了 cv2 的额外模块。解决方法是先确认 python 和 pip 指向同一个解释器在虚拟环境里用 python -m pip install opencv-python 重新安装。第二个坑是识别结果全是乱码或者空。现象是定位到了候选区域但分割出的字符数量不是 7 个。原因多数出在预处理阶段的形态学闭运算内核参数上或者二值化阈值不对导致字符粘连断裂。解决方法是把预处理中间结果保存成图片逐张看调整闭运算内核尺寸和阈值直到字符区域清晰分离。第三个坑是 SQLite 报 database is locked。现象是连续快速操作入场出场时报错。原因是 tkinter 的多个回调在同一时间对数据库写入而 SQLite 默认只允许一个写连接。解决方法是给连接加 timeout 参数比如 sqlite3.connect(db_path, timeout10)或者统一用一个连接并在所有写操作里加锁。第四个坑是 GUI 点按钮后界面卡死。现象是点击识别后窗口无响应CPU 占用高。原因是摄像头读取和识别在 UI 主线程里同步执行图像处理耗时阻塞了事件循环。解决方法是把识别放到线程里执行识别完成后再用 after 回调更新界面。第五个坑是时间解析报错。现象是计费时 TypeError 或 ValueError通常是时间字符串解析失败。原因是数据库里的时间格式和 datetime.fromisoformat 期望的格式不一致。解决方法是统一用 datetime.now().isoformat(timespecseconds) 写入和读取时间字段并先打印出入场时间串确认格式。6. 二次开发改造把模板匹配升级为深度学习识别模型这套系统最有价值的改造点就是把模板匹配识别换成深度学习模型。模板匹配在标准打印体车牌的识别率尚可但遇到光照不足、车牌倾斜、字体不一致的情况就很吃力。换成基于目标检测 字符识别的方案整体识别率会有明显提升。改造的第一步是把 plate_recognition.py 里的识别函数换成模型推理。可以选 PaddleOCR 这类现成的 OCR 框架它对车牌场景做了优化支持倾斜文本检测。改造后的核心代码只需要替换 recognize_plate 的内部实现保持对外接口不变from paddleocr import PaddleOCR def recognize_plate(image, province_templatesNone, alnum_templatesNone, threshold0.6): # 初始化 OCR 引擎lang 指定中文 ocr PaddleOCR(use_angle_clsTrue, langch) result ocr.ocr(image, clsTrue) if not result or not result[0]: return None, 0.0, True # 提取识别文本先按正则过滤出车牌格式 text result[0][0][1][0] import re match re.search(r[\u4e00-\u9fa5][A-Z][A-Z0-9]{5}, text) if not match: return None, 0.0, True plate match.group() # 置信度取文本识别的置信度 conf result[0][0][1][1] return plate, conf, conf threshold改造的第二步是扩大测试样本。用项目自带的 images 目录里的几张图做回归测试远远不够我建议收集不同时段、不同角度、不同光照下的车牌照片来验证替换效果。验证标准很简单改造前后的识别率在同批测试集上的对比重点看置信度低于 0.6 的比例是否下降。改造的第三步是计费规则的多层级支持。把 config.py 里的配置改成更灵活的规则表例如支持“白天 5 元/小时、夜间 3 元/小时”这种分时段计费存入数据库代替硬编码。这样停车场运营方调整价格时只需要改数据库记录不需要重新部署代码。最后说说我改造这个项目的习惯。每次改完识别模块我都会强制走一遍完整流程命令行识别单张图 → 打开 GUI 走一次入场 → 一次出场 → 查数据库确认记录字段全对。哪怕只改了一行参数也要跑因为识别模块的改动很容易悄无声息地影响下游。这个习惯帮我避开了很多“改完感觉没问题、一跑流程就翻车”的情况。希望这些拆解和踩坑记录能帮你更快上手这个项目跑通之后你就有了一份可以自由改造的停车管理系统底座。本文还有配套的精品资源点击获取