獨(dú)家|OpenCV1.9 如何利用OpenCV的parallel_for_并行化代碼(附代碼)
目標(biāo)
本教程的目標(biāo)是展示如何使用OpenCV的parallel_for_框架輕松實(shí)現(xiàn)代碼并行化。為了說明這個(gè)概念,我們將編寫一個(gè)程序,利用幾乎所有的CPU負(fù)載來繪制Mandelbrot集合。完整的教程代碼可見原文。如果想了解更多關(guān)于多線程的信息,請(qǐng)參考本教程中提及的參考書或課程。
預(yù)備條件
首先是搭建OpenCV并行框架。在OpenCV3.2中,可以按此順序使用以下并行框架:
1. 英特爾線程構(gòu)建模塊(第三方庫,應(yīng)該明確啟用)
2. C =并行C / C ++編程語言擴(kuò)展(第三方庫,應(yīng)該明確啟用)
3. OpenMP(集成的編譯器,應(yīng)明確啟用)
4. APPLE GCD(系統(tǒng)層面,自動(dòng)使用(僅適用APPLE))
5. Windows RT并發(fā)(系統(tǒng)層面,自動(dòng)使用(僅適用Windows RT))
6. Windows并發(fā)(部分運(yùn)行時(shí)間,自動(dòng)使用(僅適用Windows - MSVC ++> = 10))
7. Pthreads (如果適用)
正如前面所述,OpenCV庫可以使用多個(gè)并行框架。有些并行庫為第三方提供的庫,建立時(shí)應(yīng)明確地用CMake(如TBB,C =)啟用,其余均為自動(dòng)可用的平臺(tái)(例如APPLE GCD),但是,無論是直接使用并行框架還是利用CMake啟用并行框架并重建庫,首先要做的是啟用并行框架。
第二個(gè)(弱)預(yù)備條件與任務(wù)相關(guān),因?yàn)椴皇撬腥蝿?wù)的計(jì)算都可以/適合以并行方式來運(yùn)行。為了盡量保持簡單,可以將任務(wù)分解為與存儲(chǔ)器無關(guān)的多個(gè)元素,從而使其更加容易實(shí)現(xiàn)并行化。在計(jì)算機(jī)視覺處理過程中,由于大多數(shù)時(shí)間里一個(gè)像素的處理不依賴于其它像素的狀態(tài),所以往往更加容易實(shí)現(xiàn)并行化。
簡單的示例:繪制Mandelbrot集合
這個(gè)例子中將展示如何繪制Mandelbrot集合,將普通的順序代碼實(shí)現(xiàn)并行化計(jì)算。
理論
Mandelbrot集合的名稱是數(shù)學(xué)家阿德里恩·多迪(Adrien Douady)為悼念數(shù)學(xué)家蒙德布羅特(Mandelbrot),以他的名字來命名的。它在數(shù)學(xué)界之外,作為分形類的一個(gè)例子,在圖像表示領(lǐng)域非常著名。Mandelbrot集合為一組自相似的重復(fù)圖案在不同尺度下重復(fù)顯示結(jié)果。為了進(jìn)一步深入介紹,可以參考Wikipedia article。在這里,僅介紹利用公式繪制Mandelbrot集合(選自維基百科的文章)。
Mandelbrot集合是在復(fù)平面中一組值C沿著0軌跡的二次迭代映射的邊界。
即,復(fù)數(shù)c作為Mandelbrot集的一部分,從 Z0 = 0開始重復(fù)進(jìn)行迭代,當(dāng)n趨近于無窮大時(shí),Zn的絕對(duì)值的邊界值,它可以表示為:
偽代碼
生成Mandelbrot集合的簡單的算法被稱為“逃逸時(shí)間算法”。為渲染圖像中的每個(gè)像素,根據(jù)復(fù)數(shù)值是否在邊界范圍之內(nèi),利用遞推關(guān)系進(jìn)行測試。經(jīng)過數(shù)次迭代之后,不屬于Mandelbrot集合的像素將快速逃逸,留下來的將是屬于Mandelbrot集合的像素。隨著計(jì)算時(shí)間的增加,迭代后的高階值將產(chǎn)生一個(gè)更詳細(xì)的圖像。在這里使用實(shí)現(xiàn)“逃逸”所需要的迭代次數(shù)來描繪圖像中的像素值。
將偽代碼和理論相關(guān)聯(lián)之后,得到:
在上圖中,復(fù)數(shù)的實(shí)部在x軸上,復(fù)數(shù)的虛部在y軸上。通過對(duì)圖形局部放大,可以看到整個(gè)形狀均重復(fù)可見。
代碼實(shí)現(xiàn)
逃逸時(shí)間算法的實(shí)現(xiàn)
在這里,我們使用了std::complex模板類來表示復(fù)數(shù)。利用這個(gè)函數(shù)來進(jìn)行測試,以檢查像素是否在集合之中,并返回“逃逸”迭代。
順序的Mandelbrot實(shí)現(xiàn)
在此程序中,通過依次遍歷渲染圖像中的像素來進(jìn)行測試,以檢查像素是否屬于Mandelbrot集合。
需要做的另一件事是把像素坐標(biāo)轉(zhuǎn)換Mandelbrot集合空間:
最后,將灰度值分配給像素,使用以下規(guī)則:
當(dāng)?shù)螖?shù)達(dá)到最大值時(shí),像素為黑色(假定像素在Mandelbrot集合中);
否則根據(jù)逃脫“逃逸迭代”和縮放尺度,為像素分配一個(gè)灰度值,以適應(yīng)灰度范圍。
使用線性縮放轉(zhuǎn)換不足以感知的灰度變化。為了克服這個(gè)問題,使用一個(gè)平方根轉(zhuǎn)換來提升感知度(引用了Jeremy D. Frens博客中的內(nèi)容):
綠色曲線對(duì)應(yīng)于簡單的線性縮放轉(zhuǎn)換,藍(lán)色曲線對(duì)應(yīng)于平方根轉(zhuǎn)換,可以從中觀察到的最低值如何沿著斜坡正向上升。
并行Mandelbrot實(shí)現(xiàn)
在順序的Mandelbrot實(shí)現(xiàn)中,每個(gè)像素被獨(dú)立計(jì)算。為了優(yōu)化計(jì)算,我們可以利用現(xiàn)代處理器的多核架構(gòu)并行執(zhí)行多個(gè)像素的計(jì)算,利用OpenCV的CV :: parallel_for_框架可以輕松實(shí)現(xiàn)。
第一件事是聲明一個(gè)繼承CV :: ParallelLoopBody的自定義類,覆蓋virtual void operator ()(const cv::Range& range) const。
operator ()表示將通過一個(gè)獨(dú)立的線程來處理像素的子集,這種拆分是自動(dòng)完成的,以平均分配計(jì)算負(fù)荷,為此必須將像素索引坐標(biāo)轉(zhuǎn)換成2D [行,列]坐標(biāo)。還要注意的是,必須保持圖像的mat對(duì)象引用值,以便能夠適時(shí)地對(duì)圖像進(jìn)行修改。
調(diào)用并行執(zhí)行程序:
在這里,range表示將要執(zhí)行的操作總數(shù),即圖像中的像素總數(shù)。使用CV :: setNumThreads設(shè)置線程數(shù),還可以使用CV :: parallel_for_中的 nstripes參數(shù)指定拆分的數(shù)量CV :: parallel_for_。例如,如果處理器有4個(gè)線程,則設(shè)置CV :: setNumThreads(2)或者設(shè)置nstripes = 2應(yīng)該是一樣的,默認(rèn)情況下它會(huì)使用所有可用的處理器線程,但拆分后只有兩個(gè)線程。
注
為了簡化并行的實(shí)現(xiàn),C ++ 11標(biāo)準(zhǔn)刪除了ParallelMandelbrot類,采用lambda表達(dá)式代替它:
運(yùn)行結(jié)果
可以在原文找到完整的教程源代碼,并行實(shí)現(xiàn)的性能取決于CPU的種型。例如,在4核/ 8線程的CPU上,可以提速6.9倍左右。如果要問,為什么達(dá)不到8倍速,其中有很多因素;主要原因是由于:
創(chuàng)建和管理線程的額外開銷;
并行運(yùn)行的后臺(tái)進(jìn)程;
帶2個(gè)邏輯線程的4硬件核與8硬件核之間是有區(qū)別的。
由教程代碼生成的輸出圖像(可以對(duì)代碼進(jìn)行修改,以使用更多次的迭代,根據(jù)逃逸迭代次數(shù)來分配像素顏色,并使用調(diào)色板以獲得更美的圖像):
Mandelbrot集合XMIN = -2.1,XMAX = 0.6,YMIN = -1.2,YMAX = 1.2,maxIterations = 500
原文鏈接:
https://docs.opencv.org/4.5.2/d7/dff/tutorial_how_to_use_OpenCV_parallel_for_.html
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。