[Implementación del proyecto] Enseñarle paso a paso cómo implementar servicios OCR en RKNN (Parte 1)

Prefacio

He estado explorando cómo implementar modelos OCR en RKNN hace algún tiempo. Me tomó mucho tiempo implementar finalmente el modelo en la NPU de rv1126. Aunque el proceso no fue muy difícil, encontré algunos problemas durante la implementación de OCR en el Grupo RKNN. Preguntas, los estudiantes a menudo ven los registros de chat y me agregan a QQ para hacer preguntas relacionadas. Aprovechando el fin de semana reciente, planeo escribir un artículo para registrar todo el proceso de mi implementación, para compartir y facilitar revisión futura y profundizar mi impresión.

1. Trabajo de preparación

Soporte de hardware :

  • Sistema host de PC (ubuntu)
  • placa rv1126
  • Cable USB de doble extremo (para depuración adb entre la PC y la placa)

Soporte de software :

  • Configure el entorno rknn-toolkit en la PC
  • Actualizar el sistema con la placa rv1126
  • paquete SDK rv1126

Referencia del documento oficial: Guía de desarrollo rv1126

Primero, necesita configurar el entorno rknn-toolkit en el sistema ubuntu. El establecimiento es relativamente simple. La versión de rknn-toolkit que uso aquí es la versión 1.6.0, que se proporciona en la carpeta /external/rknn-toolkit en el paquete SDK rv1126. También puede descargarlo desde el centro oficial , pero el paquete whl descargado es la última versión. Preste atención al nombre del paquete. Solo admite entornos Linux y python3.6 o python3.5, por lo que no puede utilizado bajo Windows.
Insertar descripción de la imagen aquí
Cree un entorno virtual conda y ejecute el siguiente comando de instalación:

#创建名为rknn,python环境为3.6的虚拟环境
conda create -n rknn python=3.6

# 安装你需要安装的环境包,此处省略
pip install  ...

# 安装 RKNN-Toolkit
pip install rknn_toolkit-1.6.0-cp36-cp36m-linux_x86_64.whl

# 检查是否安装成功,import rknn 库
(rknn) rk@rk:~/rknn-toolkit-v1.6.0/package$  python
>>> from rknn.api import RKNN
>>>

Para el kit de desarrollo rv1126SDK y el sistema de actualización de la placa, consulte mi artículo anterior: Preparación del entorno SDK y grabación del sistema . Para otras placas, puede resolverlo usted mismo.

2. Conversión y cuantificación del modelo.

Aquí he preparado tres modelos onnx, todos basados ​​​​en PytorchOCR. Uno es una detección de texto DBNet liviana, que se convierte del peso de paddleocr a PytorchOCR y luego a onnx. El otro es un modelo de reconocimiento CRNN, que también se convierte de paddleocr a PytorchOCR. ., el último es un modelo de reconocimiento entrenado por mí mismo, porque como se mencionó anteriormente, el modelo RKNN actual no admite la conversión del operador LSTM. Intenté convertirlo a RKNN a través del archivo pt de jit::torch, pero el efecto de salida Después de la conversión, es incorrecto y no se puede cuantificar, por lo que diseñé una red de reconocimiento para la plataforma de implementación. Para el proceso de diseño, consulte mi artículo anterior: Implementación del modelo CRNN usando RKNN para avanzar en el proceso de optimización del pozo . Lo que publiqué aquí Es una versión relativamente pequeña con efecto de reconocimiento universal. CRNN no es tan bueno como paddleocr. La razón principal es que los datos de entrenamiento no son tan buenos como paddleocr. Sin embargo, es mejor usarlo en algunos escenarios específicos que paddleocr porque los datos de entrenamiento Se crea para la escena y se agregan muchas fuentes comunes. El reconocimiento general no será tan malo. Si puede cumplir con los requisitos de su propia escena, puede usarlo. Lo importante es que es muy rápido después de la cuantificación y Después de activar la precompilación, el modelo tiene solo 1,5 millones después de la cuantificación y la inferencia solo lleva unos pocos milisegundos. ¿Qué más bicicletas quieres?
Insertar descripción de la imagen aquí
Para el archivo de peso, consulte el enlace de Baidu Cloud al final del artículo.

Referencia relacionada:
https://github.com/PaddlePaddle/Paddle2ONNX

https://github.com/PaddlePaddle/PaddleOCR

https://github.com/WenmuZhou/PytorchOCR

La cuantificación del modelo rknn se completa durante la conversión del modelo, por lo que es necesario preparar un lote de imágenes para la cuantificación del modelo de detección y un lote de modelos para la cuantificación del modelo de reconocimiento. He preparado cientos de imágenes del modelo de detección y dos del modelo de reconocimiento. Alrededor de mil imágenes, y luego escriba la ruta de la imagen en un archivo txt respectivamente para cargarla durante la cuantificación: la
Insertar descripción de la imagen aquí
conversión del modelo rknn proporciona dos métodos, uno es la conversión de código Python y el otro es la conversión de interfaz. Si desea convierta la interfaz, instale rknn Después de configurar el entorno, debe ingresar en la terminal: python3 -m rknn.bin.visualization , y luego aparecerá la siguiente interfaz:
Insertar descripción de la imagen aquí
Elija de acuerdo con el formato de su modelo original. El modelo original aquí es el modelo onnx, así que seleccione onnx. Después de ingresar, el significado de cada opción es suyo. No es difícil de entender si lo traduce y lo compara (la interfaz de la versión rknn toolkit 1.7.1 parece estar en chino). A lo que se debe prestar atención es a la opción de precompilación (si habilitar la precompilación). La precompilación del modelo RKNN puede reducir el tiempo de inicialización del modelo, pero no puede pasar la simulación procesador para razonamiento o evaluación de rendimiento
Insertar descripción de la imagen aquí
: La segunda forma de convertir es mediante la conversión de código Python. El código es el siguiente, modifíquelo según sea necesario:

import os
from rknn.api import RKNN
import numpy as np
import onnxruntime as ort

onnx_model = 'model/repvgg_s.onnx' #onnx路径
save_rknn_dir = 'model/repvgg_s.rknn'#rknn保存路径

def norm(img):
    mean = 0.5
    std = 0.5
    img_data = (img.astype(np.float32)/255 - mean) / std
    return img_data

if __name__ == '__main__':

    # Create RKNN object
    rknn = RKNN(verbose=True)

    image = np.random.randn(1,3,32,448).astype(np.float32)             # 创建一个np数组,分别用onnx和rknn推理看转换后的输出差异,检测模型输入是1,3,640,640 ,识别模型输入是1,3,32,448
    onnx_net = ort.InferenceSession(onnx_model)                         # onnx推理

    onnx_infer = onnx_net.run(None, {
    
    'input': norm(image)})                    # 如果是paddle2onnx转出来的模型输入名字默认是 "x"

    # pre-process config
    print('--> Config model')
    rknn.config(mean_values=[[127.5, 127.5, 127.5]], std_values=[[127.5, 127.5, 127.5]], reorder_channel='2 1 0', target_platform=['rv1126'], batch_size=4,quantized_dtype='asymmetric_quantized-u8')  # 需要输入为RGB#####需要转化一下均值和归一化的值
    # rknn.config(mean_values=[[0.0, 0.0, 0.0]], std_values=[[255, 255, 255]], reorder_channel='2 1 0', target_platform=['rv1126'], batch_size=1)  # 需要输入为RGB
    print('done')

    # model_name = onnx_model[onnx_model.rfind('/') + 1:]
    # Load ONNX model
    print('--> Loading model %s' % onnx_model)
    ret = rknn.load_onnx(model=onnx_model)
    if ret != 0:
        print('Load %s failed!' % onnx_model)
        exit(ret)
    print('done')
    # Build model
    print('--> Building model')
    # rknn.build(do_quantization=False)
    ret = rknn.build(do_quantization=True, dataset='dataset_448.txt', pre_compile=False)
    #do_quantization是否对模型进行量化,datase量化校正数据集,pre_compil模型预编译开关,预编译 RKNN 模型可以减少模型初始化时间,但是无法通过模拟器进行推理或性能评估
    if ret != 0:
        print('Build net failed!')
        exit(ret)
    print('done')

    # Export RKNN model
    print('--> Export RKNN model')
    ret = rknn.export_rknn(save_rknn_dir)
    if ret != 0:
        print('Export rknn failed!')
        exit(ret)

    ret = rknn.init_runtime(target='rv1126',device_id="a0c4f1cae341b3df")            # 两个参数分别是板子型号和device_id,device_id在双头usb线连接后通过 adb devices查看
    if ret != 0:
        print('init runtime failed.')
        exit(ret)
    print('done')

    # Inference
    print('--> Running model')
    outputs = rknn.inference(inputs=[image])

    # perf
    print('--> Begin evaluate model performance')
    perf_results = rknn.eval_perf(inputs=[image])              # 模型评估
    print('done')
    print()

    print("->>模型前向对比!")
    print("--------------rknn outputs--------------------")
    print(outputs[0])
    print()

    print("--------------onnx outputs--------------------")
    print(onnx_infer[0])
    print()

    std = np.std(outputs[0]-onnx_infer[0])
    print(std)                                # 如果这个值比较大的话,说明模型转换后不太理想

    rknn.release()

No debería haber ningún problema al convertir la cuantificación usando det_new.onnx y repvgg_s.onnx que proporcioné. Rec_mbv3.onnx no se puede convertir. Si está interesado, puede probarlo usted mismo. Tenga en cuenta que activé la precompilación al detectar la conversión del modelo. , pero el efecto fue muy pobre, por lo tanto, los siguientes resultados de detección se obtienen cuando la precompilación no está activada y el tiempo de carga del modelo será mayor.

El siguiente es el proceso de cuantificación. Puede llevar más tiempo si la cuantificación está activada:
Insertar descripción de la imagen aquí
los resultados de la prueba después de una cuantificación exitosa y, finalmente, la varianza se calcula utilizando la salida de onnx y la salida de rknn:
Insertar descripción de la imagen aquí

El efecto después de la cuantificación del modelo de detección, modelo 640 x 640 entrada fija:
Insertar descripción de la imagen aquí
modelo de detección código de prueba rknn:

import numpy as np
import cv2
from rknn.api import RKNN
import torch
# from label_convert import CTCLabelConverter

import cv2
import numpy as np
import pyclipper
from shapely.geometry import Polygon
from matplotlib import pyplot as plt

class DBPostProcess():
    def __init__(self, thresh=0.3, box_thresh=0.7, max_candidates=1000, unclip_ratio=2):
        self.min_size = 3
        self.thresh = thresh
        self.box_thresh = box_thresh
        self.max_candidates = max_candidates
        self.unclip_ratio = unclip_ratio

    def __call__(self, pred, h_w_list, is_output_polygon=False):
        '''
        batch: (image, polygons, ignore_tags
        h_w_list: 包含[h,w]的数组
        pred:
            binary: text region segmentation map, with shape (N, 1,H, W)
        '''
        pred = pred[:, 0, :, :]
        segmentation = self.binarize(pred)
        boxes_batch = []
        scores_batch = []
        for batch_index in range(pred.shape[0]):
            height, width = h_w_list[batch_index]
            boxes, scores = self.post_p(pred[batch_index], segmentation[batch_index], width, height,
                                        is_output_polygon=is_output_polygon)
            boxes_batch.append(boxes)
            scores_batch.append(scores)
        return boxes_batch, scores_batch

    def binarize(self, pred):
        return pred > self.thresh

    def post_p(self, pred, bitmap, dest_width, dest_height, is_output_polygon=False):
        '''
        _bitmap: single map with shape (H, W),
            whose values are binarized as {0, 1}
        '''
        height, width = pred.shape
        boxes = []
        new_scores = []
        # bitmap = bitmap.cpu().numpy()
        if cv2.__version__.startswith('3'):
            _, contours, _ = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        if cv2.__version__.startswith('4'):
            contours, _ = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        for contour in contours[:self.max_candidates]:
            epsilon = 0.005 * cv2.arcLength(contour, True)
            approx = cv2.approxPolyDP(contour, epsilon, True)
            points = approx.reshape((-1, 2))
            if points.shape[0] < 4:
                continue
            score = self.box_score_fast(pred, contour.squeeze(1))
            if self.box_thresh > score:
                continue
            if points.shape[0] > 2:
                box = self.unclip(points, unclip_ratio=self.unclip_ratio)
                if len(box) > 1:
                    continue
            else:
                continue
            four_point_box, sside = self.get_mini_boxes(box.reshape((-1, 1, 2)))
            if sside < self.min_size + 2:
                continue
            if not isinstance(dest_width, int):
                dest_width = dest_width.item()
                dest_height = dest_height.item()
            if not is_output_polygon:
                box = np.array(four_point_box)
            else:
                box = box.reshape(-1, 2)
            box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width)
            box[:, 1] = np.clip(np.round(box[:, 1] / height * dest_height), 0, dest_height)
            boxes.append(box)
            new_scores.append(score)
        return boxes, new_scores

    def unclip(self, box, unclip_ratio=1.5):
        poly = Polygon(box)
        distance = poly.area * unclip_ratio / poly.length
        offset = pyclipper.PyclipperOffset()
        offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
        expanded = np.array(offset.Execute(distance))
        return expanded

    def get_mini_boxes(self, contour):
        bounding_box = cv2.minAreaRect(contour)
        points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])

        index_1, index_2, index_3, index_4 = 0, 1, 2, 3
        if points[1][1] > points[0][1]:
            index_1 = 0
            index_4 = 1
        else:
            index_1 = 1
            index_4 = 0
        if points[3][1] > points[2][1]:
            index_2 = 2
            index_3 = 3
        else:
            index_2 = 3
            index_3 = 2

        box = [points[index_1], points[index_2], points[index_3], points[index_4]]
        return box, min(bounding_box[1])

    def box_score_fast(self, bitmap, _box):
        # bitmap = bitmap.detach().cpu().numpy()
        h, w = bitmap.shape[:2]
        box = _box.copy()
        xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int), 0, w - 1)
        xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int), 0, w - 1)
        ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int), 0, h - 1)
        ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int), 0, h - 1)

        mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
        box[:, 0] = box[:, 0] - xmin
        box[:, 1] = box[:, 1] - ymin
        cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1)
        return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]


def narrow_224_32(image, expected_size=(224,32)):
    ih, iw = image.shape[0:2]
    ew, eh = expected_size
    # scale = eh / ih
    scale = min((eh/ih),(ew/iw))
    # scale = eh / max(iw,ih)
    nh = int(ih * scale)
    nw = int(iw * scale)
    image = cv2.resize(image, (nw, nh), interpolation=cv2.INTER_CUBIC)

    top = 0
    bottom = eh - nh
    left = 0
    right = ew - nw

    new_img = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
    return image,new_img

def draw_bbox(img_path, result, color=(0, 0, 255), thickness=2):
    import cv2
    if isinstance(img_path, str):
        img_path = cv2.imread(img_path)
        # img_path = cv2.cvtColor(img_path, cv2.COLOR_BGR2RGB)
    img_path = img_path.copy()
    for point in result:
        point = point.astype(int)
        cv2.polylines(img_path, [point], True, color, thickness)
    return img_path

if __name__ == '__main__':

    post_proess = DBPostProcess()
    is_output_polygon = False
    # Create RKNN object
    rknn = RKNN()

    ret = rknn.load_rknn('./model/det_new.rknn')

    # Set inputs
    img = cv2.imread('./idcard/2.jpg')
    origin_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img0 ,image = narrow_224_32(img,expected_size=(640,640))

    # init runtime environment
    print('--> Init runtime environment')
    ret = rknn.init_runtime(target='rv1126',device_id="a0c4f1cae341b3df")
    if ret != 0:
        print('Init runtime environment failed')
        exit(ret)
    print('done')

    # Inference
    print('--> Running model')
    outputs = rknn.inference(inputs=[image])

    # perf
    print('--> Begin evaluate model performance')
    perf_results = rknn.eval_perf(inputs=[image])
    print('done')

    feat_2 = torch.from_numpy(outputs[0])
    print(feat_2.size())
    box_list, score_list = post_proess(outputs[0], [image.shape[:2]], is_output_polygon=is_output_polygon)
    box_list, score_list = box_list[0], score_list[0]
    if len(box_list) > 0:
        idx = [x.sum() > 0 for x in box_list]
        box_list = [box_list[i] for i, v in enumerate(idx) if v]
        score_list = [score_list[i] for i, v in enumerate(idx) if v]
    else:
        box_list, score_list = [], []

    img = draw_bbox(image, box_list)
    img = img[0:img0.shape[0],0:img0.shape[1]]
    cv2.imshow("img",img)
    cv2.waitKey()
    rknn.release()

El efecto cuantificado del modelo de reconocimiento, modelo 32 x 448 entrada fija:
Insertar descripción de la imagen aquí
modelo de reconocimiento código de prueba rknn:

import numpy as np
import cv2
from rknn.api import RKNN
import torch
# from label_convert import CTCLabelConverter

class CTCLabelConverter(object):
    """ Convert between text-label and text-index """

    def __init__(self, character):
        # character (str): set of the possible characters.
        dict_character = []
        with open(character, "rb") as fin:
            lines = fin.readlines()
            for line in lines:
                line = line.decode('utf-8').strip("\n").strip("\r\n")
                dict_character += list(line)
        # dict_character = list(character)

        self.dict = {
    
    }
        for i, char in enumerate(dict_character):
            # NOTE: 0 is reserved for 'blank' token required by CTCLoss
            self.dict[char] = i + 1
        #TODO replace ‘ ’ with special symbol
        self.character = ['[blank]'] + dict_character+[' ']  # dummy '[blank]' token for CTCLoss (index 0)

    def encode(self, text, batch_max_length=None):
        """convert text-label into text-index.
        input:
            text: text labels of each image. [batch_size]
        output:
            text: concatenated text index for CTCLoss.
                    [sum(text_lengths)] = [text_index_0 + text_index_1 + ... + text_index_(n - 1)]
            length: length of each text. [batch_size]
        """
        length = [len(s) for s in text]
        # text = ''.join(text)
        # text = [self.dict[char] for char in text]
        d = []
        batch_max_length = max(length)
        for s in text:
            t = [self.dict[char] for char in s]
            t.extend([0] * (batch_max_length - len(s)))
            d.append(t)
        return (torch.tensor(d, dtype=torch.long), torch.tensor(length, dtype=torch.long))

    def decode(self, preds, raw=False):
        """ convert text-index into text-label. """
        preds_idx = preds.argmax(axis=2)
        preds_prob = preds.max(axis=2)
        result_list = []
        for word, prob in zip(preds_idx, preds_prob):
            if raw:
                result_list.append((''.join([self.character[int(i)] for i in word]), prob))
            else:
                result = []
                conf = []
                for i, index in enumerate(word):
                    if word[i] != 0 and (not (i > 0 and word[i - 1] == word[i])):
                        result.append(self.character[int(index)])
                        conf.append(prob[i])
                result_list.append((''.join(result), conf))
        return result_list


def narrow_224_32(image, expected_size=(224,32)):
    ih, iw = image.shape[0:2]
    ew, eh = expected_size
    scale = eh / ih
    # scale = eh / max(iw,ih)
    nh = int(ih * scale)
    nw = int(iw * scale)
    image = cv2.resize(image, (nw, nh), interpolation=cv2.INTER_CUBIC)
    top = 0
    bottom = eh - nh - top
    left = 0
    right = ew - nw - left

    new_img = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
    return new_img


if __name__ == '__main__':

    dict_path = r"./dict/dict_text.txt"
    converter = CTCLabelConverter(dict_path)
    # Create RKNN object
    rknn = RKNN()

    ret = rknn.load_rknn('./model/repvgg_s.rknn')

    # Set inputs
    img = cv2.imread('crnn_img/33925.jpg')
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    origin_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    image = narrow_224_32(img,expected_size=(448,32))


    # init runtime environment
    print('--> Init runtime environment')
    ret = rknn.init_runtime(target='rv1126',device_id="a0c4f1cae341b3df")
    if ret != 0:
        print('Init runtime environment failed')
        exit(ret)
    print('done')

    # Inference
    print('--> Running model')
    outputs = rknn.inference(inputs=[image])

    # perf
    print('--> Begin evaluate model performance')
    perf_results = rknn.eval_perf(inputs=[image])
    print('done')

    feat_2 = torch.from_numpy(outputs[0])
    # print(feat_2.size())
    #
    txt = converter.decode(feat_2.detach().cpu().numpy())
    print(txt)
    cv2.imshow("img",img)
    cv2.waitKey()
    rknn.release()

En este punto, la conversión del modelo ha sido exitosa. El artículo es demasiado largo. Pasemos al siguiente artículo: [Implementación del proyecto] Enseñarle paso a paso cómo implementar servicios OCR en RKNN (Parte 2) . El siguiente artículo es principalmente Agrega algo de implementación y escritura de código. Es un poco largo y no ha sido escrito. . .

Enlace de Baidu Cloud: https://pan.baidu.com/s/1jSirZT2LBOWQxohCEORp5g Contraseña: vrjk , los archivos proporcionados son:
Insertar descripción de la imagen aquí
El primer modelo rknn es un modelo de conversión precompilado activado, el modelo es más pequeño y el modelo es más rápido durante la inicialización Se carga más rápido. La precisión de conversión precompilada del modelo de detección es demasiado baja, por lo que no se proporciona. Puede probarlo usted mismo. rec_mbv3.onnx no se puede transferir, por lo que no hay un archivo rknn correspondiente. ppocr_keys_v1.txt son las claves del archivo rec_mbv3.onnx del modelo de reconocimiento ppocr, dict_text.txt es el archivo de claves de repvgg_s.onnx que entrené.

Supongo que te gusta

Origin blog.csdn.net/qq_39056987/article/details/123574943
Recomendado
Clasificación