从0开始学习NEON(1)

news2024/9/22 15:47:14
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) / 4
            dst_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) / 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;
        }
    }
}

通过学习向量化编程,我们可知,数据的计算可以利用单指令多数据的方式进行加速,例如上面的例子中的内层循环,下面就使用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; // 偶数行进入下一个stride
            v8_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 leftover
        src_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.coloradmin.cn/o/1488688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CSP试题回顾】201403-2-窗口

CSP-201403-2-窗口 解题思路 窗口存储结构&#xff1a;首先&#xff0c;使用一个结构体MyWindow来存储每个窗口的信息&#xff0c;包括窗口的序号&#xff08;index&#xff09;和矩形区域的四个顶点坐标&#xff08;x1, y1, x2, y2&#xff09;。所有窗口的信息存储在一个向量…

17 easy 290. 单词规律

//给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 // // 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。 // // // // 示例1: // // //输入: patte…

一篇文章教会你如何在IOS真机上完美运行React Native

一篇文章教会你如何在IOS真机上完美运行React Native 项目初始化项目配置可能遇到的问题没有账号也没有Team设备上没有打开开发者模式&#xff0c;也没有信任开发者证书 无线调试 项目初始化 在终端使用**npx react-native init ProjectName**初始化React Native项目。 进入项…

selenuim【1】$x(‘xpath’)、WebDriverWait()、try/assert

文章目录 1、执行driver webdriver.Chrome()后很久才打开浏览器2、浏览器多元素定位 $x(‘xpath语法’)3、打开浏览器driver.get("网址")执行了很久才开始定位元素&#xff1a;等待&#xff08;1&#xff09;driver.set_page_load_timeout(t)&#xff08;2&#xff…

Live Home 3D Pro:您的私人家居设计师,让家更有温度

Live Home 3D Pro是一款功能强大的家居设计软件&#xff0c;它凭借直观的用户界面和丰富的设计工具&#xff0c;为用户提供了一个全新的家居设计体验。无论您是专业设计师还是普通用户&#xff0c;Live Home 3D Pro都能帮助您轻松实现家居设计的梦想。 Live Home 3D Pro mac版…

枚举与尺取法(蓝桥杯 c++ 模板 题目 代码 注解)

目录 组合型枚举&#xff08;排列组合模板&#xff08;&#xff09;&#xff09;: 排列型枚举&#xff08;全排列&#xff09;模板&#xff1a; 题目一&#xff08;公平抽签 排列组合&#xff09;&#xff1a; ​编辑 代码&#xff1a; 题目二&#xff08;座次问题 全排…

财报解读:基本盘稳定后,联想如何进一步抢占AI时代?

从2021年下半年开始&#xff0c;受诸多因素影响&#xff0c;消费电子行业始终处在承压状态&#xff0c;“不景气”这一关键词屡次被市场提及。 但寒气没有持续&#xff0c;可以看到&#xff0c;消费电子行业正在逐渐回暖。国金证券在今年1月的研报中就指出&#xff0c;从多方面…

学习python时一些笔记

1、winr 命令提示符的快捷键 输入cmd进入终端 2、在终端运行桌面上的python文件 cd desktop(桌面) cd是进入该文件夹的意思。 cd .. 回到上一级 运行python时一定要找到文件的所在地 输入python进入&#xff0c;exit()退出%s字符串占位符%d数字占位符%f浮点数占位符input输…

手把手教你零成本获客的教育培训小程序

随着科技的发展&#xff0c;数字化教育成为现代教育的趋势。为了满足教育培训机构的需求&#xff0c;许多教育培训小程序搭建平台应运而生。今天&#xff0c;我们将为大家介绍一种简单快捷的搭建教育培训小程序的方法。 首先&#xff0c;登录【乔拓云】制作平台&#xff0c;进入…

Java毕业设计 基于SpringBoot vue 社团管理系统

Java毕业设计 基于SpringBoot vue 社团管理系统 SpringBoot vue 社团管理系统 功能介绍 社团成员: 登录 首页 社团列表 加入社团申请 我的社团 在线留言 社长: 登录 首页 用户信息管理 入团申请管理 社团活动管理 社团成员管理 社团新闻管理 社团费用管理 系统管理员: 登录…

汽车碰撞与刮伤的实用维修技术,汽车的车身修复与涂装修补教学

一、教程描述 本套汽车维修技术教程&#xff0c;大小7.44G&#xff0c;共有60个文件。 二、教程目录 01-汽车车身修复教程01-安全规则&#xff08;共3课时&#xff09; 02-汽车车身修复教程02-汽车结构&#xff08;共3课时&#xff09; 03-汽车车身修复教程03-汽车修复所使…

复合数据类型(ch3)

将array依次执行以下操作 1.把列表中的元素升序排序。 2.删除列表中的最后一个元素。 3.把列表中第一个元素移动到列表尾部。 4.返回新列表。array [85,96,2,5,3,566,0,91,5234,5555,89,62,34] #*******请输入您的代码********# #***********begin************# def sort_and_…

Groovy(第五节) Groovy 之集合

Groovy 可以直接在语言内使用集合。在 Groovy 中,不需要导入专门的类,也不需要初始化对象。集合是语言本身的本地成员。Groovy 也使集合(或者列表)的操作变得非常容易,为增加和删除项提供了直观的帮助。 可以将范围当作集合 在前一节学习了如何用 Groovy 的范围将循环变得…

项目中遇到的耗时点-(图片缩放里面添加的标记红圈无法缩放)

实现效果 1、需求&#xff1a;显示服务端返回的在线url图片&#xff0c;并且根据标记圈的坐标点和半径绘制到图片上 分析&#xff1a;由于涉及到图片上加标记&#xff0c;可考虑的方案有 1、加个父布局&#xff0c;里面嵌入两个ImageView 2、自定义ImageView、使其支持标图 …

【网站项目】182在线作业管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

docker配置数据默认存储路径graph已过时,新版本中是data-root

错误信息 我在修改/etc/docker/daemon.json文件中&#xff0c;添加存储路径graph字段。然后sudo systemctl restart docker包如下错误&#xff1a;使用journalctl -xeu docker.service错误信息&#xff0c;发现不能匹配graph字段。 原因 我的docker版本&#xff1a; 在doc…

【计算机那些事】

目录 【云计算】 【原神用的是UDP还是TCP】 【几个特殊地址】 【socket是什么】 【内网穿透是什么】 【为什么有HTTP协议&#xff0c;还要有websocket协议】 【科普路由器&#xff0c;集线器&#xff0c;交换机&#xff0c;网桥&#xff0c;光猫】 【USB接口那些事】 …

【Python】进阶学习:pandas--rename()用法详解

【Python】进阶学习&#xff1a;pandas-- rename()用法详解 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448; 希望得到您的…

ZYNQ PS 读取 TF 卡 BIN 文件中的浮点数

动机 在进行 AI 算法加速器设计时&#xff0c;需要读取模型导出的权重和数据集数据作为加速器的输入&#xff0c;而目前我个人比较常用的做法是将权重和数据集(如果数据集过大&#xff0c;可以选择一两张图片)放到 SD/TF 卡中&#xff0c;然后 PS CPU核去进行数据读取&#xff…