【问题标题】:efficient 2d mean filter implementation that minimises redundant memory loads?有效的二维均值滤波器实现,最大限度地减少冗余内存负载?
【发布时间】:2019-08-10 21:13:58
【问题描述】:

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

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

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

我确定这是一个常见问题...有谁知道如何有效地实现诸如此类的算法以消除冗余内存查找,或利用现代架构上 CPU 缓存的空间和时间局部性(例如2路关联缓存)?

我在 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 ]
            );
        }
    }
}

【问题讨论】:

  • +1 提出一个清晰且说明良好的问题
  • 这是一个包含整数或浮点值的矩阵吗?对于整数,计算通常是精确的,所以当从 (1,1) 转移到 (1,2) 时,您从 (1,1) 处计算的结果开始,减去红色列的总和,然后加上绿色列。对于浮点值,这并不准确,错误会开始累积。

标签: c performance optimization cpu


【解决方案1】:

您所做的称为卷积。您将多维数据与具有相同维数的较小内核进行卷积。这是一项非常常见的任务,并且有很多库。

一个快速的解决方案(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,其中提到了这种内存阻塞技术,并指出了实现它的数值库。

最后还有 Integral Image,它让您只需很少的内存访问即可calculate the average 任意矩形/长方体。

【讨论】:

    【解决方案2】:

    对于 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 行的重叠列总和来进一步优化。

    【讨论】:

    • 这种算法没有“最佳实践”吗?我原以为这是教科书式的案例。
    • 可能不是 - 大多数过滤器没有均值过滤器的冗余,加载/存储的相对成本与算术操作与可用寄存器数量与缓存大小等相比,意味着“最佳" 方法可能在某种程度上依赖于架构。
    • 我个人经常遇到这个问题。例如使用 Jacobi 方法求解拉普拉斯方程,使用绝对差之和或平方差之和进行计算机视觉模板匹配。基本上任何在内核上执行某种缩减的算法都需要这个。对我来说这不是“教科书”的东西似乎很奇怪。无论如何感谢您的代码!
    • @codenoob:关于这类问题有一个完整的研究领域(实际上是一个更普遍的问题领域,其中包括这个)——主要是人们在寻找编译器重组循环以优化数据访问的方法和算术运算的数量等。
    猜你喜欢
    • 1970-01-01
    • 2018-09-25
    • 2018-11-04
    • 2013-07-08
    • 1970-01-01
    • 2014-08-28
    • 1970-01-01
    • 2019-05-03
    • 1970-01-01
    相关资源
    最近更新 更多