
简介面向需要快速落地车牌识别方案的开发者和计算机视觉学习者这份资源覆盖从车牌定位、字符识别到数据库比对的完整流程。基于OpenCV与百度API实现轻量级识别集成HyperLPR进一步提高准确率支持本地图片、网络图片地址、实时截图、摄像头拍照识别同时提供车牌搜索、对比识别、数据库认证、图文检索等功能模块并对图片自适应窗口显示做了适配。包内共139个文件以jpg/png/jpeg图像样本、Python脚本、YML配置文件及Markdown说明文档为主压缩包约73.86MB目录结构清晰便于直接对照代码、样例与配置说明运行调试。资源附带多样化车牌图像样本可用于测试不同光线、角度和复杂背景下的识别效果从环境准备、模型配置、接口调用到识别模块扩展均有可参考实现适合课程设计、毕设演示或小型停车场管理等入门到进阶项目。目前已有1608人学习下载。1. 车牌识别不只是调库这份 OpenCV Python 工程能直接改成你的业务拿到这份工程前我也以为车牌识别就是pip install hyperlpr然后三行代码出结果。真正跑起来才发现识别的下半场全在工程里图片、截图、摄像头、网络图片地址七种输入要统一走同一条识别管线SVM 和 HyperLPR 要按置信度做融合识别结果还要落进一个能按车牌号检索、比对的数据库逻辑里。这套基于 OpenCV Python 的车牌识别代码把「单张图出车牌」和「找某个车牌」之间的所有环节都补齐了适合已经会写 Python 基础语法、想直接上手计算机视觉业务项目的开发者。我把几个关键模块拆开跑了一遍把环境、参数和坑都记在下面。2. 环境搭建与工程目录先把依赖和配置文件理顺2.1 Python 环境我为什么建议用虚拟环境而非全局安装工程依赖 OpenCV、HyperLPR、百度 API SDK 和基础的科学计算库。最常见的翻车现场是全局环境里已有不同版本的 numpy 或 opencv-python导致 HyperLPR 里的模型加载时报ValueError或者 cv2 的dnn模块不可用。我习惯按项目隔离环境Windows 和 Linux 都适用python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install --upgrade pip pip install opencv-python opencv-contrib-python numpy requests python-dotenv这里要说明两点。opencv-contrib-python包含cv2.ximgproc等扩展模块本项目主流程没有强制调用但后续你要对车牌图像做形态学处理、超分辨率增强时大概率会用到。python-dotenv用来读取.env配置文件工程里的百度 API Key 就是从这个文件加载的也建议你把密钥放到环境变量里而不是写死在代码中。如果你跑的是 Docker 部署仓库里提供了 Dockerfile 和 .env.docker 示例。宿主机架好容器后把 .env 映射进去即可注意镜像内必须单独安装 libgl1 和 libglib2.0-0否则 OpenCV 的imshow和VideoCapture会报找不到共享库的错误。2.2 依赖文件与模型文件清单哪些不能删工程根目录下有svm.dat和svmchinese.dat两个文件这是训练好的 SVM 分类模型分别负责普通车牌字符和汉字字符的分类。这两个文件在字符识别阶段会被cv2.ml.SVM_load()直接加载删掉或路径不对会出现error: (-212:Parsing error)。.env文件里需要配置百度 API 的API_KEY和SECRET_KEY代码会拿它换 access_token网络图片地址识别和文字识别兜底都走这条路。.env.docker是容器环境的模板字段一样。IMG_1762.jpeg、IMG_1872.jpeg是测试原图duibi.gif和log.gif是运行效果录屏可以帮你快速判断自己的输出结果对不对。# 目录结构关键文件速览 ├── svm.dat # 普通字符 SVM 模型 ├── svmchinese.dat # 中文汉字 SVM 模型 ├── .env # 百度 API 密钥配置 ├── Dockerfile # 容器化部署文件 ├── IMG_1762.jpeg # 测试图 └── duibi.gif # 识别效果演示依赖装完后先跑一张测试图。如果输出端出现了车牌的省份简称和字母数字组合说明 SVM 模型加载成功、识别主链路是通的。此时再去看识别准确率如果不理想问题大概率出在图像预处理参数上而不是模型文件损坏。3. 识别主流程从车牌定位到 SVM 字符分类的完整管线3.1 车牌定位OpenCV 颜色空间与轮廓筛选的配合车牌的准确定位是整套系统里最依赖图像预处理经验的一步。项目用的是「HSV 颜色空间提取蓝色区域 轮廓筛选」的方案这也是 OpenCV 车牌识别项目里最经典、最容易调参的策略。import cv2 import numpy as np def locate_plate(img): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 蓝色车牌 HSV 范围H 通道 100~124S 和 V 留出余量 mask cv2.inRange(hsv, (100, 100, 50), (124, 255, 255)) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5))) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) if w 80 and h 20 and 2.2 w / h 5.5: candidates.append((x, y, w, h)) return candidates这套定位逻辑的关键参数有两个。第一个是inRange的 HSV 阈值蓝色车牌的 H 通道通常落在 100124但我见过偏绿和偏紫的车牌实拍图里如果定位不到优先把 S 下界从 100 调到 80V 下界从 50 调到 40。第二个是宽高比范围标准蓝牌尺寸 440×140宽高比约 3.14所以 2.25.5 这个区间能覆盖大部分倾斜和畸变场景。MORPH_CLOSE的结构元素宽高比设置为 15×5是为了弥合车牌字符之间的空隙让整个车牌区域连成一个整体否则字符间隙可能把车牌轮廓切断。如果视频流里的车牌没定位到最常见原因是运动模糊导致 HSV 颜色提取出的区域不完整。这时可以把inRange里的 S 阈值再放宽到 60代价是背景里蓝色的广告牌、指示牌也会进入候选框靠后级的 SVM 过滤掉误检。3.2 字符分割与 SVM 识别svm.dat 和 svmchinese.dat 的分工定位到车牌区域后先对灰度图做二值化再通过垂直投影做字符分割。每分割出一个字符块就归一化到 SVM 需要的尺寸分别喂给svm.dat和svmchinese.dat。第一个字符是汉字必须走svmchinese.dat其余字母和数字走svm.dat这是两个模型分开训练的原因。def recognize_chars(plate_img, model_cn, model_en): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) chars split_by_vertical_projection(binary) # 垂直投影切分 result [] for idx, char_img in enumerate(chars): char_img cv2.resize(char_img, (20, 20)) char_img char_img.reshape(1, -1).astype(np.float32) if idx 0: result.append(model_cn.predict(char_img)[1][0]) else: result.append(model_en.predict(char_img)[1][0]) return .join(result)split_by_vertical_projection是按列统计黑色像素数找到像素数接近 0 的列作为字符边界。这里有个现实问题车牌上的圆点和铆钉会在投影图上造成假边界需要在分割时按字符宽度阈值过滤。字符归一化尺寸是 20×20这是很多经典车牌识别论文里验证过的尺寸既能保留笔画细节又不至于让 SVM 特征维度过高。SVM 的置信度输出其实没有直接暴露在最终结果里代码里只在所有字符预测完成后判断总长度是否等于 7。如果车牌被倾斜或者光照不均导致二值化后字符黏连分割出的字符数会少于 7这时就该进入 HyperLPR 兜底流程。3.3 HyperLPR 兜底第二套识别管线如何融合进来工程里把 HyperLPR 作为识别率增强手段而不是主力这是我比较认可的设计。SVM 的优点是轻量、可解释缺点是只认你训练过的字体和排版一旦遇到新能源绿牌、使馆黑牌、或者倾斜超过 15 度的图准确率会明显下降。HyperLPR 使用端到端的深度学习模型对这类干扰更鲁棒。from hyperlpr import HyperLPR def recognize_with_hyperlpr(plate_img): result HyperLPR(plate_img) if result and result[0] and result[0][plate_no]: return result[0][plate_no] return None实际用法是先跑 SVM 流程如果识别出的字符串长度不等于 7或者包含非车牌字符就切到 HyperLPR。反过来HyperLPR 的返回值里通常带 confidence 字段只有置信度高于 0.6 才采纳否则保留 SVM 结果。这种双保险在大批量测试里识别率能比单用 SVM 提升 8 到 12 个百分点代价是单张识别耗时从 80 毫秒涨到 200 毫秒左右对于人工审核场景完全可接受。4. 多路输入适配本地图片、网络地址、实时截图和摄像头怎么统一入口4.1 统一输入层设计一切皆 ndarray工程里最实用的设计是七种输入方式共用同一个识别函数。无论传入的是本地路径、URL 还是摄像头帧都先转成 numpy 的 ndarray再交给主流程处理。这样做的收益是换输入源时不需要改识别逻辑只改输入适配器。def load_image_from_source(source): if source.startswith(http): # 网络图片地址请求后解码为 ndarray resp requests.get(source, timeout5) arr np.frombuffer(resp.content, np.uint8) return cv2.imdecode(arr, cv2.IMREAD_COLOR) if source clipboard: # 从剪贴板读取截图 img ImageGrab.grabclipboard() return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) if source camera: # 摄像头取帧 cap cv2.VideoCapture(0) ret, frame cap.read() cap.release() return frame return cv2.imread(source) # 本地文件网络图片识别那块工程里同时接入了百度 API。原因是有些网络图片经过了压缩、加水印OpenCV 的定位算法很难找到车牌区域此时请求百度车牌识别接口做兜底代价是单张耗时 500 毫秒以上但胜在稳定。4.2 实时截图与图片自适应窗口配合 C 盘路径注意编码实时截图识别依赖pyautogui或PIL.ImageGrab截取屏幕指定区域然后立刻交给识别管线。这里最容易被忽略的是截图区域得用region参数固定否则全屏截图会导致车牌区域太小、inRange提取不到蓝色像素块。常见做法是先把窗口放大到实际大小再截取窗口内的固定区域。图片自适应窗口大小则在imshow时用cv2.namedWindow(plate, cv2.WINDOW_NORMAL)否则 4K 屏上显示一张 800 万像素的原图窗口会超出屏幕边界看着像程序卡死。这只是显示层的小技巧但它直接影响你肉眼判断识别结果对不对。摄像头识别需要注意分辨率设置cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)和cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)能显著提高远景车牌的识别率。默认 640×480 的画面里车牌宽通常不到 100 像素定位算法的w 80阈值可能正好被卡掉。5. 避坑清单从环境报错到识别失灵的常见问题5.1 报错No module named cv2环境和安装在作怪现象import cv2直接抛 ModuleNotFoundError。原因分三种当前 shell 激活的不是项目虚拟环境虚拟环境里安装的包名和导入名不一致pip install 过程中网络中断导致安装不完整。这种情况我十次里有六次是环境激活问题。解决先用which python确认解释器路径指向虚拟环境再用pip list | grep opencv确认 opencv-python 在列表里。如果都没有问题在项目根目录执行python -c import cv2; print(cv2.__version__)。如果到这里才报错那是 python 命令和 pip 指向了不同的环境用python -m pip install opencv-python重新安装即可。5.2 模型加载时报svmchinese.dat解析错误现象程序启动时提示error: (-212:Parsing error) Unknown node.或者模型加载路径找不到。原因工作目录不在工程根目录。cv2.ml.SVM_load(svmchinese.dat)用的相对路径你如果从别的目录执行脚本文件就找不到了。另一种原因是下载时文件损坏两个 dat 文件大小应该都在几百 KB 到几 MB 之间过小的文件基本是坏的。解决代码开头用os.chdir(os.path.dirname(os.path.abspath(__file__)))把工作目录固定到脚本所在目录。如果这样还报错重新下载模型文件并对比文件 MD5。5.3 识别结果只有 6 位或 8 位字符现象蓝牌车牌应该是 7 位但输出结果是 6 位或缺汉字。原因二值化阶段车牌图像过暗字符与底色粘连垂直投影分割把两个字符当成了一个。此外新能源绿牌是 8 位如果你拿绿牌测试蓝牌模型切分逻辑和模型类别都会错位。解决二值化前先对车牌区域做直方图均衡化cv2.equalizeHist(gray)同时检查定位阶段宽高比绿牌宽高比约 2.4 到 3.0需要单独适配。工程默认按蓝牌处理绿牌要走 HyperLPR 兜底。5.4 视频流识别偶发闪退截图后单帧正常现象摄像头连续识别能跑几分钟然后程序直接退出没有任何报错。原因摄像头缓冲区堆积。VideoCapture在低速识别场景下如果处理一帧耗时超过 100 毫秒USB 摄像头的环形缓冲会被占满旧帧的 buffer 自然溢出OpenCV 在 Windows 下会触发底层访问冲突。解决处理完一帧后立即cap.grab()丢弃缓冲区的下一帧或者开一个线程专门读帧主线程只负责识别。我自己更常用后一种方案把摄像头帧读入队列识别线程每次从队列取最新帧保证不会堆积到溢出。6. 验证与调优方法A/B 对比后我留下的参数习惯6.1 识别率验证用测试图和录屏建立基线工程自带的IMG_1762.jpeg和IMG_1872.jpeg就是两张标准测试图duibi.gif是运行效果录屏。拿到代码后先别急着改参数把原效果跑出来作为基线后续每次调参都和这个基线比对。我习惯准备一个 20 张图的测试集包含白天顺光、夜晚、倾斜三种场景每张图对应一个标注好的车牌号。跑一遍记录失败样本再针对失败样本反向调参与。# 批量预测脚本片段 import os, csv results [] for name in os.listdir(test_imgs): img cv2.imread(os.path.join(test_imgs, name)) plate recognize_main(img) # 主流程函数 gt name.split(_)[0] # 文件名前缀为标准答案 results.append((name, plate, plate gt)) print(acc , sum(r[2] for r in results) / len(results))建议这个脚本单独保存为evaluate.py每次改动预处理参数后都重跑一遍比对着单张图「看着好了」可靠得多。我在自己的测试集上用这套方法把定位失败率从最初的 25% 压到了 7%代价是每次调参都要跑十分钟。6.2 参数调优的两个快速习惯我在调参与中得出的两个习惯对你的识别率影响最直接。第一个是 HSV 蓝色范围用上下界组合调不要只改 H 通道S 和 V 的联动调整对逆光场景尤其有效。当 S 下界从 100 降到 80 时V 下界也同步降到 40否则偏暗环境下的蓝色像素会被 V 通道过滤掉。第二个是形态学结构元素尺寸跟着分辨率走。处理 1920×1080 的截图时15×5 的 MORPH_RECT 有点小我会放大到 25×9处理 640×480 的摄像头帧时缩小到 11×3 反而效果更好。这个没有固定公式我的经验是结构元素的宽度约等于车牌宽度的 1/30高度约等于 1/80。我最后保留的融合策略是SVM 结果长度合法且置信度最高字符大于阈值时直接采用否则把 HyperLPR 结果作为最终输出如果两者结果都不合法标记为「待人工确认」。这个逻辑在双模型之外又加了一道兜底也让我后来做车牌数据库检索时少了很多误匹配。从那次以后每次拿到新环境我都强制走一遍批量预测脚本不对比就不上线。希望这套流程和踩坑记录能帮你的车牌识别项目少走几步弯路。本文还有配套的精品资源点击获取