上一篇“使用ncnn在树莓派4B上部署nanoDet-m网络(12fps) - part 1”介绍了使用ncnn在树莓派上部署nanoDet网络的效果、性能、功耗以及所需软件的安装,下面的part 2主要介绍的是代码部分的编写,以及int8量化模型的效果、性能和功耗数据。(下面标题的序号延续自part 1)。
6. 推理通路代码
执行git clone https://github.com/borninfreedom/NanoDet-ncnn-Raspberry-Pi-4.git
来获取通路前后处理代码和素材文件。
其中,NanoDet.cbp
是codeblocks工程文件,可以直接双击打开一个codeblock工程。
在codeblocks中编译并运行,即可将NanoDet模型部署在树莓派上。
7. 主要代码说明
如上图是使用ncnn进行网络部署的过程。下面就以这个主线进行展开来说明代码的主要细节实现。
使用ncnn::Net nanodet
,定义ncnn的网络nanodet后,首先需要将模型文件load进来。
nanodet.load_param("nanodet_m.param");
nanodet.load_model("nanodet_m.bin");
在这里,我提供了nanodet_m
和nanodet_m-int8
两个模型,分别为float和int8量化的模型。每一个模型分为两个文件,.param参数文件
和.bin模型文件
。.param
文件可以直接使用netron软件打开,以查看网络结构。
在代码的main
函数中,load进来模型后,下面就要执行检测的主要过程。在detect_nanodet
函数中,首先对图片进行resize操作,然后调用ncnn的from_pixels_resize
函数,将cv::Mat数据转换到ncnn::Mat同时进行resize操作。
int w = width;
int h = height;
float scale = 1.f;
if (w > h)
{
scale = (float)target_size / w;
w = target_size;
h = h * scale;
}
else
{
scale = (float)target_size / h;
h = target_size;
w = w * scale;
}
// from_pixels_resize将cv::Mat数据转换到ncnn::Mat同时进行resize操作
ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR, width, height, w, h);
目前ncnn不支持同时进行图片等比例缩放并且填充到固定长宽,具体可以访问ncnn issues 1260。因此还需要调用copy_make_border
函数进行图片的填充。
// pad to target_size rectangle
// 这两个公式的作用是将 w 填充到32的倍数,需要 pad 多少,比如 w = 1,需要 pad 31才能到32, w=63,需要 pad 1到64
int wpad = (w + 31) / 32 * 32 - w;
int hpad = (h + 31) / 32 * 32 - h;
ncnn::Mat in_pad;
// in_pad是目标Mat,函数的作用是在原输入上,进行pad,扩充边界
ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 0.f);
然后对图片减均值和norm操作。
const float mean_vals[3] = {103.53f, 116.28f, 123.675f};
const float norm_vals[3] = {0.017429f, 0.017507f, 0.017125f};
in_pad.substract_mean_normalize(mean_vals, norm_vals);
下面是创建比较重要的extractor。ncnn将网络的结构用blob(用netron打开.param文件,每一个节点可以理解为一个blob)链接构成一个DAG(有向无环图),给定input以及extract之后,ncnn从extract向上查找,找到通往input的路径,让后再顺序路径forward下来。
使用下面的语句创建一个Extractor的实例。
ncnn::Extractor ex = nanodet.create_extractor();
创建了ex之后,就可以指定input和output,让ncnn进行遍历,构建网络的有向无环图。
使用ex.input
函数来指定模型的输入节点。输入节点的名字,使用netron打开.param文件,即可看到。
ex.input("input.1", in_pad);
然后调用ex.extract
来构建网络并执行forward,这里需要将output节点的名字作为参数传递进去。nanoDet-m网络,在head部分,分别有stride8、stride16、stride32三个部分。
上图所示是nanoDet的head部分,从上到下依次是stride 8、stride 16、stride 32的分支,我们需要将红框框出的输出节点,作为参数传递给ncnn的extract函数。
上图所示为stride 8分支,我们以stride 8分支为例。在netron中,我们点击sigmoid_395 -> Reshape_397 -> Transpose_398
这个class分支最后的Transpose_398
节点,如上图,可以看到这个节点的输出名字为cls_pred_stride_8
。同理,点击Reshape_400 -> Transpose_401
这个bbox分支的最后的节点Transpose_401
节点,可以看到这个节点的输出名字是dis_pred_stride_8
。这两个output的名字是我们要作为参数传递给extract函数的。stride 16、stride 32 head分支同理。
下面看一下stride 8分支的代码
// stride 8
{
ncnn::Mat cls_pred;
ncnn::Mat dis_pred;
ex.extract("cls_pred_stride_8", cls_pred);
ex.extract("dis_pred_stride_8", dis_pred);
std::vector<Object> objects8;
generate_proposals(cls_pred, dis_pred, 8, in_pad, prob_threshold, objects8);
proposals.insert(proposals.end(), objects8.begin(), objects8.end());
}
上面的代码,我们将cls_pred_stride_8
和dis_pred_stride_8
作为参数传递给ex.extract
函数。
extract函数同时会执行forward计算,后面调用generate_proposals
生成proposals;调用qsort_descent_inplace
对proposals进行排序;调用nms_sorted_bboxes
执行nms计算即可完成全部的部署过程。最后再执行画图操作即可。
8. 树莓派超频
为了发挥树莓派更高的性能,可以对树莓派进行超频。对于树莓派4b,在超频之前,需要先更新EEPROM (electrically erasable programmable read-only memory)固件。最新固件的作用不是提高了树莓派的主频,而是降低了树莓派的功耗,降低了树莓派的温度。
sudo apt-get update
sudo apt-get full-upgrade
sudo apt-get install rpi-eeprom
# to get the current status
sudo rpi-eeprom-update
# to update the firmware
sudo rpi-eeprom-update -a
sudo reboot
树莓派通过直接修改/boot/config.txt
文件即可实现超频。在文件中,主要通过修改over_voltage
,arm_freq
,gpu_freq
三项来实现cpu和gpu的超频。其中over_voltage
表示随着cpu频率的增高,相应的cpu的电压要适当提高。
树莓派4b,不同频率,以及对应频率下over_voltage
设定的值参见下表。
超频后,需要使用更为优秀的散热和电源。下面图片描述了不同频率、不同散热装置下,树莓派随着运行时间的温度走势。
我们可以使用下面的指令来查看超频之后的频率、电压和温度。
# 查看cpu频率
watch -n1 vcgencmd measure_clock arm
# 查看gpu频率
watch -n1 vcgencmd measure_clock core
# 查看电压
watch -n1 vcgencmd measure_volts core
# 查看温度
watch -n1 vcgencmd measure_temp
# 查看所有参数
vcgencmd get_config int
#
9. int8量化模型
我提供的文件中,还有nanodet_m-int8.bin
和nanodet_m-int8.param
两个文件,这是使用ncnn int8量化的模型,模型的weights和activations(节点的输出,不是激活函数,详见paper A White Paper on Neural Network Quantization)使用int8来存储。量化模型相比float16模型,计算量更小,模型体积更小,推理速度更快,精度略有损失。
从上图nanodet_m.bin
和nanodet_m-int8.bin
两个文件的大小对比,可以看到int8的模型占用的空间,基本上是float16模型的一半。
在代码中,将load_param
和load_model
函数的参数改为int8模型的名字。在main函数中修改如下,加载int8量化模型:
nanodet.load_param("nanodet_m-int8.param");
nanodet.load_model("nanodet_m-int8.bin");
从上图看,int8的量化模型,在多人的情况下,依然能够都检出来。但是摩托车没有检测出来。
我也对int8模型的性能和功耗进行了测试。
工作状态 | CPU频率(Hz) | CPU温度(℃) | GPU频率(Hz) | GPU温度(℃) | 功耗 | 内存占用(with desktop) | 平均fps |
---|---|---|---|---|---|---|---|
推理nanoDet-m-int8(超频) | 2.0G | 56.4 | 700M | 55.5 | 7.53w | 526M | 13.5fps |
然后将之前float16模型的数据一起对比来看,可以看到int8的模型的fps是最高的,平均fps达到了13.5。温度数据,由于天气变化,有波动属于正常。
工作状态 | CPU频率(Hz) | CPU温度(℃) | GPU频率(Hz) | GPU温度(℃) | 功耗 | 内存占用(with desktop) | 平均fps |
---|---|---|---|---|---|---|---|
无作业 | 600M | 34 | 200M | 33.6 | 3.135w | 318M | 无 |
推理nanoDet-m | 1.5G | 43 | 500M | 42 | 5.357w | 494M | 10.3fps |
推理nanoDet-m(超频) | 2.0G | 51.2 | 700M | 52.6 | 7.446w | 446M | 11.4fps |
推理nanoDet-m-int8(超频) | 2.0G | 56.4 | 700M | 55.5 | 7.53w | 526M | 13.5fps |
评论(0)
您还未登录,请登录后发表或查看评论