陳思潤(rùn) 顧乃杰* 蘇俊杰 賀愛(ài)香
1(中國(guó)科學(xué)技術(shù)大學(xué)計(jì)算機(jī)科學(xué)技術(shù)學(xué)院 安徽 合肥 230027)2(中國(guó)科學(xué)技術(shù)大學(xué)安徽省計(jì)算與通信軟件重點(diǎn)實(shí)驗(yàn)室 安徽 合肥 230027)3(中國(guó)科學(xué)技術(shù)大學(xué)先進(jìn)技術(shù)研究院 安徽 合肥 230027)4(安徽新華學(xué)院信息工程學(xué)院 安徽 合肥 230088)
近年來(lái),移動(dòng)終端數(shù)量爆炸式增長(zhǎng)[1],且隨著人們對(duì)移動(dòng)設(shè)備圖像視覺(jué)的追求日益提高,Android設(shè)備的圖像處理速度難以滿足移動(dòng)客戶端的海量應(yīng)用發(fā)展需求。而OpenCV作為一款從PC端到嵌入式開(kāi)發(fā)領(lǐng)域的免費(fèi)的開(kāi)源跨平臺(tái)計(jì)算機(jī)視覺(jué)庫(kù),可提供從影像過(guò)濾及轉(zhuǎn)換到特性抽象與機(jī)器學(xué)習(xí)等幾十個(gè)不同類別的數(shù)百種算法[2]。它已被成千上萬(wàn)的開(kāi)發(fā)人員所使用,而且仍在不斷發(fā)展之中。
傳統(tǒng)的嵌入式平臺(tái)依賴于CPU的串行計(jì)算方案,難以處理這類大量的計(jì)算密集型問(wèn)題[3]。針對(duì)ARM架構(gòu),OpenCV源碼使用了SIMD(Single Instruction Multiple Data)指令[4]做了部分改寫(xiě),實(shí)驗(yàn)分析其仍然存在優(yōu)化的空間。NEON技術(shù)通過(guò)對(duì)數(shù)據(jù)并行處理,能夠很好地處理矩陣運(yùn)算等存在大量數(shù)據(jù)相關(guān)性低的計(jì)算操作,因此適用于視頻轉(zhuǎn)碼、語(yǔ)音處理、圖像處理等場(chǎng)景。例如文獻(xiàn)[3]使用了NEON技術(shù)以及GPGPU對(duì)Jpeg靜態(tài)圖像編碼技術(shù)、Mpeg4動(dòng)態(tài)圖像編碼技術(shù)進(jìn)行了優(yōu)化,其編解碼性能提升了四倍左右。文獻(xiàn)[5]使用了NEON技術(shù)在ARM Cortex-A系列的架構(gòu)上實(shí)現(xiàn)了AVS多媒體視頻文件解碼工具的加速,實(shí)驗(yàn)證明NEON技術(shù)加速效果明顯。文獻(xiàn)[11]同樣在Cortex-A8處理器架構(gòu)上實(shí)現(xiàn)VC-1視頻編解碼軟件的SIMD指令優(yōu)化,解碼性能提升30%以上。
考慮到移動(dòng)終端用戶對(duì)圖像視覺(jué)質(zhì)量要求日益提高,本文針對(duì)Android平臺(tái)的ARM架構(gòu),使用NEON技術(shù)對(duì)OpenCV庫(kù)中的濾波函數(shù)進(jìn)行了優(yōu)化,濾波效率提升明顯。
ARMv8是ARM公司在2011年發(fā)布的首款支持64位指令集的處理器架構(gòu)。ARMv8保存了ARMv7架構(gòu)的特性,支持Thumb-2、針對(duì)浮點(diǎn)FPU的VFP硬件擴(kuò)展、DSP 擴(kuò)展、Thumb指令集、主流的嵌入式OS(Linux、Android、Windows Mobile、Windows Phone、Symbian)、以及支持分支預(yù)測(cè)等技術(shù)。除此之外ARMv8還進(jìn)行了一些功能擴(kuò)展,支持TrustZone、虛擬化技術(shù)以及NEON advanced SIMD技術(shù)。
本文實(shí)驗(yàn)使用的ARM Cortex-A72處理器是基于ARMv8-A 架構(gòu),支持最新的64位指令集AArch64。AArch64相較于AArch32使用了更少的條件指令,刪除了LDM/STM等實(shí)現(xiàn)復(fù)雜的指令,且AArch64指令集對(duì)32位和64位指令分別解碼,簡(jiǎn)化了解碼表,允許更多先進(jìn)的分支預(yù)測(cè)技術(shù)。此外,ARMv8針對(duì)AArch64指令集支持的SIMD指令集做了更多的改進(jìn),支持雙精度的浮點(diǎn)運(yùn)算,在支持NEON指令集擴(kuò)展的同時(shí),其ARM核和NEON核的執(zhí)行流水線分開(kāi)執(zhí)行,能夠在16FF+的處理器技術(shù)中實(shí)現(xiàn)3 GHz以上的頻率。
SIMD(Single Instruction Multiple Data)意思就是指一條指令能夠同時(shí)處理多個(gè)數(shù)據(jù)的數(shù)據(jù)級(jí)并行計(jì)算技術(shù),其執(zhí)行機(jī)制如圖1所示。
圖1 SIMD指令機(jī)制示意圖
NEON是一種基于SIMD思想的數(shù)據(jù)級(jí)并行技術(shù),它旨在加速信號(hào)處理算法,能夠加速音頻和視頻處理、語(yǔ)音和面部識(shí)別、計(jì)算機(jī)視覺(jué)和深度學(xué)習(xí)等應(yīng)用技術(shù)。NEON技術(shù)從ARMv7版本開(kāi)始被采用,目前可以在ARM Cortex-A和Cortex-R系列處理器中使用。NEON結(jié)合了64位和128位的SIMD指令集,ARMv8 NEON 指令集架構(gòu)具有16個(gè)128位的四字寄存器,命名為 Q0-Q15,這16個(gè)寄存器又可以拆分成 32個(gè)64 雙字寄存器,命名為 D0-D31。圖2描述了NEON寄存器在多通道內(nèi)同時(shí)進(jìn)行多數(shù)據(jù)并行計(jì)算的過(guò)程。
圖2 NEON指令并行計(jì)算
研究人員使用NEON技術(shù)進(jìn)行優(yōu)化主要有四種方法,具體包括匯編優(yōu)化、內(nèi)聯(lián)函數(shù)(intrinsic)、自動(dòng)向量化以及NEON優(yōu)化庫(kù)。
使用手寫(xiě)匯編的形式,理論上可以達(dá)到NEON指令最高的優(yōu)化效果,但是由于需要跟底層寄存器流水線打交道,所以在幾種方法中難度最高,優(yōu)化過(guò)程更復(fù)雜。手寫(xiě)匯編對(duì)開(kāi)發(fā)者要求頗高,要想寫(xiě)出高效的匯編代碼必須要有一定的匯編基礎(chǔ),以及體系結(jié)構(gòu)相關(guān)的知識(shí)。使用不當(dāng),則會(huì)適得其反影響其性能。
ARM為Cortex-A系列處理器生成NEON代碼定義了一組新的數(shù)據(jù)類型以及C語(yǔ)言形式的內(nèi)聯(lián)函數(shù)接口。在開(kāi)發(fā)人員進(jìn)行C/C++語(yǔ)言開(kāi)發(fā)時(shí)形同普通的函數(shù)調(diào)用,且ARMv8版本的NEON指令集支持64位的雙精度浮點(diǎn)運(yùn)算,擁有更多的指令操作。
調(diào)用ARM官方定義的內(nèi)聯(lián)函數(shù)時(shí),程序需要在代碼中包含頭文件“arm_neon.h”,內(nèi)斂函數(shù)具體數(shù)據(jù)格式以及函數(shù)原型使用方法舉例如下:
#include
uint32x4_t example_func(uint32x4_t input)
{
return(vaddq_u32(input, input));
}
其中uint32x4_t數(shù)據(jù)類型表示使用了128位Q寄存器,并且數(shù)據(jù)類型為無(wú)符號(hào)的32位整型數(shù)。函數(shù)vaddq_u32完成對(duì)兩個(gè)寄存器中各個(gè)32位無(wú)符號(hào)整型數(shù)據(jù)的加法操作。
使用內(nèi)聯(lián)函數(shù)相比較于手寫(xiě)匯編的形式要簡(jiǎn)單,對(duì)開(kāi)發(fā)人員而言是一種比較友好的開(kāi)發(fā)方式,并且大多數(shù)時(shí)候優(yōu)化效果接近或者達(dá)到手工編寫(xiě)匯編代碼的形式。
自動(dòng)向量化是由ARM向量化編譯器提供,需要在編譯時(shí)加上相關(guān)的命令參數(shù),如:gcc編譯器使用“-ftree-vectorize”、armcc編譯器使用“-vectorize”來(lái)開(kāi)啟自動(dòng)向量化功能。這種方法的優(yōu)勢(shì)是使用簡(jiǎn)單,不需要太多額外工作,且跨平臺(tái)移植方便,缺點(diǎn)是該種方式優(yōu)化的效果較差。
Ne10[4]是一個(gè)單獨(dú)的開(kāi)源庫(kù),可以把它直接嵌入到項(xiàng)目里面去(目前支持平臺(tái)有l(wèi)inux,android,ios)。Ne10已實(shí)現(xiàn)了部分功能接口,具體有4個(gè)模塊:dsp、math、imgproc、physics。目前Ne10提供的API功能有限,數(shù)據(jù)類型相較于ARM官方的定義也有差異,研究人員使用需要改動(dòng)甚至重新編寫(xiě)部分功能函數(shù),容易破壞平臺(tái)間的可移植性。
對(duì)以上介紹的四種方法,權(quán)衡優(yōu)化效果與使用復(fù)雜程度的關(guān)系,本文選擇使用內(nèi)聯(lián)函數(shù)的方式對(duì)OpenCV庫(kù)中的濾波函數(shù)進(jìn)行優(yōu)化,部分熱點(diǎn)代碼使用手寫(xiě)匯編代碼的方式實(shí)現(xiàn)進(jìn)一步的細(xì)致優(yōu)化。
本文針對(duì)2.4.13版本的開(kāi)源代碼OpenCV庫(kù),對(duì)濾波函數(shù)進(jìn)行優(yōu)化,具體使用ARM Cortex-A系列處理器支持的NEON技術(shù)對(duì)圖像處理模塊的濾波函數(shù)進(jìn)行了優(yōu)化。OpenCV主要包含imgproc、features2d、highgui、core、calib3d等模塊。由于OpenCV濾波函數(shù)較多,本文僅以中值濾波函數(shù)為例,說(shuō)明NEON優(yōu)化思路以及具體實(shí)現(xiàn)方式。
中值濾波是由Tukey提出的一個(gè)非線性濾波器,對(duì)于中值的計(jì)算傳統(tǒng)方法是使用排序?qū)崿F(xiàn),標(biāo)準(zhǔn)的一維中值濾波器定義如下:
yi=med{xi-r,xi-r+1,…,xi,…,xi+r}
(1)
式中:med表示取中值操作,具體實(shí)現(xiàn)如算法1所示。
算法1傳統(tǒng)中值濾波算法
輸入: imageXsize ism×n,kernel radiusr
輸出:medvalueY
(1) for i=r to m-r do
(2) for j=r to n-r do
(3) initialize list A[r2]
(4) for a = i-r to i+r
(5) for b = j-r to j+r
(6) add X(a, b) to A[r2]
(7) end
(8) end
(9) sort A[r2] then Y(i,j) = A[r2/2]
(10) end
(11) end
根據(jù)中值濾波函數(shù)源碼的實(shí)現(xiàn)可知,定義兩個(gè)向量X、Y,由于X[i]與Y[i]之間的操作結(jié)果并不影響X[i+1]與Y[i+1]之間的操作結(jié)果,數(shù)據(jù)之間沒(méi)有關(guān)聯(lián),理論上可以同時(shí)進(jìn)行而不會(huì)改變最終結(jié)果。本文通過(guò)使用objdump反匯編工具分析OpenCV中值濾波源碼,發(fā)現(xiàn)未優(yōu)化前每次執(zhí)行完一次加法指令需要額外增加一條cmp和jmp指令,不僅增加了額外的指令開(kāi)銷也不利于指令流水。而NEON技術(shù)一條指令最大支持8次操作同時(shí)并行執(zhí)行,不僅大大減少了條件判斷指令開(kāi)銷,而且充分利用了ARM處理的多級(jí)流水線功能。Cortex-A系列處理器擁有兩級(jí) Cache,且L2級(jí)Cache跟NEON有直接相連的接口,方便數(shù)據(jù)交互,這種特性使得數(shù)據(jù)并行能夠順利進(jìn)行大大減少因cache失效而產(chǎn)生的額外訪存開(kāi)銷。
根據(jù)分析,中值濾波函數(shù)核心代碼不存在復(fù)雜的邏輯控制語(yǔ)句,且代碼所用的數(shù)據(jù)結(jié)構(gòu)ushort,為16位無(wú)符號(hào)整數(shù)類型,而NEON的Q寄存器結(jié)構(gòu)支持16x8 bit的數(shù)據(jù)格式,滿足NEON指令集的數(shù)據(jù)對(duì)齊的要求。輸入數(shù)據(jù)類型為ushort,剛好可以通過(guò)對(duì)齊轉(zhuǎn)化到uint16x8_t從而方便NEON處理器的使用。具體轉(zhuǎn)換以定義一個(gè)ushort向量HT[16][8]為例,HT[0][0]~HT[15][7]存儲(chǔ)地址連續(xù),根據(jù)C++數(shù)組內(nèi)存為行優(yōu)先的存放規(guī)則可知該二維數(shù)組在內(nèi)存中的存放順序?yàn)椋篐T[0][0],HT[0][1],…,HT[15][7]。通過(guò)數(shù)據(jù)類型轉(zhuǎn)換為uint16x8_t類型的RT[16],具體對(duì)應(yīng)關(guān)系如圖3所示。
圖3 數(shù)據(jù)類型映射關(guān)系
NEON指令VADD.I16 Q0、Q1、Q2使用Q寄存器實(shí)現(xiàn)8路16位無(wú)符號(hào)整數(shù)的并行加法操作,對(duì)比原來(lái)的16位無(wú)符號(hào)整數(shù)的循環(huán)疊加操作,減少了大量時(shí)間開(kāi)銷,8路并行加法具體操作過(guò)程如圖4所示。
圖4 8路16位整數(shù)并行加法
vld1.16 {d18-d19}, [r3]
add r3, sp, #36
vld1.16 {d22-d23}, [r3]
add r3, sp, #20
vld1.16 {d16-d17}, [r3]
add r3, sp, #52
vld1.16 {d20-d21}, [r3
四是創(chuàng)新人才匱乏,人才結(jié)構(gòu)性矛盾突出。目前,東營(yíng)市擁有各類科技人員18.6萬(wàn)人,但大多數(shù)分布在油田、石油大學(xué)以及教育、衛(wèi)生系統(tǒng),而企業(yè)自有一線研發(fā)人員不能充分滿足技術(shù)創(chuàng)新需要,具有特殊專業(yè)技能的高層次人才匱乏,科技創(chuàng)新后勁需進(jìn)一步加強(qiáng)。
vadd.i16 q9, q9, q11
vadd.i16 q8, q8, q10
ldr r2, [sp, #68
ldr r3, [r4]
add r1, sp, #36
cmp r2, r3
add r3, sp, #52
vst1.16 {d18-d19}, [r1]
vst1.16 {d16-d17}, [r3]
算法2對(duì)輸入圖像像素每一列維護(hù)一個(gè)大小固定的一維列數(shù)組Hist,使用一維列數(shù)組對(duì)濾波窗口像素值進(jìn)行更新,極大地減少了比較操作,快速高效地得到中值。將數(shù)組更新的實(shí)現(xiàn)方式移植到ARM平臺(tái),且利用ARM Cortex-A系列支持的NEON技術(shù),使用SIMD單指令多數(shù)據(jù)流的并行計(jì)算方法,改進(jìn)中值濾波算法,具體實(shí)現(xiàn)如算法2所示。
算法2優(yōu)化中值濾波算法
輸入: imageXsize ism×n,kernel radiusr
輸出:medvalueY
(1) 初始化列數(shù)組Hist1…n,kernelH,左右圖片邊界添加m/2個(gè)像素點(diǎn),上下邊界添加n/2個(gè)像素點(diǎn);
(2) for i=1 to m do
(3) for j=1 to n step 8 do
(4) Remove Xi-r-1,j+r from Histj+r
(5) //NEON 并行計(jì)算
(6) SIMD_add Xi+r,j+r<- Histj+r
(7) SIMD_sub Histj+r<- Histj-r-1
(8) SIMD_add H <- Histj+r
(9) //更新濾波中值結(jié)果
(10) Y(i,j) <- median(H)
(11) end
(12) end
算法2的核心思想是對(duì)輸入圖像的每一列維護(hù)一個(gè)一維列數(shù)組Hist,逐行遍歷圖像像素點(diǎn),以每次遍歷的當(dāng)前像素為窗口中心像素,建立濾波窗口,提取窗口內(nèi)所有像素值(N=m×n),獲取擁有N個(gè)像素點(diǎn)的一維數(shù)組Hist。累加數(shù)組Hist中的每個(gè)像素點(diǎn)數(shù)。將步驟(8)更新后的數(shù)組H的中值賦值給窗口中心元素,完成中值濾波操作。分析可知,每次操作的空間只是對(duì)一維數(shù)組Hist進(jìn)行更新,且算法2中第(6)、(7)、(8)步定義的SIMD_add以及SIMD_sub操作均是使用NEON數(shù)據(jù)并行技術(shù)實(shí)現(xiàn),具體實(shí)現(xiàn)方式為3.4節(jié)介紹的匯編形式,且在3.4節(jié)給出了SIMD_add的詳細(xì)匯編指令序列。
對(duì)算法2時(shí)間復(fù)雜度進(jìn)行分析,Hist的更新操作可在常數(shù)時(shí)間完成,每次求中值操作,只是對(duì)大小固定的數(shù)組Hist進(jìn)行操作,其復(fù)雜度為O(1)時(shí)間,對(duì)于m×n的圖片,時(shí)間復(fù)雜度約為O(mn),相較于傳統(tǒng)方法復(fù)雜度大大降低。
本文實(shí)驗(yàn)所用平臺(tái)為ARM,系統(tǒng)使用linux kernel 3.4.35版本,具體CPU型號(hào)為Cortex-A72,GPU為maliT880。
實(shí)驗(yàn)使用arm-linux-gcc交叉編譯器,首先通過(guò)修改OpenCV目錄的cmake文件設(shè)置好編譯環(huán)境,然后在linux平臺(tái)交叉編譯OpenCV源碼,最后通過(guò)adb shell工具連接ARM開(kāi)發(fā)板將生成的可執(zhí)行文件發(fā)送到開(kāi)發(fā)板進(jìn)行相關(guān)性能測(cè)試。
針對(duì)中值濾波函數(shù),分別使用3×3、5×5的采集窗口對(duì)所選測(cè)試用例進(jìn)行算法正確性測(cè)試。給出256×256像素的帶椒鹽噪聲的圖片用例,觀察濾波函數(shù)優(yōu)化后的濾波效果。
圖5為不同窗口的中值濾波效果圖,其中(a)為帶椒鹽噪聲的測(cè)試用例圖片,(b)、(c)分別為3×3、5×5的中值濾波后的圖片,可以看出優(yōu)化后濾波效果明顯,說(shuō)明優(yōu)化后去噪性能并沒(méi)有降低。
圖5 中值濾波效果圖
使用OpenCV自帶python腳本對(duì)中值濾波優(yōu)化前后性能進(jìn)行對(duì)比測(cè)試,表1給出了中值濾波函數(shù)分別使用3×3、5×5兩種采集窗口在127×61、320×240、640×480、1 280×720這四種不同尺寸的圖片上的對(duì)比優(yōu)化效果。分析表中數(shù)據(jù)可以得知,采用NEON技術(shù)優(yōu)化后的中值濾波函數(shù),在不同尺寸圖像以及不同的采集窗口均有很好的優(yōu)化效果,平均加速比均超過(guò)了18倍,最高加速比達(dá)35倍多,優(yōu)化效果顯著。
表1 中值濾波測(cè)試結(jié)果 ms
同時(shí)本文對(duì)OpenCV庫(kù)的其他濾波函數(shù)進(jìn)行了同樣的加速比測(cè)試。由于各類濾波函數(shù)均存在大量矩陣運(yùn)算操作,數(shù)據(jù)相關(guān)性低,且存在大量重復(fù)計(jì)算操作,采用NEON SIMD數(shù)據(jù)級(jí)并行技術(shù)對(duì)各濾波函數(shù)進(jìn)行優(yōu)化,效果均表現(xiàn)良好。
測(cè)試用例統(tǒng)一使用256×256尺寸的圖片。通過(guò)python腳本測(cè)試各濾波函數(shù),多次測(cè)試分別得出的各濾波函數(shù)濾波時(shí)間,經(jīng)過(guò)計(jì)算得出各函數(shù)對(duì)應(yīng)的加速比的平均值。
圖6給出了優(yōu)化后的各濾波函數(shù)分別在3×3以及5×5兩種窗口下的加速比數(shù)據(jù)。從圖中可以看出NEON優(yōu)化加速效果明顯,各函數(shù)加速比均超過(guò)兩倍,其中中值濾波函數(shù)優(yōu)化加速比達(dá)17倍之多,且隨著濾波窗口的增大,加速比也隨之增大,對(duì)應(yīng)其優(yōu)化效果越好。
圖6 濾波函數(shù)加速比
本文針對(duì)ARM架構(gòu),根據(jù)OpenCV函數(shù)庫(kù)的性能提升需求,使用了NEON數(shù)據(jù)并行技術(shù)對(duì)濾波函數(shù)進(jìn)行了優(yōu)化。實(shí)驗(yàn)證明NEON技術(shù)在OpenCV源碼中優(yōu)化效果顯著,SIMD數(shù)據(jù)級(jí)并行優(yōu)化方法能較好地提高濾波函數(shù)的時(shí)間性能。
針對(duì)ARM架構(gòu),除了NEON技術(shù)可用于優(yōu)化,還可以考慮利用GPU優(yōu)化,以及利用ARM多線程進(jìn)行相關(guān)優(yōu)化。下一步將著重從GPU、多線程技術(shù)展開(kāi)進(jìn)一步的優(yōu)化工作。