1. 程式人生 > >我的CUDA學習之旅1——大影象分塊處理程式(包括求均值,最大值等)

我的CUDA學習之旅1——大影象分塊處理程式(包括求均值,最大值等)

引言

在我的第一篇文章中我簡單介紹了CUDA以及我的一些個人學習見解,在本文中我將開始正式開始CUDA實踐之旅,眾做周知CUDA目前應用的領域十分廣泛,它能把一些普通的CPU程式碼提速幾十倍甚至幾百倍。在本人所從事的影象處理領域,在一些大影象的處理上(4K以上影象),僅僅依靠CPU進行計算已經完全無法滿足工程專案所要求的執行時間,這時候我們就需要利用CUDA對程式碼進行加速。本文以一個8000*1000影象為例,進行程式碼實踐。

任務要求

將一個8000*1000的單通道圖分割為40x40 (可調整)的塊, 計算每個塊內的畫素值的和、最大 、最小、均值,將計算結果儲存在CPU端。

實現思路

由於我的本本的顯示卡最大支援的thread數為1024,而任務中40*40的數量顯然已經超過了1024,故在計算任務的分配上,我綜合考慮後選擇了通過兩次運算(每次計算數為40*20)來完成,運用共享記憶體儲存每個塊中傳入影象的畫素資料,最後利用歸約演算法對加法進行優化。

實現環境

VS2013 + CUDA7.5 + Opencv2.4.13

這裡寫圖片描述

實現程式碼

1)8000*1000實驗影象獲取

我用了一段簡單的Matlab程式碼來獲取實驗影象,程式碼如下:

img = uint8(zeros(8000,1000));
for i=1:8000
    for j=1:1000
        img(i,j) = uint8(255*rand(1));
    end
end
imwrite(img,'test.jpg')

該程式的目的在於產生一個隨機產生0-255值的灰度影象,大小為8000*1000,作為我們的實驗影象。
實驗影象
(2)CPU程式碼實現

我先使用Opencv的方式在CPU端實現了任務,得到了計算結果(方便與GPU實現相比較,同時也可驗證CUDA程式碼的準確性),具體的程式碼如下:

#include <iostream>
#include <opencv2\opencv.hpp>
#include <stdlib.h>
using namespace std;
using namespace cv;

int operateMat(Mat img, int startx, int starty, int size, int thresh);

int main()
{
    Mat img = imread("test.jpg",0);
    int size = 40;
    int thresh = 0;
    cout << "Plaese Input Size:"
; cin >> size; //分塊區域大小 cout << "Plaese Input Thresh value:"; cin >> thresh; //閾值 double time0 = static_cast<double>(getTickCount()); //計時器開始 int row = img.rows; int col = img.cols; int length = row / size; int width = col / size; int sumResult[10000]; //區域內求和結果 int averageResult[10000]; //區域內均值結果 int maxResult[10000]; //區域內最大值結果 int minResult[10000]; //區域內最小值結果 int threshNumber[10000]; //區域內閾值結果 int count = 0; int x = 0; for (int i = 0; i < length; i++) { for (int j = 0; j < width; j++) { sumResult[count], maxResult[count], minResult[count], threshNumber[count] = operateMat(img, i*size, j*size, size, 35); averageResult[count] = sumResult[count] / (size * size); count += 1; } } time0 = ((double)getTickCount() - time0) / getTickFrequency(); //計時器結束 cout << time0 << "s" << endl; //輸出執行時間 system("Pause"); return 0; } int operateMat(Mat img, int startx, int starty, int size, int thresh) { int sum = 0; int max = 0; int min = 255; int average = 0; int threshCount = 0; for (int i = startx; i < startx + size; i++) { uchar *data = img.ptr<uchar>(i); for (int j = starty; j < starty + size; j++) { sum += data[j]; if (max < data[j]) { max = data[j]; } if (min > data[j]) { min = data[j]; } if (data[j] > thresh) { threshCount += 1; } } } average = sum / (size*size); return sum, max, min, threshCount; }

(3)CUDA程式碼
在CUDA實現上,本程式利用了每個塊獨有的共享記憶體,將需要計算的40*40的小塊中所有的畫素資料通過兩次匯入的方式(每次匯入40*20)全部賦值給大小為1600的共享記憶體,接著通過規約演算法優化運算即可得到所需結果。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cuda.h>
#include <device_functions.h>
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

__global__ void matSum(uchar *dataIn, int *dataOutSum, int *dataOutMax, int *dataOutMin, int imgHeight, int imgWidth)
{
    //__shared__ int _data[1600];
    const int number = 2048;

    extern __shared__ int _sum[];  //小影象塊中求和共享陣列
    __shared__ int _max[number];  //小影象塊中求最大值共享陣列
    __shared__ int _min[number];  //小影象塊中求最小值共享陣列

    int thread = threadIdx.x + threadIdx.y * blockDim.x; //一個block中所有thread的索引值
    int threadIndex = threadIdx.x + threadIdx.y * imgWidth; //每個小塊中存放資料的thread索引值
    //每個小塊中存放資料的block索引值
    int blockIndex1 = blockIdx.x * blockDim.x + 2 * blockIdx.y * blockDim.y * imgWidth; //40*20的上半block索引值
    int blockIndex2 = blockIdx.x * blockDim.x + (2 * blockIdx.y + 1) * blockDim.y * imgWidth; //40*20的下半block索引值

    int index1 = threadIndex + blockIndex1; //每個block中上半部分索引值
    int index2 = threadIndex + blockIndex2; //每個block中下半部分索引值

    //將待計算的40*40小影象塊中的所有畫素值分兩次傳送到共享陣列中
    _sum[thread] = dataIn[index1]; //將上半部分的40*20中所有資料賦值到共享陣列中
    _sum[thread + blockDim.x * blockDim.y] = dataIn[index2]; //將下半部分的40*20中所有資料賦值到共享陣列中

    _max[thread] = dataIn[index1];
    _max[thread + blockDim.x * blockDim.y] = dataIn[index2];

    _min[thread] = dataIn[index1];
    _min[thread + blockDim.x * blockDim.y] = dataIn[index2];

    //memcpy(_sum, _data, 1600 * sizeof(int));
    //memcpy(_max, _data, 1600 * sizeof(int));
    //memcpy(_min, _data, 1600 * sizeof(int));  在GPU(Device)中用memcpy函式進行拷貝會導致顯示卡混亂,故不選擇此種方式

    //利用歸約演算法求出40*40小影象塊中1600個畫素值中的和、最大值以及最小值
    for (unsigned int s = number / 2; s > 0; s >>= 1)
    {
        if (thread < s)
        { 
            _sum[thread] += _sum[thread + s]; 
            if (_max[thread] < _max[thread + s]) { _max[thread] = _max[thread + s]; }
            if (_min[thread] > _min[thread + s]) { _min[thread] = _min[thread + s]; }
        }
        __syncthreads(); //所有執行緒同步
    }
    if (threadIndex == 0) 
    { 
        //將每個小塊中的結果儲存到輸出中
        dataOutSum[blockIdx.x + blockIdx.y * gridDim.x] = _sum[0]; 
        dataOutMax[blockIdx.x + blockIdx.y * gridDim.x] = _max[0];
        dataOutMin[blockIdx.x + blockIdx.y * gridDim.x] = _min[0];
    }

}

int main()
{
    Mat image = imread("test.jpg", 0); //讀取待檢測圖片
    int sum[5000]; //求和結果陣列
    int max[5000]; //最大值結果陣列
    int min[5000]; //最小值結果陣列
    imshow("src", image);

    size_t memSize = image.cols*image.rows*sizeof(uchar);
    int size = 5000 * sizeof(int);

    uchar *d_src = NULL;
    int *d_sum = NULL;
    int *d_max = NULL;
    int *d_min = NULL;

    cudaMalloc((void**)&d_src, memSize);
    cudaMalloc((void**)&d_sum, size);
    cudaMalloc((void**)&d_max, size);
    cudaMalloc((void**)&d_min, size);

    cudaMemcpy(d_src, image.data, memSize, cudaMemcpyHostToDevice);


    int imgWidth = image.cols;
    int imgHeight = image.rows;

    dim3 threadsPerBlock(40, 20); //每個block大小為40*20
    dim3 blockPerGrid(25, 200); //將8000*1000的圖片分為25*200個小影象塊

    double time0 = static_cast<double>(getTickCount()); //計時器開始

    matSum << <blockPerGrid, threadsPerBlock, 4096 * sizeof(int) >> >(d_src, d_sum, d_max, d_min, imgHeight, imgWidth);

    time0 = ((double)getTickCount() - time0) / getTickFrequency(); //計時器結束
    cout << "The Run Time is :" << time0 << "s" << endl; //輸出執行時間

    cudaMemcpy(sum, d_sum, size, cudaMemcpyDeviceToHost);
    cudaMemcpy(max, d_max, size, cudaMemcpyDeviceToHost);
    cudaMemcpy(min, d_min, size, cudaMemcpyDeviceToHost);

    cout << "The sum is :" << sum[0] << endl;
    cout << "The max is :" << max[0] << endl;
    cout << "The min is :" << min[0] << endl;

    waitKey(0);

    cudaFree(d_src);
    cudaFree(d_sum);
    cudaFree(d_max);
    cudaFree(d_min);

    return 0;
}

(4)運算結果比對

CPU與GPU程式碼運算結果一致,在效能上CPU端程式碼耗時約0.1S,GPU端程式碼為0.2ms,加速500倍~