1、HyperLPR3简介
HyperLPR3是一款高性能开源中文车牌识别。源码:https://github.com/szad670401/HyperLPR
2、快速部署与简单使用
参考文章:HyperLPR3车牌识别-五分钟搞定: 中文车牌识别光速部署与使用
3、利用OpenCV对RTSP视频流实时读取
我是参考yolo5的streamload使用python迭代器实现视频流读取,视频流读取部分可以按照自己想法继续优化,例如视频流读取分离。
dataset.py
- import math
 - import time
 - import threading
 - import platform
 - import cv2
 - import numpy as np
 -  
 - from utils.my_logger import logger
 -  
 - class LoadStreamsByRTSP:
 -     
 -     def __init__(self,camera_list):
 -  
 -         self.camera_list = camera_list
 -         
 -         n = len(camera_list)
 -         # 原尺寸图像 初始化图片 fps 总帧数(无穷大) 线程数 相机ip数组 
 -         self.orgImgs,self.imgs, self.fps, self.frames, self.threads,self.camIps,self.ids =[None] * n, [None] * n, [0] * n, [0] * n, [None] * n,[None]*n,[None]*n
 -         self.sources = []  # clean source names for later
 -         self.countFrm = {} #统计帧数,索引,帧数
 -         for i,item in enumerate(camera_list):
 -             # Start thread to read frames from video stream
 -             st = f'{i + 1}/{n}: {item[0]}... '
 -             rtspUrl = item[2]
 -             cap = cv2.VideoCapture(rtspUrl)
 -             if cap.isOpened():
 -                 print(f'{rtspUrl} open success !')
 -                 self.sources.append(rtspUrl) # rtsp地址
 -             # 如果当前Rtsp流打开失败,记录错误日志,并循环下一个流
 -             else :
 -                 logger.error(f'{st}Failed to open {item[0]}')
 -                 continue
 -  
 -             w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 获取视频宽度
 -             h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 获取视频高度
 -             fps = cap.get(cv2.CAP_PROP_FPS)  # warning: may return 0 or nan
 -             self.frames[i] = max(int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float('inf')  # infinite stream fallback
 -             self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30  # 30 FPS fallback
 -             _, self.imgs[i] = cap.read()  # guarantee first frame
 -             self.orgImgs[i] = self.imgs[i].copy()
 -             self.countFrm[i] = 0
 -             self.camIps[i] = item[0] # 记录当前相机IP,用于业务判断
 -             self.ids[i] = item[1] # 记录当前相机编号
 -             self.threads[i] = threading.Thread(target=self.update, args=([i, cap, rtspUrl]), daemon=True)
 -             logger.info(f"{st} Success ({self.frames[i]} frames {w}x{h} at {self.fps[i]:.2f} FPS)")
 -             print(f"{st} Success ({self.frames[i]} frames {w}x{h} at {self.fps[i]:.2f} FPS)")
 -             self.threads[i].start()
 -         print(' ')  # newline
 -  
 -     def update(self, i, cap, stream):
 -         if cap.isOpened():
 -             while True:
 -                 success, im = cap.read()
 -                 if success:
 -                     self.orgImgs[i] = im #原始图像
 -                 else:
 -                     logger.warning(f'WARNING: Video stream[{stream}] unresponsive, please check your IP camera connection.15s restart stream...')
 -                     self.orgImgs[i] = np.zeros_like(self.orgImgs[i])
 -                     time.sleep(15)
 -                     cap = cv2.VideoCapture(stream)
 -                     if cap.isOpened():
 -                         logger.info(f'{stream} restart success !')
 -             #time.sleep(1 / self.fps[i])  # wait time if videofile
 -  
 -     def __iter__(self):
 -         #self.count = -1
 -         return self
 -     
 -     def initImgs(self):
 -         # 需保证imgs里没有None的图片
 -         while self.imgs[0] is None:
 -             time.sleep(1)
 -         
 -         for i,im in enumerate(self.imgs):
 -             if self.imgs[i] is None:
 -                 self.imgs[i] = np.zeros_like(self.imgs[0])
 -         while self.orgImgs[0] is None:
 -             time.sleep(1)
 -         
 -         for i,im in enumerate(self.orgImgs):
 -             if self.orgImgs[i] is None:
 -                 self.orgImgs[i] = np.zeros_like(self.orgImgs[0])
 -     
 -     def __next__(self):
 -         beginTime = time.time()
 -  
 -         orgImgs = self.orgImgs.copy()
 -  
 -         return  orgImgs, beginTime,self.camIps,self.ids
 -  
 -     def __len__(self):
 -         return len(self.sources) 
 
 
4、根据自定义修改一些默认设置(使用默认onnx推理也可跳过该步骤)
4.1、下载hyperlpr3
从Github下载Prj-Python源码,并将其中的hyperlpr3文件夹放入自己项目中。
![]()
4.2、修改模型文件夹默认位置
修改其中config文件夹下的setting.py,将模型文件夹路径设置为当前项目下,这一步也可以不做,默认是在家目录下面。
- import os
 - import sys
 -  
 - _MODEL_VERSION_ = "20230229"
 -  
 - if 'win32' in sys.platform:
 -     #_DEFAULT_FOLDER_ = os.path.join(os.environ['HOMEPATH'], ".hyperlpr3")
 -     _DEFAULT_FOLDER_ = './'
 - else:
 -     #_DEFAULT_FOLDER_ = os.path.join(os.environ['HOME'], ".hyperlpr3")
 -     _DEFAULT_FOLDER_ = './'
 -  
 - _ONLINE_URL_ = "http://hyperlpr.tunm.top/raw/"
 -  
 - onnx_runtime_config = dict(
 -     det_model_path_320x=os.path.join(_MODEL_VERSION_, "onnx", "y5fu_320x_sim.onnx"),
 -     det_model_path_640x=os.path.join(_MODEL_VERSION_, "onnx", "y5fu_640x_sim.onnx"),
 -     rec_model_path=os.path.join(_MODEL_VERSION_, "onnx", "rpv3_mdict_160_r3.onnx"),
 -     cls_model_path=os.path.join(_MODEL_VERSION_, "onnx", "litemodel_cls_96x_r1.onnx"),
 - )
 -  
 - onnx_model_maps = ["det_model_path_320x", "det_model_path_640x", "rec_model_path", "cls_model_path"]
 -  
 - _REMOTE_URL_ = "https://github.com/szad670401/HyperLPR/blob/master/resource/models/onnx/"
 
 
之后将模型文件夹复制到当前项目根目录下
![]()
5、对RTSP视频流进行实时车牌识别
plateRecognizer.py
- # 导入cv相关库
 - import cv2
 - import os
 - import json
 - import time
 - import traceback
 -  
 - # 导入依赖包
 - import hyperlpr3 as lpr3
 - from hyperlpr3.common.typedef import *
 -  
 - from utils.my_logger import logger
 - from utils.dataset import LoadStreamsByRTSP
 - import utils.plateUtil as plateUtil
 -  
 - #将10位时间戳或者13位转换为时间字符串,默认为2017-10-01 13:37:04格式
 - def timestamp_to_date(time_stamp, format_string="%Y%m%d%H%M%S"):
 -     time_array = time.localtime(time_stamp)
 -     other_style_time = time.strftime(format_string, time_array)
 -     
 -     return other_style_time
 -  
 -  
 - if __name__ == '__main__':
 -     try:
 -         camera_list = []
 -         if not os.path.exists('images'):
 -             os.mkdir('images')
 -         camPlateInfoRecord = {} #记录当前相机识别到的车牌号
 -         camPlateInfoRecordTime = {}
 -         #catcher = {} # 识别对象
 -         with open("config.json", encoding='utf-8') as f:
 -             jsonData = json.load(f)
 -         for ipkey in jsonData:
 -             camera_list.append([ipkey,jsonData[ipkey]['id'],jsonData[ipkey]['rtspUrl']])
 -         
 -         for item in camera_list:
 -             print(item)
 -             camPlateInfoRecord[item[0]] = [] # 初始化
 -             camPlateInfoRecordTime[item[0]] = None
 -             # catcher[item[0]] = lpr3.LicensePlateCatcher()
 -             
 -             
 -             if not os.path.exists('./images/'+item[0]):
 -                 os.mkdir('./images/'+item[0])
 -                 
 -             
 -         # 实例化识别对象
 -         catcher = lpr3.LicensePlateCatcher() # detect_level=lpr3.DETECT_LEVEL_HIGH
 -         dataset = LoadStreamsByRTSP(camera_list)
 -         dataset.initImgs()
 -         frameSkip = 5 # 跳帧
 -         for orgImgs, beginTime,camIps,ids in dataset:
 -             recordTime = time.time()
 -             for i,orgImg in enumerate(orgImgs) :
 -                 results = catcher(orgImg)
 -                 print(results)
 -                 # print(f'catcher Done. ({time.time() - beginTime:.3f}s)') # 打印时间
 -                 if len(results)>0:
 -                     plateInfo = sorted(results,key=lambda x : x[1], reverse=True)[0]# 根据置信度进行降序排序,并取置信度最高的一项
 -                     plateNo = plateUtil.get_plate_color(plateInfo[2]) + plateInfo[0] # 车牌号
 -                     plateConf = plateInfo[1] # 置信度
 -                     camPlateInfoRecordTime[camIps[i]] = recordTime
 -                     if plateConf > 0.97:
 -                         # TODO 可直接判定为最佳车牌
 -                         camPlateInfoRecord[camIps[i]].append((plateNo,plateConf,orgImg))
 -                         break
 -                     elif plateConf > 0.8:
 -                         camPlateInfoRecord[camIps[i]].append((plateNo,plateConf,orgImg))
 -                         pass
 -                 else:
 -                     # TODO 未识别到车牌
 -                     pass
 -                 #cv2.imshow(camIps[i], cv2.resize(orgImg,(1280,720))) # cv2.resize(im0,(800,600))
 -                 #cv2.waitKey(1)  # 1 millisecond
 -                 if camPlateInfoRecordTime[camIps[i]] is not None:
 -                     currentTime = time.time()
 -                     # 判断时间是否大于2秒
 -                     if recordTime - camPlateInfoRecordTime[camIps[i]] > 2 and len(camPlateInfoRecord[camIps[i]]) > 0:
 -                         # 选取置信度最大的车牌信息保存
 -                         savePlateInfo = sorted(camPlateInfoRecord[camIps[i]], key=lambda e: e[1], reverse=True)[0]
 -                         save_path = './images/'+camIps[i]+'/'+timestamp_to_date(int(currentTime),'%Y%m%d%H%M%S')+'_'+savePlateInfo[0]+'_'+ids[i]+'.jpg'
 -                         cv2.imencode('.jpg', savePlateInfo[2])[1].tofile(save_path)
 -                         logger.info(f"{camIps[i]} 识别到车牌:{savePlateInfo[0]},置信度:{savePlateInfo[1]},保存路径:{save_path}")
 -                         camPlateInfoRecordTime[camIps[i]] = None # 清空时间记录
 -                         camPlateInfoRecord[camIps[i]] = [] # 清空车牌记录
 -             # 显示FPS
 -             fps_time = time.time()-beginTime
 -             if(fps_time > 0):
 -                 video_fps  = int(1/fps_time)
 -                 print(f'{fps_time:.3f}s'+" fps= %.2f"%(video_fps))
 -             #进行跳帧休眠处理
 -             loopTime = int(round(fps_time * 1000))
 -             if loopTime < 40*frameSkip:
 -                 time.sleep(0.04*frameSkip-loopTime/1000)
 -     except BaseException:
 -         logger.error("处理时出错!"+traceback.format_exc())
 -         time.sleep(1)
 
 
代码解释:
1)、frameSkip = 5 ,我这里使用了跳帧方式去识别所读取到的视频流,主要是避免过多过快的车牌识别检测导致的CPU过高或者其他系统性能问题,一般情况下每5帧识别一次已经足够,可根据自身需求进行调整。
2)、车牌真实性判断逻辑,我这里是当车牌的置信度大于0.8则保存该帧的车牌信息及图像,当该视频流2s内不再出现大于0.8置信度的车牌时,保存图像及车牌信息至文件夹中。该逻辑可根据自身需求进行修改代码,例如通过有效帧次数来保存识别最佳的车牌信息等。
其他文件:
config.json
- {
 -     "192.168.**.**": {"id":"XXX001","rtspUrl":"相机的RTSP地址"}
 - }
 
 
plateUtil.py
- from hyperlpr3.common.typedef import *
 -  
 -  
 - def get_plate_color(plate_type):
 -     plateColor = "蓝"
 -     if plate_type == UNKNOWN:
 -         plateColor = '未知'
 -     elif plate_type == BLUE:
 -         plateColor = "蓝"
 -     elif plate_type == YELLOW_SINGLE:
 -         plateColor = "黄"
 -     elif plate_type == WHILE_SINGLE:
 -         plateColor = "白"
 -     elif plate_type == GREEN:
 -         plateColor = "绿"
 -     return plateColor
 
 
6、使用OpenVINO检测车牌框
pip install openvino 
6.1 转换为OpenVINO格式的模型文件
onnx2openvino.py
- # coding:UTF-8
 - import os
 -  
 - import openvino as ov
 -  
 - # 定义 ONNX 模型路径
 - onnx_model_path = "y5fu_320x_sim.onnx"
 -  
 - # 定义输出目录路径
 - output_dir = "./openvino"
 -  
 - # 检查输出目录是否存在,如果不存在则创建
 - if not os.path.exists(output_dir):
 -     os.makedirs(output_dir)
 -     print(f"Created output directory at: {output_dir}")
 - else:
 -     print(f"Output directory already exists at: {output_dir}")
 -  
 - # 设置模型转换参数
 - try:
 -     # 转换 ONNX 模型为 OpenVINO 格式
 -     ov_model = ov.convert_model(onnx_model_path)
 -  
 -     # 保存 OpenVINO 模型
 -     ir_path = './openvino/y5fu_320x_sim.xml'
 -     ov.save_model(ov_model, ir_path)
 -  
 -     
 -     print(f"Model successfully converted and saved to: {output_dir}")
 - except Exception as e:
 -     print(f"Error during model conversion: {e}")
 
 
执行后即可得到OpenVINO推理文件
![]()
6.2、添加OpenVINO推理类
找到 hyperlpr3\inference\multitask_detect.py
添加:
- class MultiTaskDetectorOpenVINO(HamburgerABC):
 -  
 -     def __init__(self, openvino_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
 -         super().__init__(*args, **kwargs)
 -         import openvino as ov
 -         self.box_threshold = box_threshold
 -         self.nms_threshold = nms_threshold
 -         # 👇Create OpenVINO Core
 -         core = ov.Core()
 -         # 👇读取模型
 -         self.model = core.read_model(model=openvino_path, weights=openvino_path.replace(".xml", ".bin"))
 -         # 👇加载模型,如果用intel的显卡就把CPU改成GPU,但是要确保你的显卡驱动安装好
 -         self.compiled_model = core.compile_model(model=self.model, device_name="CPU")
 -         self.inputs_option = self.model.input(0)
 -         self.outputs_option = self.model.output(0)
 -         
 -         #input_option = self.inputs_option[0]
 -         input_size_ = tuple(self.inputs_option.shape[2:])
 -         self.input_size = tuple(self.input_size)
 -         if not self.input_size:
 -             self.input_size = input_size_
 -         assert self.input_size == input_size_, 'The dimensions of the input do not match the model expectations.'
 -         assert self.input_size[0] == self.input_size[1]
 -         self.input_name = self.inputs_option.names
 -  
 -     def _run_session(self, data):
 -         result = self.compiled_model(data)[0]
 -         return result
 -  
 -     def _postprocess(self, data):
 -         r, left, top = self.tmp_pack
 -         return post_precessing(data, r, left, top)
 -  
 -     def _preprocess(self, image):
 -         img, r, left, top = detect_pre_precessing(image, self.input_size)
 -         self.tmp_pack = r, left, top
 -  
 -         return img
 
 
6.3、添加OpenVINO推理方法
找到 hyperlpr3\hyperlpr3.py
- from .config.settings import onnx_runtime_config as ort_cfg
 - from .inference.pipeline import LPRMultiTaskPipeline
 - from .common.typedef import *
 - from os.path import join
 - from .config.settings import _DEFAULT_FOLDER_
 - from .config.configuration import initialization
 -  
 -  
 - initialization()
 -  
 - class LicensePlateCatcher(object):
 -  
 -     def __init__(self,
 -                  detect_inference: int = INFER_ONNX_RUNTIME,
 -                  inference: int = INFER_ONNX_RUNTIME,
 -                  folder: str = _DEFAULT_FOLDER_,
 -                  detect_level: int = DETECT_LEVEL_LOW,
 -                  logger_level: int = 3,
 -                  full_result: bool = False):
 -         if inference == INFER_ONNX_RUNTIME:
 -             
 -             from hyperlpr3.inference.recognition import PPRCNNRecognitionORT
 -             from hyperlpr3.inference.classification import ClassificationORT
 -             import onnxruntime as ort
 -             ort.set_default_logger_severity(logger_level)
 -             if detect_level == DETECT_LEVEL_LOW:
 -                 if detect_inference == INFER_ONNX_RUNTIME:
 -                     from hyperlpr3.inference.multitask_detect import MultiTaskDetectorORT
 -                     det = MultiTaskDetectorORT(join(folder, ort_cfg['det_model_path_320x']), input_size=(320, 320))
 -                 elif detect_inference == INFER_OPENVINO:
 -                     from hyperlpr3.inference.multitask_detect import MultiTaskDetectorOpenVINO
 -                     det = MultiTaskDetectorOpenVINO('./openvino/y5fu_320x_sim.xml', input_size=(320, 320))
 -                 elif detect_inference == INFER_NCNN:
 -                     from hyperlpr3.inference.multitask_detect import MultiTaskDetectorNCNN
 -                     det = MultiTaskDetectorNCNN('./ncnn/y5fu_320x_sim.ncnn.bin', input_size=(320, 320))
 -             elif detect_level == DETECT_LEVEL_HIGH:
 -                 det = MultiTaskDetectorORT(join(folder, ort_cfg['det_model_path_640x']), input_size=(640, 640))
 -             else:
 -                 raise NotImplemented
 -             rec = PPRCNNRecognitionORT(join(folder, ort_cfg['rec_model_path']), input_size=(48, 160))
 -             cls = ClassificationORT(join(folder, ort_cfg['cls_model_path']), input_size=(96, 96))
 -             self.pipeline = LPRMultiTaskPipeline(detector=det, recognizer=rec, classifier=cls, full_result=full_result)
 -         else:
 -             raise NotImplemented
 -  
 -     def __call__(self, image: np.ndarray, *args, **kwargs):
 -         return self.pipeline(image)
 
 
6.4、设置为OpenVINO推理
plateRecognizer.py 中
catcher = lpr3.LicensePlateCatcher(detect_inference=INFER_OPENVINO) # detect_level=lpr3.DETECT_LEVEL_HIGH 
7、使用NCNN检测车牌框
7.1、转换为NCNN格式的模型文件
注意:网上大部分都是说要下载protobuf和ncnn源码进行编译,再使用onnx2ncnn.exe进行转换,实际验证该方法对onnx转换为ncnn格式时,会报错误Unsupported slice axes !。
建议使用pnnx方式直接转换。
pip install pnnx 
pnnx y5fu_320x_sim.onnx inputshape=[1,3,320,320] 
执行后会生成一堆文件,选择ncnn的两个文件即可
![]()
在项目根目录新建一个ncnn文件夹,并上述两个模型文件放入。
7.2、添加NCNN推理类
找到 hyperlpr3\inference\multitask_detect.py
添加:
- import ncnn
 - class MultiTaskDetectorNCNN(HamburgerABC):
 -  
 -     def __init__(self, ncnn_path, box_threshold: float = 0.5, nms_threshold: float = 0.6, *args, **kwargs):
 -         super().__init__(*args, **kwargs)
 -         self.box_threshold = box_threshold
 -         self.nms_threshold = nms_threshold
 -         # 加载ncnn模型
 -         self.net = ncnn.Net()
 -         self.net.opt.use_vulkan_compute = False
 -         self.net.load_param(ncnn_path.replace(".bin", ".param"))
 -         self.net.load_model(ncnn_path)
 -         self.input_size = (320,320)
 -  
 -     def _run_session(self, data):
 -         input_data = ncnn.Mat(data[0])
 -         # 创建提取器
 -         extractor = self.net.create_extractor()
 -         extractor.input("in0", input_data)
 -         # 执行推理
 -         ret1, mat_out1 = extractor.extract("out0")
 -         # 处理输出
 -         result = np.expand_dims(np.array(mat_out1), axis=0)
 -         return result
 -  
 -     def _postprocess(self, data):
 -         r, left, top = self.tmp_pack
 -         return post_precessing(data, r, left, top)
 -  
 -     def _preprocess(self, image):
 -         img, r, left, top = detect_pre_precessing(image, self.input_size)
 -         self.tmp_pack = r, left, top
 -  
 -         return img
 
 
7.3、添加NCNN推理方法
同6.3
7.4、设置为NCNN推理
plateRecognizer.py 中
catcher = lpr3.LicensePlateCatcher(detect_inference=INFER_NCNN) # detect_level=lpr3.DETECT_LEVEL_HIGH 
8、ONNX、OpenVINO、NCNN 推理速度记录(包含预处理和后处理)
本人电脑硬件如下:
内存:16G
CPU:AMD Ryzen 5 4600U with Radeon Graphics 2.10 GHz
无独立显卡
输入视频流大小:1920*1080
8.1、在ONNX下的单帧车牌框检测速度
![]()
8.2、在OpenVINO下的单帧车牌框检测速度
![]()
不太稳定。。。。
8.3、在NCNN下的单帧车牌框检测速度
![]()
9、打包成exe可执行文件进行部署
9.1、安装auto-py-to-exe
- pip install auto-py-to-exe
 -  
 - auto-py-to-exe
 
 
9.2、部署ONNX推理方式
注意:不包含ncnn部分的代码可以直接打包
![]()
9.3、部署OpenVINO推理方式
其余一致,需在--collect-data添加openvino
![]()
9.4、部署NCNN推理方式
与9.2一样直接打包可能执行会出现错误:
Library not found: could no resolve或者显示ncnn的DLL load的问题
需要打开你的python环境所在目录找到 Lib\site-packages\ncnn.libs,将里面的DLL复制到Lib同一层目录下
![]()
再次打包即可通过并正常运行
10、结语
本人实际测试中,将上述车牌识别代码在Win732位下也部署了一套,其中OpenVINO因为不支持win7无法部署,实测发现ncnn的推理速度大概是onnx的1.5倍左右,并且ncnn推理占用CPU资源也比较低。
                                    
评论记录:
回复评论: