博客專欄

EEPW首頁 > 博客 > 獨(dú)家|OpenCV1.9 如何利用OpenCV的parallel_for_并行化代碼(附代碼)

獨(dú)家|OpenCV1.9 如何利用OpenCV的parallel_for_并行化代碼(附代碼)

發(fā)布人:數(shù)據(jù)派THU 時間:2021-08-20 來源:工程師 發(fā)布文章

目標(biāo)

本教程的目標(biāo)是展示如何使用OpenCV的parallel_for_框架輕松實現(xiàn)代碼并行化。為了說明這個概念,我們將編寫一個程序,利用幾乎所有的CPU負(fù)載來繪制Mandelbrot集合。完整的教程代碼可見原文。如果想了解更多關(guān)于多線程的信息,請參考本教程中提及的參考書或課程。

預(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)層面,自動使用(僅適用APPLE))

5. Windows RT并發(fā)(系統(tǒng)層面,自動使用(僅適用Windows RT))

6. Windows并發(fā)(部分運(yùn)行時間,自動使用(僅適用Windows  -  MSVC ++> = 10))

7. Pthreads (如果適用)

正如前面所述,OpenCV庫可以使用多個并行框架。有些并行庫為第三方提供的庫,建立時應(yīng)明確地用CMake(如TBB,C =)啟用,其余均為自動可用的平臺(例如APPLE GCD),但是,無論是直接使用并行框架還是利用CMake啟用并行框架并重建庫,首先要做的是啟用并行框架。

第二個(弱)預(yù)備條件與任務(wù)相關(guān),因為不是所有任務(wù)的計算都可以/適合以并行方式來運(yùn)行。為了盡量保持簡單,可以將任務(wù)分解為與存儲器無關(guān)的多個元素,從而使其更加容易實現(xiàn)并行化。在計算機(jī)視覺處理過程中,由于大多數(shù)時間里一個像素的處理不依賴于其它像素的狀態(tài),所以往往更加容易實現(xiàn)并行化。

簡單的示例:繪制Mandelbrot集合

這個例子中將展示如何繪制Mandelbrot集合,將普通的順序代碼實現(xiàn)并行化計算。

理論

Mandelbrot集合的名稱是數(shù)學(xué)家阿德里恩·多迪(Adrien Douady)為悼念數(shù)學(xué)家蒙德布羅特(Mandelbrot),以他的名字來命名的。它在數(shù)學(xué)界之外,作為分形類的一個例子,在圖像表示領(lǐng)域非常著名。Mandelbrot集合為一組自相似的重復(fù)圖案在不同尺度下重復(fù)顯示結(jié)果。為了進(jìn)一步深入介紹,可以參考Wikipedia article。在這里,僅介紹利用公式繪制Mandelbrot集合(選自維基百科的文章)。

Mandelbrot集合是在復(fù)平面中一組值C沿著0軌跡的二次迭代映射的邊界。

1.png

即,復(fù)數(shù)c作為Mandelbrot集的一部分,從 Z0 = 0開始重復(fù)進(jìn)行迭代,當(dāng)n趨近于無窮大時,Zn的絕對值的邊界值,它可以表示為:

2.png

偽代碼

生成Mandelbrot集合的簡單的算法被稱為“逃逸時間算法”。為渲染圖像中的每個像素,根據(jù)復(fù)數(shù)值是否在邊界范圍之內(nèi),利用遞推關(guān)系進(jìn)行測試。經(jīng)過數(shù)次迭代之后,不屬于Mandelbrot集合的像素將快速逃逸,留下來的將是屬于Mandelbrot集合的像素。隨著計算時間的增加,迭代后的高階值將產(chǎn)生一個更詳細(xì)的圖像。在這里使用實現(xiàn)“逃逸”所需要的迭代次數(shù)來描繪圖像中的像素值。

3.png

將偽代碼和理論相關(guān)聯(lián)之后,得到:

4.png

在上圖中,復(fù)數(shù)的實部在x軸上,復(fù)數(shù)的虛部在y軸上。通過對圖形局部放大,可以看到整個形狀均重復(fù)可見。

代碼實現(xiàn)

逃逸時間算法的實現(xiàn)

5.png

在這里,我們使用了std::complex模板類來表示復(fù)數(shù)。利用這個函數(shù)來進(jìn)行測試,以檢查像素是否在集合之中,并返回“逃逸”迭代。

順序的Mandelbrot實現(xiàn)

6.png

在此程序中,通過依次遍歷渲染圖像中的像素來進(jìn)行測試,以檢查像素是否屬于Mandelbrot集合。

需要做的另一件事是把像素坐標(biāo)轉(zhuǎn)換Mandelbrot集合空間:

7.png

最后,將灰度值分配給像素,使用以下規(guī)則:

當(dāng)?shù)螖?shù)達(dá)到最大值時,像素為黑色(假定像素在Mandelbrot集合中);

否則根據(jù)逃脫“逃逸迭代”和縮放尺度,為像素分配一個灰度值,以適應(yīng)灰度范圍。

8.png

使用線性縮放轉(zhuǎn)換不足以感知的灰度變化。為了克服這個問題,使用一個平方根轉(zhuǎn)換來提升感知度(引用了Jeremy D. Frens博客中的內(nèi)容): 9.png

10.png

綠色曲線對應(yīng)于簡單的線性縮放轉(zhuǎn)換,藍(lán)色曲線對應(yīng)于平方根轉(zhuǎn)換,可以從中觀察到的最低值如何沿著斜坡正向上升。

并行Mandelbrot實現(xiàn)

在順序的Mandelbrot實現(xiàn)中,每個像素被獨(dú)立計算。為了優(yōu)化計算,我們可以利用現(xiàn)代處理器的多核架構(gòu)并行執(zhí)行多個像素的計算,利用OpenCV的CV :: parallel_for_框架可以輕松實現(xiàn)。

11.png

第一件事是聲明一個繼承CV :: ParallelLoopBody的自定義類,覆蓋virtual void operator ()(const cv::Range& range) const。

 operator ()表示將通過一個獨(dú)立的線程來處理像素的子集,這種拆分是自動完成的,以平均分配計算負(fù)荷,為此必須將像素索引坐標(biāo)轉(zhuǎn)換成2D [行,列]坐標(biāo)。還要注意的是,必須保持圖像的mat對象引用值,以便能夠適時地對圖像進(jìn)行修改。

調(diào)用并行執(zhí)行程序:

12.png

在這里,range表示將要執(zhí)行的操作總數(shù),即圖像中的像素總數(shù)。使用CV :: setNumThreads設(shè)置線程數(shù),還可以使用CV :: parallel_for_中的 nstripes參數(shù)指定拆分的數(shù)量CV :: parallel_for_。例如,如果處理器有4個線程,則設(shè)置CV :: setNumThreads(2)或者設(shè)置nstripes = 2應(yīng)該是一樣的,默認(rèn)情況下它會使用所有可用的處理器線程,但拆分后只有兩個線程。

為了簡化并行的實現(xiàn),C ++ 11標(biāo)準(zhǔn)刪除了ParallelMandelbrot類,采用lambda表達(dá)式代替它:

13.png

運(yùn)行結(jié)果

可以在原文找到完整的教程源代碼,并行實現(xiàn)的性能取決于CPU的種型。例如,在4核/ 8線程的CPU上,可以提速6.9倍左右。如果要問,為什么達(dá)不到8倍速,其中有很多因素;主要原因是由于:

創(chuàng)建和管理線程的額外開銷;

并行運(yùn)行的后臺進(jìn)程;

帶2個邏輯線程的4硬件核與8硬件核之間是有區(qū)別的。

由教程代碼生成的輸出圖像(可以對代碼進(jìn)行修改,以使用更多次的迭代,根據(jù)逃逸迭代次數(shù)來分配像素顏色,并使用調(diào)色板以獲得更美的圖像):

14.png

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)友個人發(fā)布,僅代表博主個人觀點(diǎn),如有侵權(quán)請聯(lián)系工作人員刪除。



關(guān)鍵詞: AI

相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉