从0开始学习NEON(1)

1、前言

在上个博客中对NEON有了基础的了解,本文将针对一个图像下采样的例子对NEON进行学习。

学习链接:CPU优化技术 - NEON 开发进阶

上文链接:https://blog.csdn.net/weixin_42108183/article/details/136412104

2、第一个例子

现在有一张图片,需要对UV通道的数据进行下采样,对于同种类型的数据,相邻的4个元素求和并求均值。示意图如下图所示:

在这里插入图片描述

假定图像数据的宽为16的整数倍,如果使用c++代码,可以写出下面的代码:

void DownscaleUv(uint8_t *src, uint8_t *dst, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{//遍历每一行的数据for (int32_t j = 0; j < dst_height; j++){	// 偶数行起始位置,uint8_t *src_ptr0 = src + src_stride * j * 2;// 奇数行起始位置uint8_t *src_ptr1 = src_ptr0 + src_stride;// 存储起始位置uint8_t *dst_ptr = dst + dst_stride * j;// 没一次循环计算没for (int32_t i = 0; i < dst_width; i += 2){// U通道 (u1 + u2 + u3 + u4) / 4dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;// V通道 (v1 + v2 + v3 + v4) / 4dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;}}
}

通过学习向量化编程,我们可知,数据的计算可以利用单指令多数据的方式进行加速,例如上面的例子中的内层循环,下面就使用NEON来试试吧。

3、第2个例子

为了进行向量化加速,首先需要将UV数据分离,将UV数据分离的操作在NEON中很容易进行, 使用vld2交织加载或者储存即可。对于每一行的数据,交织加载的示意图如下。
在这里插入图片描述

交织加载的基本原理是按照间隔挑选数据。交织加载的例子如下所示:

void DownscaleUvNeon()
{vector<uint8_t> data;  // UVUVUVUVUV...for(int i=0;i<32;i++){data.push_back(i);}// uint8_t *src_ptr0 = (uint8_t *)data.data(); // load 第一行的数据uint8x16x2_t src;src = vld2q_u8(src_ptr0);   // 交织读取 16 * 2 的数据,需要两个q寄存器。auto a = src_odd.val[0];   // 一行的U数据vector<uint8_t> show_data(16);vst1q_u8 (show_data.data(),a);   // 将U数据顺序储存到内存中// 打印for(auto n : show_data){cout <<  static_cast<int>(n) << endl;  // 0,2,4,6,...}   
}
4、第3个例子

对于下UV数据采样来说,在偶数行进行上面的交织加载,再在奇数行上进行同样的操作。奇数行和偶数行相应的数据进行相加再求平均,即可得到最后的结果。代码实现如下:

#include <arm_neon.h>
void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{//用于加载偶数行的源数据,2组每组16个u8类型数据,(16 * 8) * 2 = 128 * 128, 因此需要两个q寄存器。 uint8x16x2_t v8_src0;//用于加载奇数行的源数据uint8x16x2_t v8_src1;//目的数据变量,需要一个Q寄存器uint8x8x2_t v8_dst;//目前只处理16整数倍部分的结果int32_t dst_width_align = dst_width & (-16);  //  dst_width & (-16),最大能够整除16的数。//向量化剩余的部分需要单独处理int32_t remain = dst_width & 15;int32_t i = 0;//外层高度循环,逐行处理for (int32_t j = 0; j < dst_height; j++){//偶数行源数据地址uint8_t *src_ptr0 = src + src_stride * j * 2;//奇数行源数据地址uint8_t *src_ptr1 = src_ptr0 + src_stride;//目的数据指针uint8_t *dst_ptr = dst + dst_stride * j;//内层循环,一次16个u8结果输出for (i = 0; i < dst_width_align; i += 16){//提取数据,进行UV分离v8_src0 = vld2q_u8(src_ptr0); src_ptr0 += 32; // 偶数行进入下一个stridev8_src1 = vld2q_u8(src_ptr1);src_ptr1 += 32; // 奇数行行进入下一个stride//水平两个数据相加uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);//上下两个数据相加,之后求均值v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);//UV通道结果交织存储vst2_u8(dst_ptr, v8_dst);dst_ptr += 16;}//process leftovers......}
}
5、第4个例子

当图像的宽度不是16的整数倍,需要考虑结尾数据处理,按照链接里面的例子,可以分为以下几种。

1、 padding

​ 也就是将数据补齐到想要的长度,如下图所示,比如我这里需要操作 uint8x8_t的数据,但是我的数据长度只有5,可以将数据的长度填充至8。
在这里插入图片描述

2、Overlap

​ 也就是重复利用其中的某些数据,在不填充其他数据的情况下进行,如下图所示,当需要利用uint8x4_t来对下面的数据进行计算时,可以先将04加载到寄存器上,再将36加载到寄存器上操作。
在这里插入图片描述

常用第二种方法对结尾数据进行处理,那么图像下采样的数据代码可以写成:

#include <arm_neon.h>void DownscaleUvNeon(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{uint8x16x2_t v8_src0;uint8x16x2_t v8_src1;uint8x8x2_t v8_dst;int32_t dst_width_align = dst_width & (-16); // 最大能够整除16的数。int32_t remain = dst_width & 15;             // 需要剩余处理的数据长度int32_t i = 0;for (int32_t j = 0; j < dst_height; j++){uint8_t *src_ptr0 = src + src_stride * j * 2;uint8_t *src_ptr1 = src_ptr0 + src_stride;uint8_t *dst_ptr = dst + dst_stride * j;// 处理完宽度为16的整数倍数据了for (i = 0; i < dst_width_align; i += 16){v8_src0 = vld2q_u8(src_ptr0);src_ptr0 += 32;v8_src1 = vld2q_u8(src_ptr1);src_ptr1 += 32;uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);vst2_u8(dst_ptr, v8_dst);dst_ptr += 16;}// process leftover// remain 剩余需要处理的数据长度if (remain > 0){// 从后往前回退一次向量计算需要的数据长度// 有部分数据是之前处理过的,这部分的数据在这里重复计算一次src_ptr0 = src + src_stride * (j * 2) + src_width - 32; src_ptr1 = src_ptr0 + src_stride;dst_ptr = dst + dst_stride * j + dst_width - 16;v8_src0 = vld2q_u8(src_ptr0);v8_src1 = vld2q_u8(src_ptr1);uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);vst2_u8(dst_ptr, v8_dst);}}
}

3、 single

将剩余的元素单独处理,就是将剩余的元素利用NEON的只加载一个元素的功能,不推荐使用,因为这里又可能for循环多次。

4、将剩余的元素当作标量处理

也就是将剩下的元素直接使用c语言编程的方式进行计算。

void DownscaleUvNeonScalar(uint8_t *src, uint8_t *dst, int32_t src_width, int32_t src_stride, int32_t dst_width, int32_t dst_height, int32_t dst_stride)
{uint8x16x2_t v8_src0;uint8x16x2_t v8_src1;uint8x8x2_t v8_dst;int32_t dst_width_align = dst_width & (-16);int32_t remain = dst_width & 15;int32_t i = 0;for (int32_t j = 0; j < dst_height; j++){uint8_t *src_ptr0 = src + src_stride * j * 2;uint8_t *src_ptr1 = src_ptr0 + src_stride;uint8_t *dst_ptr = dst + dst_stride * j;for (i = 0; i < dst_width_align; i += 16) // 16 items output at one time{v8_src0 = vld2q_u8(src_ptr0);src_ptr0 += 32;v8_src1 = vld2q_u8(src_ptr1);src_ptr1 += 32;uint16x8_t v16_u_sum0 = vpaddlq_u8(v8_src0.val[0]);uint16x8_t v16_v_sum0 = vpaddlq_u8(v8_src0.val[1]);uint16x8_t v16_u_sum1 = vpaddlq_u8(v8_src1.val[0]);uint16x8_t v16_v_sum1 = vpaddlq_u8(v8_src1.val[1]);v8_dst.val[0] = vshrn_n_u16(vaddq_u16(v16_u_sum0, v16_u_sum1), 2);v8_dst.val[1] = vshrn_n_u16(vaddq_u16(v16_v_sum0, v16_v_sum1), 2);vst2_u8(dst_ptr, v8_dst);dst_ptr += 16;}//process leftoversrc_ptr0 = src + src_stride * j * 2;src_ptr1 = src_ptr0 + src_stride;dst_ptr = dst + dst_stride * j;for (int32_t i = dst_width_align; i < dst_width; i += 2){dst_ptr[i] = (src_ptr0[i * 2] + src_ptr0[i * 2 + 2] +src_ptr1[i * 2] + src_ptr1[i * 2 + 2]) / 4;dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;}}
}
6、总结

本次学习中通过一个下采样的例子学习的NEON编程过程中的优势以及将要面临的问题,主要是剩余数据处理的方式,后面将继续深入学习。
/ 4;

        dst_ptr[i + 1] = (src_ptr0[i * 2 + 1] + src_ptr0[i * 2 + 3] +src_ptr1[i * 2 + 1] + src_ptr1[i * 2 + 3]) / 4;}
}

}


#### 6、总结本次学习中通过一个下采样的例子学习的NEON编程过程中的优势以及将要面临的问题,主要是剩余数据处理的方式,后面将继续深入学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/509525.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VS Code 的粘性滚动预览 - 类似于 Excel 的冻结首行

VS Code 的粘性滚动预览 - 类似于 Excel 的冻结首行功能&#xff0c;即滚动 UI 显示当前源代码范围。便于在代码行数比较多的时候更好的知道自己所在的位置。粘性滚动UI 显示用户在滚动期间所处的范围&#xff0c;将显示编辑器顶部所在的类/接口/命名空间/函数/方法/构造函数&a…

端接电阻没选对,DDR颗粒白费?

高速先生成员--姜杰 端接可以解决很多反射问题&#xff0c;如果还有问题&#xff0c;有没有一种可能是端接电阻阻值没选对&#xff1f; 对于点到点的拓扑&#xff0c;末端并联电阻的阻值比较容易选择&#xff0c;端接电阻阻值R与传输线特征阻抗一样即可。 VTT为1V时&#xff0c…

CSS3笔记

1.相同优先级的样式以写在后面的为主。 2.交集选择器&#xff0c;并且 条件挨在一起 p.rich{...} /*p元素class有rich的元素*/ 3.并集选择器&#xff0c;或者 逗号隔开 .class1,class2{...}/*满足其中一个类名都会使用该样式*/ 4.后代选择器 空格 隔开 所有符合的包括孙子及…

便携式启动电源的市场前景和商业机会

便携式启动电源是一种便携式电子设备&#xff0c;主要用于为飞机、火炮、汽车、船只等大型机械提供紧急启动电源。它通常由一个可充电的电池和一个充电器组成&#xff0c;可以方便地随身携带。 便携式启动电源的工作原理是通过将电池的电能转换为机械能&#xff0c;从而驱动汽…

面试题HTML+CSS+网络+浏览器篇

文章目录 Css预处理sass less是什么&#xff1f;为什么使用他们怎么转换 less 为 css&#xff1f;重绘和回流是什么http 是什么&#xff1f;有什么特点HTTP 协议和 HTTPS 区别什么是 CSRF 攻击HTML5 新增的内容有哪些Css3 新增的特性flex VS grid清除浮动的方式有哪些&#xff…

1、EmlogCms代码审计

一、SQL注入 1、后台标签删除处存在1处sql注入 漏洞条件 ● 漏洞url: http://emlog6.0.com/admin/tag.php?actiondell_all_tag ● 漏洞参数&#xff1a;tag[xx] ● 是否存在限制&#xff1a;无 ● 是否还有其他条件&#xff1a;actiondell_all_tag,token复现 POST /admin…

从零开始搭建web组态

成果展示&#xff1a;by组态[web组态插件] 一、技术选择 目前只有两种选择&#xff0c;canvas和svg Canvas: 是一个基于像素的渲染引擎&#xff0c;使用JavaScript API在画布上绘制图像&#xff0c;它的优点包括&#xff1a; Canvas渲染速度快&#xff0c;适合处理大量图像和…

是真的免费!企业 AI 学习指南:Azure 2024 年学习生成式 AI 的顶级免费资源

微软的Azure AI学习专家为了帮助企业员工快速上手AI工具&#xff0c;特别准备了一些课程&#xff0c;事先说明&#xff0c;微软出品&#xff0c;完全免费&#xff0c;非割韭菜课程&#xff01; 课程浅显易懂&#xff0c;而且每节课后都会有小结、知识测试&#xff0c;帮助复习知…

Linux配置网卡功能

提示:工具下载链接在文章最后 目录 一.network功能介绍二.配置network功能2.1 network_ip配置检查 2.2 network_br配置2.2.1 配置的网桥原先不存在检查2.2.2 配置的网桥已存在-修改网桥IP检查2.2.3 配置的网桥已存在-只添加网卡到网桥里检查 2.3 network_bond配置检查 2.4 netw…

虹科新品|Baby-LIN第三代系列产品全面升级,重塑车辆测试新体验

导读&#xff1a;虹科Baby-LIN系列产品&#xff08;LIN和CAN总线模拟器&#xff09;是在测试和生产领域控制车辆部件(ECU)的成熟解决方案。虹科Baby-LIN系统能够可靠地连接到各类自动化和测量设备&#xff0c;为汽车零部件&#xff08;如电机执行器、车灯、门板、方向盘等&…

OpenGL 实现色温、色调、亮度、对比度、饱和度、高光

1.简介 色温&#xff1a;简单理解是色彩的温度&#xff0c;越低越冷如蓝色&#xff0c;约高越暖如红色。 亮度&#xff1a;增加就是给图片所有色彩加白色&#xff0c;减少加黑色。注意是只加黑白两种颜色&#xff0c;不然容易跟纯度弄混。 对比度&#xff1a;增加就是让白的…

海思hi3519dv500,hi3516dv500移植yolov8-模型处理

上一节yolov8的训练已经完成了,现在要开始做模型的转换了,这里和yolov7方式相似,但是有一些差异,尤其是yolov7的不带NMS部分的输出顺序和yolov8的输出顺序与格式是有差异的。 首先还是要自己手动加入rpn_op,这里包含了filter,sort,nms部分。 我们一个一个看,首先filter.py…