一、本文的目標
- 將yolo8-pose例子適配安卓端,提供選擇圖片后進行姿態識別功能。
- 通過項目學習源碼和rknn api。
二、開發環境說明
- 主機系統:Windows 11
- 目標設備:搭載RK3588芯片的安卓開發板
- 核心工具:Android Studio Koala | 2024.1.1 Patch 2,NDK 27.0
三、適配(遷移)安卓
有了前兩次的遷移經驗,這次就很順利了。可以參考之前三篇文章,如果還是遇到問題(或者需要源碼),給我留言。
Yolo8-pose C語言例子請參考之前的博文《RK3588芯片NPU的使用:Windows11 Docker中編譯YOLOv8-Pose C Demo并在開發板運行實踐》。
將C Demo移植到安卓應用端的相關知識,請參考博文《手把手部署YOLOv5到RK3588安卓端:NPU加速與JNI/C/Kotlin接口開發指南》。
上一次移植,請參考《RK3588芯片NPU的使用:PPOCRv4例子在安卓系統部署》,解決圖像格式問題,很重要。
四、重要源碼解析
4.1 init_yolov8_pose_model方法
本函數主要任務是YOLOv8模型在RKNN框架下的初始化、屬性查詢和配置保存。
函數源碼如下:
int init_yolov8_pose_model(const char *model_path, rknn_app_context_t *app_ctx)
{int ret;// 1.初始化RKNN上下文rknn_context ctx = 0;ret = rknn_init(&ctx, (char *)model_path, 0, 0, NULL);if (ret < 0){printf("rknn_init fail! ret=%d\n", ret);return -1;}// 2.查詢模型的輸入輸出數量rknn_input_output_num io_num;ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));if (ret != RKNN_SUCC){printf("rknn_query fail! ret=%d\n", ret);return -1;}printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output);// 3.獲取輸入張量屬性printf("input tensors:\n");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 != RKNN_SUCC){printf("rknn_query fail! ret=%d\n", ret);return -1;}dump_tensor_attr(&(input_attrs[i]));}// 4.獲取輸出張量屬性printf("output tensors:\n");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));if (ret != RKNN_SUCC){printf("rknn_query fail! ret=%d\n", ret);return -1;}dump_tensor_attr(&(output_attrs[i]));}// 5.保存配置到應用上下文app_ctx->rknn_ctx = ctx;// 6.判斷模型是否量化:檢查第一個輸出張量是否是非FP16的仿射量化類型,設置is_quant標志,用于后續反量化處理。if (output_attrs[0].qnt_type == RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC && output_attrs[0].type != RKNN_TENSOR_FLOAT16){app_ctx->is_quant = true;}else{app_ctx->is_quant = false;}// 7. 復制輸入輸出屬性到應用上下文:動態分配內存并拷貝輸入輸出屬性,保存到app_ctx以便后續訪問。app_ctx->io_num = io_num;app_ctx->input_attrs = (rknn_tensor_attr *)malloc(io_num.n_input * sizeof(rknn_tensor_attr));memcpy(app_ctx->input_attrs, input_attrs, io_num.n_input * sizeof(rknn_tensor_attr));app_ctx->output_attrs = (rknn_tensor_attr *)malloc(io_num.n_output * sizeof(rknn_tensor_attr));memcpy(app_ctx->output_attrs, output_attrs, io_num.n_output * sizeof(rknn_tensor_attr));// 8. 解析輸入張量維度if (input_attrs[0].fmt == RKNN_TENSOR_NCHW){printf("model is NCHW input fmt\n");app_ctx->model_channel = input_attrs[0].dims[1];app_ctx->model_height = input_attrs[0].dims[2];app_ctx->model_width = input_attrs[0].dims[3];}else{printf("model is NHWC input fmt\n");app_ctx->model_height = input_attrs[0].dims[1];app_ctx->model_width = input_attrs[0].dims[2];app_ctx->model_channel = input_attrs[0].dims[3];}printf("model input height=%d, width=%d, channel=%d\n",app_ctx->model_height, app_ctx->model_width, app_ctx->model_channel);return 0;
}
4.1.1 rknn_init初始化
rknn_init初始化函數功能為創建rknn_context對象、加載RKNN模型以及根據flag和rknn_init_extend結構體執行特定的初始化行為。
函數原型:
int rknn_init(rknn_context* context, // 輸出參數:返回的 RKNN 上下文句柄void* model, // 輸入參數:模型數據或模型文件路徑uint32_t size, // 輸入參數:模型數據的大小(字節數)uint32_t flag, // 輸入參數:初始化標志位(擴展選項)rknn_in