1. 程式人生 > >CUDA學習筆記(1)——腐蝕

CUDA學習筆記(1)——腐蝕

前言

本篇部落格是在我學習別人的部落格內容上實驗出來的,所以大部分並非原創內容,參考地址:祕籍傳送。

在此篇部落格的基礎上,我將其中的程式實驗,並自己閱讀了一番,下面是具體內容。

主要目標

是對一張轉換為灰度影象進行腐蝕和膨脹,通過CUDA在GPU實現此過程,將其輸出在cpu上,儲存在本地檔案中(*.jpg)。

具體實現

影象腐蝕:

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include<opencv2/opencv.hpp>
#include <stdio.h>

using namespace cv;
using namespace std;

__global__ void test(unsigned char *d_in, unsigned char *d_out, int iwidth, int iheight, Size erodeElement){
	int xIndex = threadIdx.x + blockDim.x*blockIdx.x;
	int yIndex = threadIdx.y + blockDim.y*blockIdx.y;
	
	int ewidth = erodeElement.width/2;
	int ehight = erodeElement.height/2;
	d_out[xIndex + yIndex * iwidth] = d_in[xIndex + yIndex * iwidth];
	if ((xIndex > ewidth) && (yIndex > ehight) && (xIndex+ewidth < iwidth) && (yIndex+ehight < iheight)){
		for (int i = -ehight; i <= ehight; i++){
			for (int j = -ewidth; j <= ewidth; j++){
				if (d_in[xIndex + yIndex * iwidth] > d_in[(yIndex + j) * iwidth + xIndex + i]){
					d_out[xIndex + yIndex * iwidth] = d_in[(yIndex + j) * iwidth + xIndex + i];
				}
			}
		}
	}
}

int main(){

	Mat imgsrc = imread("D:\\che.jpg", 0);
	if (imgsrc.data == NULL){
		printf("圖片讀取錯誤!\n");
		return -1;
	}

	unsigned char *d_in;
	unsigned char *d_out;
	
	int width = imgsrc.cols;
	int height = imgsrc.rows;
	//unsigned char out[];	是否可以通過陣列和Mat轉換的方式來實現將資料儲存,雖然相比於直接放在Mat.data上要麻煩
	cudaError cudaStatus;
	cudaStatus = cudaMalloc((void**)&d_in, width*height*sizeof(unsigned char));
	if (cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMalloc failed!\n");
		cudaFree(d_in);
		return 1;
	}

	cudaStatus = cudaMalloc((void**)&d_out, width*height*sizeof(unsigned char));
	if (cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMalloc failed!\n");
		cudaFree(d_in);
		cudaFree(d_out);
		return 1;
	}

	cudaStatus = cudaMemcpy(d_in, imgsrc.data, width*height*sizeof(unsigned char), cudaMemcpyHostToDevice);
	if (cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy failed!\n");
		cudaFree(d_in);
		cudaFree(d_out);
		return 1;
	}

	dim3 threads(32, 32);
	dim3 blocks((width + threads.x - 1)/threads.x, (height + threads.y - 1)/threads.y);
	Size Element(3, 5);

	test << <blocks, threads >> >(d_in, d_out, width, height, Element);

	Mat m(height, width, CV_8UC1, cv::Scalar::all(0));

	cudaStatus = cudaMemcpy(m.data, d_out, width*height*sizeof(unsigned char), cudaMemcpyDeviceToHost);
	if (cudaStatus != cudaSuccess){
		fprintf(stderr, "cudaMemcpy2 failed!\n");
		cudaFree(d_in);
		cudaFree(d_out);
		return 1;
	}

	imwrite("D:\\test.jpg", m);

	printf("%d,%d", width, height);

	getchar();
	return 0;
}

程式碼大致解釋:

程式碼主要分幾塊:首先,對存放陣列的分配地址並初始化(主要是對傳入GPU的陣列初始化);其次,是呼叫核函式執行具體任務;最後,將執行後的結果陣列(d_out)複製給記憶體中負責顯示的Mat資料型別(m),並將其儲存到本地。

具體解釋:

  •         Mat imgsrc = imread("D:\\che.jpg", 0);
    	if (imgsrc.data == NULL){
    		printf("圖片讀取錯誤!\n");
    		return -1;
    	}
    	..........
    
    	cudaStatus = cudaMemcpy(d_in, imgsrc.data, width*height*sizeof(unsigned char), cudaMemcpyHostToDevice);
    	if (cudaStatus != cudaSuccess){
    		fprintf(stderr, "cudaMemcpy failed!\n");
    		cudaFree(d_in);
    		cudaFree(d_out);
    		return 1;
    	}

將圖片按灰度影象讀入,併為之後的GPU所需陣列分配空間,其中,cudaStatus是cudaError型別,來提示錯誤,當然也可以加一句:printf("%s\n", cudaGetErrorString(cudaStatus));來具體輸出什麼錯誤(雖然有時候提示的錯誤莫名其妙),網上有關於各種錯誤提示的解決方法。

  •         dim3 threads(32, 32);
    	dim3 blocks((width + threads.x - 1)/threads.x, (height + threads.y - 1)/threads.y);
    	Size Element(3, 5);
    
    	test << <blocks, threads >> >(d_in, d_out, width, height, Element);

這裡是定義核函式的引數和執行核函式,這裡的引數在參考的部落格中有詳細的解釋。

特別的,對於定義的blocks,我的理解是:平常對於Grid含有的block數量是:總的行數/breads(即每個block所含有的thread,當然也有例外,比如參考部落格的上一篇:祕籍傳送2),而這裡將breads設定確定了,就是(32,32),則若是不同的圖片載入進來,需要對其有些適應性的改變,舉個特殊的例子,若是影象為1x1的,那麼總不能將blocks設定為1/32,而它實際上或許只需要一個執行緒來處理(我不太肯定,懇請各位大神給出確定答案,非常感謝!),則應該將一個block塊交給它,所以算下來的blocks應該是1塊(在上面的定義式算下來的結果是1塊)。

下面是核函式:

  •         int ewidth = erodeElement.width/2;
    	int ehight = erodeElement.height/2;

在腐蝕上,可以上網查詢有關概念,這裡的程式的邏輯是:將一個畫素點和周圍比較,將最小值賦值給這個畫素點,至於“周圍”有多大,是在主程式中的Size Element(3, 5);定義的。寬3高5的矩形,通過此矩形來腐蝕影象。而具體實現起來,則是以現畫素點為原點,減加一半的寬度和高度來遍歷周圍。

  • if ((xIndex > ewidth) && (yIndex > ehight) && (xIndex+ewidth < iwidth) && (yIndex+ehight < iheight))

 這一句是防止越界,因為要從中心點加減來遍歷,要是超出影象的邊界,則要不無效要不出錯,上面四個關係對應於四周邊界。

  • if (d_in[xIndex + yIndex * iwidth] > d_in[(yIndex + j) * iwidth + xIndex + i]){
    	d_out[xIndex + yIndex * iwidth] = d_in[(yIndex + j) * iwidth + xIndex + i];
    }

 這個判斷則是取最小值存入d_out陣列中。

最後是儲存:

  •         Mat m(height, width, CV_8UC1, cv::Scalar::all(0));
    
            cudaStatus = cudaMemcpy(m.data, d_out, width*height*sizeof(unsigned char), cudaMemcpyDeviceToHost);
    	if (cudaStatus != cudaSuccess){
    		fprintf(stderr, "cudaMemcpy2 failed!\n");
    		cudaFree(d_in);
    		cudaFree(d_out);
    		return 1;
    	}
    
    	imwrite("D:\\test.jpg", m);

將資料複製到記憶體的m變數上,將其儲存到本地。

總結

以上是我對程式的理解,得到的圖片結果和參考部落格相同,上述的理解供參考,若是有理解不對的地方,懇請不吝賜教,我在cuda上還是新手,謝謝各位!