使用OpenCL+OpenCV實現圖像旋轉(一)

[題外話]近期申請了一個微信公衆號:平凡程式人生。有興趣的朋友可以關注,那裏將會涉及更多更新OpenCL+OpenCV以及圖像處理方面的文章。


最近在學習《OPENCL異構計算》,其中有一個實例是使用OpenCL實現圖像旋轉。這個實例中並沒有涉及讀取、保存、顯示圖像等操作,其中也存在一些小bug。在學習OpenCL之初,完整地實現這個實例還是很有意義的事情。

1、圖像旋轉原理

所謂圖像旋轉是指圖像以某一點爲中心旋轉一定的角度,形成一幅新的圖像的過程。這個點通常就是圖像的中心。

由於是按照中心旋轉,所以有這樣一個屬性:旋轉前和旋轉後的點離中心的位置不變.

根據這個屬性,可以得到旋轉後的點的座標與原座標的對應關係。

原圖像的座標一般是以左上角爲原點的,我們先把座標轉換爲以圖像中心爲原點。假設原圖像的寬爲w,高爲h,(x0,y0)爲原座標內的一點,轉換座標後的點爲(x1,y1)。可以得到:

X0’ = x0 -w/2;

y1’ =-y0 + h/2;

在新的座標系下,假設點(x0,y0)距離原點的距離爲r,點與原點之間的連線與x軸的夾角爲b,旋轉的角度爲a,旋轉後的點爲(x1,y1), 如下圖所示。


那麼有以下結論:

x0=r*cosb;y0=r*sinb

x1 = r*cos(b-a)= r*cosb*cosa+r*sinb*sina=x0*cosa+y0*sina;

y1=r*sin(b-a)=r*sinb*cosa-r*cosb*sina=-x0*sina+y0*cosa;

得到了轉換後的座標,我們只需要把這些座標再轉換爲原座標系即可。

x1’ = x1+w/2= x0*cosa+y0*sina+w/2

y1’=-y1+h/2=-(-x0*sina+y0*cosa)+h/2=x0*sina-y0*cosa+h/2

此處的x0/y0是新的座標系中的值,轉換爲原座標系爲:

x1’ = x0*cosa+y0*sina+w/2=(x00-w/2)*consa+(-y00+h/2)*sina+w/2

y1’= x0*sina-y0*cosa+h/2=(x00-w/2)*sina-(-y00+h/2)*cosa+h/2

=(y00-h/2)*cosa+( x00-w/2)*sina+h/2

 

2、程序設計

對於圖像旋轉這個實例,爲了處理簡單,我將在灰度圖上去做旋轉。 大致的處理流程如下:

1>     調用OpenCVAPI imread()讀取一張彩色JPEG圖片,將它存儲在MAT變量中。該變量的data成員中存儲着將JPEG圖片解碼後的RGB數據。

2>     調用OpenCVAPI cvtColor()將存儲RGB數據的MAT變量轉換爲只存儲灰度圖像數據的MAT對象。也可以使用函數imread()時直接將JPEG圖像解碼轉換爲灰度圖像。

3>     MAT對象的成員width和height存儲着解碼後圖像的分辨率信息。根據當前分辨率,分配處理圖像時所用的輸入buffer和輸出buffer。它們都按照存儲char型數據進行空間申請。

4>     將MAT對象的成員data中數據copy到輸入buffer中。同時將輸出buffer初始化爲全0。到此,我們調用OpenCV的API所要做的事情告一段落了。接下來就要調用OpenCL的API做事情了。

5>     調用OpenCLAPI clGetPlatformIDs()直接獲取第一個可用的平臺信息。該函數一般是先用它獲取支持OpenCL平臺的數目,然後再次調用它獲取某個平臺的信息。兩次調用,通過傳遞不同參數區分。

6>     調用OpenCLAPI clGetDeviceIDs()獲取第一個平臺中第一個可用的設備。同樣,這個函數也可以調用兩次,分別獲取當前平臺的設備數目,再獲取某個設備信息。

7>     調用OpenCL API clCreateContext()創建上下文。

8>     調用OpenCL API clCreateCommandQueue()創建host與device之間交互的command隊列。

9>     調用OpenCL API clCreateBuffer()在設備端分配存儲輸入圖像的buffer。

10> 調用OpenCL API clEnqueueWriteBuffer()將之前存儲灰度圖像數據的輸入buffer內存copy到設備端buffer中。

11> 調用OpenCL API clCreateBuffer()在設備端分配處理完數據的存儲buffer。

12> 調用文件讀取函數,將kernel文件ImageRotate.cl中的內容讀取到string變量中。

13> 調用OpenCL APIclCreateProgramWithSource(),使用kernel的源碼創建program對象。

14> 調用OpenCL APIclBuildProgram()編譯program對象。

15> 調用OpenCL APIclCreateKernel(),使用編譯完的程序對象創建kernel。

16> 調用OpenCL APIclSetKernelArg()爲kernel程序傳遞參數,包括輸入輸出buffer地址,圖像分辨率和sin()\cos()值。

17> 調用OpenCL APIclEnqueueNDRangeKernel()執行kernel。

18> 調用OpenCL APIclEnqueueReadBuffer,將處理完的圖像數據已經從設備端傳遞到了host端的輸出buffer中。

19> 將輸出buffer中的數據copy到MAT對象的成員data中。

20> 調用OpenCV APIimwrite()將旋轉後的灰度圖像保存到文件中,編碼爲JPEG保存起來。

21> 釋放輸入輸出buffer空間,釋放OpenCL創建的各個對象。

3、kernel程序代碼

我們先看一下kernel程序。Kernel程序是每個workitem需要執行的,它需要存儲在以cl爲後綴的文件中,比如:ImageRotate.cl

Kernel程序定義如下:

       __kernel voidimg_rotate(

       __global unsigned char*dest_data,

       __global unsigned char*src_data,

       int W,

       int H,

       floatsinTheta,

       floatcosTheta)

         有幾點需要注意的地方:

1〉  必須帶着關鍵字__kernel;

2〉  返回值必須爲void;

3〉  區分清楚所傳參數的存儲類型,比如帶__global表示存儲在globalmemory中;什麼都不帶的W、H等表示存儲在work item的private memory中。

 

Kernel程序如下:

1.	__kernel void img_rotate(  
2.	    __global unsigned char *dest_data,  
3.	    __global unsigned char *src_data,  
4.	    int W,  
5.	    int H,  
6.	    float sinTheta,  
7.	    float cosTheta){  
8.	        //work item gets its index within index space  
9.	        const int ix = get_global_id(0);  
10.	        const int iy = get_global_id(1);  
11.	          
12.	        //calculate location of data to move int (ix, iy)  
13.	        //output decomposition as mentioned  
14.	        float xpos = ((float)(ix - W / 2)) * cosTheta + ((float)(-iy + H / 2)) * sinTheta + W / 2;  
15.	        float ypos = ((float)(ix - W / 2)) * sinTheta + ((float)(iy - H / 2)) * cosTheta + H / 2;  
16.	  
17.	        //bound checking  
18.	        if (((int)xpos >=0) && ((int)xpos < W) &&  
19.	            ((int)ypos >= 0) && ((int)ypos < H)) {  
20.	                dest_data[(int)ypos * W + (int)xpos] = src_data[iy * W + ix];  
21.	        }  
22.	}  


(未完待續)






發佈了58 篇原創文章 · 獲贊 156 · 訪問量 70萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章