top of page
Search

A10-3588 License Plate Recognition: Model Conversion and NPU Execution

Introduction

This document provides a detailed guide on converting the License Plate Recognition model and performing inference on the A10 - 3588 NPU.

License Plate Recognition consists of four main parts.

  • Firstly, there is license plate detection. We utilize the YOLOv8n - pose algorithm to detect license plates.

  • Secondly, license plate localization is carried out. This step precisely determines the position of the detected license plate within the image.

  • Thirdly, character recognition is performed. The Densenet Ctc model is employed to recognize the characters on the license plate.

  • Finally, the VGG16 model is used to identify the color of the license plate.


Convert Model

  1. Lpd(YOLOV8n-pose)


Before training, please modify as follows.


For YOLOv8-pose/nets/nn.py.

         self.fpn = DarkFPN(width, depth) 
         img_dummy = torch.zeros(1, 3, 256, 256) 
-        self.head = Head(num_classes, (17, 3), (width[3], width[4], width[5])) 
+        self.head = Head(num_classes, (4, 3), (width[3], width[4], width[5])) 
         self.head.stride = torch.tensor([256 / x.shape[-2] for x in self.forwar
 d(img_dummy)[0]]) 
         self.stride = self.head.stride 
         self.head.initialize_biases()

For YOLOv8-pose/utils/args.yaml.

flip_lr: 0.5000               # image flip left-right (probability) 
mosaic: 1.00000               # image mosaic (probability) 
mix_up: 0.00000               # image mix-up (probability)
-kpt_shape: [ 17, 3 ]         # number of key-points, number of dims (2 for x,y or 3 for x,y,visible) 
+kpt_shape: [ 4, 3 ]          # number of key-points, number of dims (2 for x,y or 3 for x,y,visible) 
-flip_index: [ 0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15 ] 
+flip_index: [ 0, 1, 2, 3 ] 
 # classes 
 names:
-  0: person 
+  0: license_plate

For YOLOv8-pose/utils/util.py.

-KPT_SIGMA = numpy.array([.26, .25, .25,
-                         .35, .35, .79,
-                         .79, .72, .72,
-                         .62, .62, 1.07,
-                         1.07, .87, .87,
-                         .89, .89]) / 10.0 
+KPT_SIGMA = numpy.array([0.25, 0.25, 0.25, 0.25])

 After training, modify YOLOv8-pose/nets/nn.py as follows.

def forward(self, x): 
        x_det = self.detect_box(x) 
        x_kpt = self.detect_kpt(x) 
        if self.training: 
            return x_det, x_kpt -       return torch.cat([x_det, self.decode_kpt(x_kpt)], dim=1) 
+       output = [] 
+       print(x_det.shape) 
+       print(x_kpt[0].shape) 
+       output.append((torch.cat([x_det[:, :, :6400], x_kpt[0]], dim=1)+).view(1, 1, -1, 80, 80).permute(0, 1, 3, 4, 2)) 
+       output.append((torch.cat([x_det[:, :, 6400:8000], x_kpt[1]], di+m=1)).view(1, 1, -1, 40, 40).permute(0, 1, 3, 4, 2)) 
+       output.append((torch.cat([x_det[:, :, 8000:], x_kpt[2]], dim=1)+).view(1, 1, -1, 20, 20).permute(0, 1, 3, 4, 2)) 
+       return output 
 
    def detect_box(self, x): 
        x_det = [] 
        shape = x[0].shape[0] 
        for i in range(self.nl): 
            x_det.append(torch.cat((self.box[i](x[i]), self.cls[i](x[i])), dim=1
 )) 
        if self.training: 
            return x_det 
 
        self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x_det, self.stride, 0.5)) 
 
        x_cat = torch.cat([i.view(shape, self.no, -1) for i in x_det], dim=2) 
+       return x_cat 
        box, cls = x_cat.split((self.ch * 4, self.nc), 1) 
        a, b = self.dfl(box).chunk(2, 1) 
        a = self.anchors.unsqueeze(0) - a 
        b = self.anchors.unsqueeze(0) + b 
        box = torch.cat(((a + b) / 2, b - a), dim=1) 
        return torch.cat((box * self.strides, cls.sigmoid()), dim=1)
def detect_kpt(self, x): 
        x_kpt = [] 
        shape = x[0].shape[0] 
        for i in range(self.nl): 
            kpt = self.kpt[i](x[i]) 
            kpt = kpt.view(shape, self.num_kpt, -1) 
            x_kpt.append(kpt)
-       return torch.cat(x_kpt, dim=-1) 
+       return x_kpt


Create export.py and write in follow code.

from nets import nn 
import torch 
import yaml 
from utils.util import load_weight 
 
model_path = "weights/epoch_397.pt" 
save_path = "yolov8_pose.onnx" 
 
with open('utils/args.yaml', errors='ignore') as f: 
    params = yaml.safe_load(f) 
 
model = nn.yolo_v8_n(len(params['names'])) 
model = load_weight(model_path, model) 
# print(model) 
 
img = torch.zeros(1, 3, 640, 640) 
torch.onnx.export(model, img, save_path, verbose=False, opset_version=12, input_names=['images'])

Run export.py to convert model from pt to onnx.

python export.py

Download the convert tool.


Create a docker.

docker pull yanwyb/npu:v1 
$ docker run -it --name {name} -v $(pwd):/home/khadas/npu \ 
                               -v /etc/localtime:/etc/localtime:ro \ 
                               -v /etc/timezone:/etc/timezone:ro \ 
                               -v $HOME/.ccache:/home/khadas/.ccache --privilege
 d \ 
                               --device=/dev/loop-control:/dev/loop-control \ 
                               --device=/dev/loop0:/dev/loop0 --cap-add SYS_ADMI
N yanwyb/npu:v1

Install dependencies.

cd rknn-toolkit2
sudo apt-get install python3 python3-dev python3-pip 
sudo apt-get install libxslt1-dev zlib1g-dev libglib2.0 libsm6 libgl1-mesa-glx libprotobuf-dev gcc cmake 
pip3 install -r doc/requirements_cp38-*.txt 
pip3 install packages/rknn_toolkit2-*-cp38-cp38-linux_x86_64.whl

Modify test.py as follows.

# Create RKNN object 
    rknn = RKNN(verbose=True) 
 
    # pre-process config 
    print('--> Config model') 
    rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') 
    print('done') 
 
    # Load ONNX model 
    print('--> Loading model') 
    ret = rknn.load_onnx(model=ONNX_MODEL) 
    if ret != 0: 
        print('Load model failed!') 
        exit(ret) 
    print('done') 
 
    # Build model 
    print('--> Building model') 
    ret = rknn.build(do_quantization=False, dataset=DATASET) 
    if ret != 0: 
        print('Build model failed!') 
        exit(ret) 
    print('done') 
 
    # Export RKNN model 
    print('--> Export rknn model') 
    ret = rknn.export_rknn(RKNN_MODEL) 
    if ret != 0: 
        print('Export rknn model failed!') 
        exit(ret) 
    print('done')

Run test.py.

python3 test.py

  1. Lpr(Densenet Ctc)

Before converting the model, we suggest that you convert Keras model into ONNX model first. The converting code is as follows.


import onnx 
from keras.models import * 
import keras 
import keras2onnx 
from train import get_model 
import densenet 
 
basemodel, model = get_model(32, 88) 
basemodel.load_weights("models/weights_densenet-32-0.40.h5") 
onnx_model = keras2onnx.convert_keras(basemodel, basemodel.name, target_opset=12) 
onnx_model.graph.input[0].type.tensor_type.shape.dim[0].dim_value = int(1) 
onnx_model.graph.input[0].type.tensor_type.shape.dim[1].dim_value = int(1) 
onnx_model.graph.input[0].type.tensor_type.shape.dim[2].dim_value = int(32) 
onnx_model.graph.input[0].type.tensor_type.shape.dim[3].dim_value = int(280) 
onnx_model.graph.output[0].type.tensor_type.shape.dim[0].dim_value = int(1) 
onnx_model.graph.node.remove(onnx_model.graph.node[0]) 
onnx_model.graph.node[0].input[0] = "the_input" 
onnx.save_model(onnx_model, "./densenet_ctc.onnx") 

Put densenet_ctc model and quantized picture in densenet_ctc folder.

cd densenet_ctc 
cp -r {path}/densenet_ctc.onnx ./

Modify the parameters of test.py, including mean values, normalized values, the path to the Densenet-CTC model, and quantized images.


When you have run test.py successfully, you will find an RKNN model in your save path.



  1. Lpc(VGG16)

The code uses Keras. So, we should convert the model from h5 to pb. Here is the tool we use.

Download the Edge2 conversion tool.

Modify the parameters of test.py, including mean values, normalized values, the VGG-16 model path, and quantized images.


When you have run test.py successfully, you will find an RKNN model in your save path.


Run NPU

Connect A10-3588 and pull npu code in A10-3588.

git clone https://github.com/khadas/a10-3588-npu 
cd a10-3588-npu/C++

Install dependencies. If you use Yocto, you do not need to do this step.

sudo apt update 
sudo apt install cmake libopencv-dev

The demo is to recognize Chinese license plates. So you should install a Chinese font library first.

sudo mkdir -p /usr/share/fonts/my_fonts 
sudo cp -r license_plate_recognition/data/simfang.ttf /usr/share/fonts/my_fonts 
cd /usr/share/fonts/my_fonts 
sudo apt install mkfontscale 
mkfontscale

Then reboot Edge2. After reboot, check installation success.

fc-list :lang=zh

Copy lpd, lpr and lpc models to license_plate_recognition/data/model.

bash build.sh 
cd install/license_plate_recognition 
./license_plate_recognition data/model/lpd.rknn 
data/model/lpr.rknn data/model/lpc.rknn ./data/img/粤 AA63D8.png

The camera input demo is the license_plate_recognition_cap.

bash build.sh 
cd install/license_plate_recognition_cap 
./license_plate_recognition_cap data/model/lpd.rknn 
data/model/lpr.rknn data/model/lpc.rknn 33

 
 
 

Comments


SUBSCRIBE FOR UPDATES

Response received!

©2024 NAMTSO TECHNOLOGY CO., LTD.

bottom of page