Implementación del modelo Rockchip RV1126 (proceso de implementación completo)

1. Introducción del chip

  1. Descripción general del entorno

    Sistema de PC: Ubuntu 18.04 LTS, tipo de chip de 64 bits
    y sistema: RV1126, Linux de 32 bits

    La situación básica del chip se muestra en la siguiente figura.
    inserte la descripción de la imagen aquí

  2. cadena de herramientas RK

    Dirección de descarga de la cadena de herramientas: https://github.com/rockchip-linux/rknn-toolkit
    Versión de RKNN-ToolKit: V1.7.3

    La siguiente figura es la introducción [Léame] del enlace de software anterior. Diferentes plataformas de chips son adecuadas para diferentes cadenas de herramientas. 【RK1808/RK1806/RK3399Pro/RV1109/RV1126】La cadena de herramientas de adaptación de chip de la serie es 【RKNN-Toolkit】:
    inserte la descripción de la imagen aquí

2. Breve descripción del proceso de implementación.

El proceso básico es el siguiente: 1. Conversión de modelo, 2. Conversión de C++, 3. Operación RV1126

  1. Instale la versión Python de la cadena de herramientas ( rknn-toolkit ) en la PC y exporte (.onnx, .pt, .ckpt) a modelos (.rknn) en la PC. Este paso puede verificar si el modelo que entrenó se puede exportar con éxito, si el operador de convolución es compatible, si es cuantificable, si el efecto de predicción cumple con las expectativas y otras funciones.
  2. Configure la cadena de herramientas de la versión C ( rknpu ) en el lado de la PC y reescriba el proyecto de la versión [Python] en un proyecto de razonamiento [C++]. Utilice la cadena de herramientas [arm-32bit] para compilarlo en un archivo ejecutable ( no puede utilizar el entorno g++ de Ubuntu para compilar ).
  3. Coloque el modelo RKNN obtenido y los archivos ejecutables y algunas bibliotecas dependientes necesarias (.so) de RK en el chip, que se puede ejecutar directamente.
  4. Evaluación cuantitativa y evaluación del desempeño en el tablero.

3. Configuración del entorno de desarrollo (RKNN-Toolkit)

  Combinado con la documentación oficial, analiza principalmente la función central RKNN de la versión Python del código en ejecución. El código está organizado de la siguiente manera:

inserte la descripción de la imagen aquí

3.1 Prueba de instalación del software

  1. Instalación de software básico

    cd ~/rknn-toolkit-master/
    cd packages(软件安装,见上图的目录截图)
    pip install -r requirements-cpu.txt(cpu,gpu 版本都可以,如果下载慢,可以尝试其它方式,逐个安装软件)
    
  2. Instalación del software RKNN
    Descargue el paquete de software: rknn-toolkit-v1.7.3-packages.zip , descomprímalo en el directorio raíz y siga los pasos a continuación para instalarlo:

    cd rknn-toolkit-v1.7.3-packages/packages
    pip install rknn_toolkit-1.7.3-cp36-cp36m-linux_x86_64.whl
    

    La prueba de instalación se completa y no se imprime nada, lo que indica que la instalación se realizó correctamente, como se muestra en la siguiente figura
    inserte la descripción de la imagen aquí

  3. Ejemplo de simulación ejecutándose en PC

    Ejemplo de prueba: examples/onnx/yolov5/test.py
    Modelo a convertir: yolov5s.onnx
    Modelo convertido: yolov5s.rknn
    Resultado de visualización: Se puede ver que la operación fue exitosa en el simulador de PC y el resultado es normal.
    inserte la descripción de la imagen aquí

3.2, análisis de código de muestra

  1. Función API principal
    para crear objetos RKNN

    rknn = RKNN()
    

    Parámetros de API: imprima la información de registro durante el proceso de conversión del modelo, lo cual es útil para solucionar problemas
    inserte la descripción de la imagen aquí


    Configurar los parámetros de RKNN

    # 配置预处理参数
    rknn.config()
    

    Antes de construir el modelo RKNN, es necesario configurar los parámetros del modelo, como el valor medio del canal, el orden de los canales y el tipo de cuantificación. Estas operaciones se pueden configurar a través de la interfaz [config], de la siguiente manera :
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí


    modelo de carga

    ret = rknn.load(model='model_path')
    

    Tome la carga del modelo ONNX como ejemplo, preste atención al parámetro: salidas, a veces es necesario obtener el resultado de salida del nodo especificado del modelo, este parámetro es muy útil. La descripción específica se muestra en la siguiente figura:
    inserte la descripción de la imagen aquí


    compilar modelo

    ret = rknn.build()
    

    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí


    Exportar modelo RKNN

    ret = rknn.export_rknn(RKNN_MODEL)
    

    Exporte archivos de modelo RKNN a través de esta interfaz para la implementación del modelo.
    inserte la descripción de la imagen aquí


    Cargar modelo RKNN

    ret = rknn.load_rknn(path='./yolov5s.rknn')
    

    inserte la descripción de la imagen aquí


    Inicializar el entorno operativo

    ret = rknn.init_runtime()
    

    Inicialice el entorno operativo y especifique la plataforma operativa. Los parámetros relevantes son los siguientes:
    inserte la descripción de la imagen aquí

    razonamiento modelo

    outputs = rknn.inference(inputs=[img])
    

    Antes de la inferencia del modelo, se debe construir o cargar un modelo RKNN.
    inserte la descripción de la imagen aquí


    Evaluación del desempeño del modelo

    rknn.eval_perf(inputs=[image], is_print=True)
    

    Implementar en la junta directiva y evaluar el desempeño es un paso esencial, de la siguiente manera
    inserte la descripción de la imagen aquí


    uso de memoria

    memory_detail = rknn.eval_memory()
    

    inserte la descripción de la imagen aquí


  2. Análisis de ejemplo
    En el lado de la PC, utilice el entorno Python configurado para convertir el modelo ONNX al modelo de formato RKNN.

    import os
    import numpy as np
    import cv2
    from rknn.api import RKNN
    
    
    ONNX_MODEL = 'yolov5s.onnx'
    RKNN_MODEL = 'yolov5s.rknn'
    IMG_PATH = './bus.jpg'
    DATASET = './dataset.txt'
    
    QUANTIZE_ON = True
    
    BOX_THRESH = 0.5
    NMS_THRESH = 0.6
    IMG_SIZE = (640, 640) # (width, height), such as (1280, 736)
    
    CLASSES = ("person", "bicycle", "car","motorbike ","aeroplane ","bus ","train","truck ","boat","traffic light",
               "fire hydrant","stop sign ","parking meter","bench","bird","cat","dog ","horse ","sheep","cow","elephant",
               "bear","zebra ","giraffe","backpack","umbrella","handbag","tie","suitcase","frisbee","skis","snowboard","sports ball","kite",
               "baseball bat","baseball glove","skateboard","surfboard","tennis racket","bottle","wine glass","cup","fork","knife ",
               "spoon","bowl","banana","apple","sandwich","orange","broccoli","carrot","hot dog","pizza ","donut","cake","chair","sofa",
               "pottedplant","bed","diningtable","toilet ","tvmonitor","laptop	","mouse	","remote ","keyboard ","cell phone","microwave ",
               "oven ","toaster","sink","refrigerator ","book","clock","vase","scissors ","teddy bear ","hair drier", "toothbrush ")
    
    def sigmoid(x):
        return 1 / (1 + np.exp(-x))
    
    def xywh2xyxy(x):
        # Convert [x, y, w, h] to [x1, y1, x2, y2]
        y = np.copy(x)
        y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
        y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
        y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
        y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
        return y
    
    def process(input, mask, anchors):
    
        anchors = [anchors[i] for i in mask]
        grid_h, grid_w = map(int, input.shape[0:2])
    
        box_confidence = sigmoid(input[..., 4])
        box_confidence = np.expand_dims(box_confidence, axis=-1)
    
        box_class_probs = sigmoid(input[..., 5:])
    
        box_xy = sigmoid(input[..., :2])*2 - 0.5
    
        col = np.tile(np.arange(0, grid_w), grid_h).reshape(-1, grid_w)
        row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_w)
        col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
        row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
        grid = np.concatenate((col, row), axis=-1)
        box_xy += grid
        box_xy *= (int(IMG_SIZE[1]/grid_h), int(IMG_SIZE[0]/grid_w))
    
        box_wh = pow(sigmoid(input[..., 2:4])*2, 2)
        box_wh = box_wh * anchors
    
        box = np.concatenate((box_xy, box_wh), axis=-1)
    
        return box, box_confidence, box_class_probs
    
    def filter_boxes(boxes, box_confidences, box_class_probs):
        """Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
    
        # Arguments
            boxes: ndarray, boxes of objects.
            box_confidences: ndarray, confidences of objects.
            box_class_probs: ndarray, class_probs of objects.
    
        # Returns
            boxes: ndarray, filtered boxes.
            classes: ndarray, classes for boxes.
            scores: ndarray, scores for boxes.
        """
        boxes = boxes.reshape(-1, 4)
        box_confidences = box_confidences.reshape(-1)
        box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])
    
        _box_pos = np.where(box_confidences >= BOX_THRESH)
        boxes = boxes[_box_pos]
        box_confidences = box_confidences[_box_pos]
        box_class_probs = box_class_probs[_box_pos]
    
        class_max_score = np.max(box_class_probs, axis=-1)
        classes = np.argmax(box_class_probs, axis=-1)
        _class_pos = np.where(class_max_score* box_confidences >= BOX_THRESH)
    
        boxes = boxes[_class_pos]
        classes = classes[_class_pos]
        scores = (class_max_score* box_confidences)[_class_pos]
    
        return boxes, classes, scores
    
    def nms_boxes(boxes, scores):
        """Suppress non-maximal boxes.
    
        # Arguments
            boxes: ndarray, boxes of objects.
            scores: ndarray, scores of objects.
    
        # Returns
            keep: ndarray, index of effective boxes.
        """
        x = boxes[:, 0]
        y = boxes[:, 1]
        w = boxes[:, 2] - boxes[:, 0]
        h = boxes[:, 3] - boxes[:, 1]
    
        areas = w * h
        order = scores.argsort()[::-1]
    
        keep = []
        while order.size > 0:
            i = order[0]
            keep.append(i)
    
            xx1 = np.maximum(x[i], x[order[1:]])
            yy1 = np.maximum(y[i], y[order[1:]])
            xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
            yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
    
            w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
            h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
            inter = w1 * h1
    
            ovr = inter / (areas[i] + areas[order[1:]] - inter)
            inds = np.where(ovr <= NMS_THRESH)[0]
            order = order[inds + 1]
        keep = np.array(keep)
        return keep
    
    
    def yolov5_post_process(input_data):
        masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
        anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
                  [59, 119], [116, 90], [156, 198], [373, 326]]
    
        boxes, classes, scores = [], [], []
        for input,mask in zip(input_data, masks):
            b, c, s = process(input, mask, anchors)
            b, c, s = filter_boxes(b, c, s)
            boxes.append(b)
            classes.append(c)
            scores.append(s)
    
        boxes = np.concatenate(boxes)
        boxes = xywh2xyxy(boxes)
        classes = np.concatenate(classes)
        scores = np.concatenate(scores)
    
        nboxes, nclasses, nscores = [], [], []
        for c in set(classes):
            inds = np.where(classes == c)
            b = boxes[inds]
            c = classes[inds]
            s = scores[inds]
    
            keep = nms_boxes(b, s)
    
            nboxes.append(b[keep])
            nclasses.append(c[keep])
            nscores.append(s[keep])
    
        if not nclasses and not nscores:
            return None, None, None
    
        boxes = np.concatenate(nboxes)
        classes = np.concatenate(nclasses)
        scores = np.concatenate(nscores)
    
        return boxes, classes, scores
    
    def draw(image, boxes, scores, classes):
        """Draw the boxes on the image.
    
        # Argument:
            image: original image.
            boxes: ndarray, boxes of objects.
            classes: ndarray, classes of objects.
            scores: ndarray, scores of objects.
            all_classes: all classes name.
        """
        for box, score, cl in zip(boxes, scores, classes):
            top, left, right, bottom = box
            print('class: {}, score: {}'.format(CLASSES[cl], score))
            print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))
            top = int(top)
            left = int(left)
            right = int(right)
            bottom = int(bottom)
    
            cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
            cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
                        (top, left - 6),
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.6, (0, 0, 255), 2)
    
    
    def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
        # Resize and pad image while meeting stride-multiple constraints
        shape = im.shape[:2]  # current shape [height, width]
        if isinstance(new_shape, int):
            new_shape = (new_shape, new_shape)
    
        # Scale ratio (new / old)
        r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    
        # Compute padding
        ratio = r, r  # width, height ratios
        new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
        dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    
        dw /= 2  # divide padding into 2 sides
        dh /= 2
    
        if shape[::-1] != new_unpad:  # resize
            im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
        top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
        left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
        im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
        return im, ratio, (dw, dh)
    
    
    if __name__ == '__main__':
    
        # Create RKNN object
        # 创建RKNN对象
        rknn = RKNN()
    
        if not os.path.exists(ONNX_MODEL):
            print('model not exist')
            exit(-1)
        
        # pre-process config
        # 配置模型运行的相关参数,具体如下
        print('--> Config model')
        rknn.config(reorder_channel='0 1 2',      # 调整通道输入顺序
                    mean_values=[[0, 0, 0]],      # 均值
                    std_values=[[255, 255, 255]], # 方差
                    optimization_level=3,         # 打开所有优化选项
                    target_platform = 'rv1126',   # 指定RKNN的运行平台
                    output_optimize=1,            # 官方文档未提供该参数的说明
                    quantize_input_node=QUANTIZE_ON) # 是否对输入节点进行量化
        print('done')
    
        # Load ONNX model
        # 加载待转换模型(onnx)
        print('--> Loading model')
        ret = rknn.load_onnx(model=ONNX_MODEL)
        if ret != 0:
            print('Load yolov5 failed!')
            exit(ret)
        print('done')
    
        # Build model
        # 构建RKNN模型
        # do_quantization:是否进行量化
        # dataset:参与量化的输入数据
        print('--> Building model')
        ret = rknn.build(do_quantization=QUANTIZE_ON, dataset=DATASET)
        if ret != 0:
            print('Build yolov5 failed!')
            exit(ret)
        print('done')
    
        # Export RKNN model
        # 导出模型为rknn
        print('--> Export RKNN model')
        ret = rknn.export_rknn(RKNN_MODEL)
        if ret != 0:
            print('Export yolov5rknn failed!')
            exit(ret)
        print('done')
        # ============================================================
            # 上述模型转换成功后,后续代码对模型进行性能评估
        # ============================================================
    
    
        # init runtime environment
        # 初始化运行环境
        print('--> Init runtime environment')
        ret = rknn.init_runtime()
        # ret = rknn.init_runtime('rk1808', device_id='1808')
        if ret != 0:
            print('Init runtime environment failed')
            exit(ret)
        print('done')
    
        # Set inputs
        img = cv2.imread(IMG_PATH)
        img, ratio, (dw, dh) = letterbox(img, new_shape=(IMG_SIZE[1], IMG_SIZE[0]))
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
        # Inference
        # 模型推理
        print('--> Running model')
        outputs = rknn.inference(inputs=[img])
    
        # post process
        input0_data = outputs[0]
        input1_data = outputs[1]
        input2_data = outputs[2]
    
        input0_data = input0_data.reshape([3,-1]+list(input0_data.shape[-2:]))
        input1_data = input1_data.reshape([3,-1]+list(input1_data.shape[-2:]))
        input2_data = input2_data.reshape([3,-1]+list(input2_data.shape[-2:]))
    
        input_data = list()
        input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
        input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
        input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))
    
        boxes, classes, scores = yolov5_post_process(input_data)
    
        img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
        if boxes is not None:
            draw(img_1, boxes, scores, classes)
        # cv2.imshow("post process result", img_1)
        # cv2.waitKeyEx(0)
    
        # 性能评估
        rknn.eval_perf(inputs=[img],is_print=True)
    
        rknn.release()
    
    

4. Configuración del entorno de desarrollo (RKNN-NPU)

  A través del tutorial anterior, convertimos el modelo (onnx) al modelo (rknn) en el lado de la PC y obtuvimos los resultados esperados, para que se pueda llevar a cabo el proceso de verificación en la placa. A continuación, debemos ejecutar el modelo en un chip específico (como RV1126) para completar el proceso de implementación completo desde la PC al chip.

4.1 Estructura del código fuente

Este proyecto proporciona principalmente controladores y ejemplos para Rockchip NPU. La estructura del código fuente del proyecto se muestra en la siguiente figura.

Chips aplicables: RK1808/RK1806, RV1109/RV1126
Plataforma de configuración: Ubuntu18.04
Estructura del código fuente:
inserte la descripción de la imagen aquí


4.2 Compile el código fuente

  1. Descargar el compilador cruzado [arm]

    Dirección de descarga: https://releases.linaro.org/components/toolchain/binaries/6.4-2017.08/arm-linux-gnueabihf/
    Versión del compilador: compilador cruzado de 32 bits
    : descomprima el compilador cruzado a una ruta fija, en Ubuntu Compile el proyecto y ejecute el programa en la placa RV1126.
    inserte la descripción de la imagen aquí

    arm-none-eabi-gcc: herramienta de compilación cruzada ARM lanzada por GNU, que se puede utilizar para compilar chips ARM MCU (32 bits), como programas de chips ARM7, ARM9 y Cortex-M/R.
    aarch64-linux-gnu-gcc: la herramienta de compilación cruzada ARM basada en GCC de Linaro se puede utilizar para realizar una compilación cruzada de programas bare-metal, u-boot, kernel de Linux, sistema de archivos y aplicaciones de aplicaciones en objetivos ARMv8 (64 bits).

  2. CMakeLists.txt
    Preste atención a la versión de la biblioteca : RV1126 es un sistema de 32 bits y algunas bibliotecas dependientes también deben compilarse en bibliotecas de 32 bits con la cadena de herramientas anterior.

    cmake_minimum_required(VERSION 3.4.1)
    
    project(rknn_yolov5_demo_linux)
    
    set(CMAKE_SYSTEM_NAME Linux)
    
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s -O3")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -s -O3")
    
    if (CMAKE_C_COMPILER MATCHES "aarch64")
      set(LIB_ARCH lib64)
    else()
      set(LIB_ARCH lib)
    endif()
    
    # rga
    set(RGA_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/rga)
    include_directories(${RGA_DIR}/include)
    
    # drm
    set(DRM_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/drm)
    include_directories(${DRM_DIR}/include)
    include_directories(${DRM_DIR}/include/libdrm)
    
    include_directories(${CMAKE_SOURCE_DIR}/include)
    
    # rknn api
    set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/../../librknn_api)
    include_directories(${RKNN_API_PATH}/include)
    set(RKNN_API_LIB ${RKNN_API_PATH}/${LIB_ARCH}/librknn_api.so)
    
    #stb
    include_directories(${CMAKE_SOURCE_DIR}/../3rdparty/)
    
    set(CMAKE_INSTALL_RPATH "lib")
    
    add_executable(rknn_yolov5_demo
            src/drm_func.c
            src/rga_func.c
            src/postprocess.cc
            src/main.cc
            )
    
    target_link_libraries(rknn_yolov5_demo
    	${RKNN_API_LIB}
      dl
    )
    
    # install target and libraries
    set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install/rknn_yolov5_demo)
    install(TARGETS rknn_yolov5_demo DESTINATION ./)
    install(DIRECTORY model DESTINATION ./)
    install(PROGRAMS ${RKNN_API_LIB} DESTINATION lib)
    
  3. Configurar【build.sh】

    #!/bin/bash
    
    set -e
    
    # for rk1808 aarch64
    # GCC_COMPILER=${RK1808_TOOL_CHAIN}/bin/aarch64-linux-gnu
    
    # for rk1806 armhf
    # GCC_COMPILER=~/opts/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf
    
    # for rv1109/rv1126 armhf
    # 自己添加的编译器路径【RV1109_TOOL_CHAIN】
    RV1109_TOOL_CHAIN='/home/ll/Mount/kxh_2023/RV1126/gcc-linaro-6.4.1-2017.08-i686_arm-linux-gnueabihf'
    GCC_COMPILER=${RV1109_TOOL_CHAIN}/bin/arm-linux-gnueabihf
    
    ROOT_PWD=$( cd "$( dirname $0 )" && cd -P "$( dirname "$SOURCE" )" && pwd )
    
    # build rockx
    BUILD_DIR=${ROOT_PWD}/build
    
    if [[ ! -d "${BUILD_DIR}" ]]; then
      mkdir -p ${BUILD_DIR}
    fi
    
    cd ${BUILD_DIR}
    cmake .. \
        -DCMAKE_C_COMPILER=${GCC_COMPILER}-gcc \
        -DCMAKE_CXX_COMPILER=${GCC_COMPILER}-g++
    make -j4
    make install
    cd -
    
    # cp run_rk180x.sh install/rknn_yolov5_demo/
    cp run_rv1109_rv1126.sh install/rknn_yolov5_demo/
    

    Sobre la base del [build.sh] original, simplemente agregue la ruta del compilador cruzado [RV1109_TOOL_CHAIN] para compilar el programa.

    cd ~/RV1126/rknpu-master/rknpu-master/rknn/rknn_api/examples/rknn_yolov5_demo
    ./build.sh
    

    El archivo generado está en el directorio [instalación] y luego se copia en la placa [RV1126] para ejecutarlo.

4.3, análisis del código fuente

  1. Modelo de inicialización de la función API principal

    // 读取二进制的RKNN模型
    unsigned char* model_data      = load_model(model_name, &model_data_size);
    // 创建 rknn_context 对象
    ret = rknn_init(&ctx, model_data, model_data_size, 0)
    

    La función de inicialización rknn_init creará el objeto rknn_context, cargará el modelo RKNN y realizará comportamientos de inicialización específicos de acuerdo con [bandera]
    inserte la descripción de la imagen aquí


    rknn_query

    // 查询命令接口
    // 以查询SDK版本信息的命令为例
    ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version));
    
    // 查询输入输出个数的命令
    rknn_input_output_num io_num;
    ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
    if (ret < 0) {
          
          
       printf("rknn_init error ret=%d\n", ret);
       return -1;
    }
    printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output);
    

    La función rknn_query puede consultar y obtener información como la entrada y salida del modelo, el tiempo de ejecución y la versión del SDK. Los
    inserte la descripción de la imagen aquí
    comandos de consulta admitidos son los siguientes:
    inserte la descripción de la imagen aquí


    Establecer la entrada del modelo.

    // 设置输入的基本结构
    rknn_input inputs[1];
    memset(inputs, 0, sizeof(inputs));
    inputs[0].index        = 0;
    inputs[0].type         = RKNN_TENSOR_UINT8;
    inputs[0].size         = width * height * channel;
    inputs[0].fmt          = RKNN_TENSOR_NHWC;
    inputs[0].pass_through = 0;
    
    // 设置输入信息
    rknn_inputs_set(ctx, io_num.n_input, inputs);
    

    Los datos de entrada del modelo se pueden configurar mediante la función rknn_inputs_set. Esta función puede admitir múltiples entradas, cada una de las cuales es un objeto de estructura rknn_input, y el usuario debe configurar este objeto antes de pasarlo.
    inserte la descripción de la imagen aquí


    razonamiento modelo

    ret = rknn_run(ctx, NULL);
    

    La función rknn_run ejecutará una inferencia del modelo y los datos de entrada deben configurarse a través de la función rknn_inputs_set antes de llamar
    inserte la descripción de la imagen aquí


    Obtener resultados de inferencia del modelo

    rknn_output outputs[io_num.n_output];
    memset(outputs, 0, sizeof(outputs));
    for (int i = 0; i < io_num.n_output; i++) {
          
          
       outputs[i].want_float = 0; // 标识是否需要将输出数据转为 float 类型输出。
    }  
    ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL);
    

    La función rknn_outputs_get puede obtener los datos de salida de la inferencia del modelo. Esta función es capaz de recuperar múltiples datos de salida a la vez. Cada salida es un objeto de estructura rknn_output, y cada objeto rknn_output debe crearse y configurarse por turno antes de la llamada a la función.
    inserte la descripción de la imagen aquí


    Liberar la salida

    ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
    

    La función rknn_outputs_release liberará los recursos relacionados con la salida obtenida por la función rknn_outputs_get
    inserte la descripción de la imagen aquí

  2. Análisis de ejemplo

    Para aclarar todo el flujo de procesamiento, aquí solo se analiza la parte de la función principal del código. Los pasos básicos son los siguientes:
      a. Inicializar el modelo
      b. Obtener el número de entradas y salidas
      c. Leer la imagen
      d. Configurar los datos de entrada   ,
      por   ejemplo, posprocesamiento   h. liberar recursos


    /*-------------------------------------------
                      Main Functions
    -------------------------------------------*/
    int main(int argc, char** argv)
    {
          
          
      int            status     = 0;
      char*          model_name = NULL;
      rknn_context   ctx;
      void*          drm_buf = NULL;
      int            drm_fd  = -1;
      int            buf_fd  = -1; // converted from buffer handle
      unsigned int   handle;
      size_t         actual_size = 0;
      int            img_width   = 0;
      int            img_height  = 0;
      int            img_channel = 0;
      rga_context    rga_ctx;
      drm_context    drm_ctx;
      const float    nms_threshold      = NMS_THRESH;
      const float    box_conf_threshold = BOX_THRESH;
      struct timeval start_time, stop_time;
      int            ret;
      memset(&rga_ctx, 0, sizeof(rga_context));
      memset(&drm_ctx, 0, sizeof(drm_context));
    
      if (argc != 3) {
          
          
        printf("Usage: %s <rknn model> <bmp> \n", argv[0]);
        return -1;
      }
    
      printf("post process config: box_conf_threshold = %.2f, nms_threshold = %.2f\n", box_conf_threshold, nms_threshold);
    
      model_name       = (char*)argv[1];
      char* image_name = argv[2];
    
      if (strstr(image_name, ".jpg") != NULL || strstr(image_name, ".png") != NULL) {
          
          
        printf("Error: read %s failed! only support .bmp format image\n", image_name);
        return -1;
      }
    
      /* Create the neural network */
      // 1. 加载二进制rknn模型
      // 2. 初始化 rknn_context 对象 
      printf("Loading mode...\n");
      int            model_data_size = 0;
      unsigned char* model_data      = load_model(model_name, &model_data_size);
      ret                            = rknn_init(&ctx, model_data, model_data_size, 0);
      if (ret < 0) {
          
          
        printf("rknn_init error ret=%d\n", ret);
        return -1;
      }
      
      // 3. 查询SDK的版本信息
      rknn_sdk_version version;
      ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version));
      if (ret < 0) {
          
          
        printf("rknn_init error ret=%d\n", ret);
        return -1;
      }
      printf("sdk version: %s driver version: %s\n", version.api_version, version.drv_version);
    	
      // 4. 查询模型输入和输出的数量
      rknn_input_output_num io_num;
      ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
      if (ret < 0) {
          
          
        printf("rknn_init error ret=%d\n", ret);
        return -1;
      }
      printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output);
    	
      // 5. 查询输入的属性信息:比如大小,shape,name
      rknn_tensor_attr input_attrs[io_num.n_input];
      memset(input_attrs, 0, sizeof(input_attrs));
      for (int i = 0; i < io_num.n_input; i++) {
          
          
        input_attrs[i].index = i;
        ret                  = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
        if (ret < 0) {
          
          
          printf("rknn_init error ret=%d\n", ret);
          return -1;
        }
        dump_tensor_attr(&(input_attrs[i]));
      }
    
      // 6. 查询输出的属性信息:比如大小,shape,name,输出个数
      rknn_tensor_attr output_attrs[io_num.n_output];
      memset(output_attrs, 0, sizeof(output_attrs));
      for (int i = 0; i < io_num.n_output; i++) {
          
          
        output_attrs[i].index = i;
        ret                   = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
        dump_tensor_attr(&(output_attrs[i]));
        if (output_attrs[i].qnt_type != RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC || output_attrs[i].type != RKNN_TENSOR_UINT8) {
          
          
          fprintf(stderr,
                  "The Demo required for a Affine asymmetric u8 quantized rknn model, but output quant type is %s, output "
                  "data type is %s\n",
                  get_qnt_type_string(output_attrs[i].qnt_type), get_type_string(output_attrs[i].type));
          return -1;
        }
      }
    
      int channel = 3;
      int width   = 0;
      int height  = 0;
      if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) {
          
          
        printf("model is NCHW input fmt\n");
        width  = input_attrs[0].dims[0];
        height = input_attrs[0].dims[1];
      } else {
          
          
        printf("model is NHWC input fmt\n");
        width  = input_attrs[0].dims[1];
        height = input_attrs[0].dims[2];
      }
    
      printf("model input height=%d, width=%d, channel=%d\n", height, width, channel);
    
      // Load image
      // 7. 读取图像数据,也可以用opencv
      CImg<unsigned char> img(image_name);
      unsigned char*      input_data = NULL;
      input_data                     = load_image(image_name, &img_height, &img_width, &img_channel, &input_attrs[0]);
      if (!input_data) {
          
          
        return -1;
      }
      
      // 8. 设置输入的属性信息
      rknn_input inputs[1];
      memset(inputs, 0, sizeof(inputs));
      inputs[0].index        = 0;
      inputs[0].type         = RKNN_TENSOR_UINT8;
      inputs[0].size         = width * height * channel;
      inputs[0].fmt          = RKNN_TENSOR_NHWC;
      inputs[0].pass_through = 0;
    
      // DRM alloc buffer
      drm_fd  = drm_init(&drm_ctx);
      drm_buf = drm_buf_alloc(&drm_ctx, drm_fd, img_width, img_height, channel * 8, &buf_fd, &handle, &actual_size);
      memcpy(drm_buf, input_data, img_width * img_height * channel);
      void* resize_buf = malloc(height * width * channel);
    
      // init rga context
      RGA_init(&rga_ctx);
      img_resize_slow(&rga_ctx, drm_buf, img_width, img_height, resize_buf, width, height);
      inputs[0].buf = resize_buf;
      gettimeofday(&start_time, NULL);
    
      // 8. 设置输入的属性信息
      rknn_inputs_set(ctx, io_num.n_input, inputs);
    
      rknn_output outputs[io_num.n_output];
      memset(outputs, 0, sizeof(outputs));
      for (int i = 0; i < io_num.n_output; i++) {
          
          
        outputs[i].want_float = 0;
      }
    	
      // 9. 执行推理一次
      ret = rknn_run(ctx, NULL);
      // 10. 获取推理结果
      ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL);
      gettimeofday(&stop_time, NULL);
      printf("once run use %f ms\n", (__get_us(stop_time) - __get_us(start_time)) / 1000);
    
      // post process
      // 10. 对输出进行后处理
      float scale_w = (float)width / img_width;
      float scale_h = (float)height / img_height;
    
      detect_result_group_t detect_result_group;
      // 10.1:获取量化常量值,用于反量化
      std::vector<float>    out_scales;
      std::vector<uint32_t> out_zps;
      for (int i = 0; i < io_num.n_output; ++i) {
          
          
        out_scales.push_back(output_attrs[i].scale);
        out_zps.push_back(output_attrs[i].zp);
      }
    
      // 10.2:开始后处理
      post_process((uint8_t*)outputs[0].buf, (uint8_t*)outputs[1].buf, (uint8_t*)outputs[2].buf, height, width,
                   box_conf_threshold, nms_threshold, scale_w, scale_h, out_zps, out_scales, &detect_result_group);
    
      // Draw Objects
      // 10.3: 绘制预测结果
      char                text[256];
      const unsigned char blue[]  = {
          
          0, 0, 255};
      const unsigned char white[] = {
          
          255, 255, 255};
      for (int i = 0; i < detect_result_group.count; i++) {
          
          
        detect_result_t* det_result = &(detect_result_group.results[i]);
        sprintf(text, "%s %.2f", det_result->name, det_result->prop);
        printf("%s @ (%d %d %d %d) %f\n", det_result->name, det_result->box.left, det_result->box.top,
               det_result->box.right, det_result->box.bottom, det_result->prop);
        int x1 = det_result->box.left;
        int y1 = det_result->box.top;
        int x2 = det_result->box.right;
        int y2 = det_result->box.bottom;
        // draw box
        img.draw_rectangle(x1, y1, x2, y2, blue, 1, ~0U);
        img.draw_text(x1, y1 - 12, text, white);
      }
      img.save("./out.bmp");
    
      // 11:释放内存中的输出数据
      ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
    
      // release
      // 12: 释放rknn_context对象,以及其它资源
      ret = rknn_destroy(ctx);
      drm_buf_destroy(&drm_ctx, drm_fd, buf_fd, handle, drm_buf, actual_size);
    
      drm_deinit(&drm_ctx, drm_fd);
      RGA_deinit(&rga_ctx);
      if (model_data) {
          
          
        free(model_data);
      }
    
      if (resize_buf) {
          
          
        free(resize_buf);
      }
      stbi_image_free(input_data);
    
      return 0;
    }
    

4.4, operación del lado del chip

  1. adb push instalar /datos de usuario/
  2. exportar LD_LIBRARY_PATH=./install/lib
  3. rknn_yolov5_demo <ruta_modelo> <ruta_imagen>

O puede crear un script [sh] para ejecutar, el script específico es el siguiente:

#!/bin/bash
set -e # 如果单个文件运行错误,则报错,并停止运行;如果没有该语句,则报错,但不会停止运行

# 输入数据文件夹
data_path="image_dir/"

# 保存数据文件夹
savepath="result/"

export LD_LIBRARY_PATH=/tmp/lib

# 遍历路径下的每一个图片,运行程序
for dir in $(ls $data_path)
do
   echo dir
   ./model/yolov5_rknn_demo--model_path=./model/yolov5.rknn --data_path=$path/$dir --save_path=$savepath/
done 

5. Análisis del algoritmo de cuantificación.

  1. introducción básica

    El propósito de la cuantificación: el modelo de cuantificación utiliza una precisión más baja (como int8 / uint8 / int16) para guardar la información de peso del modelo y puede usar menos espacio de almacenamiento durante la implementación para obtener una velocidad de inferencia más rápida. Sin embargo, cada marco de aprendizaje profundo generalmente utiliza datos de punto flotante al entrenar y guardar el modelo, por lo que la cuantificación del modelo es una parte muy importante del proceso de conversión del modelo.

    RKNN Toolkit admite modelos de cuantificación principalmente en las dos formas siguientes:
    inserte la descripción de la imagen aquí

  2. Cuantización estática después del entrenamiento.

    Al utilizar este método, RKNN Toolkit carga el modelo de punto flotante entrenado por el usuario y luego lo estima de acuerdo con el método de cuantificación especificado por la interfaz de configuración y el conjunto de datos de calibración proporcionado por el usuario (un pequeño subconjunto de datos de entrenamiento o verificación). datos, alrededor de 100 ~ 500). El rango (mínimo, máximo) de todos los datos flotantes en el modelo. Actualmente, RKNN Toolkit admite 3 métodos de cuantificación.

    Método 1: ametric_quantized-u8 (método de cuantificación predeterminado)

    Este es el algoritmo de cuantificación posterior al entrenamiento compatible con TensorFlow y recomendado por Google. Según la descripción del artículo "Cuantización de redes convolucionales profundas para una inferencia eficiente: un documento técnico", este método de cuantificación tiene la menor pérdida de precisión. La fórmula de cálculo es la siguiente:
    inserte la descripción de la imagen aquí
    quant: representa el número después de la cuantificación, float_num: representa el número de coma flotante antes de la cuantificación, escala: factor de escala (float32), zero-points representa el valor de cuantificación correspondiente (tipo int32) cuando el número real es 0, y finalmente satura el cuanto a [range_min, range_max]. Porque debe cuantificarse al tipo uint8, range_max=255, range_min=0.
    inserte la descripción de la imagen aquí

    Método 2: punto_fijo_dinámico-i8

    Aquí está la cita

    Método 3: punto_fijo_dinámico-i16

    La fórmula de cuantificación de Dynamic_fixed_point-i16 es la misma que la de Dynamic_fixed_point-i8, excepto que su ancho de bits bw es 16, que es un rango saturado. La NPU de RK3399Pro o RK1808 viene con una unidad informática 300Gops int16. Para algunos modelos que pierden mucha precisión después de ser cuantificados a 8 bits, se puede considerar este método de cuantificación.
    inserte la descripción de la imagen aquí

  3. Entrenamiento consciente de la cuantificación

    Se puede obtener un modelo con pesos cuantificados mediante un entrenamiento consciente de la cuantificación. RKNN Toolkit actualmente admite TensorFlow y PyTorch, dos marcos que cuantifican los modelos obtenidos mediante entrenamiento perceptivo. Consulte el siguiente enlace para obtener detalles técnicos del entrenamiento con reconocimiento de cuantificación:
    TensorFlow: https://www.tensorflow.org/model_optimization/guide/quantization/training
    Pytorch: https://pytorch.org/blog/introduction-to -cuantización-en-pytorch /
    inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/kxh123456/article/details/129370265
Recomendado
Clasificación