AI 模型部署指南

SDK AI 模型应用类型

目前,FreeRTOS 和 Arduino SDK 提供了多个预部署模型,如下表所示:

RTL8735B:
SDK 中预部署的 AI 模型

类别

模型

仓库

目标检测

Yolov3-tiny Yolov4-tiny Yolov7-tiny

https://github.com/AlexeyAB/darknet

目标检测

YOLOv7-tiny-pt

https://github.com/WongKinYiu/yolov7

人脸检测

SCRFD

https://github.com/deepinsight/insightface/tree/master/detection/scrfd

人脸识别

MobileFaceNet

https://github.com/deepinsight/insightface/tree/master/recognition

声音分类

YAMNet

https://github.com/tensorflow/models/tree/master/research/audioset/yamnet

参考: NN 模型库

Arduino SDK 定制模型部署指南

本指南说明如何在 Ameba IC 上使用 Arduino SDK 部署自定义的 NN 模型( .nb 文件)。

从 Flash 加载模型

步骤 1:重命名 .nb 文件

将自定义的 .nb 文件重命名为预期的文件名。有关预期的文件名,请参考 默认支持的模型文件名 章节。

步骤 2:复制到项目文件夹

Windows:
C:\Users\<USERNAME>\AppData\Local\Arduino15\packages\realtek\hardware\<Ameba_IC>\<VERSION>\libraries\NeuralNetwork\examples

步骤 3:打开示例

在 Arduino IDE 中,导航至:

File → Examples → AmebaNN → ObjectDetectionCallback

步骤 4:设置加载源

Tools → NN Model Load from: → Load from Flash

步骤 5:更新 modelSelect()

更新代码中的模型选择:

ObjDet.modelSelect(OBJECT_DETECTION, CUSTOMIZED_YOLOV4TINY, NA_MODEL, NA_MODEL);

步骤 6:编译并上传

在 Arduino IDE 中点击 上传.nb 文件将自动打包到 flash 中。

从 SD 卡加载模型

步骤 1:重命名 .nb 文件

将自定义的 .nb 文件重命名为预期的文件名。请参考 默认支持的模型文件名 章节。

步骤 2:打开示例

在 Arduino IDE 中,导航至:

File → Examples → AmebaNN → ObjectDetectionCallback

步骤 3:设置加载源

Tools → NN Model Load from: → Load from SD

步骤 4:更新 modelSelect()

ObjDet.modelSelect(OBJECT_DETECTION, CUSTOMIZED_YOLOV4TINY, NA_MODEL, NA_MODEL);

步骤 5:准备 SD 卡

.nb 文件放置在 SD 卡的以下目录结构中:

SD card root/
└── NN_MDL/
    └── yolov4_tiny.nb

步骤 6:(可选)在代码中更改 SD 文件名

编辑 SD_Model.cpp,路径为:

Arduino15\packages\realtek\hardware\<Ameba IC>\<VERSION>\libraries\NeuralNetwork\src\SD_Model.cpp
static void *yolov4_get_SD_filename(void)
{
    return (void *)"sd:/NN_MDL/yolov4_tiny.nb";  // update filename here
}

步骤 7:编译、上传并运行

点击 上传,然后在按下复位按钮之前插入 SD 卡。

默认支持的模型文件名

RTL8735B:
默认支持的模型文件名

分类

预期文件名

物体检测

yolov3_tiny.nb
yolov4_tiny.nb
yolov7_tiny.nb

人脸检测

scrfd_500m_bnkps_640x640_u8.nb
scrfd_500m_bnkps_576x320_u8.nb

人脸识别

mobilefacenet_int8.nb
mobilefacenet_int16.nb

音频分类

yamnet_fp16.nb
yamnet_s_hybrid.nb

图像分类

mobilenetv2_int16.nb
img_class_cnn.nb

手势识别

palm_detection_lite_int16.nb
hand_landmark_lite_int16.nb

备注

  • 对于 SD 卡加载,所有文件必须放置在 SD 卡 NN_MDL/ 目录下,路径格式为 sd:/NN_MDL/ + 文件名 + .nb

  • 对于 Flash 加载(Arduino SDK),文件名必须完全匹配并放置在 NeuralNetwork 示例文件夹中。

  • 同时运行物体检测和人脸检测时,使用 scrfd_500m_bnkps_576x320_u8.nb 搭配 yolov4_tiny_576x320.nb (相同分辨率)。

  • 为获得更好的人脸识别精度,推荐使用 mobilefacenet_int16.nb 而非 int8 版本。

FreeRTOS SDK 定制模型部署指南

本指南说明如何在 Ameba IC 上使用 FreeRTOS SDK 部署自定义的 NN 模型( .nb 文件)。 相关说明基于 mmf2_video_example_vipnn_rtsp_init YOLOv4 示例。

关键文件

RTL8735B:
FreeRTOS SDK 中用于加载自定义模型的关键文件

文件

用途

component/file_system/nn/nn_file_op.c

控制 Flash 与 SD 加载方式

mmf2_video_example_vipnn_rtsp_init.c

包含 USER_LOAD_MODEL 块的主示例

project/.../test_model/model_yolo.c

YOLO 模型的前/后处理

从 Flash 加载模型

步骤 1:重命名 .nb 文件

将自定义的 .nb 文件重命名为预期的文件名。有关预期的文件名列表,请参考 Arduino 部署指南中的 默认支持的模型文件名 章节。

步骤 2:将 .nb 复制到 SDK 中

将模型二进制文件放置到以下路径:

RTL8735B:
project/realtek_<ameba_ic>_v0_example/src/test_model/
└── model_nb/
   └── yolov4_tiny.nb    ← 替换为您的自定义模型

步骤 3:确认 MODEL_SRC 为 Flash(默认)

RTL8735B:

component/file_system/nn/nn_file_op.c 中:

#define MODEL_SRC  MODEL_FROM_FLASH   // 默认值,无需修改

步骤 4:在 FWFS JSON 中注册模型

RTL8735B:

编辑 project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/mp/<ameba_ic>_fwfs_nn_models.json

{
    "msg_level": 3,
    "PROFILE": ["FWFS"],
    "FWFS": {
        "files": ["MODEL0"]
    },
    "MODEL0": {
        "name": "yolov4_tiny.nb",
        "source": "binary",
        "file": "yolov4_tiny.nb"
    }
}

备注

仅列出您实际使用的模型——未使用的模型会增加最终固件的体积。

步骤 5:配置示例

RTL8735B:

mmf2_video_example_vipnn_rtsp_init.c 中,设置模型和输入分辨率:

#define YOLO_MODEL      1
#define USE_NN_MODEL    YOLO_MODEL

#if (USE_NN_MODEL == YOLO_MODEL)
#define NN_WIDTH    416
#define NN_HEIGHT   416
static float nn_confidence_thresh = 0.4;
static float nn_nms_thresh = 0.3;
#endif

video_example_media_framework.c 中启用示例:

mmf2_video_example_vipnn_rtsp_init();

步骤 6:编译

cmake .. -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake -DVIDEO_EXAMPLE=ON
cmake --build . --target flash_nn

输出:flash_ntz.nn.bin 位于 GCC-RELEASE/build/

步骤 7:烧录到开发板

Windows:
uartfwburn.exe -p COM? -f flash_ntz.nn.bin -b 3000000 -U -x 32

从 SD 卡加载模型

步骤 1:重命名 .nb 文件

将自定义的 .nb 文件重命名为预期的文件名。

步骤 2:准备 SD 卡

SD card root/
└── NN_MDL/
    └── yolov4_tiny.nb

步骤 3:将 MODEL_SRC 设置为 SD

RTL8735B:

component/file_system/nn/nn_file_op.c 中:

#define MODEL_FROM_FLASH  0x01
#define MODEL_FROM_SD     0x02
#define MODEL_SRC         MODEL_FROM_SD

步骤 4:在示例中启用 USER_LOAD_MODEL

RTL8735B:

mmf2_video_example_vipnn_rtsp_init.c 中,将 USER_LOAD_MODEL 设置为 1 并定义模型对象:

#define USER_LOAD_MODEL     1

#include "vfs.h"
static void *example_get_model_name(void)
{
   return (void *)"sd:/NN_MDL/yolov4_tiny.nb";
}

extern void yolov4_set_network_init_info(void *m);
extern int yolo_preprocess(void *data_in, nn_data_param_t *data_param, void *tensor_in, nn_tensor_param_t *tensor_param);
extern int yolo_postprocess(void *tensor_out, nn_tensor_param_t *param, void *res);
extern void yolo_set_confidence_thresh(void *confidence_thresh);
extern void yolo_set_nms_thresh(void *nms_thresh);

nnmodel_t yolov4_tiny_from_sd = {
   .nb                    = example_get_model_name,
   .set_init_info         = yolov4_set_network_init_info,
   .preprocess            = yolo_preprocess,
   .postprocess           = yolo_postprocess,
   .model_src             = MODEL_SRC_FILE,
   .set_confidence_thresh = yolo_set_confidence_thresh,
   .set_nms_thresh        = yolo_set_nms_thresh,
   .name = "YOLOv4t_SD"
};

video_example_media_framework.c 中启用示例:

mmf2_video_example_vipnn_rtsp_init();

步骤 5:编译

cmake .. -G\"Unix Makefiles\" -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake -DVIDEO_EXAMPLE=ON
cmake --build . --target flash_nn

步骤 6:烧录并运行

Windows:
uartfwburn.exe -p COM? -f flash_ntz.nn.bin -b 3000000 -U -x 32

然后,在按下复位按钮之前插入 SD 卡。

進階新模型部署指南

本指南涵盖在 Ameba IC 上部署定制化神经网络模型的高级主题,包括 SDK 配置、内存评估、预处理节点、模型名称修改、模型安全性以及后处理 PC 开发工具。

定制化神经网络模型的 SDK 配置

将模型转换为 .nb 格式后,下一步是将其添加到 SDK 中,并实现必要的预处理和后处理。

SDK 中的神经网络相关文件

RTL8735B:

以下目录和文件与 RTL8735B SDK 中的 AI 模型部署相关:

<Ameba_ic>_SDK/
|-- component/media/mmfv2/              --> 多媒体模块 (mmf)
|   |-- module_vipnn.c                 --> 调用 viplite 驱动 API 部署神经网络模型并触发推理
|   |-- module_vipnn.h
|-- project/realtek_<ameba_ic>_v0_example/src/test_model/   --> 测试神经网络模型和数据处理
|   |-- model_yolo.c                   --> YOLO 系列模型的预处理和后处理
|   |-- model_yolo.h
|   |-- model_nb/                      --> 模型二进制文件
|       |-- yolov3_tiny.nb
|       |-- yolov4_tiny.nb
|-- project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/mp/
|   |-- <ameba_ic>_partitiontable.json  --> 闪存分区表,为神经网络模型分配区域
|   |-- <ameba_ic>_fwfs_nn_models.json  --> 选择要包含在最终固件中的模型
|-- project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/application/
|   |-- <rtl_ic>_ram.ld               --> 链接脚本,为神经网络设置 DDR 内存空间
|-- component/file_system/nn/
   |-- nn_file_op.c                   --> viplite 驱动神经网络文件操作层(闪存或 SD 加载)

将定制化模型网络二进制文件添加到 SDK

生成 AmebaNet.nb 后,将其添加到 SDK:

project/realtek_<ameba_ic>_v0_example/src/test_model/
|-- model_nb/
|   |-- yolov3_tiny.nb
|   |-- yolov4_tiny.nb
|   |-- yolov7_tiny.nb
|   |-- AmebaNet.nb
|-- model_yolo.c
|-- model_yolo.h
|-- model_AmebaNet.c       --> AmebaNet 的预处理和后处理实现
|-- model_AmebaNet.h

备注

记得将你的 model_AmebaNet.c 添加到 project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/application/application.cmake。 同时检查闪存和 DDR 配置是否满足你的模型需求。

接下来,在 project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/mp/<ameba_ic>_fwfs_nn_models.json 中注册模型:

{
    "msg_level": 3,
    "PROFILE": ["FWFS"],
    "FWFS": {
        "files": ["MODEL0", "MODEL1"]
    },
    "MODEL0": {
        "name": "yolov4_tiny.nb",
        "source": "binary",
        "file": "yolov4_tiny.nb"
    },
    "MODEL1": {
        "name": "AmebaNet.nb",
        "source": "binary",
        "file": "AmebaNet.nb"
    }
}

备注

FWFSfiles 中只选择你实际使用的模型。包含 未使用的模型会使最终的固件体积不必要地增大。

为 VIPNN 模块创建模型对象

vipnn 模块使用模型对象( nnmodel_t)来部署模型、 运行预处理、触发推理以及运行后处理。

model_AmebaNet.c 中创建该对象:

nnmodel_t AmebaNet = {
    .nb         = AmebaNet_get_network_filename,
    .preprocess     = AmebaNet_preprocess,
    .postprocess    = AmebaNet_postprocess,
    .model_src  = MODEL_SRC_FILE,
    .name = "AmebaNet"
};

设置模型文件名:

void *AmebaNet_get_network_filename(void)
{
    return (void *) "NN_MDL/AmebaNet.nb";
}

备注

神经网络驱动程序默认使用固件文件系统( component/file_system/fwfs) 从闪存中打开和读取模型。详情请参阅 component/file_system/nn/nn_file_op.c

实现预处理:

int AmebaNet_preprocess(void *data_in, nn_data_param_t *data_param,
                        void *tensor_in, nn_tensor_param_t *tensor_param)
{
    void **tensor = (void **)tensor_in;

    // do pre-process here, user can refer to model_yolo.c
    (uint8_t *)data_in;
    (uint8_t *)tensor[0];
    (uint8_t *)tensor[1];

    // clean cache since data will be accessed by NN engine directly
    dcache_clean_by_addr((uint32_t *)tensor[0], data_length);

    return 0;
}

实现后处理:

int AmebaNet_postprocess(void *tensor_out, nn_tensor_param_t *param, void *res)
{
    void **tensor = (void **)tensor_out;
    int output_count = param->count;

    // decode tensor data, user can refer to model_yolo.c
    for (int n = 0; n < output_count; n++) {
        (uint8_t *)tensor[n];
    }

    // fill result
    int od_num = 0;
    objdetect_res_t *od_res = (objdetect_res_t *)res;
    for (int i = 0; i < box_idx; i++) {
        box_t *obj = &res_box[i];
        if (obj->invalid == 0) {
            od_res[od_num].result[0] = obj->class_idx;
            od_res[od_num].result[1] = obj->prob;
            od_res[od_num].result[2] = obj->x;
            od_res[od_num].result[3] = obj->y;
            od_res[od_num].result[4] = obj->x + obj->w;
            od_res[od_num].result[5] = obj->y + obj->h;
            od_num++;
        }
    }
    return od_num;
}

神经网络内存与闪存使用评估

评估模型大小,以确保 DDR 内存和闪存空间充足。

模型内存与大小

Category

Model

Input size

Quantized

DDR memory

File size

Object detection

Yolov3-tiny
Yolov4-tiny
Yolov4-tiny
Yolov7-tiny
NanoDet-Plus-m
NanoDet-Plus-m
416x416
416x416
576x320
416x416
416x416
576x320
uint8
uint8
uint8
uint8
uint8
uint8
6.9 MB (6,946,128 bytes)
7.7 MB (7,712,412 bytes)
7.48 MB (7,840,836 bytes)
8.2 MB (8,597,072 bytes)
4.33 MB (4,542,016 bytes)
4.53 MB (4,746,556 bytes)
5.6 MB (5,568,384 bytes)
4.1 MB (4,131,712 bytes)
3.85 MB (4,043,136 bytes)
4.44 MB (4,664,512 bytes)
1.86 MB (1,959,040 bytes)
1.83 MB (1,924,096 bytes)

Face detection

SCRFD
SCRFD
640x640
576x320
uint8
uint8
4.1 MB (4,291,200 bytes)
2.6 MB (2,753,864 bytes)
0.68 MB (715,584 bytes)
0.56 MB (583,232 bytes)

Face Recognition

MobileFaceNet
MobileFaceNet
112x112
112x112
int8
int16
1.72 MB (1,799,716 bytes)
5.1 MB (5,343,948 bytes)
0.86 MB (904,576 bytes)
3.42MB (3,590,656 bytes)

Sound classification

YAMNet
YAMNet_s
15600x1
96x64
fp16
hybrid
9.2 MB (9,172,348 bytes)
0.73 MB (729,608 bytes)
8.7 MB (8,669,888 bytes)
0.67 MB (678,336 bytes)

使用 NBinfo 评估内存使用

使用 Verisilicon_SW_VIP_NBInfo 在 PC 上评估 DDR 内存使用情况:

********************************************************************************
Memory Info
********************************************************************************
Total Read Only Memory (bytes):                                   3737536
Total Command buffer (bytes):                                     167552
Total Load States (bytes):                                        34176
Total NN and TP instruction (bytes):                              132864
Total PPU instruction (bytes):                                    512
********************************************************************************
Total Operation Memory (bytes):                                   3939264
Total Input Memory (bytes):                                       519168
Total Output Memory (bytes):                                      215552
Memory Pool (bytes):                                              2769920
Video memory heap node reserved (bytes):                          20480
********************************************************************************
Total Video Memory (bytes):                                       7464448
Total System Memory (bytes):                                      247964
********************************************************************************

确保链接脚本中的神经网络 DDR 区域足够大。检查并修改:

project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/application/<rtl_ic>_ram.ld

/* DDR memory */
VOE    (rwx)    : ORIGIN = 0x70000000, LENGTH = 0x70100000 - 0x70000000  /*  1MB */
DDR    (rwx)    : ORIGIN = 0x70100000, LENGTH = 0x73000000 - 0x70100000  /* 49MB */
NN     (rwx)    : ORIGIN = 0x73000000, LENGTH = 0x74000000 - 0x73000000  /* 16MB */

备注

同时修改 bootloader 目录中的 <rtl_ic>_boot_mp.ld 以保持 神经网络区域一致。对于 TrustZone 项目,请修改 <rtl_ic>_ram_ns.ld

评估闪存上的模型大小

确保分区表中的神经网络区域大于你的模型大小。 对于单个模型(例如,为 4MB 的 yolov4-tiny 分配 7MB):

"nn": {
    "start_addr" : "0x770000",
    "length" : "0x700000",
    "type": "PT_NN_MDL",
    "valid": true
}

对于多个模型,将所有模型大小相加并分配足够的闪存空间。

例如,如果您要部署 4 个模型:yolov4-tiny、yamnet-s、mobilefacenet 和 centerface,则 "<ameba_ic>_fwfs_nn_models.json" 的内容将变为:

{
    "msg_level":3,

    "PROFILE":["FWFS"],
    "FWFS":{
        "files":[
            "MODEL0",
            "MODEL1",
            "MODEL2",
            "MODEL3"
        ]
    },
    "MODEL0":{
        "name" : "yolov4_tiny.nb",
        "source":"binary",
        "file":"yolov4_tiny.nb"

    },
    "MODEL1":{
        "name" : "yamnet_s.nb",
        "source":"binary",
        "file":"yamnet_s.nb"

    },
    "MODEL2":{
        "name" : "mobilefacenet_int16.nb",
        "source":"binary",
        "file":"mobilefacenet_int16.nb"

    },
    "MODEL3":{
        "name" : "centerface_uint8.nb",
        "source":"binary",
        "file":"centerface_uint8.nb"

    }
}

检查每个模型的大小,并计算总大小: 1,535KB + 3,507KB + 663KB + 4,053KB = 9,740KB. 因此,NN 至少需要 10MB 的 Flash 空间。

../../../_images/model_nb_size.png

模型网络 NB 文件大小

因此,以下文件中的 nn 区域长度: "project\realtek_<ameba_ic>_v0_example\GCC-RELEASE\mp\<ameba_ic>_partitiontable.json" 应不小于 10MB。

"nn":{
            "start_addr" : "0x770000",
            "length" : "0xA00000",   --> 10MB > total size(9,740KB)
            "type": "PT_NN_MDL",
            "valid": true
      },

如何添加预处理节点(可选)

有时你需要在推理之前执行数据预处理。Acuity Toolkit 可以自动生成一个在神经网络引擎上运行的预处理节点, 从而减轻 CPU 的负担。该节点负责颜色空间转换、缩放和裁剪。

通过配置``inputmeta.yml`` 来启用它:

input_meta:
  databases:
  - path: dataset.txt
    type: TEXT
    ports:
    - lid: input.1_137
      category: image
      dtype: float32
      sparse: false
      tensor_name:
      layout: nchw
      shape:
      - 1
      - 3
      - 320
      - 576
      fitting: scale
      preprocess:
        reverse_channel: false
        mean:
        - 127.5
        - 127.5
        - 127.5
        scale: 0.0078125
        preproc_node_params:
          add_preproc_node: true
          preproc_type: IMAGE_NV12
          preproc_image_size:
          - 576
          - 320

备注

此功能需要 Acuity 6.18.8VIPLite 驱动 1.12.0 或 更新版本。较旧版本不完全支持此功能。

如何在转换后修改定制化模型名称(可选)

网络二进制图格式在偏移量 12 字节处存储网络名称, 长度为 64 字节。

二进制图格式

Section

Field

Data Type

Count

Size in Bytes

Meaning

Header

Magic ... ... Network_name ...

CHAR UINT32 UINT32 CHAR UINT32

4 1 1 64 1

4 4 4 64 4

Must be "VPMN" for a valid binary graph file ... ... Indicates the name of a network ...

你可以使用任何十六进制编辑器编辑这 64 个字节。

../../../_images/hex_model_name.png

该名称在运行时通过以下方式在 module_vipnn.c 查询:

vip_query_network(ctx->network, VIP_NETWORK_PROP_NETWORK_NAME, ctx->network_name);
dprintf(LOG_INF, "network name:%s\n\r", ctx->network_name);

修改后,你应该会看到以下日志,

注意: 用户可能需要将调试日志级别更改为 LOG_INF,才能在控制台中看到此信息。

../../../_images/log_model_name.png

模型名称

YOLOv9-tiny 部署指南

SDK 配置自定义 YOLOv9-tiny 模型

上一节已获取模型二进制文件 (.nb)。本节将介绍如何将该模型二进制文件添加到 SDK, 并实现必要的前处理和后处理。

SDK 中与 NN 相关的文件

以下是 Ameba SDK 中与 AI 模型部署相关的目录和文件:

Ameba_SDK/
|-- component/media/mmfv2/  --> 多媒体模块 (mmf: multi-media framework)
    |-- module_vipnn.c  --> 调用 viplite 驱动 API 部署 NN 模型并触发推理的模块
    |-- module_vipnn.h
|-- project/realtek_<ameba_ic>_v0_example/src/test_model/   --> 提供 NN 测试模型及其数据处理
    |-- model_yolov9.c   --> yolov9 模型的前处理与后处理实现
    |-- model_yolov9.h
    |-- model_nb/   --> 模型二进制文件所在文件夹
        |-- yolov9_tiny.nb  --> yolov9-tiny 模型二进制文件
|-- project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/mp/
    |-- <ameba_ic>_partitiontable.json  --> 闪存分区表,需为 NN 模型安排合适的区域
    |-- <ameba_ic>_fwfs_nn_models.json  --> 选择使用的模型,这些模型将被合并到最终固件中
|-- project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/application/
    |-- rtl8735b_ram.ld  --> 链接脚本,为 NN 设置足够的 DDR 内存空间
|-- component/file_system/nn/
    |-- nn_file_op.c  --> viplite 驱动通过此 "nn 文件操作层" 从闪存或 SD 卡加载模型(默认:闪存文件系统)

将自定义模型网络二进制文件添加到 SDK

生成 yolov9_tiny.nb 后,请将该文件添加到 SDK 文件夹: project/realtek_<ameba_ic>_v0_example/src/test_model/model_nb。所有 模型网络二进制文件都将放置在此处,结构如下:

project/realtek_<ameba_ic>_v0_example/src/test_model/
|-- model_nb/
|   |-- yolov3_tiny.nb  --> yolov3-tiny 网络二进制图文件
|   |-- yolov4_tiny.nb  --> yolov4-tiny 网络二进制图文件
|   |-- yolov7_tiny.nb  --> yolov7-tiny 网络二进制图文件
|   |-- yolov9_tiny.nb  --> yolov9-tiny 网络二进制图文件
|-- model_yolo.c
|-- model_yolo.h
|-- model_yolov9.c  --> yolov9 前处理与后处理实现
|-- model_yolov9.h

备注

请记得将您的 model_yolov9.c 添加到 project/realtek_<ameba_ic>_v0_example/scenario.cmake。同时,请检查闪存大小和 DDR 大小的配置是否足以容纳 NN 模型。请参考 進階新模型部署指南 中的"评估模型在闪存中的大小"章节进行评估。

接下来,将模型添加到模型列表。

进入 project/realtek_<ameba_ic>_v0_example/GCC-RELEASE/mp/<ameba_ic>_fwfs_nn_models.json 并将 yolov9_tiny.nb 添加到此列表:

{
    "msg_level":3,

    "PROFILE":["FWFS"],
    "FWFS":{
        "files":[
            "MODEL0",
            "MODEL1"
        ]
    },
    "MODEL0":{
        "name" : "yolov4_tiny.nb",
        "source":"binary",
        "file":"yolov4_tiny.nb"
    },
    "MODEL1":{
        "name" : "yolov9_tiny.nb",
        "source":"binary",
        "file": "yolov9_tiny.nb"
    }
}

备注

如果您只想使用 yolov9_tiny.nb,请在 "FWFS"-"files" 中仅选择 "MODEL1"。否则,最终固件将因包含一些未使用的模型二进制文件而变得非常庞大。

创建可供 VIPNN 模块使用的模型对象

vipnn 模块将使用模型对象来部署模型、进行模型前处理、触发模型推理以及进行模型后处理。

因此,请在 model_yolov9.c 中创建 nnmodel_t yolov9_tiny。 以下是 VIPNN 模块将使用的必要函数, 请在完成实现后将这些函数指针注册到 yolov9_tiny 对象中:

nnmodel_t yolov9_tiny = {
    .nb         = yolov9_get_network_filename,
    .preprocess     = yolov9_preprocess,
    .postprocess    = yolov9_postprocess,
    .model_src  = MODEL_SRC_FILE,
    .name = "YOLOv9t"
};

设置 NN 驱动使用的模型文件名

需要设置模型名称,以便 NN 驱动在运行时部署时能够通过文件系统打开并加载 网络二进制文件。

void *yolov9_get_network_filename(void)
{
    return (void *) "NN_MDL/yolov9_tiny.nb";
}

备注

NN 驱动默认使用固件文件系统 (component/file_system/fwfs) 从闪存中打开并读取模型。更多信息,请参考 NN 驱动使用的 "nn 文件操作层" – component/file_system/nn/nn_file_op.c。

通过 NN 驱动设置 NN 模型的 desired_class

由于 yolov9 的输出特性,算法的运行速度取决于待检测目标的数量(3549 个锚点和 80 个类别),这意味着 无需检测全部 80 个类别,只需查找所需类别的相关信息即可。 因此,仅输入所需类别数组以减少后处理函数的运行时间。

要在 VIPNN 模块中注册所需类别,请在 module_vipnn.cmodule_vipnn.h 中创建 nn_desired_class_t。 以下是将添加到 VIPNN 模块中的步骤:

在 module_vipnn.h 中

  • 定义结构体,并在结构体 nnmodel_t 中添加 nn_desired_class_t

  • 定义 set_desired_class 函数,并在 nnmodel_t 结构体中添加 nn_set_desired_class_t

typedef struct nn_desired_class_s {
    int *class_info;
    int len;
} nn_desired_class_t;

typedef void (*nn_set_desired_class_t)(nn_desired_class_t *desired_class_list);
typedef struct nnmodel_s {
    ...
    nn_set_confidence_thresh_t set_confidence_thresh;
    nn_set_nms_thresh_t set_nms_thresh;
    nn_set_desired_class_t set_desired_class;
    ...
} nnmodel_t;
在 module_vipnn.c 中
  • 定义注册 vipnn 的情况

case CMD_VIPNN_SET_DESIRED_CLASS:
    if (ctx->params.model->set_desired_class) {
        ctx->params.model->set_desired_class((nn_desired_class_t *)arg);
    }
    break;

在 model_yolov9.c 中

  • 为 yolov9 设置所需类别

void yolov9_set_desired_class(nn_desired_class_t *desired_class_list)
{
    yolov9_desired_class_list_len = desired_class_list->len;
    yolov9_desired_class_list = desired_class_list->class_info;
}
在 mmf2_video_example_vipnn_rtsp_init.c 中
  • 指定目标类别并通过 vipnn 模块注册

static int desired_class_list[] = {0, 2, 5, 7};
static const int class_size = (sizeof(desired_class_list) / sizeof(int));
static nn_desired_class_t desired_class_param = {
    .class_info = desired_class_list,
    .len = class_size
};

mm_module_ctrl(vipnn_ctx, CMD_VIPNN_SET_DESIRED_CLASS, (int)&desired_class_param);

实现自定义前处理和后处理

用户可以在将图像传递给 NN 模型推理之前进行自定义前处理; 此外,用户还可以进行自定义后处理,以解码推理结果中的输出张量。

model_yolov9.c 中实现前处理:

int yolov9_preprocess(void *data_in, nn_data_param_t *data_param, void *tensor_in, nn_tensor_param_t *tensor_param)
{
    void **tensor = (void **)tensor_in;

    //在此处进行前处理,用户可参考 model_yolo.c
    (uint8_t *)data_in;
    (uint8_t *)tensor[0];
    //…

    //清理缓存,因为数据将由 NN 引擎直接访问
    dcache_clean_by_addr((uint32_t *)tensor[0], data_length);

    return 0;
}

model_yolov9.c 中实现后处理:

../../../_images/yolov9_postprocess.png

yolov9 张量输出格式

yolov9_tiny 的锚点数量为 3549,类别数量为 80,yolov9_tiny 的输出包括 center_x、center_y、width、height 以及每个类别的得分(共 80 个类别)

  • cx, cy, ow, oh: center_x, center_y, width, height

  • p1: 类别 1 的概率,以此类推

  • 下标: 锚点编号,例如 p2_1 表示锚点 1 中类别 2 的得分,p80_3500 表示锚点 3500 中类别 80 的得分

int yolov9_postprocess(void *tensor_out, nn_tensor_param_t *param, void *res)
{
    void **tensor = (void **)tensor_out;
    for(int idx=0; i < num_anchor; idx++){
        int cur_label = yolov9_desired_class_list[0]*num_anchor;
        uint8_t *tmp_pred_u8 = (uint8_t *)preds + idx;
        //该算法执行更快,因为只搜索 desired_class_list
        for(int i=0; i < yolov9_desired_class_list_len; i++){
            if (tmp_pred_u8[yolov9_desired_class_list[i]*num_anchor] > tmp_pred_u8[cur_label]) {
                cur_label = yolov9_desired_class_list[i]*num_anchor;
            }
        }
        //该算法执行较慢,因为搜索全部 80 个类别
        for (int label = 0; label < num_class; label++) {
            if (tmp_pred_u8[label*num_anchor] > tmp_pred_u8[cur_label]) {
                cur_label = label*num_anchor;
            }
        }
    }
}

模型安全

部分客户拥有内部自训练的模型需要保护。 SDK 支持 模型认证模型加密 功能, 以保护知识产权。

  • 模型图二进制认证(完整性 + 可信度)

  • 模型图二进制加密(机密性)

认证和解密由加密硬件加速器处理。用于解密的密钥存储在芯片内 eFuse OTP 中。

模型安全算法与密钥管理

安全特性

支持的算法

密钥管理

模型认证

Hash: sha256
Signature: EdDSA_ED25519

使用私钥在 PC 或服务器上签名模型。

使用公钥验证模型签名。

使用固件签名密钥签名模型。NN 模块使用固件清单中的公钥在运行时验证签名。

注意:启用可信启动,使公钥通过信任链验证。

模型加密

AES_256_CBC

使用 AES-256 密钥在 PC 或服务器上加密模型。

使用 "user eFuse OTP KEY 0" 加密模型。NN 模块在运行时使用此密钥解密。

注意:若无 "user eFuse OTP KEY 0",可通过 efuse_crypto_key_write(key, 0, 1) 注入。此密钥一次性可编程。

模型认证 — 哈希与签名检查

模型认证包括完整性检查和可信度检查。对模型签名后, SHA-256 哈希值和 EdDSA 签名会附加在 .nb 文件末尾。 签名验证哈希的可信度,哈希验证模型的完整性。

../../../_images/signed_model_format.png

签名后模型格式:

    1. 仅加密:[model header (encrypted)] [rest of model] [IV]

    1. 仅签名:[model] [hash] [signature]

    1. 签名 + 加密:[model header (encrypted)] [rest of model] [IV] [hash] [signature]

模型加密

用户可对模型图二进制文件加密以防止解析。通常只需加密模型的前 512 字节 (固定头部)。

二进制图中的固定头部

大小(字节)

头部和表格

512(固定)

数据段

动态

SDK 使用用户 OTP eFuse 密钥通过 AES-256-CBC 解密加密的模型头部。

备注

设备上的硬件加密引擎可加速解密过程。

安全功能的 SDK 配置

模型签名验证和解密 默认禁用。在 platform_opts.h 中启用:

/* For NN configuration */
#define CONFIG_NN_AES_ENCRYPTION 1
#define CONFIG_NN_HASH_SIGNATURE_CHECK 1

备注

这两个功能可独立启用。

安全部署流程

在 SDK 中启用 NN 解密或哈希/签名校验功能后,模型将按照如下流程进行安全部署。

../../../_images/secure_nn_deployment.png

NN 安全部署

签名与加密 PC 工具

工具位于:

project/realtek_<ameba_ic>_v0_example/src/test_model/model_nb/model_signature/model_sign_ed25519.py

安装所需包 (PyNaCl, PyCrypto):

$ pip install pynacl
$ pip install pycryptodome

任何密钥的格式都是一个十六进制字符串文件。例如,32 字节签名密钥(model-sign-key)的内容如下所示:

104008de9c2fed8fbb20139ea3eafb6b60e8fb8a603b488c90586e2750b7f3ae

以下是用于对模型进行签名或加密的命令用法。该工具也提供了相应的验证命令。

仅签名:

使用签名密钥对模型进行签名(ED25519 公钥)

$ python3 model_sign_ed25519.py --sign-key "model-sign-key" --model "../yolov4_tiny.nb"

使用验证密钥验证模型(ED25519 私钥)

$ python3 model_sign_ed25519.py --verify-key "model-verify-key" --signed-model "../yolov4_tiny.nb.sig"

备注

签名后,用户可以得到已签名模型 yolov4_tiny.nb.sig。用户应将该模型下载到 Flash 分区或文件系统中。

仅加密:

使用 AES 密钥对模型进行加密(AES-256-CBC 对称密钥),IV 将由工具随机生成。

$ python3 model_sign_ed25519.py --model "../yolov4_tiny.nb" --enc-key "model-enc-key"

使用相同的 AES 密钥解密模型

$ python3 model_sign_ed25519.py --signed-model "../yolov4_tiny.nb.enc" --enc-key "model-enc-key"

备注

加密后,用户可以得到已加密模型 yolov4_tiny.nb.enc。用户应将该模型下载到 Flash 分区或文件系统中。

签名并加密:

使用签名密钥和加密密钥对模型进行签名并加密

$ python3 model_sign_ed25519.py --sign-key "model-sign-key" --model "../yolov4_tiny.nb" --enc-key "model-enc-key"

使用验证密钥和加密密钥解密并验证模型签名

$ python3 model_sign_ed25519.py --verify-key "model-verify-key" --signed-model "../yolov4_tiny.nb.enc.sig" --enc-key "model-enc-key"

备注

签名并加密后,用户可以得到带签名的加密模型 yolov4_tiny.nb.enc.sig。用户应将该模型下载到 Flash 分区或文件系统中。

备注

  • 签名后模型:yolov4_tiny.nb.sig

  • 加密后模型:yolov4_tiny.nb.enc

  • 签名并加密:yolov4_tiny.nb.enc.sig

将生成的文件下载到闪存分区或文件系统中。

性能测试结果

性能测试结果(yolov4-tiny 416x416)

安全特性

测试项

时间 (ms)

备注

认证

签名检查

3

检查 32 字节模型哈希签名。时间固定。

认证

哈希检查

38

取决于模型大小(yolov4-tiny: 4MB)。

解密

密文解密

3

始终解密 512 字节固定头部。时间固定。

后处理 PC 开发工具

您可以在部署到设备之前,先在 PC 上开发和验证后处理。在 Acuity 工具包中运行推理脚本后, 您可以获取模型的输出张量并在 PC 上解码。

PC 工具和 <RTL_IC> 设备使用的后处理 API 接口相同,便于从开发过渡到部署。

步骤

步骤 1:model_yolo_sim.c 中开发后处理。

步骤 2:main.c 中根据 NB 文件设置张量参数:

static void yolo_pc_configure_tensor_param(nn_tensor_param_t *input_param, nn_tensor_param_t *output_param)
{
    char *nbg_filename = "../../test_model/model_nb/yolov4_tiny.nb";
    config_param_from_nb_file(nbg_filename, input_param, output_param);
}

int yolo_simulation(void)
{
    nn_tensor_param_t input_param, output_param;
    yolo_pc_configure_tensor_param(&input_param, &output_param);
    // ...
}

步骤 3: 从 Acuity 推理获取输出张量并设置文件路径:

int yolo_simulation(void)
{
    // ...
    char *acuity_tensor_name[16];
    acuity_tensor_name[0] = "../data/yolo_data/iter_0_output_30_65_out0_1_255_13_13.tensor";
    acuity_tensor_name[1] = "../data/yolo_data/iter_0_output_37_76_out0_1_255_26_26.tensor";
    void *pp_tensor_out[16];
    memset(pp_tensor_out, 0, sizeof(pp_tensor_out));
    acuity_output_tensor_conversion(acuity_tensor_name, pp_tensor_out, &output_param);
    // ...
}

步骤 4: 编译:

mkdir build && cd build
cmake .. -G"Unix Makefiles"
make -j4

步骤 5: 执行:

./nn_postprocess

步骤 6: 检查结果。带有边界框的图像将保存在 data/yolo_data/prediction.jpg

../../../_images/detection_result.jpg

检查结果

附录 A:Acuity 支持的操作层

ONNX 到 ACUITY 操作映射

ONNX 到 ACUITY 操作映射

ONNX Operation

ACUITY Operation

Abs

abs

Add

add

And

logical_and

ArgMax

argmax

ArgMin

argmin

Atan

atan

Atanh

atanh

BatchNormalization

batchnormalize

Cast

cast

CastLike

cast

Ceil

ceil

Celu

celu

Clip

clipbyvalue

Concat

concat

Conv

conv1d/group_conv1d/depthwise_conv1d/convolution/conv2d_op/depthwise_conv2d_op/conv3d

ConvTranspose

deconvolution/deconvolution1d

Cos

cos

Cumsum

cumsum

DepthToSpace

depth2space

DequantizeLinear

dequantize

DFT

dft

Div

divide

Dropout

dropout

Einsum

einsum

Elu

elu

Equal

equal

Erf

erf

Exp

exp

Expand

expand_broadcast

Floor

floor

Gather

gather

GatherElements

gather_elements

GatherND

gathernd

Gemm

matmul/fullconnect

Greater

greater

GreaterOrEqual

greater_equal

GridSample

gridsample

GRU

gru

HammingWindow

hammingwindow

HannWindow

hannwindow

HardSigmoid

hard_sigmoid

HardSwish

hard_swish

InstanceNormalization

instancenormalize

LeakyRelu

leakyrelu

Less

less

LessOrEqual

less_equal

Log

log

Logsoftmax

log_softmax

LRN

localresponsenormalization

LSTM

lstm

MatMul

matmul/fullconnect

Max

eltwise(MAX)

MaxPool/AveragePool/GlobalAveragePool/GlobalMaxPool

pooling/pool1d/pool3d

MaxRoiPool

roipooling

Mean

eltwise(MEAN)

MeanVarianceNormalization

instancenormalize

Min

eltwise(MIN)

Mish

mish

Mod

mod

Mul

multiply

Neg

neg

NonZero

nonzero

OneHot

onehot

Or

logical_or

Pad

pad

Pow

pow

Prelu

prelu

QLinearConv

convolution/conv1d

QLinearMatMul

matmul

QuantizeLinear

quantize

Reciprocal

variable+divide

ReduceL1

abs+reducesum

ReduceL2

reducesum+multiply+sqrt

ReduceLogSum

reducesum+log

ReduceLogSumExp

exp+reducesum+log

ReduceMax

reducemax

ReduceMean

reducemean

ReduceMin

reducemin

ReduceProd

reduceprod

ReduceSum

reducesum

ReduceSumSquare

multiply+reducesum

Relu

relu

Reshape/Squeeze/Unsqueeze/Flatten

reshape

Resize

image_resize

ReverseSequence

reverse_sequence

Round

round

ScatterND

scatter_nd_update

Selu

selu

Shape

shapelayer

Sigmoid

sigmoid

Sign

sign

Silu

swish

Sin

sin

Size

size

Slice

slice/stridedslice

Softmax

softmax

Softplus

softrelu

Softsign

abs+add+divide+variable

SpaceToDepth

space2depth

Split

split/slice

Sqrt

sqrt

Squeeze

squeeze

STFT

stft

Sub

subtract

Sum

eltwise(SUM)

Tanh

tanh

Tile

tile

TopK

topk

Transpose

permute

Unsqueeze

reshape

Upsample

image_resize

Where

where

Xor

not_equal

Darknet 到 ACUITY 操作映射

Darknet 到 ACUITY 操作映射

Darknet Operation

ACUITY Operation

avgpool

pooling

batch_normalize

batchnormalize

connected

fullconnect

convolutional

convolution

depthwise_convolutional

convolution

leaky

leakyrelu

logistic

sigmoid

maxpool

pooling

mish

mish

region

region

relu

relu

reorg

reorg

route

concat/slice

scale_channels

multiply

shortcut

add/slice+add/pad+add

softmax

softmax

swish

swish

upsample

upsampling

yolo

yolo

附录 B:Acuity 支持的 AI 框架

支持的 AI 框架及导入文件格式

AI Framework

Import File Format

Caffe

.caffemodel

TensorFlow

.pb

TensorFlow Lite

.tflite

Darknet

.cfg

ONNX

.onnx

PyTorch

.pt

Keras

.h5

备注

对于从已量化的 ONNX、TensorFlow 或 TensorFlow Lite 模型转换而来的 Acuity 网络,无需再进行量化。 逐通道 量化模型 不受 NPU 支持——请确保您的模型使用 逐张量 量化。

小技巧

对于 PyTorch 框架,强烈建议先将文件导出为 .onnx 格式,以确保转换成功。

参考:ACUITY Toolkit 用户指南

NN MMF 示例

RTL8735B:

请参阅 应用笔记 了解关于结合 VIPNN 模块使用 NN MMF 示例的说明。