【问题标题】:CUDA - median filter implementation does not produce desired resultsCUDA - 中值滤波器实施不会产生预期的结果
【发布时间】:2014-04-14 11:38:33
【问题描述】:

我一直在尝试实现 Wiki 文章中介绍的中值滤波器算法:http://en.wikipedia.org/wiki/Median_filter#2D_median_filter_pseudo_code

据我所知,我知道我所实施的是正确的。但是,当我查看结果时,我似乎无法获得与 OpenCV 中 median blur 函数产生的输出相似的输出。目前,我并不关心通过使用共享内存或纹理内存来加速我的代码。我只想让事情先发挥作用。我的输入图像的大小是1024 x 256 像素。

我做错了什么?我的代码中是否存在线程泄漏?我知道我应该使用共享内存来防止全局读取,因为目前我正在从全局内存中读取很多数据。

http://snag.gy/OkXzP.jpg -- 第一张图是输入,第二张图是我的算法结果,第三张图是openCVmedianblur函数结果。理想情况下,我希望我的算法输出与medianblur 函数相同的结果。

这是我写的所有代码:

内核实现

#include "cuda.h"
#include "cuda_runtime_api.h"
#include "device_launch_parameters.h"
#include "device_functions.h"
#include "highgui.h"
//#include "opencv2/core/imgproc.hpp"
//#include "opencv2/core/gpu.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

// includes, project

#include "cufft.h"
#include "cublas_v2.h"
#include "CUDA_wrapper.h"   // contains only func_prototype for function take_input()


// define the threads and grids for CUDA
#define BLOCK_ROWS 32
#define BLOCK_COLS 16

// define kernel dimensions
#define KERNEL_DIMENSION 3
#define MEDIAN_DIMENSION 3
#define MEDIAN_LENGTH 9

// this is the error checking part for CUDA
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
   if (code != cudaSuccess) 
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) exit(code);
   }
}

// create two vars for the rows and cols of the image
    int d_imgRows;
    int d_imgCols; 

__global__ void FilterKernel (unsigned short *d_input_img, unsigned short *d_output_img, int d_iRows, int d_iCols)

{  
    unsigned short window[BLOCK_ROWS*BLOCK_COLS][KERNEL_DIMENSION*KERNEL_DIMENSION];

    unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
    unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;   

    unsigned int tid = threadIdx.y*blockDim.y+threadIdx.x;

    if(x>d_iCols || y>d_iRows)
        return;

    window[tid][0]= (y==0||x==0) ? 0.0f : d_input_img[(y-1)*d_iCols+(x-1)];
    window[tid][1]= (y==0) ? 0.0f : d_input_img[(y-1)*d_iCols+x]; 
    window[tid][2]= (y==0||x==d_iCols-1) ? 0.0f : d_input_img[(y-1)*d_iCols+(x+1)];
    window[tid][3]= (x==0) ? 0.0f : d_input_img[y*d_iCols+(x-1)];
    window[tid][4]= d_input_img[y*d_iCols+x];
    window[tid][5]= (x==d_iCols-1) ? 0.0f : d_input_img[y*d_iCols+(x+1)];
    window[tid][6]= (y==d_iRows-1||x==0) ? 0.0f : d_input_img[(y+1)*d_iCols+(x-1)];
    window[tid][7]= (y==d_iRows-1) ? 0.0f : d_input_img[(y+1)*d_iCols+x];
    window[tid][8]= (y==d_iRows-1||x==d_iCols-1) ? 0.0f : d_input_img[(y+1)*d_iCols+(x+1)];

   __syncthreads();

    // Order elements 
    for (unsigned int j=0; j<9; ++j)
    {
        // Find position of minimum element
        int min=j;
        for (unsigned int l=j+1; l<9; ++l)
            if (window[tid][l] < window[tid][min])
                min=l;

        // Put found minimum element in its place
        const unsigned char temp=window[tid][j];
        window[tid][j]=window[tid][min];
        window[tid][min]=temp;

        __syncthreads();
    }

    d_output_img[y*d_iCols + x] = (window[tid][4]);

}

void take_input(const cv::Mat& input, const cv::Mat& output)
{

    unsigned short *device_input; 
    unsigned short *device_output;

    size_t d_ipimgSize = input.step * input.rows;
    size_t d_opimgSize = output.step * output.rows;

    gpuErrchk( cudaMalloc( (void**) &device_input, d_ipimgSize) ); 
    gpuErrchk( cudaMalloc( (void**) &device_output, d_opimgSize) ); 

    gpuErrchk( cudaMemcpy(device_input, input.data, d_ipimgSize, cudaMemcpyHostToDevice) );

    dim3 Threads(BLOCK_ROWS, BLOCK_COLS);  // 512 threads per block
    dim3 Blocks((input.cols + Threads.x - 1)/Threads.x, (input.rows + Threads.y - 1)/Threads.y);    

    //int check = (input.cols + Threads.x - 1)/Threads.x;
    //printf( "blockx %d", check);

    FilterKernel <<< Blocks, Threads >>> (device_input, device_output, input.rows, input.cols);

    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk( cudaMemcpy(output.data, device_output, d_opimgSize, cudaMemcpyDeviceToHost) );

    //printf( "num_rows_cuda %d", num_rows);
    //printf("\n");

    gpuErrchk(cudaFree(device_input));
    gpuErrchk(cudaFree(device_output));

}

主要功能

#pragma once
#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/gpu/gpu.hpp>

#include <CUDA_wrapper.h>

using std::cout;
using std::endl;

int main()
{   

    //Read the image from harddisk, into a cv::Mat
    //IplImage *img=cvLoadImage("image.jpg");
    //cv::Mat input(img);
    cv::Mat input = cv::imread("C:/Users/OCT/Documents/Visual Studio 2008/Projects/MedianFilter/MedianFilter/pic1.bmp",CV_LOAD_IMAGE_GRAYSCALE);

    //IplImage* input = cvLoadImage("G:/Research/CUDA/Trials/OCTFilter/Debug/pic1.bmp");
    if(input.empty())
    {
        cout<<"Image Not Found"<<endl;
        getchar();
        return -1;
    }

    cv::Mat output(input.rows,input.cols,CV_8UC1);

    // store the different details of the input image like img_data, rows, cols in variables 
    int Rows = input.rows;
    int Cols = input.cols;
    unsigned char* Data = input.data;

    cout<<"image rows "<<Rows<<endl;
    cout<<"image cols "<<Cols<<endl;
    cout<<"\n"<<endl;
    cout<<"data "<<(int)Data<<endl;
    cv::waitKey(0);

    // call the device function to take the image as input
    take_input(input, output);

    cv::Mat dest; 

    medianBlur ( input, dest, 3 );

    //Show the input and output
    cv::imshow("Input",input);
    cv::imshow("Output",output);
    cv::imshow("Median blur",dest);

    //Wait for key press
    cv::waitKey();
}

【问题讨论】:

  • 我认为您确实将threadIdx.x 误认为是行,而将threadIdx.y 误认为是列。在你的内核设置中你写了dim3 Threads(BLOCK_ROWS, BLOCK_COLS);。因此threadIdx.x 将用于行,threadIdx.y 将用于列!在内核中你以错误的方式使用它 - if(x&gt;d_iCols || y&gt;d_iRows)!你对tid 的计算对我来说也很奇怪。 window 分配有window[BLOCK_ROWS*BLOCK_COLS][],但tid 计算为tid = threadIdx.y*blockDim.y+threadIdx.x;。在我的意见中,tid 会以错误的方式访问window!在你的内核之后添加cudaGetLastError()
  • 另外,您似乎对数据类型感到困惑。您的图像似乎由unsigned char(8 位)像素组成,但您将它们复制到设备上的unsigned short(16 位)像素。如果您的像素是unsigned char,为什么内核会使用unsigned short 做所有事情?
  • @hubs 是的,我怀疑我已经将行与列互换,因此我将内核越界检查更改为if(x&gt;d_iRows || y&gt;d_iCols)。但是,我不明白我应该如何索引tid
  • @RobertCrovella 好的,我更正了数据类型 - 将每个 unsigned short 更改为 unsigned char。但是,我仍然不确定如何索引tid
  • 我不清楚您是否打算 x 代表行或列。当您说 1024x256 时,我假设您指的是典型的图像描述,即宽度 x 高度,即。科尔斯克斯行。如果 x 代表行(不确定?)那么 block-unique tid 可能是 tid = threadIdx.x *blockDim.y + threadIdx.y 我不确定我会这样写代码,但你应该能够得到它工作。

标签: c++ algorithm opencv cuda


【解决方案1】:

我相信您的“内核实现”文件中存在各种错误和不必要的复杂性。

您可能会在以下方面获得更好的运气:

$ cat t376.cu
#include <stdlib.h>
#include <stdio.h>

#define DCOLS 1024
#define DROWS 256

typedef struct {
  size_t step;
  size_t rows;
  size_t cols;
  unsigned char *data;
} mat;


// define the threads and grids for CUDA
#define BLOCK_ROWS 32
#define BLOCK_COLS 16

// define kernel dimensions
#define MEDIAN_LENGTH 9

// this is the error checking part for CUDA
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
   if (code != cudaSuccess)
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) exit(code);
   }
}


__global__ void FilterKernel (unsigned char *d_input_img, unsigned char *d_output_img, int d_iRows, int d_iCols)

{

    unsigned int row = blockIdx.y*blockDim.y + threadIdx.y;
    unsigned int col = blockIdx.x*blockDim.x + threadIdx.x;
    unsigned char window[MEDIAN_LENGTH];

    if(col>=d_iCols || row>=d_iRows)
        return;

    window[0]= (row==0||col==0) ? 0 :                 d_input_img[(row-1)*d_iCols+(col-1)];
    window[1]= (row==0) ? 0 :                         d_input_img[(row-1)*d_iCols+col];
    window[2]= (row==0||col==d_iCols-1) ? 0 :         d_input_img[(row-1)*d_iCols+(col+1)];
    window[3]= (col==0) ? 0 :                         d_input_img[row*d_iCols+(col-1)];
    window[4]=                                        d_input_img[row*d_iCols+col];
    window[5]= (col==d_iCols-1) ? 0 :                 d_input_img[row*d_iCols+(col+1)];
    window[6]= (row==d_iRows-1||col==0) ? 0 :         d_input_img[(row+1)*d_iCols+(col-1)];
    window[7]= (row==d_iRows-1) ? 0 :                 d_input_img[(row+1)*d_iCols+col];
    window[8]= (row==d_iRows-1||col==d_iCols-1) ? 0 : d_input_img[(row+1)*d_iCols+(col+1)];

    // Order elements
    for (unsigned int j=0; j<5; ++j)
    {
        // Find position of minimum element
        unsigned char temp = window[j];
        unsigned int  idx  = j;
        for (unsigned int l=j+1; l<9; ++l)
            if (window[l] < temp){ idx=l; temp = window[l];}
        // Put found minimum element in its place
        window[idx] = window[j];
        window[j] = temp;
    }

    d_output_img[row*d_iCols + col] = (window[4]);

}

void take_input(const mat& input, const mat& output)
{

    unsigned char *device_input;
    unsigned char *device_output;

    size_t d_ipimgSize = input.step * input.rows;
    size_t d_opimgSize = output.step * output.rows;

    gpuErrchk( cudaMalloc( (void**) &device_input, d_ipimgSize) );
    gpuErrchk( cudaMalloc( (void**) &device_output, d_opimgSize) );

    gpuErrchk( cudaMemcpy(device_input, input.data, d_ipimgSize, cudaMemcpyHostToDevice) );

    dim3 Threads(BLOCK_COLS, BLOCK_ROWS);  // 512 threads per block
    dim3 Blocks((input.cols + Threads.x - 1)/Threads.x, (input.rows + Threads.y - 1)/Threads.y);

    //int check = (input.cols + Threads.x - 1)/Threads.x;
    //printf( "blockx %d", check);

    FilterKernel <<< Blocks, Threads >>> (device_input, device_output, input.rows, input.cols);
    gpuErrchk(cudaDeviceSynchronize());
    gpuErrchk(cudaGetLastError());

    gpuErrchk( cudaMemcpy(output.data, device_output, d_opimgSize, cudaMemcpyDeviceToHost) );

    //printf( "num_rows_cuda %d", num_rows);
    //printf("\n");

    gpuErrchk(cudaFree(device_input));
    gpuErrchk(cudaFree(device_output));

}

int main(){
  mat input_im, output_im;
  input_im.rows  = DROWS;
  input_im.cols  = DCOLS;
  input_im.step  = input_im.cols;
  input_im.data  = (unsigned char *)malloc(input_im.step*input_im.rows);
  output_im.rows = DROWS;
  output_im.cols = DCOLS;
  output_im.step = input_im.cols;
  output_im.data = (unsigned char *)malloc(output_im.step*output_im.rows);

  for (int i = 0; i < DCOLS*DROWS; i++) {
    output_im.data[i] = 0;
    input_im.data[i] = 0;
    int temp = (i%DCOLS);
    if (temp == 5) input_im.data[i] = 20;
    if ((temp > 5) && (temp < 15)) input_im.data[i] = 40;
    if (temp == 15) input_im.data[i] = 20;
    }

  take_input(input_im, output_im);
  for (int i = 2*DCOLS; i < DCOLS*(DROWS-2); i++)
    if (input_im.data[i] != output_im.data[i]) {printf("mismatch at %d, input: %d, output: %d\n", i, (int)input_im.data[i], (int)output_im.data[i]); return 1;}
  printf("Success\n");
  return 0;
}


$ nvcc -o t376 t376.cu
$ ./t376
Success
$

一些注意事项:

  1. 我已经针对我在代码中注入的简单案例对此进行了测试(不使用 OpenCV)。
  2. 您对window 的使用过于复杂。请注意,按照您的方式,每个线程 将实例化其自己的 window 本地副本,独立于其他线程,并且对其他线程不可见。 (也许您打算在这里使用共享内存?但我离题了。)
  3. 您的排序程序被破坏了。我将其修改为我认为可行的版本。
  4. 将像素数据类型全部替换为unsigned char
  5. xy 令人困惑,因此我将它们更改为 rowcol,这似乎不那么令人困惑了。
  6. 略微改进了内核错误检查
  7. 有很多方法可以对此进行优化。但是,您的明智目标是首先让某些东西正常工作。所以我不会花太多时间在优化上,除了指出共享内存的两个通用区域以重用window 数据,以及改进的排序例程。
  8. 您需要针对 openCV 进行相应修改
  9. 请注意,如果将其修改为 DROWS = 1024 和 DCOLS = 256,它仍然有效。

编辑:在阅读了您的 cmets 之后,事情仍然无法正常工作,看来您的 OpenCV 代码设置不正确,无法同时提供单通道 8 位灰度 (CV_8UC1) 图像 你的take_input函数。问题出在这一行:

cv::Mat input = cv::imread("C:/Users/OCT/Documents/Visual Studio 2008/Projects/MedianFilter/MedianFilter/pic1.bmp",1);

传递给imread1 参数指定RGB 图像加载。参考imread documentation

Now we call the imread function which loads the image name specified by the first argument (argv[1]). The second argument specifies the format in what we want the image. This may be:

CV_LOAD_IMAGE_UNCHANGED (<0) loads the image as is (including the alpha channel if present)
CV_LOAD_IMAGE_GRAYSCALE ( 0) loads the image as an intensity one
CV_LOAD_IMAGE_COLOR (>0) loads the image in the RGB format

如果您在此处指定CV_LOAD_IMAGE_GRAYSCALE 而不是1,您的运气可能会更好。

否则,您应该研究如何加载图像,使其最终成为 CV_8UC1 类型。

但如果你将 input 原样传递给 take_input,它肯定行不通。

【讨论】:

  • 您的col x row 解释是正确的,但是我提供的图像尺寸是相反的。正确的尺寸是cols = 256rows = 1024(可以从屏幕截图中验证)。因此,我已经更正了我的内核实现以及所有数据类型,但是这不起作用。所以,我尝试了你的实现,即使这样也行不通。它确实可以编译,但这不是安慰。
  • 好的,我已经编辑了代码以包含一个未使用 OpenCV 的经过测试且完整的工作示例。无论您设置 cols = 256 还是 cols = 1024,它都有效。您只需将其重新集成到您的 OpenCV 线束中。否则,您需要比“它不起作用”更具描述性。
  • 它确实适用于您创建的简单图像。但是,当我在 openCV 中输入我想使用的图像时,结果却提供了相同的输出。我尝试输入另一个图像 512x512,这是输出:snag.gy/AzXMZ.jpg。该死,我真的想不通——我的openCV代码太简单了,我只需要输入/输出并显示它。
  • 知道了!我意识到我正在使用 openCV 加载彩色图像。将输入转换为灰度并且有效!
  • 看来你的 OpenCV 坏了。编辑了我的答案。
【解决方案2】:

在 CUDA 6.0 中,NPP 库现在包括针对所有数据类型和像素格式的中值过滤器的实现。因此,如果您只想要一个功能性中值过滤例程,您可以调用它。如果您需要帮助调试内核,请查看所有其他答案...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多