对 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 中得到结果的主要内容,如果未能解决你的问题,请参考以下文章
如何对 AngularJS 中的两个字段求和并在标签中显示结果?