arm neon transpose 4x4 uint32

时间:2016-04-23 20:24:58

标签: c++ image-processing arm neon

我正在尝试旋转并逆时针旋转90度,然后水平翻转。

我的第一个方法是使用OpenCV:

cv::transpose(in, tmp); // transpose around top left
cv::flip(tmp, out, -1); // flip on both axes

为了表现,我正在尝试将这两个功能合并为一个。

我的代码:

void ccw90_hflip_640x480(const cv::Mat& img, cv::Mat& out)
{
    assert(img.cols == 640 && img.rows == 480);
    assert(out.cols == 480 && out.cols == 640);

    uint32_t* imgData = (uint32_t*)img.data;
    uint32_t* outData = (uint32_t*)out.data;

    uint32_t *pRow = imgData;
    uint32_t *pEnd = imgData + (640 * 480);
    uint32_t *dstCol = outData + (480 * 640) - 1;

    for( ; pRow != pEnd; pRow += 640, dstCol -= 1)
    {
        for(uint32_t *ptr = pRow, *end = pRow + 640, *dst = dstCol;
            ptr != end;
            ++ptr, dst -= 480)
        {
            *dst = *ptr;
        }
    }
}

我认为上面会更快,但事实并非如此。除了可能使用NEON的OpenCV之外,我想不出任何理由它会更快。

我发现这篇文章/介绍: http://shervinemami.info/NEON_RotateBGRX.swf

换位和翻转一起模糊,使得很难修改到其他方向旋转的位置,并且像我需要的那样在水平轴上翻转。这篇文章很老了,所以我希望有一种更直接的方式来做我需要的。

那么使用arm NEON转换uint32的4x4矩阵的最简单方法是什么?

2 个答案:

答案 0 :(得分:2)

以下代码相当于原始帖子中的OpenCV调用,但执行速度提高了几倍(至少在我的设备上)。

使用Neon确实显着提高了性能。由于转换发生在CPU内部,因此可以简化存储器访问,以一组四个而不是一次读取和写入像素,如评论中所述。

void ccw90_hflip_640x480_neon(const cv::Mat& img, cv::Mat& out)
{
    assert(img.cols == 640 && img.rows == 480);
    assert(out.cols == 480 && out.cols == 640);

    uint32_t *pRow = (uint32_t*)img.data;
    uint32_t *pEnd = (uint32_t*)img.data + (640 * 480);
    uint32_t *dstCol = (uint32_t*)out.data + (480 * 640) - (480 * 3) - 4;

    for( ; pRow != pEnd; pRow += 640 * 4, dstCol -= 4)
    {
        for(uint32_t *ptr = pRow, *end = pRow + 640, *dst = dstCol;
            ptr != end;
            ptr += 4, dst -= 480 * 4)
        {
            uint32_t* in0 = ptr;
            uint32_t* in1 = in0 + 640;
            uint32_t* in2 = in1 + 640;
            uint32_t* in3 = in2 + 640;

            uint32_t* out0 = dst;
            uint32_t* out1 = out0 + 480;
            uint32_t* out2 = out1 + 480;
            uint32_t* out3 = out2 + 480;

            asm("vld1.32 {d0, d1}, [%[in0]]    \n"
                "vld1.32 {d2, d3}, [%[in1]]    \n"
                "vld1.32 {d4, d5}, [%[in2]]    \n"
                "vld1.32 {d6, d7}, [%[in3]]    \n"
                "vtrn.32 q0, q1                \n"
                "vtrn.32 q2, q3                \n"
                "vswp d1, d4                   \n"
                "vswp d3, d6                   \n"
                "vrev64.32 q0, q0              \n"
                "vrev64.32 q1, q1              \n"
                "vrev64.32 q2, q2              \n"
                "vrev64.32 q3, q3              \n"
                "vswp d0, d1                   \n"
                "vswp d2, d3                   \n"
                "vswp d4, d5                   \n"
                "vswp d6, d7                   \n"
                "vst1.32 {d6, d7}, [%[out0]]   \n"
                "vst1.32 {d4, d5}, [%[out1]]   \n"
                "vst1.32 {d2, d3}, [%[out2]]   \n"
                "vst1.32 {d0, d1}, [%[out3]]   \n"
                :
                : [out0] "r" (out0), [out1] "r" (out1), [out2] "r" (out2), [out3] "r" (out3),
                    [in0] "r" (in0), [in1] "r" (in1), [in2] "r" (in2), [in3] "r" (in3)
                : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"
                );
        }
    }
}

答案 1 :(得分:1)

霓虹灯不会显着帮助注意。您的代码只是移动数据;霓虹灯不能使底层记忆更快。见this article;使用PLD也会有所帮助。我建议您按顺序处理dst并使用ptr跳转。缓存将预先填充ptrdst将填充行。

这是遍历内存的另一种形式(变量名称可能没有意义),

uint32_t *pEnd = imgData + 640;
uint32_t *dstCol = outData;

for( ; pRow != pEnd; pRow ++)
{
    for(uint32_t *ptr = pRow, *dst = dstCol, *end = dst + 480;
        dst != end;
        ptr += 640, dst++)
    {
        *dst = *ptr;
    }
    // could flush `dstCol` here as it is complete or hope the system clues in.
    dstCol += 480;
}

我们的想法是按顺序填写dst并跳转访问imgData。如果按顺序写出来,所有现代记忆都会更有效地填充。高速缓存和同步DRAM通常一次填充几个32位字。我们可以使用L1缓存的知识来展开内部循环。它是32或64字节,代表8或16个32位像素。填充将是类似的数量,因此您可以将转置减少到可缓存的块并一次处理每个块。将640x480图像视为由8 * 8像素图块组成(最小L1缓存大小)并依次处理每个图像。

执行此操作后,NEON指令可能会获得一定的百分比。但是,优化加载/存储单元(所有CPU单元通用)应该是第一步。

注意:Neon是SIMD(单指令,多数据),它擅长数字处理像素,通过一次处理几个来提高计算能力。它确实有一些优化内存遍历的指令,但CORE CPU单元和SIMD / NEON单元的底层内存是相同的。有可能NEON会提升,但我认为在优化内存系统的访问顺序之前,这是徒劳的。