本文主要是介绍模型在rv1126上跑起来遇到的坑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
rknn对模型是彩色图片输入的很友好,如果输入是灰度图片,需要好好理解它的整套数据处理流程。
上面是数据处理的整个流程,cpu拿到的图片数据,需要经过一系列的预处理(颜色通道转换,归一化,量化,通道转换)这一过程是在rknn_inputs_set里面完成。
我的模型信息:
模型输入1个,单通道,数据类型是uint8, 量化类型为asymmetric affine非对称量化,即float32和uint8之间的转换。fl zp scale都是量化的参数。
模型输出2个,一个数据类型为float16 没有量化
第二个类型为uint8, 量化类型为asymmetric affine.
rknn_inputs_set耗时严重
我碰到的问题就是rknn_inputs_set非常的耗时!大概33ms左右!我是单通道图片,1400640,fmt是NCHW,也就是需要进行中间两步即可。先归一化成float32,再量化为uint8,然后把数据从cpu拷贝到npu,不知道为啥耗时这么长。最后问了rk的工作人员,说是对彩色图片内部做了优化,而灰度图片没有。
所以只能自己做优化。
rk的文档提供了两种方式:
1.是rknn_inputs_set里设置pass_through=1,相当于数据不再需要rknn_inputs_set做预处理,我自己处理好。我理解的这种应该是只节省了预处理的时间,拷贝的时间是仍然需要的。我之前自己实现了归一化和量化步骤(官网也有demo),耗时在10ms左右。后来我仔细阅读文档,发现如果三通道均值和方差相同,则输入 uint8 数据等于归一化后量化的 uint8 数据。
所以我的归一化和量化是可以省略的,也就是中间两步也省了,图片原始数据直接透传给NPU。这时rknn_inputs_set变成了1ms左右!我理解的拷贝耗时呢???
2.第二种是零拷贝,即把拷贝也省了。官方给了很详细的demo。原理大概是在内核核申请一片内存,数据放到这片内存上,npu直接去拿。不需要从cpu拷贝到npu。
rknn_outputs_set耗时
我已经设置了output_optimize=1,耗时在14ms左右,耗时主要是因为第二个输出,需要从uint8变为float32。
为了解决这个问题,我决定自己在外面做反量化。
反量化比量化简单很多,就是((float)qnt - (float)zp) * scale 我的模型第二个输出zp = 136 scale = 4.625256.
验证结果:
按照uint8_t直接把结果拿出来:
outputs[1].want_float = 0;
uint8_t* pdes = (uint8_t*)outputs[1].buf;
cv::Mat des_mat(400/8 * 256, 640/8, CV_8UC1, pdes);
std::cout << "des " << des_mat << std::endl;
输出结果:
耗时变成8ms。
按照float把结果拿出来:
outputs[1].want_float = 1;
float* pdes = (float*)outputs[1].buf;
cv::Mat des_mat(400/8 * 256, 640/8, CV_32FC1, pdes);
std::cout << "des " << des_mat << std::endl;
输出结果:
耗时19ms。
拿第一个做反量化运算:(124-136)* 4.625256 = −55.503072 恰好等于float拿出来的结果。所以这种方式是ok。
注意opencv恰好有数据类型转换的函数convertTo
/** @brief Converts an array to another data type with optional scaling.The method converts source pixel values to the target data type. saturate_cast\<\> is applied atthe end to avoid possible overflows:\f[m(x,y) = saturate \_ cast<rType>( \alpha (*this)(x,y) + \beta )\f]@param m output matrix; if it does not have a proper size or type before the operation, it isreallocated.@param rtype desired output matrix type or, rather, the depth since the number of channels are thesame as the input has; if rtype is negative, the output matrix will have the same type as the input.@param alpha optional scale factor.@param beta optional delta added to the scaled values.*/void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;
只需要转换一下参数即可。代码如下:
uint8_t* pdes = (uint8_t*)outputs[1].buf;cv::Mat des_mat(400/8 * 256, 640/8, CV_8UC1, pdes);cv::Mat des;des.convertTo(des, CV_32FC1, 4.625256, -136*4.625256);
其实第一个输出也可以做类似的工作,模型的输出是float16, 我们可以自己在外面转为float32,(32 位浮点 和16 位浮点数相互转换 请参考 IEEE-754标准)。我看了一下,第一个也outputs[1].want_float = 0;模型输出耗时为4ms,测试convertTo的耗时为3.6855。加上第一个输出convertTo的耗时,和rk内部转换耗时8ms差不多,可以不采用自己转换。而计算描述子是放在后端5hz的,我们可以把第二个输出的convertTo到后端,不计入整个提点耗时里面。
故第一个输出采用want_float = 1,由rk内部转换;第二个采用want_float = 0,自己转换;
输出结果和在电脑的输出差异很大
应该是量化带来的误差。
1.首先确保 float 类型的精度和原始平台测试结果相近,rknn.build(do_quantization=False)
结果如下:
在电脑跑出来的结果
没有量化的结果
可见结果还是比较相近,误差在百分位上面。
2.测试量化后的精度
明显已经误差很大了,我用的图片数量10张,文档建议给出大于200张图片。
结果还是还上面一样
结果还是和上面一样,说明不是图片量太少的问题,需要分析每一层的量化精度。
用rknn.accuracy_analysis(inputs='./image/dataset1.txt', target='rv1126')
分析。
首先找到每一层对应的npu表示术语。
x = self.relu(self.conv1a(data)) | convolution_at_input0.1_1_1_out0_nhwc_1_400_640_64.tensor relu_at_89_2_2_out0_nhwc_1_400_640_64.tensor |
---|---|
x = self.relu(self.conv1b(x)) | convolution_at_input.3_3_3_out0_nhwc_1_400_640_64.tensor relu_at_101_4_4_out0_nhwc_1_400_640_64.tensor |
x = self.pool(x) | max_pooling_at_input.4_5_5_out0_nhwc_1_200_320_64.tensor |
x = self.relu(self.conv2a(x)) | convolution_at_input.5_6_6_out0_nhwc_1_200_320_64.tensor relu_at_122_7_7_out0_nhwc_1_200_320_64.tensor |
x = self.relu(self.conv2b(x)) | convolution_at_input.6_8_8_out0_nhwc_1_200_320_64.tensor relu_at_134_9_9_out0_nhwc_1_200_320_64.tensor |
x = self.pool(x) | max_pooling_at_input.7_10_10_out0_nhwc_1_100_160_64.tensor |
x =self.relu(self.conv3a(x)) | convolution_at_input.8_11_11_out0_nhwc_1_100_160_128.tensor relu_at_155_12_12_out0_nhwc_1_100_160_128.tensor |
x = self.relu(self.conv3b(x)) | convolution_at_input.9_13_13_out0_nhwc_1_100_160_128.tensor relu_at_167_14_14_out0_nhwc_1_100_160_128.tensor |
x = self.pool(x) | max_pooling_at_input.10_15_15_out0_nhwc_1_50_80_128.tensor |
x = self.relu(self.conv4a(x)) | convolution_at_input.11_16_16_out0_nhwc_1_50_80_128.tensor relu_at_188_17_17_out0_nhwc_1_50_80_128.tensor |
x = self.relu(self.conv4b(x)) | convolution_at_input.12_18_18_out0_nhwc_1_50_80_128.tensor relu_at_200_19_19_out0_nhwc_1_50_80_128.tensor |
之后就是分开部分了,由于我们只关心描述子,就只看描述子部分的。
cDa = self.relu(self.convDa(x)) | convolution_at_input.1_40_34_out0_nhwc_1_50_80_256.tensor relu_at_235_41_35_out0_nhwc_1_50_80_256.tensor |
---|---|
descriptors = self.convDb(cDa) | convolution_at_246_43_37_out0_nhwc_1_50_80_256.tensor |
这篇关于模型在rv1126上跑起来遇到的坑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!