ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenCV+Python车牌识别实战:hyperlpr双路识别与避坑指南

OpenCV+Python车牌识别实战:hyperlpr双路识别与避坑指南 简介面向计算机视觉、人工智能方向的开发者和学生这份车牌识别资源包提供了一套覆盖车牌检测、识别、搜索与数据库比对功能的完整实践方案。压缩包共139个文件约73.86MB以18个Python脚本为核心实现多种识别流程50个jpg与32个png等图片文件用于测试本地车牌图并可扩展网络图片地址、实时截图和摄像头拍照识别同时包含SVM数据模型dat/yml、环境配置说明、Docker部署文件、4个Markdown文档和2个GIF动态演示方便快速搭建运行环境并直观了解识别效果。资源支持车牌搜索识别、对比识别、车牌数据库认证与图文搜索系统还提供HyperLPR提高识别率的方法与图片自适应窗口大小处理让用户能直接配置环境进行简单有效的车牌识别实验。已有1608人学习下载适合作为毕业设计参考或快速搭建项目原型的实用工具包。1. 车牌识别项目OpenCV 加 Python 就能跑通的最小方案不少做车牌识别的人第一反应是上 YOLO、训练自己的检测模型结果光标注数据就花了半个多月模型还未必收敛。实际在停车场、门禁、小区出入口这类画面相对固定的场景里OpenCV 加 Python 搭配 hyperlpr 这个开源车牌识别库再兜底一个百度车牌识别 API已经能把“识别出车牌号”“把结果入库”“按车牌搜索”三条核心链路跑通。这个项目做的就是这件事本地图片、网络图片、摄像头截图都能作为输入识别蓝牌、绿牌、黄牌识别结果可以写进 SQLite 做对比认证。适合刚接触计算机视觉的开发者也适合毕设里需要一个“能跑、能演示、能讲清原理”的车牌识别系统。它能解决的诉求很直接给一张图返回车牌号并且告诉你这个号牌是不是在白名单里。2. 搭建环境与第一个可复现 Demo依赖版本、窗口自适应与识别链路项目里带了 Dockerfile、svm.dat、svmchinese.dat 这些模型文件理论上可以直接构建镜像跑。但我的习惯是先在本地建一个干净虚拟环境因为 hyperlpr 这类老牌识别库对 OpenCV 版本非常敏感一个版本号没锁对就能在调用识别函数时直接抛 cv2.error。虚拟环境里翻车了删掉重建就是不影响系统 Python。2.1 依赖清单与安装命令python -m venv plate_env source plate_env/bin/activate # Windows 用 plate_env\Scripts\activate pip install --upgrade pip pip install numpy pillow requests pip install opencv-python3.4.2.16 pip install hyperlpr依赖安装的重点是 opencv-python 的版本。hyperlpr 内部调用的是旧版 OpenCV 接口直接装最新版 opencv-python 很容易在第一次识别时报CV_LOAD_IMAGE_COLOR这类找不到常量的错误。所以先把版本锁成 3.4.2.16这是我在多个车牌项目里验证过的版本搭配。numpy 和 pillow 是图像处理的基础依赖requests 用于后面拉取网络图片。装的时候注意别执行pip install opencvOpenCV 的 Python 包名是opencv-python装错包会出现ModuleNotFoundError: No module named opencv。如果你在 VSCode 里跑装完依赖后一定要确认左下角解释器选的是plate_env否则会出现“明明装好了却找不到 cv2”的尴尬。树莓派或 Ubuntu 上也不要手动编译 OpenCV直接用 pip 装预编译 wheel 就行手动编译一个 OpenCV 几个小时起步收益很低。2.2 第一个可复现的本地图片车牌识别 Demoimport cv2 from hyperlpr import HyperLPR_plate_recognition image cv2.imread(IMG_1762.jpeg) if image is None: print(图片读取失败先检查路径) exit(1) results HyperLPR_plate_recognition(image) print(results)IMG_1762.jpeg是项目里自带的示例图片也可以换成自己的车照片。results 是一个列表每个元素的结构大致是(车牌号, 置信度, 车牌类型, 四个顶点坐标)例如(粤BD12345, 0.94, blue, [(x1,y1),...])。第一项是车牌字符串第二项是置信度第三项是 blue/yellow/green 这样的类型标记。这里要特别强调项目里的svm.dat和svmchinese.dat是 hyperlpr 初始化时要加载的 SVM 模型文件不要删掉也不要挪到别处。如果模型文件缺失识别函数不会立刻报错而是返回空列表看起来像“黑匣子”实际是模型没加载上。第一次能打印出车牌号说明环境已经通了如果报错问题基本集中在 OpenCV 版本或中文路径上第四章会专门讲。2.3 图片自适应窗口大小等比缩放再显示def show_image_auto(name, img, max_width800, max_height600): h, w img.shape[:2] scale min(max_width / w, max_height / h, 1.0) if scale 1.0: img cv2.resize(img, (int(w * scale), int(h * scale)), interpolationcv2.INTER_AREA) cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows()cv2.imshow默认按原像素 1:1 显示一张 4000x3000 的停车场照片会把窗口撑出屏幕范围而且看不到全貌。这个函数通过scale min(max_width / w, max_height / h, 1.0)计算等比缩放系数只缩小不放大。如果图片本身小于窗口就保持原尺寸放大显示会让车牌边缘发虚反而影响观察。max_width和max_height控制最大显示尺寸800x600 是我常用的值。显示用的缩放图只给人看真正传给 hyperlpr 识别的一定要用原图缩放后的图在车牌字符细节上有损失识别率会下降。2.4 把识别结果可视化画框与调试输出def draw_plate(image, result): plate, confidence, plate_type, vertices result pts [(int(v[0]), int(v[1])) for v in vertices] cv2.polylines(image, [pts], isClosedTrue, color(0, 255, 0), thickness2) label f{plate} {confidence:.2f} cv2.putText(image, label, (pts[0][0], pts[0][1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) return imagehyperlpr 返回的 vertices 是车牌区域的四个顶点坐标按顺序连起来就是车牌的包围框。polylines的isClosedTrue让四条边闭合否则只画三条边。putText把识别出的车牌号和置信度写在框上方。调试时把这些带框图片存到本地比只看 print 结果直观得多。项目里的 log.gif 和 duibi.gif 本质上就是这种调试输出的对比图自己跑一遍后就能理解那些 gif 里为什么有的框准、有的框偏。3. 把识别结果做成系统车牌数据库认证与图文搜索的落地方案识别出一张图里的车牌只是第一步。项目里反复提到“车牌数据库认证系统”“车牌图文搜索系统”说明它的目标不是单张识别而是识别之后能精确查找、重复判断、白名单认证。我把这套逻辑拆成数据库结构、车牌搜索、对比认证、多样化输入适配四个部分下面逐个落地。3.1 用 SQLite 建一张车牌记录表import sqlite3 conn sqlite3.connect(plate_records.db) conn.execute( CREATE TABLE IF NOT EXISTS plate_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, plate TEXT NOT NULL, plate_type TEXT DEFAULT , image_path TEXT DEFAULT , confidence REAL DEFAULT 0, recognized_at TEXT DEFAULT (datetime(now, localtime)) )) conn.commit()plate 字段存识别出的车牌号plate_type 存蓝牌黄牌绿牌image_path 存图片来源方便回溯confidence 存置信度用于筛选低质量记录recognized_at 默认写当前本地时间后面按天统计进出场可以直接用它做分组。选 SQLite 而不是 MySQL是因为单机摄像头场景下 SQLite 单文件、零配置、不需要启动数据库服务等以后并发量上来了再换 MySQL表结构不用大改。这种结构化存法对应 Python 里常说的“结构化数据”处理思路把非结构的图片识别结果转成一行行可查询的字段。3.2 车牌搜索识别先统一格式再精确匹配def find_target(results, target): target_clean target.replace( , ).replace(-, ).upper() for plate, confidence, plate_type, *_ in results: plate_clean plate.replace( , ).replace(-, ).upper() if plate_clean target_clean: return plate, confidence return Nonehyperlpr 识别结果里偶尔会带空格比如“粤B D12345”直接拿字符串比较永远匹配不上。所以比较之前先把空格和连字符去掉再统一转大写。这套清洗逻辑要写成一个函数搜索、入库、认证三处共用否则就会出现“存进去一个样查的时候另一个样”的脏数据问题。如果只记得车牌后半段就用模糊搜索兜底。SQLite 的 LIKE 查询天然支持这类需求def fuzzy_search(conn, keyword): return conn.execute( SELECT * FROM plate_records WHERE plate LIKE ? ORDER BY recognized_at DESC, (f%{keyword}%,) ).fetchall()模糊搜索适合用户只输入“12345”就把所有含这段的车牌都捞出来的场景。注意 LIKE 的%是通配符keyword 里如果有%或_需要转义否则会被当成通配符。3.3 对比识别与数据库认证白名单与去重逻辑对比识别系统的本质是拿当前识别结果和历史库或白名单做比对。常见流程是识别 → 精确匹配 → 无匹配再模糊匹配 → 记录。认证逻辑可以收敛成一个函数def auth(conn, plate, whitelist, confidence0.7): if confidence 0.7: return reject, 置信度过低 plate_clean plate.replace( , ).replace(-, ).upper() if plate_clean in set(w.replace( , ) for w in whitelist): return allow, 白名单用户 return deny, 未登记第一步先过滤低置信度结果防止模糊画面误放行。白名单建议从数据库加载一次放到内存里不要每次认证都查库高频进出时性能差别很大。对比识别还有一个用途是去重同一个车牌在短时间内连续出现多次可能是相机抖动或车辆停留。比较稳妥的做法是记录首次出现时间和最后出现时间判断车辆是否真的重新进场而不是单纯看识别次数。3.4 网络图片地址识别与摄像头拍照识别网络图片地址识别本质上是把图片下载到内存再交给 OpenCV 解码import requests import numpy as np def url_to_image(url, timeout10): resp requests.get(url, timeouttimeout, headers{User-Agent: Mozilla/5.0}) resp.raise_for_status() img_array np.frombuffer(resp.content, dtypenp.uint8) return cv2.imdecode(img_array, cv2.IMREAD_COLOR)为什么不直接用open(url)requests 拿到的是字节流cv2.imdecode能从内存直接解码成 cv2 图片省去写临时文件的步骤。timeout 设 10 秒防止某个 URL 挂起拖垮整个程序给一个常见 UA 头防止部分图床拒绝空 User-Agent。网络图片识别适合对接停车场监控的截图接口输入是 URL 字符串输出直接进识别和入库流程。摄像头拍照识别更简单但要注意预热cap cv2.VideoCapture(0) for _ in range(5): cap.read() # 抛弃前几帧 ok, frame cap.read() if ok: frame cv2.resize(frame, (800, int(frame.shape[0] * 800 / frame.shape[1]))) results HyperLPR_plate_recognition(frame) cap.release()VideoCapture(0) 的 0 是默认摄像头第二个摄像头改成 1。预热 5 帧很关键很多 USB 摄像头刚打开时首帧是黑的直接 read 会识别空图。1080P 画面直接丢给 hyperlpr一帧可能要 100 到 300 毫秒做实时识别时先把宽度压到 800速度能快一倍识别率不会明显下降。4. 车牌识别避坑指南hyperlpr 兼容性、中文路径与识别率翻车记录这一章是最想让你先看的部分。hyperlpr 和 OpenCV 的兼容性问题几乎每个人都会遇到而且它不是那种一眼能看懂的报错更多是“看起来没问题结果全空”。下面按现象、原因、解决整理成四条踩坑记录。4.1 hyperlpr 与 OpenCV 版本冲突新版本上的经典翻车现象调用HyperLPR_plate_recognition时抛cv2.error网上搜到的日志里经常带着Opencv(4.4.0)的编译路径也有一部分是前面识别正常画框时找不到CV_FONT_HERSHEY_SIMPLEX这类常量。原因hyperlpr 是很早之前的项目底层调用的是旧版 OpenCV 接口和常量名。新版 OpenCV 4.x 把部分常量移出了公共命名空间调用处直接抛异常。解决锁版本重装pip uninstall opencv-python -y pip install opencv-python3.4.2.16装完用pip list | grep opencv确认当前版本。如果同时装了 opencv-contrib-python也要降到对应版本两个包不要混装。我从不在车牌识别项目里追最新版 OpenCV这不是配置升级的问题而是 hyperlpr 这类老库的生存土壤就是旧版本。4.2 中文路径和文件名编码问题现象cv2.imread(D:/停车场/车牌.jpg)返回 None文件明明存在但程序就是不识别。原因OpenCV 的 imread 对中文路径支持不完整遇到非 ASCII 字符时静默失败不报错只返回 None。解决用np.fromfile读字节流再交给cv2.imdecodeimport numpy as np import cv2 def imread_chinese(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)保存带中文路径的图片也同理cv2.imwrite会失败要换cv2.imencode加tofiledef imwrite_chinese(path, img): ext path.rsplit(., 1)[-1] ok, buf cv2.imencode(. ext, img) buf.tofile(path)imencode返回布尔值和字节缓冲tofile写盘不经过 OpenCV 的文件接口从而绕开中文路径问题。我的习惯是项目里所有图片读写统一走这两个函数一次把坑填平不做例外。4.3 识别率不稳定单帧结果跳变与多帧投票现象同一张图多跑几次车牌号总在几个字符之间跳变“鲁B”被识别成“鲁D”单帧置信度 0.9 但结果还是错的。原因车牌倾斜、光照不均、画面模糊都会影响单帧识别。hyperlpr 是逐帧识别没有时间维度的决策某一次识别结果差不能代表整体能力。解决摄像头场景做多帧投票别拿一帧定死结果。from collections import Counter def vote_plates(plates, top_n3): return Counter(plates).most_common(top_n)连续取 5 帧每帧识别一次拿到 5 个车牌号取出现次数最多的作为最终结果。如果最高票数只有 2 票且剩下的各不相同说明画面质量太差这时应该提示“重新对焦或补光”而不是硬猜一个结果。另外不要过度预处理hyperlpr 内部已经内置了灰度化和归一化你再做一次二值化反而会丢掉字符信息。我一般只在画面明显偏暗时做 CLAHE 增强其他情况直接喂原图。4.4 装错包和模型文件缺失ModuleNotFoundError 的两种常见来源现象执行识别时报ModuleNotFoundError: No module named hyperlpr或者No module named opencv。原因第一种是环境里确实没装 hyperlpr第二种是装错了包名有些人执行了pip install opencv而正确包名是opencv-python。还有一种隐蔽情况是 VSCode 里解释器没切换虚拟环境装好了但运行的 Python 不是虚拟环境里的那个。解决先诊断再补装。which python pip list | grep -iE opencv|hyperlpr确认当前解释器和已装包然后执行pip install hyperlpr pip install opencv-python3.4.2.16关于模型文件svm.dat 和 svmchinese.dat 要放在 hyperlpr 能读取到的路径下。最简单的方式是保持项目目录结构不动不要自作聪明把模型文件单独复制到自定义路径。如果你在项目里还看到 ddddocr、rapidocr 之类的 OCR 库它们是另一条识别路线不能替代 hyperlpr 的车牌定位装了它们也不代表 hyperlpr 被安装。缺什么看报错信息一个一个补不要一次装一堆。5. 提高识别率的组合方案hyperlpr 与百度 API 双路识别只靠 hyperlpr 单路识别在干净画面上没问题但遇到逆光、轻微模糊、特殊车牌时结果会开始飘。这个项目摘要里明确提到了“使用 hyperlpr 提高识别率”又提到“利用 opencv 和百度的 API”。我的理解是hyperlpr 做本地第一判断百度 API 做云端兜底两路结果互相验证。5.1 hyperlpr 的边界什么时候不需要上大招hyperlpr 的定位是车牌专用识别。它通过 SVM 模型也就是项目里的 svm.dat做车牌候选区定位再用字符识别模型输出结果。优点是离线、模型小、对蓝牌和绿牌支持不错缺点是遇到大角度车牌、夜间反光、模糊画面时置信度会明显下降。如果你需要的是通用 OCR比如识别行驶证、发票上的所有文字rapidocr 这类方案也能做但在纯 CPU 上跑通用 OCR 太吃 CPU实时性反而不如 hyperlpr。车牌识别和通用 OCR 是两条路线别混用。固定机位停车场、门禁、小区出入口的画面角度相对固定hyperlpr 完全够用只有结果不可信时才需要云端 API 兜底。5.2 百度车牌识别 API 的接入与配额管理百度智能云的车牌识别接口是典型 REST 风格先拿 token再调识别接口。官方给出的接口地址格式如下用自己的 api_key 和 secret_key 替换即可import requests import base64 TOKEN_URL https://aip.baidubce.com/oauth/2.0/token OCR_URL https://aip.baidubce.com/rest/2.0/image-classify/v1/license_plate def get_token(api_key, secret_key): resp requests.post( TOKEN_URL, params{ grant_type: client_credentials, client_id: api_key, client_secret: secret_key, }, ) return resp.json()[access_token] def baidu_plate(path, api_key, secret_key): token get_token(api_key, secret_key) with open(path, rb) as f: img_base64 base64.b64encode(f.read()).decode() resp requests.post( OCR_URL, params{access_token: token}, data{image: img_base64}, timeout10, ) return resp.json()api_key 和 secret_key 在百度智能云控制台里创建应用后就能拿到。图片必须转 base64 字符串传给接口这是云端 API 的通用要求。配额管理是重点免费额度有限千万不能每帧都调云端 API。我的做法是本地 hyperlpr 的置信度低于阈值时才走百度正常情况下云端调用量可以压得很低。5.3 双路识别策略置信度优先与交叉验证双路识别的核心是定一个阈值决定 hyperlpr 的结果能不能直接信def recognize_with_fallback(image, api_key, secret_key, threshold0.75): hyper None try: result HyperLPR_plate_recognition(image) if result: hyper result[0] except Exception as e: print(hyperlpr error:, e) if hyper and hyper[1] threshold: return hyper[0], hyper[1], hyperlpr baidu baidu_plate_from_cv(image, api_key, secret_key) number baidu.get(words_result, {}).get(number, ) if number: return number, baidu.get(words_result, {}).get(probability, 0), baidu return None, 0, nonethreshold 从 0.75 开始调。画面越干净可以调到 0.85越脏就降到 0.65具体值要看你的验证集数据。如果做认证系统对误放行零容忍就用交叉验证hyperlpr 和百度 API 两路结果完全一致才放行不一致的全部标记“待人工确认”。这种策略牺牲了一点通过率换来了更干净的数据库。5.4 车牌过滤规则正则与应用层校验不管哪一路识别返回的结果最后都要过应用层校验否则“粤B1234I”和“粤B12341”这种相似字符串会污染数据库。车牌格式有固定规律省份汉字加一位大写字母后面跟 5 到 6 位字母数字。蓝牌和黄牌是 7 位新能源绿牌是 8 位import re PLATE_PATTERN re.compile(r^[\u4e00-\u9fa5][A-Z][A-Z0-9]{5,6}$) def normalize_plate(plate): plate plate.replace( , ).replace(-, ).upper() plate plate.replace(O, 0).replace(I, 1) return plate def is_valid_plate(plate): return bool(PLATE_PATTERN.match(normalize_plate(plate)))正则里\u4e00-\u9fa5匹配省份汉字第二位是大写字母后面 5 到 6 位字母数字。字母 O 和 I 在真实车牌里基本不用但识别模型容易把数字 0 和 1 识别成 O 和 I归一化这一步能显著减少脏数据。所有进入数据库的车牌都先过 normalize_plate 再存查询时也走同一套归一化两边格式一致才可能精确匹配。6. 实时截图识别坐标裁剪与定时抓取做成顺手的小工具很多场景底下你拿不到摄像头 API只有一段视频流或者一个已经打开的监控客户端窗口。这时候最省事的办法是直接截屏。用 pyautogui 截取屏幕指定区域再把区域转成 OpenCV 图像做识别import pyautogui import cv2 import numpy as np def screenshot_plate(region(0, 0, 1280, 720)): screen pyautogui.screenshot(regionregion) frame cv2.cvtColor(np.array(screen), cv2.COLOR_RGB2BGR) results HyperLPR_plate_recognition(frame) return frame, resultsregion 是屏幕上的(left, top, width, height)区域先开一个窗口把监控画面放在那里再把坐标填进去。pyautogui.screenshot 返回的是 PIL 图像OpenCV 用的是 BGR所以必须转颜色空间否则画面会偏蓝。识别结果出来后通过第 2 章的 draw_plate 函数把车牌框画回 frame再显示。验证时可以挑一段循环播放的车流视频跑 5 分钟统计识别成功帧数和误检率顺手把识别结果写进第三张表的 SQLite这就有了自己的回归样本库。从那以后我每次跑这种识别项目都会先强制走一遍固定流程选一个固定场景拍 5 张图连续识别 20 次把每次都错的字符记到一张表里。很土但这是最便宜的后悔药——等摄像头接上再回头查问题耗掉的时间就不止这 10 分钟了。希望帮到你。本文还有配套的精品资源点击获取
返回列表