处理CUDA中的边界条件/ Halo区域

时间:2011-04-19 10:50:13

标签: image-processing cuda convolution

我正在使用CUDA进行图像处理,我对像素处理有疑问。

在应用m x m卷积滤镜时,经常使用图像的边界像素做什么?

3 x 3卷积内核中,忽略图像的1像素边界更容易处理,尤其是在使用共享内存改进代码时。实际上,在这种情况下,不需要检查给定像素是否具有所有可用的邻域(即,在坐标(0, 0)处的像素没有离开,左上,上邻居)。但是,删除原始图像的1像素边界可能会产生部分结果。

与此相反,我想处理所有图像中的像素,同样在使用共享内存改进时,例如,加载16 x 16像素,但计算内在14 x 14。同样在这种情况下,忽略边界像素会产生更清晰的代码。

在这种情况下通常会做什么?

是否有人通常使用我的方法忽略边界像素?

当然,我知道答案取决于问题的类型,即以像素方式添加两个图像没有这个问题。

提前致谢。

3 个答案:

答案 0 :(得分:10)

处理边框效果的一种常用方法是用额外的行填充原始图像。根据您的过滤器大小的列。填充值的一些常见选择是:

  • 常数(例如零)
  • 根据需要多次复制第一行和最后一行/列
  • 反射边界处的图像(例如列[-1] =列[1],列[-2] =列[2])
  • 换行图像值(例如列[-1] =列[宽度-1],列[-2] =列[宽度-2])

答案 1 :(得分:5)

tl;博士:这取决于你想要解决的问题 - 没有适用于所有问题的解决方案。事实上,从数学上讲,我怀疑可能根本就没有“解决方案”,因为我认为这是一个你被迫处理的不适合的问题。

(为我不顾一切地滥用数学提前道歉)

为了演示让我们考虑假设所有像素组件和内核值都为正的情况。为了了解其中一些答案如何导致我们误入歧途,让我们进一步考虑一个简单的平均(“盒子”)过滤器。如果我们将图像边界外的值设置为零,那么这将明显降低边界的ceil(n / 2)(曼哈顿距离)内每个像素的平均值。因此,您将在滤镜图像上获得“暗”边框(假设单个强度分量或RGB色彩空间 - 您的结果将因色彩空间而异!)。请注意,如果我们将边界外的值设置为任意常量,则可以进行类似的参数 - 平均值将倾向于该常量。如果典型图像的边缘趋向于0,则常数为零可能是合适的。如果我们考虑像高斯这样更复杂的滤波器内核,情况也是如此,但问题会不那么明显,因为内核值会随着距离中心的距离而迅速减小。

现在假设我们选择重复边缘值,而不是使用常量。这与在图像周围创建边框并复制行,列或角足够次以确保过滤器保持在新图像“内部”相同。您还可以将其视为夹紧/饱和样品坐标。这对我们简单的盒式滤镜有问题,因为它过分强调了边缘像素的值。一组边缘像素将出现不止一次,但它们都会获得相同的权重w=(1/(n*n))。 假设我们采样值为K 3的边缘像素。这意味着它对平均值的贡献是:

K*w + K*w + K*w  = K*3*w

有效地说,一个像素的平均权重更高。请注意,由于这是一个平均过滤器,因此权重是内核的常量。然而,这个论点适用于权重也因位置而异的内核(再次:考虑高斯内核......)。

假设我们包装或反映采样坐标,以便我们仍然使用图像边界内的值。与使用常数相比,这具有一些有价值的优点,但也不一定是“正确的”。例如,你拍摄了多少张照片,上面边框的物体与底部的物体相似?除非你拍摄镜面光滑的湖泊,否则我怀疑这是真的。如果你正在拍摄岩石的照片用作游戏中的纹理包装或反射可能是合适的。我确信在这里有一些重要的观点,关于包装和反射如何可能减少使用傅立叶变换产生的任何伪影。然而,这又回到了同样的想法:你有一个周期性的信号,你不希望通过引入虚假的新频率或过高估计现有频率的幅度来扭曲。

那么如果你在蓝天下过滤鲜红色岩石的照片,你会怎么做?很显然,你不想在蓝天上添加橙色的阴霾,在红色的岩石上添加蓝色的模糊。反射样本坐标是有效的,因为我们期望在反射坐标处找到相似的颜色......除非为了参数,我们想象滤波器内核太大以至于反射坐标会超出地平线。

让我们回到盒子过滤器示例。使用此过滤器的另一种方法是停止考虑使用静态内核并回想一下此内核的用途。平均/盒滤波器被设计为对像素分量求和,然后除以求和的像素数。这个想法是这可以消除噪音。如果我们愿意在抑制边界附近的噪声方面降低效率,我们可以简单地将较少的像素相加并除以相应较小的数字。这可以扩展到具有类似于我将要调用的“标准化”术语的过滤器 - 与过滤器的面积或体积相关的术语。对于“区域”术语,您可以计算边界内的内核权重数,并忽略那些不是的权重。然后使用此计数作为“区域”(可能涉及额外的乘法)。对于音量(再次:假设正权重!),只需求核心权重。这个想法对于衍生滤波器来说可能很糟糕,因为与噪声像素竞争的像素较少,而差分对噪声非常敏感。此外,一些过滤器是通过数值优化和/或经验数据而不是从ab-initio / analytic方法得出的,因此可能缺乏一个明显的“标准化”因子。

答案 2 :(得分:1)

你的问题有点宽泛,我认为它混合了两个问题:

  1. 处理边界条件;
  2. 处理晕区
  3. 遇到第一个问题(边界条件),例如,计算图像与3 x 3内核之间的卷积时。当卷积窗口越过边界时,会出现将图像扩展到其边界之外的问题。

    遇到第二个问题(晕区),例如,在共享内存中加载16 x 16磁贴时,必须处理内部14 x 14磁贴以进行计算二阶导数。

    对于第二个问题,我认为一个有用的问题如下:Analyzing memory access coalescing of my CUDA kernel

    关于信号在其边界之外的扩展,在这种情况下,由于提供了不同的寻址模式,纹理存储器提供了一个有用的工具,请参阅The different addressing modes of CUDA textures

    下面,我提供了一个示例,说明如何使用纹理存储器使用周期性边界条件实现中值滤波器。

    #include <stdio.h>
    
    #include "TimingGPU.cuh"
    #include "Utilities.cuh"
    
    texture<float, 1, cudaReadModeElementType> signal_texture;
    
    #define BLOCKSIZE 32
    
    /*************************************************/
    /* KERNEL FUNCTION FOR MEDIAN FILTER CALCULATION */
    /*************************************************/
    __global__ void median_filter_periodic_boundary(float * __restrict__ d_vec, const unsigned int N){
    
        unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;
    
        if (tid < N) {
    
            float signal_center = tex1D(signal_texture, tid - 0);
            float signal_before = tex1D(signal_texture, tid - 1);
            float signal_after  = tex1D(signal_texture, tid + 1);
    
            printf("%i %f %f %f\n", tid, signal_before, signal_center, signal_after);
    
            d_vec[tid] = (signal_center + signal_before + signal_after) / 3.f;
    
        }
    }
    
    
    /********/
    /* MAIN */
    /********/
    int main() {
    
        const int N = 10;
    
        // --- Input host array declaration and initialization
        float *h_arr = (float *)malloc(N * sizeof(float));
        for (int i = 0; i < N; i++) h_arr[i] = (float)i;
    
        // --- Output host and device array vectors
        float *h_vec = (float *)malloc(N * sizeof(float));
        float *d_vec;   gpuErrchk(cudaMalloc(&d_vec, N * sizeof(float)));
    
        // --- CUDA array declaration and texture memory binding; CUDA array initialization
        cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
        //Alternatively
        //cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat);
    
        cudaArray *d_arr;   gpuErrchk(cudaMallocArray(&d_arr, &channelDesc, N, 1));
        gpuErrchk(cudaMemcpyToArray(d_arr, 0, 0, h_arr, N * sizeof(float), cudaMemcpyHostToDevice));
    
        cudaBindTextureToArray(signal_texture, d_arr); 
        signal_texture.normalized = false; 
        signal_texture.addressMode[0] = cudaAddressModeWrap;
    
        // --- Kernel execution
        median_filter_periodic_boundary<<<iDivUp(N, BLOCKSIZE), BLOCKSIZE>>>(d_vec, N);
        gpuErrchk(cudaPeekAtLastError());
        gpuErrchk(cudaDeviceSynchronize());
    
        gpuErrchk(cudaMemcpy(h_vec, d_vec, N * sizeof(float), cudaMemcpyDeviceToHost));
    
        for (int i=0; i<N; i++) printf("h_vec[%i] = %f\n", i, h_vec[i]);
    
        printf("Test finished\n");
    
        return 0;
    }