对 3 个向量求和并在 neon 中得到结果

Posted

技术标签:

【中文标题】对 3 个向量求和并在 neon 中得到结果【英文标题】:Summing 3 vectors and get the result in neon 【发布时间】:2013-03-20 12:38:24 【问题描述】:

我正在尝试对 d0,d1,d2 + d3,d4,d5+ d6,d7,d8 求和。我不知道最好的指令,然后取平均值为 9。我知道如何使用近似值进行平均,但是将这些车道求和,我找不到相应的指令?我也有不正确的输出图像,所以我怀疑平均操作是否正确。

inline void downsample3dOnePass( uint8_t* src, uint8_t *dst, int srcWidth)


    for (int r = 0; r < (int)srcWidth/3; r++)
    
       // load 24 pixels (grayscale)
       uint8x8x3_t r0       = vld3_u8(src);
       // move to next 24 byes
       src+=24;
       uint8x8x3_t r1       = vld3_u8(src);
       src+=24;
       uint8x8x3_t r2       = vld3_u8(src);

       uint16x8_t  d0  = vmovl_u8(r0.val[0]);
       uint16x8_t  d1  = vmovl_u8(r0.val[1]);
       uint16x8_t  d2  = vmovl_u8(r0.val[2]);

       uint16x8_t  d3  = vmovl_u8(r1.val[0]);
       uint16x8_t  d4  = vmovl_u8(r1.val[1]);
       uint16x8_t  d5  = vmovl_u8(r1.val[2]);

       uint16x8_t  d6  = vmovl_u8(r2.val[0]);
       uint16x8_t  d7  = vmovl_u8(r2.val[1]);
       uint16x8_t  d8  = vmovl_u8(r2.val[2]);

       uint16x8_t d0d3Sum      = vaddq_u16 ( d0, d3);
       uint16x8_t d0d3d6Sum    = vaddq_u16 ( d0d3Sum,  d6 );

       uint16x8_t d1d4Sum      = vaddq_u16 ( d1, d4);
       uint16x8_t d1d4d7Sum    = vaddq_u16 ( d1d4Sum, d7);

       uint16x8_t d2d5Sum      = vaddq_u16 ( d2, d5 );
       uint16x8_t d2d5d8Sum    = vaddq_u16 ( d2d5Sum, d8);

       uint16x8_t firstSum     = vaddq_u16(d0d3d6Sum, d1d4d7Sum);
       uint16x8_t secondSum    = vaddq_u16(firstSum, d2d5d8Sum);
       uint16x8_t totalSum     = vaddq_u16 ( firstSum, secondSum);

       // average = r0+r1+r2/8 ~9 for test
       uint16x8_t totalAverage = vshrq_n_u16(totalSum,3);
       uint8x8_t  finalValue   = vmovn_u16(totalAverage);
       // store 8 bytes
       vst1_u8(dst, finalValue);

       src+=24;
       // move to next row
       dst+=8;

   



void downsample3d( uint8_t* src, uint8_t *dest, int srcWidth, int srcHeight )

    for (int r = 0; r < (int)srcHeight/3; r++)
    
         downsample3dOnePass(src, dest, srcWidth);
    

更新:根据比特银行的回答:

    inline void downsample3dOnePass( uint8_t* src, uint8_t *dst, int srcWidth, int srcHeight, int strideSrc, int strideDest)
    
        int iDestPitch = (strideDest);
        uint8_t *s, *d;
        uint8x8x3_t u88line0;
        uint8x8x3_t u88line1;
        uint8x8x3_t u88line2;
        uint8x8_t   u88Final;
        uint16x8_t  u168Sum;
        int16x8_t   i168divisor = vdupq_n_s16(7282/2); // 65536/9 - used with doubling saturating return high multiply

        for (int r = 0; r < srcHeight/3; r++)
        
            d = &dst[iDestPitch * r];
            s = &src[srcWidth * r*3];

            for (int c = 0; c < srcWidth/3; c+=8)
            
                // load 8 sets of 3x3 pixels (grayscale)
                u88line0 = vld3_u8(&s[0]);
                u88line1 = vld3_u8(&s[srcWidth]);
                u88line2 = vld3_u8(&s[srcWidth*2]);
                s += 24;
                // Sum vertically
                u168Sum = vaddl_u8(u88line0.val[0], u88line0.val[1]); // add with widening
                u168Sum = vaddw_u8(u168Sum, u88line0.val[2]); // accumulate with widening (horizontally)
                u168Sum = vaddw_u8(u168Sum, u88line1.val[0]); // add the other vectors together
                u168Sum = vaddw_u8(u168Sum, u88line1.val[1]);
                u168Sum = vaddw_u8(u168Sum, u88line1.val[2]);
                u168Sum = vaddw_u8(u168Sum, u88line2.val[0]);
                u168Sum = vaddw_u8(u168Sum, u88line2.val[1]);
                u168Sum = vaddw_u8(u168Sum, u88line2.val[2]);
                // we now have the 8 sets of 3x3 pixels summed to 8 16-bit values
                // To divide by 9 we will instead multiply by the inverse (65536/9) = 7282
                u168Sum = vreinterpretq_u16_s16(vqrdmulhq_s16(i168divisor, vreinterpretq_s16_u16(u168Sum)));
                u88Final = vmovn_u16(u168Sum); // narrow to 8 bits
                // store 8 bytes
                vst1_u8(d, u88Final);
                d += 8;
             // for column
         // for row
    


usage: 
//1280*920*grayscale
QImage normalImage("/data/normal_image.png");

uint8_t *resultImage = new uint8_t[440*306];
  downsample3dOnePass(normalImage.bits(),resultImage, normalImage.width(), normalImage.height(), 1280, 440);

【问题讨论】:

您将多个字节相加并将它们存储在一个字节中,然后取其平均值。举例来说,如果两个向量中的相应字节是 0xff 和 0x01 并且您将它们加在一起作为字节会发生什么。您要么必须在求和时将所有像素扩展为 16 位值,要么在相加之前右移(如果可以,请避免使用后一种方法,因为它会导致不必要的精度损失)。 @Michael 我正在寻找将 uint8x8 转换为 uint16x8 的内在函数,但找不到它。 +1 通知 :) VMOVL (Vector Move Long) takes each element in a doubleword vector, sign or zero extends them to twice their original length, and places the results in a quadword vector.。所以你想要的内在是uint16x8_t vmovl_u8 (uint8x8_t) 【参考方案1】:

您的代码存在几个问题。 NEON 内在函数在 VLDx 处理方面非常糟糕,但您的大错误是您溢出了字节值并水平而不是垂直加载像素。这是一个更好的算法,它将一次将 8*3x3 源像素处理成 8 个目标像素。您的函数也缺少 rows 参数。

inline void downsample3dOnePass( uint8_t* src, uint8_t *dst, int srcWidth, int srcHeight)

int iDestPitch = ((srcWidth/3)+3) & 0xfffffffc; // DWORD aligned
uint8_t *s, *d;
uint8x8x3_t u88line0, u88line, u88line2;
uint8x8_t u88Final;
uint16x8_t u168Sum;
int16x8_t i168divisor = vdupq_n_s16(7282/2); // 65536/9 - used with doubling saturating return high multiply

  for (int r = 0; r < srcHeight/3; r++)
    
    d = &dst[iDestPitch * r];
    s = &src[srcWidth * r*3];

    for (int c = 0; c < srcWidth/3; c+=8)
    
       // load 8 sets of 3x3 pixels (grayscale)
       u88line0 = vld3_u8(&s[0]);
       u88line1 = vld3_u8(&s[srcWidth]);
       u88line2 = vld3_u8(&s[srcWidth*2]);
       s += 24;
       // Sum vertically
       u168Sum = vaddl_u8(u88Line0.val[0], u88Line0.val[1]); // add with widening
       u168Sum = vaddw_u8(u168Sum, u88Line0.val[2]); // accumulate with widening (horizontally)
       u168Sum = vaddw_u8(u168Sum, u88Line1.val[0]); // add the other vectors together
       u168Sum = vaddw_u8(u168Sum, u88Line1.val[1]);
       u168Sum = vaddw_u8(u168Sum, u88Line1.val[2]);
       u168Sum = vaddw_u8(u168Sum, u88Line2.val[0]);
       u168Sum = vaddw_u8(u168Sum, u88Line2.val[1]);
       u168Sum = vaddw_u8(u168Sum, u88Line2.val[2]);
       // we now have the 8 sets of 3x3 pixels summed to 8 16-bit values   
       // To divide by 9 we will instead multiply by the inverse (65536/9) = 7282
       u168Sum = vreinterpretq_u16_s16(vqrdmulhq_s16(i168divisor, vreinterpretq_s16_u16(u168Sum)));
       u88Final = vmovn_u16(u168Sum); // narrow to 8 bits
       // store 8 bytes
       vst1_u8(d, u88Final);
       d += 8;    
    // for column
 // for row

【讨论】:

图像失真。这就是我如何称呼它 uint8_t *resultImage = new uint8_t[450*310]; downsample3dOnePass(normalImage.bits(),resultImage, normalImage.width(), normalImage.height()); normalImage 为 1280*920 +1 "distorted" 的描述性不是很好。检查源图像和目标图像的间距是否正确。在我上面的代码中,我使用了一个双字对齐的目标音高。您可能没有假设此值,因此将其更改为 srcWidth/3。从您的原始代码来看,您似乎没有牢牢掌握图像在内存中的布局方式,这听起来也是当前的问题。 对不起,我没听明白。我应该改变 int iDestPitch = ((srcWidth/3)+3) & 0xfffffffc; to int iDestPitch = (srcWidth/3);图片在这里显示i45.tinypic.com/2wnwqw9.png 这就是我的观点。图像的间距(每行字节数)未正确处理。你对这个概念的误解是阻碍你编写工作代码的绊脚石之一。 PITCH 或 STRIDE 是图像每行的字节数,无论有多少像素。您显示的 png 的音高错误,这就是它具有对角线图案的原因。无论您选择什么音高,读取/显示图像的任何内容都必须使用相同的值。 PNG 是字节对齐的,而不是双字对齐的。 我指定了int iDestPitch = stride,stride是dest图像的宽度,也就是360。diagnoal image变了,现在水平边有GL_Repeated的效果...跨度> 【参考方案2】:

为了避免在将多个向量的字节相加时发生溢出,您应该在求和之前从字节扩展为半字(16 位)。将所有像素相加并划分结果后,您可以将结果缩小到字节。

在 GCC 中用于将字节扩展为半字的 NEON 内在函数是uint16x8_t vmovl_u8 (uint8x8_t)

而相应的缩小内在函数是uint8x8_t vmovn_u16 (uint16x8_t)

请注意,如果将 9 个像素相加并除以 8,则在缩小回字节时仍可能存在溢出风险。在这种情况下,您可以使用vqmovn_u16,它的行为类似于vmovn_u16,但也会执行饱和。

【讨论】:

我仍然有扭曲的图像:/ 我已经更新了完整的代码。 uint16x8_t totalSum = vaddq_u16 ( firstSum, secondSum); 这行对我来说看起来不正确。 secondSum 此时应该已经包含所有像素的总和,所以再做一次加法只会多次求和像素。 那么可能是时候开始转储和分析输出了。创建一个小的单色或棋盘图案位图,在其上运行您的算法并将结果转储到标准输出或文件中,看看您是否不能基于此发现问题。 如果你只想水平平均应该没问题。但是你添加像素的方式看起来有点不对劲。请记住,vld3 对加载的数据执行去交错。因此,例如(除非我弄错了),您的输出像素 0 将是输入像素 0,24,48,1,25,49,2,26 and 50 的平均值,而不是我假设您想要使用的 0,1,2,3,4,5,6,7 and 8 将 24 添加到源指针不会使您垂直移动 - 请参阅我的答案以获得正确的解决方案。

以上是关于对 3 个向量求和并在 neon 中得到结果的主要内容,如果未能解决你的问题,请参考以下文章

在cupy中使用元素内核对条目求和的问题

Python Numpy中的几个矩阵乘法

如何对 AngularJS 中的两个字段求和并在标签中显示结果?

如何将给定矩阵的每一行中的所有元素与给定向量的相应元素相乘并在 MATLAB 中求和?

Neon Intrinsics各函数介绍

NEON 如何处理溢出?