1.背景
为了实现低延时,所以开始看看C版本的rknn的使用,确实有不足的地方,请指正(代码借鉴了rk官方的仓库文件)。
2.基本的操作流程
1.读取模型初始化
// ======================= 设置基本信息 ===================
// 在postprocess.h文件中定义,详见include/postprocess.h文件
const float nms_threshold = NMS_THRESH; // 默认的NMS阈值
const float box_conf_threshold = BOX_THRESH; // 默认的置信度阈值
// 默认的模型输入输出信息letterbox,默认使用LetterBox的预处理
std::string option = "letterbox";
// 默认的图片输出路径,根据路径设置
std::string out_path = "/home/ubuntu/unet/unet_rknn_c++/rknn_api_test/result/out.png";
// 默认的模型路径,根据路径设置
const char model_path[] = "../weights/yolov5s-640-640.rknn";
// ======================= 设置rknn模型的基本信息 ===================
//初始化rknn_context对象,数据类型:rknn_context,成员变量:ctx
rknn_context ctx = 0;
int ret;
// RKNN模型的二进制数据或者RKNN模型路径。当参数size大于0时,model表示二进制数据;当参数size等于0时,model表示RKNN模型路径。
int model_len = 0;
unsigned char *model;
// ======================= 初始化RKNN模型 ===================
model = load_model(model_path, &model_len);
ret = rknn_init(&ctx, model, model_len, 0, NULL);
if (ret < 0)
{
printf("rknn_init fail! ret=%d\n", ret);
return -1;
}
else{
printf("model load success\n");
}
if (ctx == 0)
{
printf("rknn_init fail! ret=%d\n", ret);
return -1;
}
rknn_core_mask core_mask = RKNN_NPU_CORE_0_1_2;
ret = rknn_set_core_mask(ctx, core_mask);
2.获取输入和输出的相关信息
// ======================= 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);
// ======================= 获取模型输入输出信息 ===================
/*调佣rknn_query接口查询tensor输入输出个数*/
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);
// =======================结构体rknn_tensor_attr表示模型的tensor的属性 ===================
// (1)input的tensor信息
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]));
}
//(2)output的tensor信息
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]));
}
3.设置输入格式
// 查看模型的输入格式NCHW或者NHWC,获取输入的宽高和通道数
int channel = 3;
int width = 0;
int height = 0;
if (input_attrs[0].fmt == RKNN_TENSOR_NCHW)
{
printf("model is NCHW input fmt\n");
channel = input_attrs[0].dims[1];
height = input_attrs[0].dims[2];
width = input_attrs[0].dims[3];
}
else
{
printf("model is NHWC input fmt\n");
height = input_attrs[0].dims[1];
width = input_attrs[0].dims[2];
channel = input_attrs[0].dims[3];
}
printf("model input height=%d, width=%d, channel=%d\n", height, width, channel);
// ======================= 设置模型输入 ===================
// rknn_input 结构体,用于描述输入数据,包括索引、类型、大小、格式等。1表示输入的数量,可换成io_num.n_input
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;
4.读取图片进行预处理
string input_path= "../images/bus.jpg";
// ======================= 读取图片 ===================
printf("Read %s ...\n", input_path.c_str());
cv::Mat orig_img = cv::imread(input_path, 1);
if (!orig_img.data)
{
printf("cv::imread %s fail!\n", input_path.c_str());
return -1;
}
cv::Mat img;
cv::cvtColor(orig_img, img, cv::COLOR_BGR2RGB);
int img_width = img.cols;
int img_height = img.rows;
printf("img width = %d, img height = %d\n", img_width, img_height);
// 这里去除了rga的操作,
// 指定目标大小和预处理方式,默认使用LetterBox的预处理
BOX_RECT pads;
memset(&pads, 0, sizeof(BOX_RECT));
cv::Size target_size(width, height);
cv::Mat resized_img(target_size.height, target_size.width, CV_8UC3);
// 计算缩放比例
float scale_w = (float)target_size.width / img.cols;
float scale_h = (float)target_size.height / img.rows;
if (img_width != width || img_height != height) {
if (option == "letterbox") {
printf("resize image with letterbox\n");
float min_scale = std::min(scale_w, scale_h);
scale_w = min_scale;
scale_h = min_scale;
letterbox(img, resized_img, pads, min_scale, target_size);
// 保存预处理图片
cv::imwrite("letterbox_input.jpg", resized_img);
} else {
fprintf(stderr, "Invalid resize option. Use 'resize' or 'letterbox'.\n");
return -1;
}
inputs[0].buf = resized_img.data;
}
else{
inputs[0].buf = img.data;
}
5.模型输入和推理
// 使用rknn_inputs_set函数设置模型输入
ret = rknn_inputs_set(ctx, io_num.n_input, inputs);
if (ret < 0)
{
printf("rknn_input_set fail! ret=%d\n", ret);
return -1;
}
// ======================= rknn模型推理 ===================
printf("rknn_run\n");
ret = rknn_run(ctx, nullptr);
if (ret < 0)
{
printf("rknn_run fail! ret=%d\n", ret);
return -1;
}
6.获取输出
// 多输出
rknn_output outputs[io_num.n_output];
memset(outputs, 0, sizeof(outputs));
// 为每个输出设置属性,这里假设我们希望所有输出都转换为浮点数
for (int i = 0; i < 3; ++i) {
// want_float标识是否需要将输出数据转为float类型输出,0表示不需要,1表示需要
outputs[i].want_float = 0;
}
// 使用rknn_outputs_get函数获取模型输出
ret = rknn_outputs_get(ctx, 3, outputs, NULL);
if (ret < 0) {
printf("rknn_outputs_get fail! ret=%d\n", ret);
return -1;
}
/***
int8数据格式(int占用1字节),查看模型输出的shape: (1*255*80*80),(1*255*40*40),(1*255*40*40)
output[0] shape: 1632000
output[1] shape: 408000
output[2] shape: 102000
float数据格式(float占用4字节),查看模型输出的shape: (1*255*80*80),(1*255*40*40),(1*255*40*40)
output[0] shape: 1632000*4
output[1] shape: 408000*4
output[2] shape: 102000*4
***/
// int8_t *pblob[3];
// for (int i = 0; i < io_num.n_output; ++i)
// {
// cout << "output[" << i << "] shape: " << outputs[i].size << endl;
// pblob[i] = (int8_t*)outputs[i].buf;
// }
7.后处理
// 后处理
detect_result_group_t detect_result_group;
std::vector<float> out_scales;
std::vector<int32_t> out_zps;
// rknn量化的零点和缩放因子
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);
}
// 后处理
post_process((int8_t *)outputs[0].buf, (int8_t *)outputs[1].buf, (int8_t *)outputs[2].buf, height, width,
box_conf_threshold, nms_threshold, pads, scale_w, scale_h, out_zps, out_scales, &detect_result_group);
// 画框和概率
char text[256];
for (int i = 0; i < detect_result_group.count; i++)
{
detect_result_t *det_result = &(detect_result_group.results[i]);
sprintf(text, "%s %.1f%%", det_result->name, det_result->prop * 100);
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;
rectangle(orig_img, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(256, 0, 0, 256), 3);
putText(orig_img, text, cv::Point(x1, y1 + 12), cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(255, 255, 255));
}
imwrite(out_path, orig_img);
printf("save detect result to %s\n", out_path.c_str());
8.资源释放
// 释放rknn_outputs_get函数得到的输出的相关资源
ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
// 释放传入的rknn_context及其相关资源
ret = rknn_destroy(ctx);
if (model)
{
free(model);
}
3.后记
详细的内容我已经上传到yolov5-rk文件中,可以详细的研究