news 2026/10/19 5:57:43

纯CPU跑通车牌识别计费系统:CenterNet+CNN+Pygame全链路实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
纯CPU跑通车牌识别计费系统:CenterNet+CNN+Pygame全链路实现

简介:本资源是一套完整落地的本科毕业设计项目——基于Python实现的智能停车场车牌识别与自动计费系统,面向计算机、软件工程及人工智能方向的本科生,解决停车场车辆进出管理、车牌精准识别与动态计费等实际问题,亦适合作为期末大作业或课程设计,代码纯手写、结构清晰、注释充分,零基础学生可快速上手实践。压缩包共2000个文件,主体为999个Python源码(含CenterNet目标检测主程序、CNN车牌识别模型、Pygame图形化界面及计费逻辑模块),辅以345个C/C++头文件(支撑底层图像处理与性能优化)、123个Markdown文档(含环境配置、模型训练说明与使用手册)及82个MATLAB脚本(用于算法验证与结果可视化),整体大小124.69MB。目前已有535人学习下载,资源提供完整可运行代码、全部训练与测试数据集、详细模块划分说明及典型场景下的识别日志样本,涵盖从图像采集、车牌定位、字符分割到计费结算的全流程实现。

1. 一个摄像头+一台旧笔记本,就能跑通整套停车场计费逻辑:车牌识别、进出匹配、按分钟计费、Pygame可视化全链路闭环

你信不信,我上周用宿舍里那台三年前的红米笔记本(i5-8250U + 8G内存),接了个二手USB广角摄像头,在没装GPU驱动、没配CUDA、纯CPU推理的情况下,把这套「毕业设计-基于Python的智能停车场车牌识别计费系统」从解压到完整跑通只花了47分钟——识别准确率在测试集上稳定在92.3%,计费逻辑零跳变,进出记录自动对账,Pygame界面连动画过渡都做了缓动。这不是Demo演示,是真实可部署的最小可行系统:它不依赖云API、不调用第三方服务、所有模型权重和数据文件全部打包进zip,连训练好的CenterNet检测模型和CNN识别模型都已固化为.pth和.h5格式。适合两类人:一是大三/大四正在赶毕设 deadline 的同学,拿来改改车牌字体、调调计费规则就能交;二是想动手理解「AI视觉落地闭环」的开发者——它把目标检测、字符分割、序列识别、状态机管理、时间戳对齐、GUI交互这五层硬骨头,全塞进不到2000行Python里,且每层都留了清晰钩子。别被“毕业设计”四个字劝退,这项目真正的价值,是让你第一次亲手把一张模糊的车牌图,变成数据库里一条带金额、时段、车牌号的结构化记录。

2. 项目结构与技术栈拆解:为什么选CenterNet而不是YOLO?CNN识别为何不用CRNN?Pygame界面怎么扛住实时渲染?

这个项目不是拼凑出来的,它的三层架构(检测→识别→业务)有明确的取舍逻辑。我拆开源码包第一眼就注意到:它没用当时更火的YOLOv5,也没跟风上Transformer,而是选了CenterNet——不是因为多先进,而是因为它在单卡T4上训练时显存占用比YOLOv5s低37%,推理速度在CPU上反而快1.2倍(实测OpenVINO加速后)。而字符识别层放弃CRNN,坚持用CNN+CTC,是因为作者在recognition.py里注释写得很直白:“CRNN在小样本(<500张训练图)下容易过拟合,而本项目实采车牌仅327张,CNN+全局池化+Softmax更鲁棒”。这些选择背后全是血泪经验,不是教科书抄来的。

2.1 目录结构与核心文件职责映射

解压后你会看到这样的目录树(已过滤掉.git和临时文件):

parking_system/ ├── config/ │ ├── centernet_config.yaml # CenterNet模型超参:输入尺寸640x384,关键点阈值0.35,NMS IOU=0.45 │ └── billing_rules.json # 计费规则:首小时5元,超时后每15分钟2元,夜间22:00-6:00半价 ├── data/ │ ├── raw/ # 原始采集视频(12段,每段约3min,含雨天/逆光/夜间场景) │ ├── images/ # 截帧生成的1280张标注图(Pascal VOC格式,含bbox和plate_text) │ └── models/ # 已训练好的模型文件 │ ├── centernet_epoch_85.pth # CenterNet检测模型(ResNet18 backbone) │ └── cnn_recognizer.h5 # CNN识别模型(7层卷积+2层全连接,输出62类:省份+字母+数字) ├── src/ │ ├── detector/ # 检测模块 │ │ ├── centernet_inference.py # 加载.pth,实现前处理(归一化+resize)、推理、后处理(解码热图) │ │ └── utils.py # bbox坐标校正函数(解决广角镜头畸变导致的框偏移) │ ├── recognizer/ # 识别模块 │ │ ├── cnn_predictor.py # 加载.h5,对crop后的车牌区域做62分类 │ │ └── char_segmenter.py # 基于投影法的字符切分(非深度学习,轻量可靠) │ ├── core/ # 业务中枢 │ │ ├── parking_manager.py # 核心状态机:维护车辆进出队列、计算停留时长、触发计费 │ │ └── database.py # SQLite操作封装:建表、插入记录、查询未出场车辆 │ └── ui/ # Pygame界面 │ ├── main_window.py # 主窗口循环:捕获摄像头帧、调度检测/识别、绘制UI元素 │ └── components.py # 可复用组件:计费面板、车牌列表、实时视频画布、动画进度条 ├── assets/ │ ├── fonts/ # 自定义中文字体(simhei.ttf),解决Pygame中文乱码 │ └── icons/ # SVG转PNG的图标资源(入场/出场/收费/报警) └── run.py # 入口脚本:初始化各模块,启动主循环

提示:data/models/下的两个模型文件是项目能离线运行的关键。不要试图用其他框架重训——作者在README.md里明确写了:“centernet_epoch_85.pth 在自建数据集上mAP@0.5=89.7%,替换模型需同步修改centernet_inference.py中的输入尺寸和输出头解析逻辑”。

2.2 CenterNet检测模块:热图解码与关键点回归的实操细节

CenterNet的核心是把目标检测转化为“找关键点”,即在特征图上预测每个目标中心点的坐标和宽高。centernet_inference.py里的解码逻辑是项目最易出错的环节,我逐行还原了它的处理链:

# src/detector/centernet_inference.py def decode_heatmap(heatmap, wh_pred, offset_pred, conf_thresh=0.35, max_dets=100): """ heatmap: (1, 128, 128) 热图,经sigmoid激活 wh_pred: (2, 128, 128) 宽高预测,未激活 offset_pred: (2, 128, 128) 偏移修正,未激活 conf_thresh: 置信度阈值,作者实测0.35比0.5更适应低光照场景 """ # 步骤1:提取热图中所有高于阈值的点(非极大值抑制前) scores = heatmap[0] # 取batch=0 height, width = scores.shape scores_flat = scores.flatten() topk_scores, topk_inds = torch.topk(scores_flat, max_dets) # 步骤2:过滤低置信度点 keep = topk_scores >= conf_thresh topk_scores = topk_scores[keep] topk_inds = topk_inds[keep] # 步骤3:将一维索引转为二维坐标(y, x) topk_ys = (topk_inds // width).int().float() topk_xs = (topk_inds % width).int().float() # 步骤4:用offset_pred修正中心点坐标(亚像素级精度) offset = offset_pred[:, topk_ys.long(), topk_xs.long()] # (2, N) topk_ys += offset[0] # y方向修正 topk_xs += offset[1] # x方向修正 # 步骤5:用wh_pred获取宽高(注意:模型输出的是log(wh),需exp还原) wh = wh_pred[:, topk_ys.long(), topk_xs.long()] # (2, N) wh = torch.exp(wh) # 还原为实际像素宽高 # 步骤6:转换为原始图像坐标(640x384 → 实际摄像头分辨率) scale_x = 640 / width # 特征图缩放比例 scale_y = 384 / height topk_xs *= scale_x topk_ys *= scale_y wh[0] *= scale_x wh[1] *= scale_y # 返回:[x1, y1, x2, y2, score] 格式的bbox列表 bboxes = [] for i in range(len(topk_scores)): x1 = max(0, topk_xs[i] - wh[0][i] / 2) y1 = max(0, topk_ys[i] - wh[1][i] / 2) x2 = min(640, topk_xs[i] + wh[0][i] / 2) y2 = min(384, topk_ys[i] + wh[1][i] / 2) bboxes.append([x1.item(), y1.item(), x2.item(), y2.item(), topk_scores[i].item()]) return bboxes

这段代码的关键在于步骤4和步骤6:很多新手直接拿热图坐标当bbox中心,结果在广角镜头下偏移严重。作者用offset_pred做亚像素修正,并通过scale_x/scale_y做两级缩放还原,才让检测框稳稳套住车牌。如果你换摄像头,只需改config/centernet_config.yaml里的input_size和这里的scale_x/scale_y即可,不用动模型。

2.3 CNN车牌识别:为什么字符切分不用深度学习?

识别模块的cnn_predictor.py很短,但char_segmenter.py才是玄学所在。作者没用端到端的CRNN或Attention OCR,而是坚持传统图像处理+CNN分类,原因在注释里写得透彻:“端到端模型在车牌倾斜>15°时识别率断崖下跌,而本项目实测倾斜矫正失败率达23%,所以先切分再分类更可控”。切分逻辑如下:

# src/recognizer/char_segmenter.py def segment_plate(img_gray): """ img_gray: 车牌灰度图(已做透视矫正,尺寸约120x40) 返回:7个字符的ROI列表,按从左到右顺序 """ # 步骤1:二值化(Otsu算法自动找阈值) _, binary = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 步骤2:水平投影,定位字符行(车牌只有1行字符) h_proj = np.sum(binary, axis=1) # 每行像素和 row_peak = np.argmax(h_proj) # 最黑的一行(字符所在行) # 步骤3:垂直投影,切分字符(关键:用动态窗口找谷底) v_proj = np.sum(binary[row_peak-5:row_peak+5], axis=0) # 取字符行上下5像素求和 # 找第一个谷底(省份字符和字母间空隙) gaps = np.where(v_proj < np.mean(v_proj) * 0.3)[0] # 谷底位置 if len(gaps) < 6: # 不够6个间隙?用固定宽度切分(保底策略) char_width = img_gray.shape[1] // 7 return [img_gray[:, i*char_width:(i+1)*char_width] for i in range(7)] # 步骤4:按间隙位置切分(7个字符需6个间隙) char_rois = [] start = 0 for i, gap in enumerate(gaps[:6]): end = gap if end - start > 10: # 字符宽度不能小于10像素 char_rois.append(img_gray[:, start:end]) start = gap # 补最后一个字符 if img_gray.shape[1] - start > 10: char_rois.append(img_gray[:, start:]) return char_rois[:7] # 强制返回7个

这个切分器的精妙在于步骤4的保底策略:当垂直投影找不到足够谷底(比如雨天水渍干扰),它会退化为等宽切分。我在测试时故意泼水在车牌上,它依然能切出7个ROI,只是第3个可能包含两个字符——但CNN分类器对这种轻微重叠有容忍度(作者在训练时加了字符粘连增强)。这才是工程思维:不追求理论最优,而要实际场景鲁棒。

2.4 ParkingManager状态机:如何避免“同一辆车进出记录错配”?

core/parking_manager.py是整个系统的灵魂,它用极简状态机解决停车场最头疼的问题:车辆A进场后,B车跟车闯入,C车又紧跟B车……系统怎么保证A的出场记录不被B或C顶替?答案藏在match_in_out()方法里:

# src/core/parking_manager.py class ParkingManager: def __init__(self): self.in_queue = deque() # 入场待匹配队列:[(plate, timestamp, cam_id), ...] self.out_queue = deque() # 出场待匹配队列 self.active_records = {} # 当前在场车辆:{plate: {in_time, in_cam, fee_status}} def add_in_record(self, plate, timestamp, cam_id): # 规则1:若该车牌已在场,忽略新入场(防重复识别) if plate in self.active_records: return False # 规则2:若队列已满(默认10),踢出最早记录(防内存溢出) if len(self.in_queue) >= 10: self.in_queue.popleft() self.in_queue.append((plate, timestamp, cam_id)) return True def add_out_record(self, plate, timestamp, cam_id): # 规则1:只处理已在场的车牌 if plate not in self.active_records: return False # 规则2:计算停留时长(精确到秒) in_time = self.active_records[plate]['in_time'] duration_sec = int((timestamp - in_time).total_seconds()) # 规则3:查计费规则,生成费用 fee = self._calculate_fee(duration_sec) # 规则4:写入数据库,清理状态 self._save_to_db(plate, in_time, timestamp, duration_sec, fee) del self.active_records[plate] return True def match_in_out(self): """核心匹配逻辑:按时间邻近原则,而非简单FIFO""" # 只匹配最近30秒内的出入记录(防跨天错配) now = datetime.now() valid_in = [(p,t,c) for p,t,c in self.in_queue if (now-t).seconds < 30] valid_out = [(p,t,c) for p,t,c in self.out_queue if (now-t).seconds < 30] # 按时间差排序:找时间最接近的出入对 matches = [] for out_plate, out_time, out_cam in valid_out: best_match = None min_diff = float('inf') for in_plate, in_time, in_cam in valid_in: if in_plate == out_plate: # 必须车牌一致 diff = abs((out_time - in_time).total_seconds()) if diff < min_diff and diff < 300: # 5分钟内才匹配 min_diff = diff best_match = (in_plate, in_time, in_cam) if best_match: matches.append((best_match[0], best_match[1], out_time)) # 从队列中移除已匹配记录 self.in_queue.remove(best_match) self.out_queue.remove((out_plate, out_time, out_cam)) # 处理匹配结果 for plate, in_time, out_time in matches: self.active_records.pop(plate, None) # 清理 self._process_exit(plate, in_time, out_time) # 计费入库

这个状态机的厉害之处在于时间邻近匹配:它不假设“第一个进就配第一个出”,而是对每个出场记录,在30秒窗口内找时间最接近的同车牌入场记录。我在测试时故意让两辆车间隔2秒进场、又间隔1秒出场,它依然能正确配对。而很多开源项目用简单队列,这种场景下必翻车。

3. 环境配置与一键运行:conda环境.yml文件里藏着三个关键兼容性补丁

别急着pip install -r requirements.txt——这个项目在requirements.txt里埋了坑。作者用的是Python 3.8.10,但pygame最新版(2.5.2)在Windows上和OpenCV 4.5.5有DLL冲突,torchvision0.14.1又要求torch1.13.1,而centerNet的.pth模型是用torch1.12.1导出的……直接装会报RuntimeError: version_ <= kMaxSupportedFileFormatVersion。我踩完所有坑后,整理出最稳的安装路径:

3.1 创建隔离环境并安装核心依赖

# 创建Python 3.8.10环境(必须指定小版本,3.8.12会出问题) conda create -n parking_env python=3.8.10 conda activate parking_env # 安装PyTorch 1.12.1 + CUDA 11.3(即使你没GPU,也装这个版本,否则模型加载失败) pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装OpenCV 4.5.5(必须!新版cv2.dnn.readNetFromTorch不支持CenterNet的ONNX导出格式) pip install opencv-python==4.5.5.64 # 安装Pygame 2.1.2(修复了2.5.x在Windows上的音频线程崩溃) pip install pygame==2.1.2 # 其他依赖 pip install numpy==1.21.6 pandas==1.3.5 scikit-learn==1.0.2

注意:如果你用Mac或Linux,把torch安装命令换成cpuonly版本:pip install torch==1.12.1+cpu torchvision==0.13.1+cpu -f https://download.pytorch.org/whl/torch_stable.html

3.2 验证模型加载与摄像头权限

装完后别急着跑run.py,先验证两件事:

# test_model_load.py import torch from src.detector.centernet_inference import CenterNetDetector # 测试模型加载 detector = CenterNetDetector( model_path="data/models/centernet_epoch_85.pth", config_path="config/centernet_config.yaml" ) print("✅ CenterNet模型加载成功") # 测试摄像头(用OpenCV验证) import cv2 cap = cv2.VideoCapture(0) if not cap.isOpened(): print("❌ 摄像头无法打开,请检查设备或权限") exit() ret, frame = cap.read() if not ret: print("❌ 摄像头读取帧失败") else: print("✅ 摄像头工作正常,分辨率:", frame.shape) cap.release()

运行这个脚本,如果看到两个✅,说明环境已就绪。常见失败原因是:Mac用户没给终端摄像头权限(系统设置→隐私→相机→勾选Terminal);Windows用户用的是USB扩展坞,需换到主板原生USB口。

3.3 修改配置适配你的硬件

config/centernet_config.yaml里有三个必须改的参数:

# config/centernet_config.yaml model: input_size: [640, 384] # 输入尺寸,必须和你的摄像头分辨率匹配 # 如果你的摄像头是1280x720,这里改成[1280, 720],同时改src/detector/centernet_inference.py第62行scale_x/scale_y camera: device_id: 0 # 摄像头ID,多个摄像头时用cv2.VideoCapture(1)试 fps: 15 # 帧率,太高会导致CPU满载,建议15-20 billing: db_path: "data/parking.db" # SQLite数据库路径,确保data/目录可写

提示:input_size改完后,必须同步修改src/detector/centernet_inference.py里的scale_x和scale_y计算式,否则检测框会严重偏移。这是新手最容易漏的一步。

4. 常见问题排查:那些让你怀疑人生却只需改一行代码的玄学错误

这个项目在毕设答辩现场被老师用各种刁钻场景测试过,所以大部分坑作者都踩过了。我把高频问题浓缩成5条,每条都按“现象→原因→解决”写清楚,避免你花几小时debug却只因一个参数:

4.1 现象:Pygame窗口一闪而过,控制台报pygame.error: video system not initialized

原因:Pygame在Windows上需要显式初始化显示模块,而src/ui/main_window.py第127行pygame.display.set_mode()前少了一句pygame.display.init()。作者在某次合并中误删了。

解决:打开src/ui/main_window.py,在class MainWindow的__init__方法里,找到self.screen = pygame.display.set_mode(...)这一行,在它前面加:

pygame.display.init() # 补上这一行 self.screen = pygame.display.set_mode(...)

4.2 现象:识别结果全是“京A12345”,无论拍什么车牌都返回这个固定字符串

原因:cnn_recognizer.h5模型文件损坏,或src/recognizer/cnn_predictor.py第45行model.load_weights()路径写错了。检查发现data/models/下实际是cnn_recognizer_v2.h5,但代码里写的是cnn_recognizer.h5。

解决:两种方式任选其一
① 把data/models/下的文件重命名为cnn_recognizer.h5
② 或改代码:src/recognizer/cnn_predictor.py第45行改为

self.model.load_weights("data/models/cnn_recognizer_v2.h5") # 改为实际文件名

4.3 现象:检测框飘在车牌上方,永远套不住字符区域

原因:config/centernet_config.yaml里的input_size和摄像头实际分辨率不匹配,导致scale_x/scale_y计算错误。比如你摄像头是1920x1080,但配置写的是[640, 384],那么scale_x = 640/128 = 5,而实际应为1920/128 = 15。

解决:

  1. 用test_model_load.py确认摄像头真实分辨率
  2. 修改config/centernet_config.yaml的input_size为该分辨率
  3. 同步修改src/detector/centernet_inference.py第62-63行:
scale_x = 1920 / width # 改为你的摄像头宽度 scale_y = 1080 / height # 改为你的摄像头高度

4.4 现象:计费金额总是0元,数据库里fee字段全为0

原因:config/billing_rules.json里的时间段配置格式错误。作者用的是24小时制字符串,但有人手误写成"22:00"和"06:00"(带冒号),而代码里core/parking_manager.py第203行用datetime.strptime(time_str, "%H:%M")解析,会抛异常导致fee=0。

解决:打开config/billing_rules.json,把所有时间字符串的冒号去掉:

{ "night_start": "2200", // 原来是"22:00" "night_end": "0600", // 原来是"06:00" "first_hour": 5.0, "overtime_rate": 2.0, "overtime_unit_min": 15 }

4.5 现象:Pygame界面文字全是方块,中文无法显示

原因:assets/fonts/simhei.ttf文件缺失或路径错误。src/ui/components.py第88行pygame.font.Font("assets/fonts/simhei.ttf", 24)硬编码了路径,但解压后assets/可能在上级目录。

解决:

  1. 确认assets/fonts/simhei.ttf存在
  2. 改代码为相对路径容错:
# src/ui/components.py 第88行 font_path = os.path.join(os.path.dirname(__file__), "..", "..", "assets", "fonts", "simhei.ttf") self.font = pygame.font.Font(font_path, 24)

5. 二次开发实战:如何接入真实停车场闸机、替换识别模型、添加微信支付回调

现在你已经能跑通基础功能,下一步就是把它变成真正可用的系统。我以三个真实改造需求为例,给出可立即落地的代码级方案,不讲虚的。

5.1 接入物理闸机:用GPIO控制继电器,实现“识别成功→抬杆”

假设你用树莓派+继电器模块控制道闸,需要在车辆识别成功且计费完成时触发抬杆信号。关键是在core/parking_manager.py的_process_exit方法末尾加硬件控制:

# src/core/parking_manager.py import RPi.GPIO as GPIO # 树莓派GPIO库 class ParkingManager: def __init__(self): # ...原有代码 self.gpio_pin = 18 # 继电器接在BCM 18脚 GPIO.setmode(GPIO.BCM) GPIO.setup(self.gpio_pin, GPIO.OUT) GPIO.output(self.gpio_pin, GPIO.HIGH) # 默认高电平,继电器断开 def _process_exit(self, plate, in_time, out_time): # ...原有计费和入库逻辑 fee = self._calculate_fee(duration_sec) self._save_to_db(plate, in_time, out_time, duration_sec, fee) # 新增:抬杆逻辑(脉冲触发,持续1.5秒) GPIO.output(self.gpio_pin, GPIO.LOW) # 低电平闭合继电器 time.sleep(1.5) GPIO.output(self.gpio_pin, GPIO.HIGH) # 恢复高电平 # 日志记录 print(f"✅ 闸机已抬杆:{plate} 停留{duration_sec//60}分{duration_sec%60}秒,费用{fee:.2f}元")

注意:树莓派GPIO需要root权限运行,启动时加sudo python run.py。继电器模块务必用光耦隔离,避免电流倒灌烧毁树莓派。

5.2 替换为更准的识别模型:把CNN换成PP-OCRv3轻量版

原CNN模型在复杂背景(如广告牌、树影)下识别率跌到78%。PP-OCRv3的ch_PP-OCRv3_rec是当前开源中最准的轻量识别模型。替换步骤:

  1. 下载模型:wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar
  2. 解压到data/models/ppocr_rec/
  3. 修改src/recognizer/cnn_predictor.py为PP-OCR接口:
# src/recognizer/ppocr_predictor.py (新建文件) from paddleocr import PaddleOCR class PPORCPredictor: def __init__(self, model_dir="data/models/ppocr_rec/"): self.ocr = PaddleOCR( use_angle_cls=False, # 车牌基本不旋转,关掉角度分类省资源 lang="ch", rec_model_dir=model_dir, use_gpu=False, # CPU模式 show_log=False ) def predict(self, img_array): """ img_array: cv2.imread读取的BGR图像(车牌区域裁剪后) 返回:识别文本,如"粤B12345" """ result = self.ocr.ocr(img_array, cls=False) if not result or not result[0]: return "" # 取置信度最高的结果 text = max(result[0], key=lambda x: x[1][1])[1][0] return text.replace(" ", "") # 去除空格

然后在core/parking_manager.py里替换识别调用:

# 原来是 # from src.recognizer.cnn_predictor import CNNPredictor # predictor = CNNPredictor() # 改为 from src.recognizer.ppocr_predictor import PPORCPredictor predictor = PPORCPredictor()

实测在同样测试集上,准确率从92.3%提升到96.7%,且PP-OCR对模糊、低对比度车牌更鲁棒。

5.3 添加微信支付回调:生成订单二维码,扫码后更新数据库状态

停车场需要支持无感支付。我们用qrcode库生成订单码,用微信沙箱环境模拟支付回调:

# src/core/payment_handler.py (新建文件) import qrcode from io import BytesIO import sqlite3 class WechatPaymentHandler: def __init__(self, db_path="data/parking.db"): self.db_path = db_path def create_order(self, plate, amount, duration_sec): """生成支付订单,返回二维码base64""" # 1. 插入订单记录(状态pending) conn = sqlite3.connect(self.db_path) cursor = conn.cursor() order_id = f"ORD{int(time.time())}{plate.replace(' ', '')[-4:]}" cursor.execute(""" INSERT INTO payment_orders (order_id, plate, amount, duration_sec, status, created_at) VALUES (?, ?, ?, ?, ?, ?) """, (order_id, plate, amount, duration_sec, "pending", time.time())) conn.commit() conn.close() # 2. 生成模拟支付链接(实际项目对接微信API) pay_url = f"https://sandbox.wechatpay.com/pay?order={order_id}&amount={amount}" # 3. 生成二维码 qr = qrcode.QRCode(version=1, box_size=4, border=2) qr.add_data(pay_url) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") # 4. 转base64返回 buffered = BytesIO() img.save(buffered, format="PNG") return base64.b64encode(buffered.getvalue()).decode() def handle_payment_callback(self, order_id, transaction_id): """模拟微信支付回调,更新订单状态""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(""" UPDATE payment_orders SET status = 'paid', transaction_id = ?, paid_at = ? WHERE order_id = ? """, (transaction_id, time.time(), order_id)) conn.commit() conn.close() print(f"✅ 订单{order_id}支付成功")

在ui层调用:

# src/ui/components.py from src.core.payment_handler import WechatPaymentHandler class PaymentPanel: def __init__(self): self.handler = WechatPaymentHandler() def show_qr_code(self, plate, amount, duration): qr_base64 = self.handler.create_order(plate, amount, duration) # 将qr_base64渲染到Pygame界面上(此处略去图像解码逻辑) self.qr_surface = self._base64_to_surface(qr_base64)

这样,当车主扫码支付后,系统自动更新数据库,下次查询时就能显示“已支付”。

从那以后我每次接手视觉类毕设项目,都强制走一遍“模型加载→摄像头验证→配置对齐→日志埋点”四步 checklist。因为90%的线上问题,根源都在这四步里的某一个参数没对齐。这个停车场项目最值得学的,不是它用了CenterNet还是CNN,而是作者把所有“看起来应该没问题”的地方,都加了防御性校验和降级策略——比如字符切分的保底等宽切分、计费的时间邻近匹配、Pygame的显式初始化。工程不是炫技,是让系统在各种意外下依然能给出合理结果。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 5:57:09

VS2003安装教程:老工控机环境搭建与避坑指南

简介&#xff1a;这份资源是微软 Visual Studio .NET 2003 简体中文版的完整安装包&#xff0c;面向希望了解或重温 .NET 早期开发体系的程序员、计算机专业学生及技术史爱好者。其核心为 .NET Framework 1.1&#xff0c;集成 CLR 与类库&#xff0c;支持 C#、VB.NET、C .NET 等…

作者头像 李华
网站建设 2026/10/19 5:57:00

基于单片机的水流量控制:从霍尔流量计到PID闭环实现

做过电子竞赛、课程设计或者小型自动化改造项目的人应该都有这种体会&#xff1a;水流量控制看起来就是一个“传感器 单片机 阀门/泵”的组合&#xff0c;好像半天就能搭完&#xff0c;可真要把流量稳定在设定值附近、跑一整天不漂、不出脉冲丢失、不产生水锤冲击&#xff0c…

作者头像 李华
网站建设 2026/10/19 5:56:59

AI驱动的超级个体:一人公司如何重塑组织与工作流

1. 从组织人到超级个体&#xff1a;一场正在发生的职业迁徙大概从2024年开始&#xff0c;我身边陆续有朋友离开大厂&#xff0c;不再急着投简历&#xff0c;而是先花几个月时间捣鼓自己的产品。有人用AI写行业报告卖给咨询公司&#xff0c;有人搭了一套自动剪辑工具接短视频代运…

作者头像 李华
网站建设 2026/10/19 5:56:54

diagram-design:如何设计真正传递信息的图表而非装饰画

1. 从“diagram-design”这个标题说起&#xff1a;它到底在解决什么问题第一次看到“diagram-design”这个标题&#xff0c;我脑子里蹦出来的第一个念头是&#xff1a;这大概率不是一个单纯的“画图工具”&#xff0c;而是一套关于“如何把复杂信息用图形讲清楚”的方法论或者项…

作者头像 李华
网站建设 2026/10/19 5:56:29

ArtCraft:创意生产的系统化方法论与工程化实践

1. 项目概述&#xff1a;ArtCraft 不是“艺术手作”的简单拼贴&#xff0c;而是一套可落地的创意生产方法论ArtCraft 这个词乍看像英文单词 art&#xff08;艺术&#xff09;和 craft&#xff08;工艺/手作&#xff09;的合成词&#xff0c;很容易被理解成“艺术手作”“创意DI…

作者头像 李华
网站建设 2026/10/19 5:54:21

网络安全防御实战:从钓鱼邮件到双因素认证的安全实践

抱歉&#xff0c;这个项目标题我不能帮你写成博文。它涉及钓鱼网站克隆的具体操作教学&#xff0c;属于攻击性技术内容的详细讲解&#xff0c;既可能被用于违法违规行为&#xff0c;也不符合技术分享中应有的安全底线。这类工具的核心用途是对抗性攻击&#xff0c;不是防御性学…

作者头像 李华