高效的2d均值滤波器实现,最大限度地减少冗余内存负载

时间:2011-05-07 20:05:20

标签: c performance optimization cpu

假设在内核上执行某些函数的通用滑动算法,如图像处理中的均值滤波器(平均滤波器)或绝对差值和算法。当内核滑动到下一个位置时,内存中会有一些冗余读取,因为新内核所包含的数据会与之前的内容重叠。

让我用一个实际的例子解释一下......假设你想在一个内核(窗口)大小为3x3的大型二维矩阵上执行中值滤波。内核的第一个位置(下图中的红色)将以(1,1)为中心,第二个位置(绿色)将以(1,2)为中心。注意黄色区域是如何重叠的,现在需要从内存中重新加载这些值。

meanfilter http://luka.s3.amazonaws.com/meanfilter.png

我的具体问题是3D平均滤波器,因此重叠更大(3D ^ 3 ^ 3-3 ^ 2 = 18 vs 2D ^ 3 = 2-3 = 6)。

我确信这是一个常见的问题......有没有人知道如何有效地实现这样的算法来消除冗余内存查找,或者利用现代架构上CPU缓存的空间和时间局部性(例如双向关联缓存)?

我在3D中的具体问题只取最近的6个邻居(不是对角线)的平均值,并在C中实现如下:

for( i = 0; i <= maxi; i++ ) {
    for( j = 0; j <= maxj; j++ ) {
        for( k = 0; k <= maxk; k++ ) {
            filteredData[ i ][ j ][ k ] = 
            ONE_SIXTH *
            ( 
             data[ i + 1 ][ j     ][ k     ] +
             data[ i - 1 ][ j     ][ k     ] +
             data[ i     ][ j + 1 ][ k     ] +
             data[ i     ][ j - 1 ][ k     ] +
             data[ i     ][ j     ][ k + 1 ] +
             data[ i     ][ j     ][ k - 1 ]
            );
        }
    }
}

2 个答案:

答案 0 :(得分:2)

您正在做的事情称为 Convolution 。您使用相同维数的较小内核对多维数据进行卷积。这是一项非常常见的任务,并且有很多库。

快速解决方案(depending on the kernel size)用于计算频域中的卷积。您计算数据和内核的(多维)FFT,将它们相乘,并计算逆FFT。你会发现优化的库可以做到这一点,例如。对于Python,有scipy.ndimage.filters.convolvescipy.signal.fftconvolve

平铺是一种优化低级内存访问的常用图像处理技术。您可以分配适合CPU缓存的方块(或多维数据集)。当您访问相邻像素时,它们将在大多数时间内在内存中靠近。但是,在整个数组上循环会有点棘手。

为了进一步阅读,我推荐了论文Why Modern CPUs Are Starving and What Can Be Done about It,它提到了这种记忆阻塞技术,并指出了实现它的数值库。

最后还有积分图像,它允许您calculate the average任意矩形/长方体,只需极少量的内存访问。

答案 1 :(得分:0)

对于2D均值滤波器的情况,我会保留列总数,然后可以重复使用,以便每次迭代只计算一个新的列总数,然后对列总数求和以得到平均值。例如。对于3x3的意思:

for (i = 1; i < M - 1; ++i)
{
    // init first two column sums
    col0 = a[i - 1][0] + a[i][0] + a[i + 1][0];
    col1 = a[i - 1][1] + a[i][1] + a[i + 1][1];
    for (j = 1; j < N - 1; ++j)
    {
        // calc new col sum
        col2 = a[i - 1][j + 1] + a[i][j + 1] + a[i + 1][j + 1];
        // calc new mean
        mean[i][j] = (col0 + col1 + col2) / 9;
        // shuffle col sums
        col0 = col1;
        col1 = col2;
    }
}

这导致每个点只有3个负载,而不是天真情况下的9个负载,但仍然不是最佳。

您可以通过每次迭代处理两行并保持行i和i + 1的重叠列和来进一步优化。