前言
从J5芯片算法工具链的1.1.49b版本开始,DSP的cv示例新增了RoiResize算子,本文会基于此版本的OE交付包,对该算子的功能和使用做详细介绍。
在正式阅读前,希望您已经对DSP的软硬件特点、编程思路和板端运行方法有基本的了解,关于这方面的内容可以查看社区文章《DSP开发快速上手》。
RoiResize功能介绍
在J5上,可以使用DSP进行RoiResize操作,该算子的原理如下:

首先判断ROI是否超出了原图的边界,如果超出,调整ROI边界至原图有效范围内,再从原图中进行crop,等比例resize至小于等于模型输入尺寸,最后对短边进行对称padding,完成整个RoiResize操作。若ROI不超出原图边界,则直接做crop、等比例resize和对称padding。
以下图情况举例:

原图的边长为1000x800,ROI的边长为600x600,模型的输入尺寸为200x200。可以看到,ROI超出了原图边界,因此首先需要修改ROI边界,将600x600调整为420x600,crop之后进行等比例resize,将600的长边变为200,420的短边变为140,最后进行对称padding,在140的短边两侧各填充30列数,得到符合模型输入要求的200x200的边长。
padding值配置说明
地平线工具链支持在模型前端插入一个预处理节点,内部依次完成如下操作:
颜色空间转换:如 nv12 --> rgb
数据归一化:(data - mean_value) * scale_value
假设 rgb 训练的原始模型需要 padding 0 值,mean_value 在 3 个通道上的均值分别为 R、G、B,那么应通过如下方程组换算回 nv12 runtime 输入数据上的 padding 值:
Y + 1.14V - R = 0
Y - 0.39U - 0.58V - G = 0
Y + 2.03U - B = 0
求解后得:
Y = 0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V = 0.615R - 0.515G - 0.100B
与BPU SDK API对比
hbDSPRoiResize调用了一个封装在DSP侧的cv算子,用于将原图中的单个ROI调整成合适的大小,常用于推理前的数据处理。hbDNNRoiInfer是BPU SDK API,处理多个ROI后调用模型执行推理,详细介绍可以查看社区文章《resizer模型使用与部署》。
这两个接口对ROI处理的主要区别在于,前者会对ROI做padding并等比例缩放到模型输入大小,物体的宽高比不发生变化,后者则是将ROI直接缩放到模型输入大小,物体的宽高比有可能改变。
ARM接口介绍
DSP侧的RoiResize功能,地平线以库的形式进行了封装,开发者只需重点关注在ARM侧的调用方式。对此,地平线提供了hbDSPRoiResize接口,以下是针对该接口的具体说明。
输入值:
- task 任务句柄。
- dstImg 输出图像。
- srcImg 输入图像,数据类型只支持Y和NV12(YUV420)。
- roi ROI区域,有效范围取ROI区域与srcImg的交集。
- roiResizeParam 算子参数,包括插值类型和padding的值。
- ctrlParam 任务控制参数。
返回值:
返回 0 则表示API成功执行,否则执行失败。
成员名称及描述:
- left ROI区域的左边下标。
- top ROI区域的上边下标。
- right ROI区域的右边下标。
- bottom ROI区域的下边下标。
成员名称及描述:
- interpolation 插值类型,0表示最邻近插值,1表示双线性插值。
- paddingValue[4] 具体的padding值,每个通道对应一个值,Y数据类型使用一个通道,NV12数据类型使用三个通道,未使用的通道可不赋值,或填写任意值,不起实际作用。
示例代码解读
地平线在OE包中提供了示例代码,指导开发者如何调用hbDSPRoiResize接口执行任务。可以进入OE包的ddk/samples/vdsp_rpc_sample/arm/cv/src/目录,打开test_roi_resize.cc文件查看详细代码。以下从示例代码中拆解出重要部分进行讲解。
此段代码用于读取本地的NV12图片文件,并将y分量数据存放在src_img_y地址,uv分量数据存放在src_img_uv地址。src_stride指的是图像每行占用的字节数,等于宽度乘以每个像素的字节大小,在数值上等于src_width。因为4个y分量对应1组uv分量,1组uv分量的高和宽为y分量的一半,所以src_uv_width和src_uv_height的数值分别是基于src_width和src_height向右移位一次得到的位运算结果。而src_uv_stride是uv在width方向上占用的字节数,数值上等于src_uv_width的两倍。
src_y_mem和src_uv_mem存储了图像各个分量的信息,在arm侧申请分配,同时也在dsp侧访问处理,两端在操作时需要做内存同步处理,因为y分量的字节数就等于图片的像素个数,所以src_y_mem分配的内存空间为src_width * src_height字节,而u和v在height方向是y的一半,width方向上总量和y相同,所以src_uv_mem分配的内存空间为src_uv_stride * src_uv_height。之后用hbDSPImage结构体定义src,存储DSP输入数据的相关信息。
先使用hbDSPRoi结构体定义ROI左上右下四条边的位置,之后代码编写思路同上一步,初始化相关参数并为DSP的输出内存分配空间。
关于paddingValue的数值设定,如果训练时使用的数据类型就是Y或NV12,那么直接使用训练时设置的padding值即可,如果训练时使用的是BGR/RGB数据,那么paddingValue的数值需要基于训练时设置的padding值做rgb转yuv的公式得到。
之后初始化DSP任务参数ctrl_param并定义任务句柄,就可以调用hbDSPRoiResize接口让DSP执行RoiResize的操作了。
DSP任务执行结束后,使用dst_img_y和dst_img_uv存储输出的结果并保存,最后再释放掉申请的内存空间。
板端运行及可视化展示
首先将script文件夹复制到J5开发板的/userdata目录下,然后我们编写一个脚本用于配置DSP镜像:
之后cd进入/userdata/script/cv目录,依次运行以下命令:
chmod 777 ../lib/dsp_relay_server 为DSP的relay模式所需的依赖文件赋予运行权限;
chmod 777 ./bin/test_cv 为cv算子示例的可执行文件赋予运行权限;
sh run_cv_test.sh roiResize 运行cv算子的RoiResize示例。
成功运行后,终端会打印如下信息,表明DSP的RoiResize算子成功执行:

此时,在/userdata/script/cv/目录下会生成一个output文件夹,保存了图像处理结果,RoiResize算子的输出文件名为roi_resize_output.480x380.yuv,原始输入图像为/userdata/script/cv/data/500x480.lena.yuv。
https://github.com/IENT/YUView/releases
原始数据和处理结果的可视化效果图如下所示:


可以看到处理结果符合预期,DSP成功使用RoiResize算子执行了crop、等比例resize和对称padding操作。
