OPENCV與OPENCL


OpenCL是用於編寫在異構平台上運行程序的框架,所謂異構平台,一般情況我們指GPU和CPU兩種處理器混合的平台。OpenCL由一門用於編寫kernels (在OpenCL設備上運行的函數)的語言(基於C99)和一組用於定義並控制平台的API組成。OpenCL可以實現GPGPU(General-purpose computing on graphics processing units, 通用圖形處理器)運算, GPGPU是一種利用處理圖形任務的GPU來計算原本由CPU處理的通用計算任務。這些通用計算常常與圖形處理沒有任何關系。由於現代圖形處理器強大的並行處理能力和可編程流水線,令流處理器可以處理非圖形數據。特別在面對單指令流多數據流(SIMD),且數據處理的運算量遠大於數據調度和傳輸的需要時,通用圖形處理器在性能上大大超越了傳統的中央處理器應用程序。

簡單解釋一下這段話中幾個重點:

利用GPU強大的並行能力代替CPU進行運算

由於GPU本身特殊的硬件架構,GPU被設計成擁有非常強大的並行運算能力。以OpenCV為例,把GPGPU融入到OpenCV的首要原因是GPU的並行能力特別適合於關於矩陣的運算。利用GPU,我們可以發起很多個輕量級線程,每個線程僅處理一個元素的計算來實現數據並行;而對於CPU,我們只能按順序每個元素迭代運算。GPU和CPU運算對比起來可以想象成4輛坦克與1萬個士兵的戰斗力水平的對比;孰勝孰劣,還要看具體進行的任務。因此,並不是所有的OpenCV函數都適合移植到GPU上進行運算;這就是為什么只有一部分的函數被移植到了GPU上運算。

OpenCL由在OpenCL設備上運行的kernel函數語言和控制平台的API組成

OpenCL包含兩個主要部分:device和host。在CPU和GPU組成的異構平台中,我們一般把運行核函數的GPU處理器部分稱為device,把控制平台API的CPU稱為host。相應的,把host上的內存(就是內存)稱為host memory;而把device上的內存(例如GPU顯存)稱為device memory或者device buffer。在OpenCV里,我們把這兩種內存封裝為cv::Matcv::ocl::oclMat結構。

數據調度和傳輸

OpenCV的OCL模塊中,在GPU上進行運算之前我們必須把內存轉成GPU可以直接調用的顯存。而在GPU上的運算結束后,我們還需要將在GPU顯存上的數據轉移到CPU可用的內存上。這兩個操作在oclMat中定義為兩個成員函數,分別為oclMat::downloadoclMat::upload。由於這兩個數據傳輸操作受PCI總線寬帶的限制,在實際應用中應盡量減少數據傳輸,把盡可能多的運算在gpu device上計算完成后,再把數據傳回cpu host,以達到最大的數據吞吐量。

我們正在考慮在未來版本中加入對於通過設備內存映射,直接讓設備讀取主機內存的方式。這種方法可能對於AMD APU或者Intel Sandy Bridge集顯來說有先天優勢。

OpenCV's CUDA Module

介紹OpenCL模塊前,不得不先提一下OpenCV的GPU(以下特指CUDA模塊)模塊。由於OCL模塊有很大一部分直接移植自GPU的代碼,所以我們可以先來了解下他的前身。

來源:http://opencv.org/platforms/cuda.html

歷史

GPU模塊最初由NVIDIA公司在2010年起支持開發,2011年春發布了第一個帶有GPU模塊的OpenCV版本。GPU模塊包含並加速了很大一部分原先只能運行在CPU設備上的庫函數,並且隨着新的計算技術和GPU架構不斷發展和更新。

目標

  • 為開發者提供一個便於使用CUDA的計算機視覺框架,同時在概念上保持了當前的CPU的功能性。
  • 把用最高效的方式優化GPU模塊函數作為目標。這些優化方法包含:適應最新的硬件架構;非同步模式核函數執行;重疊式拷貝和零拷貝等。
  • 功能完整性。意思就是說即使有些函數性能並沒有提高的情況下,盡可能的把CPU模塊函數移植到GPU上去做,以減少數據傳輸產生的延遲。

OPENCV與OPENCL

模塊設計

OpenCV的GPU模塊還加入了CUDA第三方函數的支持,如NVIDIA NPP和CUFFT。(相應的,OCL模塊也加入了AMD提供的amdBlas和amdFft庫)

GPU模塊被設計成host上能調用的CUDA API擴展集。這個設計模式讓開發者能明確的控制數據在CPU和GPU的內存間的傳輸。盡管用戶必須要多寫一點代碼來開始使用GPU模塊,但是這個過程是靈活的,並且允許用戶對GPU數據控制的代碼進行優化。

GPU模塊的gpu::GpuMat類是一個封裝了儲存在在GPU顯存的容器,而他的接口與CPU的cv::Mat類非常相似。所有的GPU模塊函數以GpuMat作為輸入輸出函數,這樣的設計允許多個GPU算法在數據不下載到CPU內存就能完全調用,增加了數據吞吐效率。並且GPU函數接口也盡可能的和CPU函數保持移植,這樣熟悉OpenCV CPU操作的開發者能直接轉移到GPU模塊上進行開發。

由於OpenCL的開發模式與CUDA非常類似,包括host API和device上運行的核函數語法,所以移植工作並不困難。移植過程中,我們保持了GPU模塊的設計理念,並且在保證代碼質量的基礎上,盡可能的讓OCL模塊的函數跟上GPU模塊的更新節奏。

Compile Latest OpenCV trunk repository

以下以windows 7 32bit + visual studio 2010 + AMD顯卡為例。

由於ocl模塊剛剛加入OpenCV的主版本,用戶想要基於ocl開發的話,需要從OpenCV的git服務器上pull一下最新trunk repository的OpenCV代碼。git地址如下:

git://code.opencv.org/opencv.git

或者github的鏡像

https://github.com/itseez/opencv

下載完成后,你還需要一個新的OpenCL SDK。以AMD顯卡系列為例,APP SDK v2.7下載地址http://developer.amd.com/sdks/amdappsdk/downloads/pages/default.aspx

你還需要CMake2.8版本來生成Visual Studio的sln項目。cmake的使用方法就不多說了,網上有很詳細的教程。

應注意的是在用CMake對OpenCV項目進行配置時,要手動打開WITH_OPENCL選項,這個是默認關閉的。如果一切正常的話,在CMake的命令行輸出終究會提示找到OpenCL的靜態庫和include文件夾;如果提示沒有找到的話,需要自己手動在cmake中找到這兩個選項(分別是OPENCL_INCLUDE_DIROPENCL_LIBRARY),添加include文件夾和靜態庫文件(OpenCL.lib)路徑。

上面步驟完成后,就可以打開OpenCL.sln文件編譯OpenCV了。

Using OCL module

使用ocl模塊的方法跟gpu非常類似(本來就是無腦移植什么的)

為了跟gpu模塊使用方式保持一致,目前官網的版本(2.4.6)的部分函數已經可以隱式的初始化OpenCL環境了,例如ocl::Canny。調用任意OpenCV函數后,會自動尋找環境中的OpenCL設備,並把找到的第一個加入全局中。

默認情況下,將會把找到的第一個平台的第一個設備的上下文cl_context和一個命令執行隊列cl_command_queue加入到全局環境中的Context()。你還可以調用ocl::setDevice()手動選擇使用的OpenCL設備。

如果用戶的電腦有多個OpenCL平台/設備,可以在環境變量中加入一個新的字段OPENCV_OPENCL_DEVICE,內容為::。比如,我想使用AMD的Tahiti顯卡,就可以寫AMD:GPU:Tahiti

上文提到,所有的ocl模塊調用的矩陣類型格式是oclMatoclMatMat結構類似,包含大部分的成員函數和成員變量,但是最重要的是封裝了OpenCL的buffer數據(cl_mem)並控制他的內存釋放與傳輸。

把一個Mat轉化成oclMat非常簡單,你可以調用oclMat的構造函數:

oclMat myOclMat(mat); // mat is a Mat object 

oclMat的構造函數會自動復制據Mat的矩陣頭(header),如列、行數,元素類型,通道數等等,分配一個足夠大小的設備儲存,並且隱式的把cpu host上的內存轉移到gpu device的顯存上。如果用戶想顯示的轉移(或者稱為“上傳”),可以調用:

oclMat myOclMat; myOclMat.upload(mat); 

這樣我們就有了一個上傳到device上的oclMat矩陣。這個矩陣數據就可以傳遞給ocl模塊的函數,進行你所需要的運算。但是由於oclMat矩陣的數據是儲存在gpu顯存上的,我們在host(cpp文件中)是不能直接去取值的(除非利用host內存地址映射)。如果計算完畢后,我們想取得oclMat的結果,需要把在顯存上的  oclMat數據轉移成Mat格式,這個操作叫做”下載”。跟上傳類似,我們也有隱式和顯示兩種方法:

mat = (Mat)myOclMat; myOclMat.download(mat); 

一般情況下,你不必擔心oclMat數據的釋放問題,因為跟Mat相同,它是有reference count控制顯存的釋放。有些情況下GPU顯存十分緊張的時候,就需要用戶自己去釋放oclMat,或者考慮顯存的重復利用。

概括地說,使用ocl模塊有這么幾個過程:

  1. 注冊全局OpenCL設備。 //此步可以省去,新版本的OpenCV會自動注冊OpenCL設備
  2. 把內存的數據上傳到顯存。//把Mat轉化成oclMat
  3. 在OpenCL設備上進行計算。//調用ocl模塊函數
  4. 把顯存的數據下載到內存。//把oclMat轉化成Mat
  5. 在host上進行剩余的運算。//調用cv::函數

雖然開發者已經盡量通過函數的封裝減少了用戶對於實際函數調用流程的透明度,但是用戶使用模塊前應了解以下幾點:

  1. 盡量減少在OpenCL函數調用之間加入數據傳輸指令。這是因為上傳下載操作會嚴重影響核函數在命令隊列上的調度,尤其是下載操作,因為這個操作在庫中被設計成同步指令。 有的函數有隱式的下載調用,比如cv::ocl::sum,應盡量調整其執行的順序,必要的時候使用AMD CodeXL看一下命令隊列的執行密度。
  2. 用戶使用過程中會注意到,目前的OCL模塊編譯速度要比CUDA(GPU)模塊快的多,后者可能要編譯一小時以上。 但是運行時,OCL模塊的函數在第一次調用的時候會有很明顯的延遲,但CUDA模塊沒有這種現象。 這是因為CUDA模塊在OpenCV編譯其會把核函數編譯為cubin文件,這樣在運行時就不會出現啟動延遲,但是為了兼容不同的CUDA版本和CUDA文件的高度模版化,導致編譯時間相當漫長;相比較OpenCL,我們不能在OCL函數運行之前就確定核函數的編譯選項和目標運行平台,因此只能在運行時進行核函數的編譯。 作為一個補救措施,我們加入了一個功能,OCL模塊在第一次運行時將把這一次編譯好的核函數二進制文件保存到磁盤,這樣下一次使用的時候就避免了編譯造成的啟動延遲。

以下是OCL模塊的例子SURF matcher的輸出結果示例:

OPENCV與OPENCL


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM