ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

车牌识别系统源码:Python+OpenCV+SVM完整闭环与部署实践

车牌识别系统源码:Python+OpenCV+SVM完整闭环与部署实践 简介面向计算机视觉方向毕业设计的一套车牌识别项目基于Python与OpenCV实现并集成HyperLPR提升识别率。项目覆盖车牌搜索、车牌号对比识别、数据库认证、图文搜索、实时截图识别、摄像头拍照识别等常用功能同时支持图片自适应窗口大小无论使用本地图片还是网络图片地址都能快速完成车牌定位与识别适合本专科生直接作为课设或毕设的代码基座。整个压缩包共119个文件约22.37MB其中以Python源码、JPG与PNG样本图片为主辅以模型数据、运行说明和Docker部署配置方便在不同环境下复现目前已有413人学习下载。除可运行的车牌识别主程序外还提供多场景测试图片、数据库比对逻辑、容器化部署文件以及详细的项目启动说明读者可据此快速跑通流程并在此基础上扩展车牌比对、检索或认证业务。整体结构清晰便于按模块阅读与二次开发。1. 车牌识别系统源码从 SVM 识别到数据库认证的完整闭环车牌识别License Plate Recognition是 OpenCV 方向最经典的毕业设计题目但网上大多数 Demo 只做到“能从一张干净图片里抠出车牌”离一个能交差、能演示的系统还差着数据库和多种输入源。这份基于 Python OpenCV 的源码把链路做完整了svm.dat 判断候选区域是不是车牌svmchinese.dat 识别省份汉字识别结果能拿去车牌数据库做认证和图文搜索输入源覆盖文件图片、网络图片、实时截图、摄像头拍照还带 Dockerfile 一键部署。适合三类人正在选毕设题目、想直接复用源码的在校生要在内部系统里快速加车牌识别模块的开发者想搞懂 SVM 分类器怎么落地的人。下面按拆包顺序讲识别链路、部署方式、数据库部分、避坑、HyperLPR 进阶。2. 识别链路拆解svm.dat、字符分割与 SVM 分类是怎么串起来的车牌识别本质上是一条图像处理流水线不是某一个算法单打独斗。这套源码里最值钱的部分是两个已经训练好的 SVM 模型以及把模型串起来的处理顺序。先把这个顺序讲透后面部署和调参才有依据。2.1 先拆包每个文件在识别链路里的角色拿到压缩包先把文件摊开看一遍。这份源码不是单个 py 文件而是带模型、带 Docker、带样本图的完整工程。核心是svm.dat和svmchinese.dat两个模型文件文件在链路里的角色使用方式svm.dat车牌区域判定二分类器cv2.ml.SVM_create() load()svmchinese.dat中文字符分类器加载后对单个字符预测Dockerfile / .env.docker容器化运行入口docker build / --env-file 注入配置2-1.JPEG、2.jpeg、3_2.jpg测试样本图cv2.imread 直接读取验证duibi.gif / log.gif演示素材对比识别效果和日志输出两个 SVM 分开训练是有道理的判断“这块区域是不是车牌”和判断“这个字符是哪个汉字”是两个差异很大的任务。前者输入的是整块候选区域的 HOG 特征后者输入的是单个字符的归一化图像。混在一个模型里特征空间会互相干扰训练数据也不好组织。不少初学者试图用一个模型包打天下实际效果往往是定位不准、字符也认错两头都不沾。这个包把两个模型拆开正好符合工程上的常规做法。2.2 四步识别流程定位 → 校验 → 分割 → 分类标准流水线是预处理 → 车牌定位 → 字符分割 → 字符识别。定位阶段先用颜色和形态学把候选区域抠出来再用 svm.dat 做二次确认确认后的区域交给垂直投影分割字符最后每个字符交给 svmchinese.dat 分类。第一步HSV 颜色定位加形态学处理import cv2 import numpy as np def locate_plate_candidates(image): # 转 HSVOpenCV 里 H 范围是 0~179蓝色车牌集中在 100~124 hsv cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # inRange 生成蓝色掩膜S/V 下限提上去避免把白车、浅色物体带进来 blue_lower np.array([100, 80, 80]) blue_upper np.array([124, 255, 255]) mask cv2.inRange(hsv, blue_lower, blue_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) # 宽高比 2~5.5、最小尺寸 80x20用于排除车身贴条、招牌等干扰 if w 80 and h 20 and 2.0 w / h 5.5: candidates.append((x, y, w, h)) return candidates颜色定位是整条链路里召回率的关键。蓝底白字是传统车牌的主流HSV 空间把颜色信息从亮度里拆出来比直接在 BGR 里判蓝稳定得多。形态学核设成 (17, 5) 是配合车牌的横向矩形特征核太窄连不起字符太宽会把旁边的区域并进来。宽高比 2.0~5.5 是经验值标准蓝牌宽高比在 3 到 4 之间放宽一点是为了容忍拍照角度造成的形变。第二步用 svm.dat 对候选区域做二次确认svm cv2.ml.SVM_create() svm.load(svm.dat) # 模型用 OpenCV FileStorage 序列化保存 def is_plate(roi): # 统一缩放到训练时的尺寸 (136, 36) roi cv2.resize(roi, (136, 36)) gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) # HOG 参数必须和训练时完全一致否则特征维度对不上 hog cv2.HOGDescriptor((136, 36), (16, 16), (8, 8), (8, 8), 9) features hog.compute(gray).reshape(1, -1) _, result svm.predict(features) # 训练时把车牌样本标为 1非车牌标为 0 return result[0][0] 1这里有个很隐蔽的坑HOG 的窗口、块、胞元、步长、方向数任何一个参数跟训练时不一致特征长度就变了SVM 预测结果会直接变成垃圾。所以这几个参数最好写成常量和训练脚本共用一份配置文件。另外 svm.load 如果报错多半是 OpenCV 大版本不一致3.x 序列化出来的模型在 4.x 上加载不稳定建议固定 OpenCV 版本再跑。第三步字符分割和识别def segment_chars(plate_img): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 自适应阈值光照不均时比全局 Otsu 稳定 binary cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 21, 10) # 垂直投影统计每列白点数量字符列明显大于背景列 col_sum np.sum(binary, axis0) / 255 chars [] in_char False start 0 for i, v in enumerate(col_sum): if v 0 and not in_char: in_char, start True, i elif v 0 and in_char: in_char False if i - start 8: # 小于 8 像素的孤立列当噪点丢弃 chars.append((start, i)) return chars def predict_char(svm_chinese, char_img): # 与 is_plate 相同的 HOG predict 逻辑换成 svmchinese.dat char_img cv2.resize(char_img, (20, 20)) # 返回类别编号再映射到省份汉字表 return label垂直投影是字符分割最朴素也最常用的方法前提是车牌本身没有大角度倾斜。分割后还要做一次合法性校验传统蓝牌是 7 位字符1 个省份汉字 1 个字母 5 位字母数字新能源车牌是 8 位。如果分割出来的字符数不对直接判定为误检丢弃这个过滤能干掉相当一部分定位阶段的误检比单纯改颜色阈值有效得多。2.3 为什么用 SVM 而不是 YOLO 这类检测模型这是毕设答辩必被问到的问题也是选型时最先要想清楚的。SVM HOG 的优势在三个地方第一不需要 GPU笔记本 CPU 跑一张图几十毫秒训练时几百张样本几分钟就好第二模型文件只有几百 KBsvm.dat 和 svmchinese.dat 加起来远小于一个深度学习模型第三可解释性强答辩时能说清楚“HOG 提取的是局部梯度方向统计SVM 在特征空间里找最大间隔超平面”层层可拆解。深度学习的优势是鲁棒性倾斜车牌、夜间、遮挡场景下 YOLO 一类检测器明显更稳但代价是标注几百上千张车牌、搭 CUDA 环境、调训练参数对毕设周期不划算。实际项目里常见做法是主链路保留 SVM遇到识别置信度低的样本再叠加 HyperLPR这条路放到第 6 章展开。3. 部署与运行本地虚拟环境与 Docker 两条路3.1 本地环境venv OpenCV 的版本搭配先讲最快能跑起来的方式。我习惯在 VSCode 里新建项目后把解释器指到 venv这样装了一堆包也不污染系统 Python省得后面别的项目依赖冲突。python -m venv venv # Windows 用venv\Scripts\activate source venv/bin/activate pip install opencv-python numpy requests pyautogui依赖里有两点要注意。第一opencv-python和opencv-contrib-python只装一个就行contrib 包含 SIFT、ORB 这类额外算法模块如果项目里用到特征点匹配就需要它。第二pyautogui 只在“实时截图识别车牌”这个功能里用到不跑这块可以不装。如果从 requirements.txt 安装装完执行一遍python -c import cv2; print(cv2.__version__)确认版本顺手把识别主脚本跑一次。提示VSCode 里装完依赖后按 CtrlShiftP 选 “Python: Select Interpreter”指到 venv 路径否则终端里能 import编辑器里却标红很影响排查。3.2 Docker 部署镜像构建与 .env.docker如果不想折腾本地 Python 环境或者要把这套识别服务发给别人演示Docker 是最省事的路。包里自带的 Dockerfile 基本长这样关键点在于必须先装 libGLFROM python:3.8-slim # OpenCV 在 slim 镜像里缺少 libGL / libglib2.0import 阶段直接崩溃 RUN apt-get update apt-get install -y --no-install-recommends \ libgl1 libglib2.0-0 rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD [python, main.py]构建和运行的命令docker build -t lpr-demo . docker run --rm \ --env-file .env.docker \ -v $(pwd)/data:/app/data \ -v $(pwd)/models:/app/models \ lpr-demo python main.py --source image --input 2-1.JPEG.env.docker里一般是数据库路径、模型目录、摄像头索引这类可配置项典型内容如下DATABASE_PATH/app/data/plates.db MODEL_DIR/app/models CAMERA_INDEX0这类环境变量用 --env-file 注入比把路径写死在代码里强。要注意的是数据卷挂载模型和图片库通过-v挂进去容器销毁后数据还在宿主机不用每次重新拷贝。3.3 四种输入源的运行路径这套系统对外暴露的核心函数是“识别一张图像返回车牌号”四种输入源本质上是把图像从不同渠道拿到手再喂给同一个识别函数。文件图片最简单直接 imreadimage cv2.imread(2-1.JPEG) # 路径不存在时返回 None需要判空 plate extract_plate_number(image) print(识别结果:, plate)网络图片地址要先把字节流解码成图像import requests import numpy as np def recognize_from_url(url): resp requests.get(url, timeout10) if resp.status_code ! 200: return None # 字节数组必须转成 uint8imdecode 才能正确解码 img_array np.frombuffer(resp.content, np.uint8) image cv2.imdecode(img_array, cv2.IMREAD_COLOR) if image is None: return None return extract_plate_number(image)实时截图用 pyautogui 抓屏再转回 BGRimport pyautogui def recognize_from_screen(): shot pyautogui.screenshot() # PIL 图像 frame cv2.cvtColor(np.array(shot), cv2.COLOR_RGB2BGR) return extract_plate_number(frame)摄像头拍照识别是典型循环cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(摄像头索引 0 打不开试 1 或 2) while True: ret, frame cap.read() if not ret: break plate extract_plate_number(frame) if plate: cv2.putText(frame, plate, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) # 自适应窗口大小按画面宽度等比缩放到 800 以内 scale 800 / frame.shape[1] show cv2.resize(frame, (0, 0), fxscale, fyscale) cv2.imshow(plate-demo, show) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()注意Windows 下 pyautogui 截图如果屏幕缩放比例不是 100%会只截到左上角一块需要先把进程的 DPI 感知打开或者在截图前用 ctypes 设置缩放。4. 车牌数据库认证与图文搜索让识别结果进入业务闭环只把车牌号识别出来其实没什么业务价值真正有用的是把结果拿去做认证、检索、比对。这套源码里的“车牌数据库认证系统”和“车牌图文搜索系统”就是把识别输出接到一个小型数据库上。4.1 数据库建模与认证逻辑认证场景常见于停车场、园区门禁识别到车牌后去库里查这个车牌是否在准入名单里。库里存的不只是车牌号还有车主、登记时间之类的扩展字段。项目规模不大SQLite 完全够用而且单文件、零部署毕设演示和内部工具都很合适。import sqlite3 def init_db(db_path): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS plates ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate_no TEXT NOT NULL UNIQUE, owner TEXT, create_time TEXT DEFAULT (datetime(now, localtime)) ) ) conn.commit() return conn def auth_plate(db_path, plate_no): 认证库里能查到返回 True查不到返回 False plate_no plate_no.strip().upper() # 先做标准化再查库 conn sqlite3.connect(db_path) cur conn.cursor() cur.execute(SELECT 1 FROM plates WHERE plate_no ?, (plate_no,)) ok cur.fetchone() is not None conn.close() return ok这里有个容易被忽略的细节识别结果里的车牌号要统一小写转大写、去掉空格再入库查询。SVM 对字母和数字的预测结果本身是类别编号转成字符串后大小写混用是常态不标准化的话“京A12345”和“京a12345”会查询两次完全不同的记录。另外数据量到几万条以后记得给 plate_no 列建唯一索引不然每次认证都是全表扫描响应会明显变慢。4.2 车牌图文搜索与对比识别“车牌图文搜索”换个说法就是给我一批图片找出里面出现指定车牌号的那几张。实现方式是把这批图片逐张识别再和目标车牌比对。代码很直白from pathlib import Path def search_plate_in_dir(image_dir, target): 在图片目录里找出包含目标车牌的图片 target target.strip().upper() matches [] for img_file in Path(image_dir).glob(*.jpg): image cv2.imread(str(img_file)) if image is None: continue plate extract_plate_number(image) if plate and plate.upper() target: matches.append(str(img_file)) return matches批量识别的性能瓶颈在 SVM 预测和 HOG 特征计算上图片多时可以先用定位阶段粗筛连蓝色区域都没有的直接跳过能省下不少时间。包里 duibi.gif 演示的“对比识别”本质上就是两种结果的交叉验证一种是同一张图跑两套引擎内置 SVM 和 HyperLPR两个结果一致才写库另一种是两张相邻帧的识别结果做一致性校验防止单帧误检。这个思路做门禁类应用特别有用——单帧识别错了连续几帧结果一致才能放行误报率能降一个量级。5. 避坑与常见问题识别率与运行环境里的五个真实翻车点5.1 import cv2 报 libGL.so.1 找不到现象pip 安装 opencv-python 成功后import cv2直接抛ImportError: libGL.so.1: cannot open shared object file。原因opencv-python 的 wheel 依赖系统里的 libGL 和 libglib2.0 动态库精简版容器或裸服务器上默认没装。这个报错和你项目代码没关系纯粹是系统缺库。解决Debian/Ubuntu 系执行apt-get install -y libgl1 libglib2.0-0Docker 场景把安装命令放到 Dockerfile 里并且必须在pip install之前完成否则构建缓存会引入脏环境。5.2 svm.dat 加载成功但预测结果全是同一个值现象svm.load 不报错predict 返回的结果全都一样或者干脆 load 阶段就异常退出。原因OpenCV 3.x 和 4.x 的 ml 模块序列化格式不完全兼容训练时用 3.x 保存的模型在 4.x 上预测会翻车另外模型路径用了相对路径工作目录一换就找不到文件。HOG 参数和训练时不一致也会导致特征维度对不上预测结果变成垃圾。解决固定 OpenCV 大版本建议 4.5.x 以上并统一用opencv-contrib-python模型路径通过配置项传入绝对路径load 后打印svm.getSupportVectors().shape确认模型非空HOG 参数写成常量并和训练脚本共用配置。5.3 蓝色车身、蓝色招牌被当成车牌现象定位阶段框出一堆候选区域车身的深蓝贴膜、路边蓝色招牌全在里面SVM 也没拦住。原因HSV 的 H 范围设置太宽S 饱和度下限太低把深蓝车身一并圈进来了形态学核太大把周边区域也连成一块。解决把 H 收敛到 100~124S 和 V 下限提到 80 以上形态学核宽度控制在 17 左右不要超过字符间距太多最后加一道字符数过滤——分割后不足 7 位新能源 8 位的直接丢弃。另外新能源绿牌是渐变底色要单独加一组 HSV 区间和蓝牌分别处理后再合并候选。5.4 省份汉字识别乱码现象字母和数字基本准第一个字总错或者映射成错误的省份缩写。原因svmchinese.dat 训练时的类别编号和代码里 char_table 的顺序对不上字符 resize 到 (20, 20) 时失真严重汉字特征被压没了。解决从训练代码里把 label 顺序导出来逐一比对 char_table京沪津渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼字符归一化时保持黑字居中、四边留白不要拉伸变形。这类问题通常不是模型不准而是映射表错了。5.5 摄像头打不开或画面卡死现象VideoCapture(0)返回 False或者识别跑一会儿画面冻住程序按 q 也没反应。原因摄像头索引不对设备被其他程序占用比如微信、OBSDocker 里没挂载视频设备识别逻辑堵塞了主循环waitKey 来不及处理退出事件。解决用cap.isOpened()判断索引依次试 0/1/2Docker 启动加--device/dev/video0识别放到后台线程UI 主循环只做 imshow 和 waitKey不要在两件事之间插重计算。这是摄像头类项目最常见的卡死根源本质上是把耗时操作堵在了渲染线程里。6. 进阶用 HyperLPR 兜底识别率并建立量化验证集6.1 HyperLPR 双引擎接入内置的 SVM 链路在标准正脸蓝牌上表现稳定但一到倾斜、暗光、模糊场景就露怯。包里提到的 HyperLPR 就是干这个的它是一个开源的车牌识别方案对非标准角度的容错比手工特征好不少。常见接入方式是做成回退引擎SVM 识别结果置信度低时切到 HyperLPR 再试一次try: from hyperlpr import LicensePlate results LicensePlate.recognize(image) plate results[0][0] if results else None engine hyperlpr except ImportError: # 没装 hyperlpr 时回退到内置 SVM 链路保证原来功能不挂 plate extract_plate_number(image) engine svm双引擎最有价值的用法不是“谁替代谁”而是“两者一致才算数”。门禁、认证这类场景里SVM 和 HyperLPR 都给出同一个车牌才写数据库不一致就标记待人工复核误报率比单引擎低得多。包里的 duibi.gif 演示的对比逻辑就是这个思路。6.2 自建验证集让每一次参数改动都有量化结果识别类项目最怕靠感觉调参——“好像准了一点”在汇报和答辩时毫无说服力。我拿到这套源码后做的第一件事就是建一个验证集准备二十到三十张覆盖不同角度、光照、背景的图片手写一个 gt.csv格式就两列图片路径、真实车牌号。然后跑批量识别# gt.csv 示例 # 2-1.JPEG,京A12345 # 2.jpeg,沪B67890 def run_batch(gt_csv): total correct 0 with open(gt_csv, encodingutf-8) as f: for line in f: path, truth line.strip().split(,) img cv2.imread(path) pred extract_plate_number(img) total 1 if pred truth: correct 1 print(f识别率: {correct / total:.2%} ({correct}/{total}))从那以后我每次调 HSV 区间、改形态学核、换字符分割阈值都强制自己先跑一遍这个脚本用数字说话而不是用眼睛判断。参数改动有没有退化一跑就知道答辩时把这个识别率表格甩出来比任何口头解释都有力。这套源码和模型文件可以直接拿去跑希望帮到你。本文还有配套的精品资源点击获取
返回列表