Cuda Image平均滤镜

时间:2013-01-15 09:05:51

标签: image image-processing matrix cuda gpu

平均滤波器是线性类的窗口滤波器,用于平滑信号(图像)。过滤器用作低通过滤器。滤波器背后的基本思想是信号(图像)的任何元素在其邻域中取平均值。


如果我们有m x n矩阵并且我们想要对其应用大小为k的平均过滤器,那么对于矩阵p:(i,j)中的每个点,该点的值将为广场上所有点的平均值

Square Kernel

此图适用于尺寸为2的平方内核,黄色框是要平均的像素,所有网格都是相邻像素的平方,像素的新值将是平均值他们。

问题是这个算法很慢,特别是在大图像上,所以我考虑使用GPGPU

现在的问题是,如果有可能,如何在cuda中实施?

3 个答案:

答案 0 :(得分:19)

这是embarrassingly parallel图像处理问题的经典案例,可以很容易地映射到CUDA框架。平均滤波器在图像处理域中被称为Box Filter

最简单的方法是将CUDA纹理用于过滤过程,因为纹理可以非常轻松地处理边界条件。

假设您在主机上分配了源指针和目标指针。程序就是这样的。

  1. 分配足够大的内存以将源图像和目标图像保存在设备上。
  2. 将源图像从主机复制到设备。
  3. 将源图像设备指针绑定到纹理。
  4. 指定适当的块大小和足够大的网格以覆盖图像的每个像素。
  5. 使用指定的网格和块大小启动过滤内核。
  6. 将结果复制回主机。
  7. 取消绑定纹理
  8. 免费设备指针。
  9. Box Filter的示例实现

    <强>内核

    texture<unsigned char, cudaTextureType2D> tex8u;
    
    //Box Filter Kernel For Gray scale image with 8bit depth
    __global__ void box_filter_kernel_8u_c1(unsigned char* output,const int width, const int height, const size_t pitch, const int fWidth, const int fHeight)
    {
        int xIndex = blockIdx.x * blockDim.x + threadIdx.x;
        int yIndex = blockIdx.y * blockDim.y + threadIdx.y;
    
        const int filter_offset_x = fWidth/2;
        const int filter_offset_y = fHeight/2;
    
        float output_value = 0.0f;
    
        //Make sure the current thread is inside the image bounds
        if(xIndex<width && yIndex<height)
        {
            //Sum the window pixels
            for(int i= -filter_offset_x; i<=filter_offset_x; i++)
            {
                for(int j=-filter_offset_y; j<=filter_offset_y; j++)
                {
                    //No need to worry about Out-Of-Range access. tex2D automatically handles it.
                    output_value += tex2D(tex8u,xIndex + i,yIndex + j);
                }
            }
    
            //Average the output value
            output_value /= (fWidth * fHeight);
    
            //Write the averaged value to the output.
            //Transform 2D index to 1D index, because image is actually in linear memory
            int index = yIndex * pitch + xIndex;
    
            output[index] = static_cast<unsigned char>(output_value);
        }
    }
    

    包装功能:

    void box_filter_8u_c1(unsigned char* CPUinput, unsigned char* CPUoutput, const int width, const int height, const int widthStep, const int filterWidth, const int filterHeight)
    {
    
        /*
         * 2D memory is allocated as strided linear memory on GPU.
         * The terminologies "Pitch", "WidthStep", and "Stride" are exactly the same thing.
         * It is the size of a row in bytes.
         * It is not necessary that width = widthStep.
         * Total bytes occupied by the image = widthStep x height.
         */
    
        //Declare GPU pointer
        unsigned char *GPU_input, *GPU_output;
    
        //Allocate 2D memory on GPU. Also known as Pitch Linear Memory
        size_t gpu_image_pitch = 0;
        cudaMallocPitch<unsigned char>(&GPU_input,&gpu_image_pitch,width,height);
        cudaMallocPitch<unsigned char>(&GPU_output,&gpu_image_pitch,width,height);
    
        //Copy data from host to device.
        cudaMemcpy2D(GPU_input,gpu_image_pitch,CPUinput,widthStep,width,height,cudaMemcpyHostToDevice);
    
        //Bind the image to the texture. Now the kernel will read the input image through the texture cache.
        //Use tex2D function to read the image
        cudaBindTexture2D(NULL,tex8u,GPU_input,width,height,gpu_image_pitch);
    
        /*
         * Set the behavior of tex2D for out-of-range image reads.
         * cudaAddressModeBorder = Read Zero
         * cudaAddressModeClamp  = Read the nearest border pixel
         * We can skip this step. The default mode is Clamp.
         */
        tex8u.addressMode[0] = tex8u.addressMode[1] = cudaAddressModeBorder;
    
        /*
         * Specify a block size. 256 threads per block are sufficient.
         * It can be increased, but keep in mind the limitations of the GPU.
         * Older GPUs allow maximum 512 threads per block.
         * Current GPUs allow maximum 1024 threads per block
         */
    
        dim3 block_size(16,16);
    
        /*
         * Specify the grid size for the GPU.
         * Make it generalized, so that the size of grid changes according to the input image size
         */
    
        dim3 grid_size;
        grid_size.x = (width + block_size.x - 1)/block_size.x;  /*< Greater than or equal to image width */
        grid_size.y = (height + block_size.y - 1)/block_size.y; /*< Greater than or equal to image height */
    
        //Launch the kernel
        box_filter_kernel_8u_c1<<<grid_size,block_size>>>(GPU_output,width,height,gpu_image_pitch,filterWidth,filterHeight);
    
        //Copy the results back to CPU
        cudaMemcpy2D(CPUoutput,widthStep,GPU_output,gpu_image_pitch,width,height,cudaMemcpyDeviceToHost);
    
        //Release the texture
        cudaUnbindTexture(tex8u);
    
        //Free GPU memory
        cudaFree(GPU_input);
        cudaFree(GPU_output);
    }
    

    好消息是您不必自己实施过滤器。 CUDA工具包附带了由NVIDIA制作的名为NVIDIA Performance Primitives aka NPP的免费信号和图像处理库。 NPP利用支持CUDA的GPU加速处理。平均滤波器已在NPP中实现。当前版本的NPP(5.0)支持8位,1通道和4通道图像。 功能是:

    • nppiFilterBox_8u_C1R用于1频道图片。
    • nppiFilterBox_8u_C4R用于4频道图片。

答案 1 :(得分:4)

一些基本想法/步骤:

  1. 将图像数据从CPU复制到GPU
  2. 调用内核以构建每行的平均值(水平)并将其存储在共享内存中。
  3. 调用内核以构建每列的平均值(垂直)并将其存储在全局内存中。
  4. 将数据复制回CPU内存。
  5. 您应该可以使用2D内存和多维内核调用轻松扩展它。

答案 2 :(得分:3)

如果过滤器的大小正常而且不大,那么平均过滤器是使用CUDA实现的一个非常好的案例。您可以使用方块设置它,并且块的每个线程负责通过对其邻居求和和求平均来计算一个像素的值。

如果将图像存储在全局存储器中,则可以轻松编程。一种可能的优化是将图像块加载到块的共享内存中。使用幻像元素(以便在查找相邻像素时不会超过共享块的尺寸),您可以计算块内像素的平均值。

唯一认为你必须要小心的是如何在最后完成“拼接”,因为共享内存块将重叠(因为额外的“填充”像素)并且你不想两次计算它们的值。