【全志开发板部署】系列2:LPRNet模型推理环境搭建及推理流程
·
模型链接 https://github.com/sirius-ai/LPRNet_Pytorch/tree/master
环境依赖
pytorch >= 1.0.0
opencv-python 3.x
python 3.x
imutils
Pillow
numpy
如果已安装pytorch ,只需要再安装opencv库即可
pip install opencv-python==3.4.18.65 imutils -i https://pypi.tuna.tsinghua.edu.cn/simple
LPRNet模型推理
pytorch 模型测试
#
# -*- coding: utf-8 -*-
# /usr/bin/env/python3
'''
test pretrained model.
Author: aiboy.wei@outlook.com .
'''
from data.load_data import CHARS, CHARS_DICT, LPRDataLoader
from PIL import Image, ImageDraw, ImageFont
from model.LPRNet import build_lprnet
# import torch.backends.cudnn as cudnn
from torch.autograd import Variable
import torch.nn.functional as F
from torch.utils.data import *
from torch import optim
import torch.nn as nn
import numpy as np
import argparse
import torch
import time
import cv2
import os
def get_parser():
parser = argparse.ArgumentParser(description='parameters to train net')
parser.add_argument('--img_size', default=[94, 24], help='the image size')
parser.add_argument('--test_img_dirs', default="./data/test", help='the test images path')
parser.add_argument('--dropout_rate', default=0, help='dropout rate.')
parser.add_argument('--lpr_max_len', default=8, help='license plate number max length.')
parser.add_argument('--test_batch_size', default=100, help='testing batch size.')
parser.add_argument('--phase_train', default=False, type=bool, help='train or test phase flag.')
parser.add_argument('--num_workers', default=8, type=int, help='Number of workers used in dataloading')
parser.add_argument('--cuda', default=True, type=bool, help='Use cuda to train model')
parser.add_argument('--show', default=False, type=bool, help='show test image and its predict result or not.')
parser.add_argument('--pretrained_model', default='./weights/Final_LPRNet_model.pth', help='pretrained base model')
args = parser.parse_args()
return args
def collate_fn(batch):
imgs = []
labels = []
lengths = []
for _, sample in enumerate(batch):
img, label, length = sample
imgs.append(torch.from_numpy(img))
labels.extend(label)
lengths.append(length)
labels = np.asarray(labels).flatten().astype(np.float32)
return (torch.stack(imgs, 0), torch.from_numpy(labels), lengths)
def test():
args = get_parser()
lprnet = build_lprnet(lpr_max_len=args.lpr_max_len, phase=args.phase_train, class_num=len(CHARS), dropout_rate=args.dropout_rate)
device = torch.device("cuda:0" if args.cuda else "cpu")
lprnet.to(device)
print("Successful to build network!")
# load pretrained model
if args.pretrained_model:
lprnet.load_state_dict(torch.load(args.pretrained_model))
print("load pretrained model successful!")
else:
print("[Error] Can't found pretrained mode, please check!")
return False
test_img_dirs = os.path.expanduser(args.test_img_dirs)
test_dataset = LPRDataLoader(test_img_dirs.split(','), args.img_size, args.lpr_max_len)
try:
Greedy_Decode_Eval(lprnet, test_dataset, args)
finally:
cv2.destroyAllWindows()
def Greedy_Decode_Eval(Net, datasets, args):
# TestNet = Net.eval()
epoch_size = len(datasets) // args.test_batch_size
batch_iterator = iter(DataLoader(datasets, args.test_batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=collate_fn))
Tp = 0
Tn_1 = 0
Tn_2 = 0
t1 = time.time()
for i in range(epoch_size):
# load train data
images, labels, lengths = next(batch_iterator)
start = 0
targets = []
for length in lengths:
label = labels[start:start+length]
targets.append(label)
start += length
targets = np.array([el.numpy() for el in targets])
imgs = images.numpy().copy()
if args.cuda:
images = Variable(images.cuda())
else:
images = Variable(images)
# forward
prebs = Net(images)
# greedy decode
prebs = prebs.cpu().detach().numpy()
preb_labels = list()
for i in range(prebs.shape[0]):
preb = prebs[i, :, :]
preb_label = list()
for j in range(preb.shape[1]):
preb_label.append(np.argmax(preb[:, j], axis=0))
no_repeat_blank_label = list()
pre_c = preb_label[0]
if pre_c != len(CHARS) - 1:
no_repeat_blank_label.append(pre_c)
for c in preb_label: # dropout repeate label and blank label
if (pre_c == c) or (c == len(CHARS) - 1):
if c == len(CHARS) - 1:
pre_c = c
continue
no_repeat_blank_label.append(c)
pre_c = c
preb_labels.append(no_repeat_blank_label)
for i, label in enumerate(preb_labels):
# show image and its predict label
if args.show:
show(imgs[i], label, targets[i])
if len(label) != len(targets[i]):
Tn_1 += 1
continue
if (np.asarray(targets[i]) == np.asarray(label)).all():
Tp += 1
else:
Tn_2 += 1
Acc = Tp * 1.0 / (Tp + Tn_1 + Tn_2)
print("[Info] Test Accuracy: {} [{}:{}:{}:{}]".format(Acc, Tp, Tn_1, Tn_2, (Tp+Tn_1+Tn_2)))
t2 = time.time()
print("[Info] Test Speed: {}s 1/{}]".format((t2 - t1) / len(datasets), len(datasets)))
def show(img, label, target):
img = np.transpose(img, (1, 2, 0))
img *= 128.
img += 127.5
img = img.astype(np.uint8)
lb = ""
for i in label:
lb += CHARS[i]
tg = ""
for j in target.tolist():
tg += CHARS[int(j)]
flag = "F"
if lb == tg:
flag = "T"
# img = cv2.putText(img, lb, (0,16), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.6, (0, 0, 255), 1)
img = cv2ImgAddText(img, lb, (0, 0))
cv2.imshow("test", img)
print("target: ", tg, " ### {} ### ".format(flag), "predict: ", lb)
cv2.waitKey()
cv2.destroyAllWindows()
def cv2ImgAddText(img, text, pos, textColor=(255, 0, 0), textSize=12):
if (isinstance(img, np.ndarray)): # detect opencv format or not
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
fontText = ImageFont.truetype("data/NotoSansCJK-Regular.ttc", textSize, encoding="utf-8")
draw.text(pos, text, textColor, font=fontText)
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
if __name__ == "__main__":
test()
(py310) xxxxxxxx@xxxxxx:~/demo/LPRNet_Pytorch-master$ python test_LPRNet.py --show true
Successful to build network!
load pretrained model successful!
target: 皖AW112U ### T ### predict: 皖AW112U
target: 皖AJY444 ### F ### predict: 皖AJY444皖
target: 皖AR5L78 ### T ### predict: 皖AR5L78
target: 皖AW710M ### T ### predict: 皖AW710M
target: 皖AK2A68 ### T ### predict: 皖AK2A68
target: 皖A88G02 ### F ### predict: 皖A686G0291
target: 皖AWG139 ### T ### predict: 皖AWG139
target: 皖A92888 ### T ### predict: 皖A92888
target: 皖AM150X ### T ### predict: 皖AM150X
target: 皖A225L0 ### T ### predict: 皖A225L0
target: 皖A288J3 ### T ### predict: 皖A288J3
target: 皖AU860J ### T ### predict: 皖AU860J
target: 皖AT152M ### T ### predict: 皖AT152M
target: 皖AT721J ### T ### predict: 皖AT721J
target: 皖AM516P ### T ### predict: 皖AM516P
target: 皖AJ055A ### T ### predict: 皖AJ055A
target: 皖AN679S ### T ### predict: 皖AN679S
target: 皖AH6X77 ### T ### predict: 皖AH6X77
target: 皖AYV170 ### T ### predict: 皖AYV170
target: 皖A9C139 ### T ### predict: 皖A9C139

onnx格式模型测试
安装onnxruntime
pip install onnxruntime-gpu -i https://pypi.tuna.tsinghua.edu.cn/simple
模型转换
import torch
import torch.nn as nn
import onnx
import os
# Model Import
class small_basic_block(nn.Module):
def __init__(self, ch_in, ch_out):
super(small_basic_block, self).__init__()
self.block = nn.Sequential(
nn.Conv2d(ch_in, ch_out // 4, kernel_size=1),
nn.ReLU(),
nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(3, 1), padding=(1, 0)),
nn.ReLU(),
nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(1, 3), padding=(0, 1)),
nn.ReLU(),
nn.Conv2d(ch_out // 4, ch_out, kernel_size=1),
)
def forward(self, x):
return self.block(x)
class LPRNet(nn.Module):
def __init__(self, lpr_max_len, phase, class_num, dropout_rate):
super(LPRNet, self).__init__()
self.phase = phase
self.lpr_max_len = lpr_max_len
self.class_num = class_num
self.backbone = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1), # 0
nn.BatchNorm2d(num_features=64),
nn.ReLU(), # 2
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1)),
small_basic_block(ch_in=64, ch_out=128), # *** 4 ***
nn.BatchNorm2d(num_features=128),
nn.ReLU(), # 6
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(2, 1, 2)),
small_basic_block(ch_in=64, ch_out=256), # 8
nn.BatchNorm2d(num_features=256),
nn.ReLU(), # 10
small_basic_block(ch_in=256, ch_out=256), # *** 11 ***
nn.BatchNorm2d(num_features=256), # 12
nn.ReLU(),
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2)), # 14
nn.Dropout(dropout_rate),
nn.Conv2d(in_channels=64, out_channels=256, kernel_size=(1, 4), stride=1), # 16
nn.BatchNorm2d(num_features=256),
nn.ReLU(), # 18
nn.Dropout(dropout_rate),
nn.Conv2d(in_channels=256, out_channels=class_num, kernel_size=(13, 1), stride=1), # 20
nn.BatchNorm2d(num_features=class_num),
nn.ReLU(), # *** 22 ***
)
self.container = nn.Sequential(
nn.Conv2d(in_channels=448+self.class_num, out_channels=self.class_num, kernel_size=(1, 1), stride=(1, 1)),
# nn.BatchNorm2d(num_features=self.class_num),
# nn.ReLU(),
# nn.Conv2d(in_channels=self.class_num, out_channels=self.lpr_max_len+1, kernel_size=3, stride=2),
# nn.ReLU(),
)
def forward(self, x):
keep_features = list()
for i, layer in enumerate(self.backbone.children()):
x = layer(x)
if i in [2, 6, 13, 22]: # [2, 4, 8, 11, 22]
keep_features.append(x)
global_context = list()
for i, f in enumerate(keep_features):
if i in [0, 1]:
f = nn.AvgPool2d(kernel_size=5, stride=5)(f)
if i in [2]:
f = nn.AvgPool2d(kernel_size=(4, 10), stride=(4, 2))(f)
f_pow = torch.pow(f, 2)
f_mean = torch.mean(f_pow)
f = torch.div(f, f_mean)
global_context.append(f)
x = torch.cat(global_context, 1)
x = self.container(x)
logits = torch.mean(x, dim=2)
return logits
def build_lprnet(lpr_max_len=8, phase=False, class_num=66, dropout_rate=0.5):
Net = LPRNet(lpr_max_len, phase, class_num, dropout_rate)
if phase == "train":
return Net.train()
else:
return Net.eval()
if __name__ == "__main__":
lpr_max_len = 8
phase = False
class_num = 68
dropout_rate = 0.5
model_path = 'Final_LPRNet_model.pth' # Your model weights file path
temp_onnx_path = 'LPRNet_temp.onnx'
temp_data_path = temp_onnx_path + '.data'
output_onnx_path = 'LPRNet.onnx'
opset_version = 18
model = build_lprnet(lpr_max_len, phase, class_num, dropout_rate)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model_statedict = torch.load(model_path, map_location=device)
model.load_state_dict(model_statedict)
model.to(device)
model.eval()
input_data = torch.randn(1, 3, 24, 94).to(device)
# Model Export
torch.onnx.export(
model=model,
args=(input_data,),
f=temp_onnx_path,
opset_version=opset_version,
export_params=True,
do_constant_folding=True,
input_names=['input'],
output_names=['output'],
verbose=False # Disable verbose output
)
model = onnx.load(temp_onnx_path)
inferred_model = onnx.shape_inference.infer_shapes(model)
onnx.save_model(inferred_model, output_onnx_path)
if os.path.exists(temp_onnx_path):
os.remove(temp_onnx_path)
if os.path.exists(temp_data_path):
os.remove(temp_data_path)
#执行脚本
python convert_pth2onnx.py
#进行onnx模型推理时要注意,代码路径要根据实际情况修改,本文中把模型权重和代码放在同一个文件夹


执行脚本
注意test_LPRNet_onnx.py的路径,可通过命令行修改。
# -*- coding: utf-8 -*-
# /usr/bin/env/python3
'''Test pretrained ONNX model.
Author: zhongzixins
Modified for ONNX Runtime with CUDA support.
'''
from data.load_data import CHARS, CHARS_DICT, LPRDataLoader
from PIL import Image, ImageDraw, ImageFont
# import torch.backends.cudnn as cudnn
from torch.autograd import Variable
from torch.utils.data import *
from torch import optim
import torch.nn as nn
import numpy as np
import argparse
import torch
import time
import cv2
import os
import onnxruntime as ort
def get_parser():
parser = argparse.ArgumentParser(description='parameters to train net')
parser.add_argument('--img_size', default=[94, 24], help='the image size')
parser.add_argument('--test_img_dirs', default="./data/test", help='the test images path')
parser.add_argument('--dropout_rate', default=0, help='dropout rate.')
parser.add_argument('--lpr_max_len', default=8, help='license plate number max length.')
parser.add_argument('--test_batch_size', default=1, help='testing batch size.')
parser.add_argument('--phase_train', default=False, type=bool, help='train or test phase flag.')
parser.add_argument('--num_workers', default=8, type=int, help='Number of workers used in dataloading')
parser.add_argument('--cuda', default=True, type=bool, help='Use cuda to train model')
parser.add_argument('--show', default=False, type=bool, help='show test image and its predict result or not.')
parser.add_argument('--pretrained_model', default='LPRNet.onnx', help='pretrained base model')
args = parser.parse_args()
return args
def collate_fn(batch):
imgs = []
labels = []
lengths = []
for _, sample in enumerate(batch):
img, label, length = sample
imgs.append(torch.from_numpy(img))
labels.extend(label)
lengths.append(length)
labels = np.asarray(labels).flatten().astype(np.float32)
return (torch.stack(imgs, 0), torch.from_numpy(labels), lengths)
def test():
args = get_parser()
# 设置ONNX Runtime providers
if args.cuda and 'CUDAExecutionProvider' in ort.get_available_providers():
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
device_name = 'CUDA'
else:
providers = ['CPUExecutionProvider']
device_name = 'CPU'
print(f"Using device: {device_name}")
print(f"Available providers: {ort.get_available_providers()}")
# load pretrained model
if args.pretrained_model:
if not os.path.exists(args.pretrained_model):
print(f"[Error] ONNX model not found: {args.pretrained_model}")
print(f"Please check the model path or download the model first.")
return False
# 创建InferenceSession
try:
session = ort.InferenceSession(args.pretrained_model, providers=providers)
input_name = session.get_inputs()[0].name
output_name = session.get_outputs()[0].name
print("load pretrained model successful!")
except Exception as e:
print(f"[Error] Failed to load ONNX model: {e}")
return False
else:
print("[Error] Can't found pretrained mode, please check!")
return False
test_img_dirs = os.path.expanduser(args.test_img_dirs)
test_dataset = LPRDataLoader(test_img_dirs.split(','), args.img_size, args.lpr_max_len)
try:
Greedy_Decode_Eval(session, input_name, output_name, test_dataset, args)
finally:
cv2.destroyAllWindows()
def Greedy_Decode_Eval(session, input_name, output_name, datasets, args):
# TestNet = Net.eval()
epoch_size = len(datasets) // args.test_batch_size
batch_iterator = iter(DataLoader(datasets, args.test_batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=collate_fn))
Tp = 0
Tn_1 = 0
Tn_2 = 0
t1 = time.time()
for i in range(epoch_size):
# load train data
images, labels, lengths = next(batch_iterator)
start = 0
targets = []
for length in lengths:
label = labels[start:start+length]
targets.append(label)
start += length
targets = np.array([el.numpy() for el in targets])
imgs = images.numpy().copy()
# 准备ONNX输入,确保batch维度为1
images_np = images.cpu().numpy()
if images_np.shape[0] != 1:
images_np = images_np[:1] # 只取第一个样本,适配ONNX模型batch=1
# ONNX推理
prebs = session.run([output_name], {input_name: images_np})[0]
# print
# print(f"Raw output shape: {prebs.shape}")
# print(f"Raw output (first sample): \n{prebs[0]}")
# greedy decode
preb_labels = list()
for i in range(prebs.shape[0]):
preb = prebs[i, :, :]
preb_label = list()
for j in range(preb.shape[1]):
preb_label.append(np.argmax(preb[:, j], axis=0))
no_repeat_blank_label = list()
pre_c = preb_label[0]
if pre_c != len(CHARS) - 1:
no_repeat_blank_label.append(pre_c)
for c in preb_label: # dropout repeate label and blank label
if (pre_c == c) or (c == len(CHARS) - 1):
if c == len(CHARS) - 1:
pre_c = c
continue
no_repeat_blank_label.append(c)
pre_c = c
preb_labels.append(no_repeat_blank_label)
for i, label in enumerate(preb_labels):
# show image and its predict label
if args.show:
show(imgs[i], label, targets[i])
if len(label) != len(targets[i]):
Tn_1 += 1
continue
if (np.asarray(targets[i]) == np.asarray(label)).all():
Tp += 1
else:
Tn_2 += 1
Acc = Tp * 1.0 / (Tp + Tn_1 + Tn_2)
print("[Info] Test Accuracy: {} [{}:{}:{}:{}]".format(Acc, Tp, Tn_1, Tn_2, (Tp+Tn_1+Tn_2)))
t2 = time.time()
print("[Info] Test Speed: {}s 1/{}"
.format((t2 - t1) / len(datasets), len(datasets)))
def show(img, label, target):
img = np.transpose(img, (1, 2, 0))
img *= 128.
img += 127.5
img = img.astype(np.uint8)
lb = ""
for i in label:
lb += CHARS[i]
tg = ""
for j in target.tolist():
tg += CHARS[int(j)]
flag = "F"
if lb == tg:
flag = "T"
# img = cv2.putText(img, lb, (0,16), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.6, (0, 0, 255), 1)
img = cv2ImgAddText(img, lb, (0, 0))
cv2.imshow("test", img)
print("target: ", tg, " ### {} ### ".format(flag), "predict: ", lb)
cv2.waitKey()
cv2.destroyAllWindows()
def cv2ImgAddText(img, text, pos, textColor=(255, 0, 0), textSize=12):
if (isinstance(img, np.ndarray)): # detect opencv format or not
img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
try:
fontText = ImageFont.truetype("data/NotoSansCJK-Regular.ttc", textSize, encoding="utf-8")
except Exception:
# If font file not found, use default font
print("[Warning] Font not found, using default font.")
fontText = ImageFont.load_default()
draw.text(pos, text, textColor, font=fontText)
return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
if __name__ == "__main__":
test()

python test_LPRNet_onnx.py --show true
输出结果
(py310) xxxxx@xxxxx:~/demo/LPRNet_Pytorch-master$ python test_LPRNet_onnx.py --show true
Using device: CUDA
Available providers: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
2025-12-26 19:45:07.579859915 [W:onnxruntime:, transformer_memcpy.cc:111 ApplyImpl] 6 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.
load pretrained model successful!
target: 皖AF5126 ### T ### predict: 皖AF5126
target: 皖AKG821 ### T ### predict: 皖AKG821
target: 皖ALW489 ### T ### predict: 皖ALW489
target: 皖APR948 ### T ### predict: 皖APR948
target: 皖K39J62 ### T ### predict: 皖K39J62
target: 皖AT7T56 ### T ### predict: 皖AT7T56

常见问题
Q1:已安装opencv库,但无法导入
(py310) xxxxxx@xxxxx:~/demo/LPRNet_Pytorch-master$ python te st_LPRNet.py --show true
RuntimeError: module compiled against ABI version 0x1000009 but this vers ion of numpy is 0x2000000
Traceback (most recent call last):
File "xxxx/demo/LPRNet_Pytorch-master/test_LPRNet.py", lin e 9, in <module>
from data.load_data import CHARS, CHARS_DICT, LPRDataLoader
File "xxxx/demo/LPRNet_Pytorch-master/data/__init__.py", l ine 1, in <module>
from .load_data import *
File "xxxx/demo/LPRNet_Pytorch-master/data/load_data.py", line 2, in <module>
from imutils import paths
File "xxxx/anaconda3/envs/py310/lib/python3.10/site-packag es/imutils/__init__.py", line 8, in <module>
from .convenience import translate
File "xxxx/anaconda3/envs/py310/lib/python3.10/site-packag es/imutils/convenience.py", line 6, in <module>
import cv2
File "xxxx/anaconda3/envs/py310/lib/python3.10/site-packag es/cv2/__init__.py", line 114, in <module>
bootstrap()
File "xxxx/anaconda3/envs/py310/lib/python3.10/site-packag es/cv2/__init__.py", line 102, in bootstrap
import cv2
ImportError: numpy.core.multiarray failed to import
A1:Numpy版本过高,不兼容
把Numpy版本降低到1.x版本
卸载原有Numpy
pip uninstall -y numpy
重新安装
pip install numpy==1.24.4 -i https://pypi.tuna.tsinghua.edu.cn/simple更多推荐
所有评论(0)