模型链接 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
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐