-04-实时Prewitt边缘检测,第二步:Prewitt的NEON加速实现【ARM NEON加速】

下面进行Prewitt的NEON加速设计,将实现的具体思路描述一下。

S0. Prewitt的C语言实现

我把Prewitt算子的计算过程按下图重新进行表示:
这里写图片描述
可以看到该算法主要分为了三个步骤去实现:

1. 像素灰度化,将源图像数像素点的彩色图像转换为灰度值,使用常用的公式:GRAY=(R*77+G*151+G*28)/256
2. x、y方向一阶梯度计算
3. 两个方向梯度均方根计算

C代码:

unsigned char pixel_rgb2gray(unsigned char * rgb)
{
    unsigned char gray;
    unsigned int y = (rgb[0] * 77) + (rgb[1] * 151) + (rgb[2] * 28);
    gray = (y >> 8);
    return gray;
}

int prewitt_c_perfect_loop(IMG_SRC_MAT src, IMG_DST_MAT dst, unsigned int uiParam2, unsigned int uiParam3)
{
    unsigned int i,j;
    unsigned int gx,gy,g;
    unsigned int sum_check=0;
    unsigned int array[FILTER_WINDOW][FILTER_WINDOW];
    //perfect loop
    for(i=1; i<(IMG_ROWS-1); i++){
        for(j=1; j<(IMG_COLS-1); j++){
            array[0][0]=pixel_rgb2gray((*src)[i-1][j-1]);
            array[0][1]=pixel_rgb2gray((*src)[i-1][j]);
            array[0][2]=pixel_rgb2gray((*src)[i-1][j+1]);
            array[1][0]=pixel_rgb2gray((*src)[i][j-1]);
            array[1][1]=pixel_rgb2gray((*src)[i][j]);
            array[1][2]=pixel_rgb2gray((*src)[i][j+1]);
            array[2][0]=pixel_rgb2gray((*src)[i+1][j-1]);
            array[2][1]=pixel_rgb2gray((*src)[i+1][j]);
            array[2][2]=pixel_rgb2gray((*src)[i+1][j+1]);
            gx = (array[0][2] + array[1][2] + array[2][2]) - (array[0][0] + array[1][0] + array[2][0]);
            gy = (array[2][0] + array[2][1] + array[2][2]) - (array[0][0] + array[0][1] + array[0][2]);
            g = sqrt(gx*gx + gy*gy);
            if (g > 255) g = 255;
            //else if (g < 0) g = 0;
            (*dst)[i][j][0] = g;
            (*dst)[i][j][1] = g;
            (*dst)[i][j][2] = g;
            sum_check += g;
        }
    }
    return sum_check;
}

可见,针对输出图像的每个像素点都要进行单独的Prewitt计算,整体计算的循环次数的数值是比较大的,而且在RGB转换灰度、均方根计算时用到了乘法和平方根的计算,这对于CPU来说占用大量的计算时间。具体的计算时间就不在这里说了,总之要达到60fps(16.667ms)的速率是达不到的,实现不了Prewitt的实时计算。

S1. Prewitt的NEON加速方案

Prewitt同众多滤波算法类似,采用的是3*3滑动窗口逐个扫描整幅输入图像,然后进行加加权、平方根等计算得到最终一个像素的结果。这些算法有一个很明显的特点就是计算滤波结果时,前一个像素滤波与接下来的一个像素会共用到6个同样的原始像素点,即两次的3*3窗口是有重叠部分的。如下图所示,第一次的窗口为红框,第二次为绿框,它们有6个共用的原始像素点。
这里写图片描述
因连续的两个像素滤波会共用原始像素点数据,若在计算时能够进行结果缓存或者其他一些处理,将能够大大降低算法对从DDR读取源数据、灰度变换等计算过程的CPU处理时间。

NEON有并行计算的能力,可以同时针对多种数据格式进行并行的加、减、乘、移位、平方根倒数等计算。而NEON并行计算一般情况下是针对8个元素的向量进行计算的。根据这个特点,我们将Prewitt算法一次进行3*3数据读取的方式改变,每次直接对8*3的原始数据进行处理。而8*3的原始数据实际处理中,只能够产生6个准确的计算结果,如下图加深的6个像素点对应的位置可以获得计算结果。
这里写图片描述
上面这个可以说是这个加速设计中最为重要的一环!!!精华所在
是我自己想了一段时间想出来的,但我感觉肯定有别人想到的比我更早~~我可没有抄袭哦
如果有人受到这个的启发,欢迎交流哦,微信:vacajk

接着说——因此,我们将对Prewitt算法进行NEON并行方式的加速,每次读取8*3个原始像素点(针对RGB原始数据格式,每个像素点内有R、G、B三个颜色通道,数据量为3倍:24*3字节),进行处理后得到6个滤波结果进行保存,接着向右步进6个像素进行下一次的处理。如下图,红色虚线框内为第一次8*3的原始像素点,绿色虚线框内为第二次的8*3原始像素点。每次计算使用并行处理方式实现6个像素点的滤波结果计算,即图中最底下的0~5像素点。
这里写图片描述

针对设计要求,我们本需要进行640*360=230,400次循环(去除边界,实际为638*358=228,404次),但是使用上面的加速方案,可以将循环减少为原来的1/6:638/6 * 358=107*358=38306次循环。
NEON加速的循环次数仅为之前次数的16.77%,可见在循环次数这一项上,借助NEON的并行计算能力,就能够使得速度有很大的提升。而在这实现后,我们还可以进行预加载、dual-issue功能的优化。最终速度肯定还能进一步的提升!

S2. Prewitt的NEON实现

下面用图片的方式来介绍Prewitt在NEON中加速的实现过程,在这之前需要你了解基本的NEON汇编指令及其用法,以及准备一份手册:DUI0204IC_rvct_assembler_guide

8像素的RGB2GRAY
这里写图片描述

8*3窗口的RGB2GRAY
这里写图片描述

x方向一阶梯度
这里写图片描述

y方向一阶梯度
这里写图片描述

两个方向梯度均方根
这里写图片描述

上面的左边是数据的处理与变化格式,右边含有对应的NEON汇编代码。仔细研究应该可以看懂的吧,我就不再详细说明了。要注意的是x方向和y方向一阶梯度的计算是根据Prewitt算子公式的特点进行设计的,所以计算步骤不太相同。

在XC7Z010中裸奔的计算速度对比:

C代码(-O0): 635.9ms
原始汇编代码:9.66ms
调整汇编指令顺序(dual issue):8.62ms
增加预加载指令(pld):6.97ms

这一篇介绍了Prewitt的NEON加速设计思路与具体的实现方式,汇编源码的裸奔测试也OK。下一篇介绍如何将汇编代码加入整个ZYBO HDMI Demo工程中,实现HDMI视频输入、实时Prewitt的NEON加速计算、VGA视频输出,也就是系统总体功能的集成。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值