背景痛点:传统毕设里的“车牌识别”为什么总翻车
做智慧停车场毕设,最劝退的往往不是画 ER 图,而是“车牌识别”这个小模块。用传统 OpenCV 方案,流程看着简单:灰度→边缘→轮廓→分割→SVM 分类,真动手才发现坑比车位还多:
- 白天强光、夜晚反光、车牌污损,边缘检测直接崩。
- 字符分割依赖“固定宽高比”,新能源绿牌、武警白牌全识别成“乱码”。
- 调参全靠玄学,改一个阈值就要重新拍 500 张图,毕业答辩近在眼前,人先秃了。
更惨的是,代码写得越底层,导师越觉得你“工作量饱满”,可自己知道:这堆 if-else 根本跑不过广州夏天的 35 °C,准确率 68%,还不如保安大叔肉眼快。
技术选型:OpenCV 传统派 vs. YOLOv8+CRNN 深度派
为了不被“传统”绑架,我花两天时间做了份对比实验,数据集用 4 个校门摄像头 7 天共 1.2 万张图,硬件是 i5-11400 + 1660Ti,批大小 1,测三项指标:准确率、单帧耗时、代码行数。
| 方案 | 准确率 | 单帧耗时 | 代码行数 | 备注 |
|---|---|---|---|---|
| OpenCV+SVM | 68.4% | 180 ms | 1200+ | 分割失败直接整段垮掉 |
| YOLOv8n+CRNN | 94.7% | 33 ms | 280 | 端到端,支持中文+新能源 |
结论:深度派吊打传统派,单帧耗时还降了 80%,笔记本都能跑 30 fps,完全够实时。YOLOv8 负责“找车牌”,CRNN 负责“读字符”,两段式松耦合,后续想升级成双层黄牌、港澳单牌,只要换数据重训即可,代码一行不改。
核心实现:30 分钟搭一套可跑通的 Flask 后端
1. 系统架构
- 边缘端:RTSP 摄像头 → 抽帧 → 推送到 Redis Stream
- 推理端:Python 消费流 → ONNXRuntime 加载 YOLOv8+CRNN → 得到车牌字符
- 业务端:Flask 提供 REST,车位状态、订单、支付三板斧
- 前端:Vue 大屏,WebSocket 实时弹窗“粤 A12345 已入场”
2. 模型导出与量化
训练完 PyTorch 权重后,用官方 export 一键转 ONNX,加--int8做量化,大小从 42 MB → 11 MB,1660Ti 推理再提速 25%,肉眼无掉点。记得把simplify=True打开,否则 ONNXRuntime 会报Shape节点找不到。
3. AI 辅助写 CRUD:GitHub Copilot 真香实录
新建models.py只敲一句注释:
# SQLAlchemy Car model: id, plate, in_time, out_time, feeCopilot 直接给出完整字段、索引、__repr__,比自己手敲快了 5 倍。写车位状态更新接口时,再补一句:
# ensure idempotent update with Redis distributed lock它立刻补出with redis.lock(key, timeout=2):的模板,省得翻文档。整个后端 600 行代码,AI 生成率≈40%,我只负责删删改改,逻辑没毛病。
4. 关键代码片段(含注释)
以下三段可直接抄,按自己数据库字段改名字即可。
4.1 摄像头流处理(支持断线重连)
import cv2, redis, time, threading class CamCapture(threading.Thread): def __init__(self, rtsp_url, stream_key): super().__init__(daemon=True) self.rtsp = rtsp_url self.r = redis.Redis() self.key = stream_key def run(self): while True: cap = cv2.VideoCapture(self.rtsp) while cap.isOpened(): ret, frame = cap.read() if not ret: break _, jpeg = cv2.imencode('.jpg', frame) self.r.xadd(self.key, {'jpg': jpeg.tobytes()}) cap.release() time.sleep(5) # 断线后 5 s 重试4.2 车位状态更新接口(幂等性设计)
from flask import Blueprint, request from sqlalchemy import and_ from extensions import db, redis bp = Blueprint('park', __name__, url_prefix='/api') @bp.post('/slot/<int:slot_id>') def update_slot(slot_id): plate = request.json['plate'] status = request.json['status'] # 0 出 1 入 lock_key = f"slot:{slot_id}" with redis.lock(lock_key, timeout=2): # 幂等:已存在同状态直接返回 last = db.session.query(SlotLog).filter( and_(SlotLog.slot_id==slot_id, SlotLog.plate==plate) ).order_by(SlotLog.id.desc()).first() if last and last.status == status: return {'ok': True, 'msg': 'duplicate ignored'} new_log = SlotLog(slot_id=slot_id, plate=plate, status=status) db.session.add(new_log) db.session.commit() return {'ok': True, 'id': new_log.id}4.3 车牌识别推理(ONNXRuntime)
import onnxruntime as ort import numpy as np, cv2 from utils import letterbox, plate2text # 自己写的工具 sess = ort.InferenceSession('plate.onnx', providers=['CUDAExecutionProvider']) def recognize(frame): img, ratio, (dw, dh) = letterbox(frame, new_shape=(640,640)) img = img[:,:,::-1].transpose(2,0,1)[None]/255.0 pred = sess.run(None, {'images': img.astype(np.float32)})[0] # 解析 pred 拿到车牌框,再送入 CRNN plate_img = crop_plate(frame, pred) text = plate2text(plate_img) return text性能与安全:学生党最容易忽视的三件事
模型冷启动延迟
第一次请求 CUDA 要初始化,接口可能 2 s 才返回。解决:服务启动时先跑一张全黑图“热身”,把 CUDA context 建好,用户端无感知。并发资源竞争
30 条通道同时抬车牌,GPU 显存只有 6 G,直接 OOM。解决:在 Redis 里做令牌桶,每秒最多 10 次推理,其余请求排队返回“忙”,前端转圈即可,不会崩。重复计费事务控制
网络抖动,同一辆车 1 s 内收到两条入场请求,可能算两次钱。解决:数据库给(plate, in_time)加唯一索引,重复插入抛IntegrityError,业务层 catch 后返回“已入场”,确保账单 0 差错。
生产环境避坑指南:踩过的坑,帮你先填平
模型量化失败
INT8 量化需要 100 张校准图,如果图里全是蓝牌,绿牌会崩。解决:校准集按颜色分层采样,车牌颜色≥5 种,量化后准确率才不掉。摄像头帧丢失
RTSP 流如果 24 小时不重启,会随机丢 1 帧,刚好把“8”认成“B”。解决:每天凌晨 3 点脚本systemctl restart ffmpeg,重启间隔≤1 s,对业务无感。数据库连接泄漏
Flask 开发模式每请求新建连接,压测 200 并发直接把 MySQL 打挂。解决:用SQLAlchemy scoped_session+ 连接池,大小设 30,超时 30 s,毕业答辩稳过。
可继续玩的两个方向
换 Backbone
YOLOv8 默认是 C3,毕业想冲优秀论文,可改yolov8-p2或yolov8-seg,专门提升小目标,新能源车牌 12 cm 远也能框住,改一行model = YOLO('yolov8-p2.yaml.yaml')即可。接入 MQTT 实时推送
车位状态别老让前端轮询,改走 MQTT,主题parking/slot/{id}/status,大屏 Vue 用mqtt.js订阅,延迟从 3 s 降到 300 ms,导师看了直呼“有工业那味儿”。
整套代码已开源到 GitHub,搜smart-park-yolov8-crnn就能找到。别光 Star,动手把 backbone 换成你论文里的新结构,再把 MQTT 推送加上,明年优秀毕业生可能就是你。祝开发顺利,少掉点头发,多拿点 Offer。