2013-01-15 91 views
13

Bộ lọc trung bình là bộ lọc cửa sổ của lớp tuyến tính, làm mịn tín hiệu (hình ảnh). Bộ lọc hoạt động như một bộ lọc thấp. Ý tưởng cơ bản đằng sau bộ lọc là cho bất kỳ phần tử nào của tín hiệu (hình ảnh) lấy trung bình trên vùng lân cận của nó.Bộ lọc trung bình Hình ảnh Cuda


Nếu chúng ta đã là một ma trận m x n và chúng tôi muốn áp dụng bộ lọc trung bình với kích thước k vào nó, sau đó cho mỗi điểm trong ma trận p:(i,j) giá trị của điểm sẽ là mức trung bình của tất cả các điểm trong hình vuông

Square Kernel

con số này là dành cho kernel Quảng trường lọc với kích thước 2, rằng hộp màu vàng là pixel được tính trung bình, và tất cả các lưới là bình phương của pixel láng giềng, rằng giá trị mới của pixel sẽ trung bình của họ.

Vấn đề là thuật toán này rất chậm, đặc biệt trên hình ảnh lớn, vì vậy tôi đã nghĩ đến việc sử dụng GPGPU.

Câu hỏi bây giờ là, Cách thực hiện điều này trong cuda, nếu có thể?

+0

Xin chào @SamehKamal, tôi tò mò tò mò. Các mã sử dụng CUDA nhanh như thế nào so với các mã truyền thống trong kết quả của bạn? –

+2

Đây là một thời đại dài tôi không nhớ chính xác yếu tố tăng tốc cho thuật toán này nhưng hiệu suất thay đổi từ thuật toán này sang thuật toán khác từ tỷ lệ tăng tốc x7 đến x22 cho thuật toán tôi đã sử dụng. –

Trả lời

16

Đây là trường hợp cổ điển của embarrassingly parallel vấn đề xử lý hình ảnh có thể dễ dàng được ánh xạ tới khung công tác CUDA. Bộ lọc trung bình được biết là Box Filter trong các miền xử lý hình ảnh.

Cách tiếp cận dễ nhất là sử dụng kết cấu CUDA cho quy trình lọc vì điều kiện biên có thể được xử lý rất dễ dàng bằng kết cấu.

Giả sử bạn có con trỏ nguồn và đích được phân bổ trên máy chủ lưu trữ. Các thủ tục sẽ là một cái gì đó như thế này.

  1. Phân bổ bộ nhớ đủ lớn để giữ hình ảnh nguồn và đích trên thiết bị.
  2. Sao chép hình ảnh nguồn từ máy chủ sang thiết bị.
  3. Liên kết con trỏ thiết bị nguồn hình ảnh với kết cấu.
  4. Chỉ định kích thước khối thích hợp và lưới đủ lớn để bao phủ mọi pixel của hình ảnh.
  5. Khởi chạy hạt nhân lọc bằng cách sử dụng lưới và kích thước khối được chỉ định.
  6. Sao chép kết quả về máy chủ.
  7. Bỏ kết cấu
  8. Con trỏ thiết bị miễn phí.

mẫu thực hiện của hộp lọc

Kernel

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); 
    } 
} 

Wrapper Chức năng:

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); 
} 

Tin tốt là bạn không cần phải thực hiện tự lọc. Bộ công cụ CUDA đi kèm với thư viện xử lý hình ảnh và tín hiệu miễn phí có tên là NVIDIA Performance Primitives, còn gọi là NPP, do NVIDIA sản xuất. NPP sử dụng CUDA cho phép GPU tăng tốc quá trình xử lý. Bộ lọc trung bình đã được triển khai trong NPP. Phiên bản hiện tại của NPP (5.0) hỗ trợ hình ảnh 8 bit, 1 kênh và 4 kênh. Các chức năng là:

  • nppiFilterBox_8u_C1R cho hình ảnh 1 kênh.
  • nppiFilterBox_8u_C4R cho hình ảnh 4 kênh.
+0

Câu trả lời của bạn có vẻ là rất tốt, nhưng tôi không thực sự biết tất cả những gì bạn đang mô tả ở đó, vì tôi chủ yếu là lập trình trên MATLAB, và tôi có kiến ​​thức tốt về lập trình C, những gì tôi cần là trợ giúp mã, Tôi nghĩ nguyên mẫu chức năng hạt nhân sẽ là: '__global__ void Áp dụngAverageFilter (int ** Hình ảnh, int ** Result, int filterSize);', tôi cần trợ giúp về mã. –

+1

Ồ. Tôi đã cập nhật câu trả lời của mình và thêm một liên kết cho hạt nhân CUDA để lọc hộp. Nhưng trước tiên bạn phải học CUDA để sử dụng nó. Nếu không NPP là một lựa chọn tốt hơn nếu bạn không có nhiều nền CUDA. – sgarizvi

+0

Tôi nghĩ câu trả lời của bạn là đủ đủ cho câu hỏi hiện tại, Cảm ơn rất nhiều :) –

4

Vài suy nghĩ Basic/bước sau:

  1. Sao chép dữ liệu hình ảnh từ CPU sang GPU
  2. Gọi một hạt nhân để xây dựng trung bình cho mỗi dòng (ngang) và lưu nó trong bộ nhớ chia sẻ.
  3. Gọi nhân để xây dựng mức trung bình cho mỗi cột (dọc) và lưu trữ nó trong bộ nhớ chung.
  4. Sao chép dữ liệu trở lại CPU-Bộ nhớ.

Bạn sẽ có thể mở rộng quy mô này một cách dễ dàng với các cuộc gọi hạt nhân 2D và bộ nhớ đa chiều.

3

Nếu kích thước của bộ lọc là bình thường và không ồn, bộ lọc trung bình là một trường hợp rất tốt để thực hiện với CUDA. Bạn có thể thiết lập điều này bằng cách sử dụng các khối vuông và mỗi chuỗi của khối chịu trách nhiệm tính giá trị của một pixel, bằng cách tính tổng và tính trung bình các giá trị của nó.

Nếu bạn lưu trữ hình ảnh trong Bộ nhớ toàn cầu thì nó có thể được lập trình dễ dàng nhưng bạn sẽ có rất nhiều xung đột ngân hàng. Một tối ưu có thể là bạn tải khối hình ảnh vào Bộ nhớ chia sẻ của khối. Sử dụng các phần tử ảo (để bạn không vượt quá kích thước của khối chia sẻ khi tìm kiếm các pixel lân cận), bạn có thể tính trung bình của các pixel trong một khối. Chỉ cần nghĩ rằng bạn phải cẩn thận là làm thế nào "khâu" sẽ được thực hiện cuối cùng, bởi vì các khối bộ nhớ chia sẻ sẽ chồng lên nhau (vì có thêm "padding" pixel) và bạn không muốn để tính giá trị của chúng hai lần.

Các vấn đề liên quan