陳思潤 顧乃杰* 蘇俊杰 賀愛香
1(中國科學技術大學計算機科學技術學院 安徽 合肥 230027)2(中國科學技術大學安徽省計算與通信軟件重點實驗室 安徽 合肥 230027)3(中國科學技術大學先進技術研究院 安徽 合肥 230027)4(安徽新華學院信息工程學院 安徽 合肥 230088)
近年來,移動終端數量爆炸式增長[1],且隨著人們對移動設備圖像視覺的追求日益提高,Android設備的圖像處理速度難以滿足移動客戶端的海量應用發展需求。而OpenCV作為一款從PC端到嵌入式開發領域的免費的開源跨平臺計算機視覺庫,可提供從影像過濾及轉換到特性抽象與機器學習等幾十個不同類別的數百種算法[2]。它已被成千上萬的開發人員所使用,而且仍在不斷發展之中。
傳統的嵌入式平臺依賴于CPU的串行計算方案,難以處理這類大量的計算密集型問題[3]。針對ARM架構,OpenCV源碼使用了SIMD(Single Instruction Multiple Data)指令[4]做了部分改寫,實驗分析其仍然存在優化的空間。NEON技術通過對數據并行處理,能夠很好地處理矩陣運算等存在大量數據相關性低的計算操作,因此適用于視頻轉碼、語音處理、圖像處理等場景。例如文獻[3]使用了NEON技術以及GPGPU對Jpeg靜態圖像編碼技術、Mpeg4動態圖像編碼技術進行了優化,其編解碼性能提升了四倍左右。文獻[5]使用了NEON技術在ARM Cortex-A系列的架構上實現了AVS多媒體視頻文件解碼工具的加速,實驗證明NEON技術加速效果明顯。文獻[11]同樣在Cortex-A8處理器架構上實現VC-1視頻編解碼軟件的SIMD指令優化,解碼性能提升30%以上。
考慮到移動終端用戶對圖像視覺質量要求日益提高,本文針對Android平臺的ARM架構,使用NEON技術對OpenCV庫中的濾波函數進行了優化,濾波效率提升明顯。
ARMv8是ARM公司在2011年發布的首款支持64位指令集的處理器架構。ARMv8保存了ARMv7架構的特性,支持Thumb-2、針對浮點FPU的VFP硬件擴展、DSP 擴展、Thumb指令集、主流的嵌入式OS(Linux、Android、Windows Mobile、Windows Phone、Symbian)、以及支持分支預測等技術。除此之外ARMv8還進行了一些功能擴展,支持TrustZone、虛擬化技術以及NEON advanced SIMD技術。
本文實驗使用的ARM Cortex-A72處理器是基于ARMv8-A 架構,支持最新的64位指令集AArch64。AArch64相較于AArch32使用了更少的條件指令,刪除了LDM/STM等實現復雜的指令,且AArch64指令集對32位和64位指令分別解碼,簡化了解碼表,允許更多先進的分支預測技術。此外,ARMv8針對AArch64指令集支持的SIMD指令集做了更多的改進,支持雙精度的浮點運算,在支持NEON指令集擴展的同時,其ARM核和NEON核的執行流水線分開執行,能夠在16FF+的處理器技術中實現3 GHz以上的頻率。
SIMD(Single Instruction Multiple Data)意思就是指一條指令能夠同時處理多個數據的數據級并行計算技術,其執行機制如圖1所示。

圖1 SIMD指令機制示意圖
NEON是一種基于SIMD思想的數據級并行技術,它旨在加速信號處理算法,能夠加速音頻和視頻處理、語音和面部識別、計算機視覺和深度學習等應用技術。NEON技術從ARMv7版本開始被采用,目前可以在ARM Cortex-A和Cortex-R系列處理器中使用。NEON結合了64位和128位的SIMD指令集,ARMv8 NEON 指令集架構具有16個128位的四字寄存器,命名為 Q0-Q15,這16個寄存器又可以拆分成 32個64 雙字寄存器,命名為 D0-D31。圖2描述了NEON寄存器在多通道內同時進行多數據并行計算的過程。

圖2 NEON指令并行計算
研究人員使用NEON技術進行優化主要有四種方法,具體包括匯編優化、內聯函數(intrinsic)、自動向量化以及NEON優化庫。
使用手寫匯編的形式,理論上可以達到NEON指令最高的優化效果,但是由于需要跟底層寄存器流水線打交道,所以在幾種方法中難度最高,優化過程更復雜。手寫匯編對開發者要求頗高,要想寫出高效的匯編代碼必須要有一定的匯編基礎,以及體系結構相關的知識。使用不當,則會適得其反影響其性能。
ARM為Cortex-A系列處理器生成NEON代碼定義了一組新的數據類型以及C語言形式的內聯函數接口。在開發人員進行C/C++語言開發時形同普通的函數調用,且ARMv8版本的NEON指令集支持64位的雙精度浮點運算,擁有更多的指令操作。
調用ARM官方定義的內聯函數時,程序需要在代碼中包含頭文件“arm_neon.h”,內斂函數具體數據格式以及函數原型使用方法舉例如下:
#include
uint32x4_t example_func(uint32x4_t input)
{
return(vaddq_u32(input, input));
}
其中uint32x4_t數據類型表示使用了128位Q寄存器,并且數據類型為無符號的32位整型數。函數vaddq_u32完成對兩個寄存器中各個32位無符號整型數據的加法操作。
使用內聯函數相比較于手寫匯編的形式要簡單,對開發人員而言是一種比較友好的開發方式,并且大多數時候優化效果接近或者達到手工編寫匯編代碼的形式。
自動向量化是由ARM向量化編譯器提供,需要在編譯時加上相關的命令參數,如:gcc編譯器使用“-ftree-vectorize”、armcc編譯器使用“-vectorize”來開啟自動向量化功能。這種方法的優勢是使用簡單,不需要太多額外工作,且跨平臺移植方便,缺點是該種方式優化的效果較差。
Ne10[4]是一個單獨的開源庫,可以把它直接嵌入到項目里面去(目前支持平臺有linux,android,ios)。Ne10已實現了部分功能接口,具體有4個模塊:dsp、math、imgproc、physics。目前Ne10提供的API功能有限,數據類型相較于ARM官方的定義也有差異,研究人員使用需要改動甚至重新編寫部分功能函數,容易破壞平臺間的可移植性。
對以上介紹的四種方法,權衡優化效果與使用復雜程度的關系,本文選擇使用內聯函數的方式對OpenCV庫中的濾波函數進行優化,部分熱點代碼使用手寫匯編代碼的方式實現進一步的細致優化。
本文針對2.4.13版本的開源代碼OpenCV庫,對濾波函數進行優化,具體使用ARM Cortex-A系列處理器支持的NEON技術對圖像處理模塊的濾波函數進行了優化。OpenCV主要包含imgproc、features2d、highgui、core、calib3d等模塊。由于OpenCV濾波函數較多,本文僅以中值濾波函數為例,說明NEON優化思路以及具體實現方式。
中值濾波是由Tukey提出的一個非線性濾波器,對于中值的計算傳統方法是使用排序實現,標準的一維中值濾波器定義如下:
yi=med{xi-r,xi-r+1,…,xi,…,xi+r}
(1)
式中:med表示取中值操作,具體實現如算法1所示。
算法1傳統中值濾波算法
輸入: 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

根據中值濾波函數源碼的實現可知,定義兩個向量X、Y,由于X[i]與Y[i]之間的操作結果并不影響X[i+1]與Y[i+1]之間的操作結果,數據之間沒有關聯,理論上可以同時進行而不會改變最終結果。本文通過使用objdump反匯編工具分析OpenCV中值濾波源碼,發現未優化前每次執行完一次加法指令需要額外增加一條cmp和jmp指令,不僅增加了額外的指令開銷也不利于指令流水。而NEON技術一條指令最大支持8次操作同時并行執行,不僅大大減少了條件判斷指令開銷,而且充分利用了ARM處理的多級流水線功能。Cortex-A系列處理器擁有兩級 Cache,且L2級Cache跟NEON有直接相連的接口,方便數據交互,這種特性使得數據并行能夠順利進行大大減少因cache失效而產生的額外訪存開銷。
根據分析,中值濾波函數核心代碼不存在復雜的邏輯控制語句,且代碼所用的數據結構ushort,為16位無符號整數類型,而NEON的Q寄存器結構支持16x8 bit的數據格式,滿足NEON指令集的數據對齊的要求。輸入數據類型為ushort,剛好可以通過對齊轉化到uint16x8_t從而方便NEON處理器的使用。具體轉換以定義一個ushort向量HT[16][8]為例,HT[0][0]~HT[15][7]存儲地址連續,根據C++數組內存為行優先的存放規則可知該二維數組在內存中的存放順序為:HT[0][0],HT[0][1],…,HT[15][7]。通過數據類型轉換為uint16x8_t類型的RT[16],具體對應關系如圖3所示。

圖3 數據類型映射關系
NEON指令VADD.I16 Q0、Q1、Q2使用Q寄存器實現8路16位無符號整數的并行加法操作,對比原來的16位無符號整數的循環疊加操作,減少了大量時間開銷,8路并行加法具體操作過程如圖4所示。

圖4 8路16位整數并行加法
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
四是創新人才匱乏,人才結構性矛盾突出。目前,東營市擁有各類科技人員18.6萬人,但大多數分布在油田、石油大學以及教育、衛生系統,而企業自有一線研發人員不能充分滿足技術創新需要,具有特殊專業技能的高層次人才匱乏,科技創新后勁需進一步加強。
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對輸入圖像像素每一列維護一個大小固定的一維列數組Hist,使用一維列數組對濾波窗口像素值進行更新,極大地減少了比較操作,快速高效地得到中值。將數組更新的實現方式移植到ARM平臺,且利用ARM Cortex-A系列支持的NEON技術,使用SIMD單指令多數據流的并行計算方法,改進中值濾波算法,具體實現如算法2所示。
算法2優化中值濾波算法
輸入: imageXsize ism×n,kernel radiusr
輸出:medvalueY
(1) 初始化列數組Hist1…n,kernelH,左右圖片邊界添加m/2個像素點,上下邊界添加n/2個像素點;
(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 并行計算
(6) SIMD_add Xi+r,j+r<- Histj+r
(7) SIMD_sub Histj+r<- Histj-r-1
(8) SIMD_add H <- Histj+r
(9) //更新濾波中值結果
(10) Y(i,j) <- median(H)
(11) end
(12) end
算法2的核心思想是對輸入圖像的每一列維護一個一維列數組Hist,逐行遍歷圖像像素點,以每次遍歷的當前像素為窗口中心像素,建立濾波窗口,提取窗口內所有像素值(N=m×n),獲取擁有N個像素點的一維數組Hist。累加數組Hist中的每個像素點數。將步驟(8)更新后的數組H的中值賦值給窗口中心元素,完成中值濾波操作。分析可知,每次操作的空間只是對一維數組Hist進行更新,且算法2中第(6)、(7)、(8)步定義的SIMD_add以及SIMD_sub操作均是使用NEON數據并行技術實現,具體實現方式為3.4節介紹的匯編形式,且在3.4節給出了SIMD_add的詳細匯編指令序列。
對算法2時間復雜度進行分析,Hist的更新操作可在常數時間完成,每次求中值操作,只是對大小固定的數組Hist進行操作,其復雜度為O(1)時間,對于m×n的圖片,時間復雜度約為O(mn),相較于傳統方法復雜度大大降低。
本文實驗所用平臺為ARM,系統使用linux kernel 3.4.35版本,具體CPU型號為Cortex-A72,GPU為maliT880。
實驗使用arm-linux-gcc交叉編譯器,首先通過修改OpenCV目錄的cmake文件設置好編譯環境,然后在linux平臺交叉編譯OpenCV源碼,最后通過adb shell工具連接ARM開發板將生成的可執行文件發送到開發板進行相關性能測試。
針對中值濾波函數,分別使用3×3、5×5的采集窗口對所選測試用例進行算法正確性測試。給出256×256像素的帶椒鹽噪聲的圖片用例,觀察濾波函數優化后的濾波效果。
圖5為不同窗口的中值濾波效果圖,其中(a)為帶椒鹽噪聲的測試用例圖片,(b)、(c)分別為3×3、5×5的中值濾波后的圖片,可以看出優化后濾波效果明顯,說明優化后去噪性能并沒有降低。

圖5 中值濾波效果圖
使用OpenCV自帶python腳本對中值濾波優化前后性能進行對比測試,表1給出了中值濾波函數分別使用3×3、5×5兩種采集窗口在127×61、320×240、640×480、1 280×720這四種不同尺寸的圖片上的對比優化效果。分析表中數據可以得知,采用NEON技術優化后的中值濾波函數,在不同尺寸圖像以及不同的采集窗口均有很好的優化效果,平均加速比均超過了18倍,最高加速比達35倍多,優化效果顯著。

表1 中值濾波測試結果 ms
同時本文對OpenCV庫的其他濾波函數進行了同樣的加速比測試。由于各類濾波函數均存在大量矩陣運算操作,數據相關性低,且存在大量重復計算操作,采用NEON SIMD數據級并行技術對各濾波函數進行優化,效果均表現良好。
測試用例統一使用256×256尺寸的圖片。通過python腳本測試各濾波函數,多次測試分別得出的各濾波函數濾波時間,經過計算得出各函數對應的加速比的平均值。
圖6給出了優化后的各濾波函數分別在3×3以及5×5兩種窗口下的加速比數據。從圖中可以看出NEON優化加速效果明顯,各函數加速比均超過兩倍,其中中值濾波函數優化加速比達17倍之多,且隨著濾波窗口的增大,加速比也隨之增大,對應其優化效果越好。

圖6 濾波函數加速比
本文針對ARM架構,根據OpenCV函數庫的性能提升需求,使用了NEON數據并行技術對濾波函數進行了優化。實驗證明NEON技術在OpenCV源碼中優化效果顯著,SIMD數據級并行優化方法能較好地提高濾波函數的時間性能。
針對ARM架構,除了NEON技術可用于優化,還可以考慮利用GPU優化,以及利用ARM多線程進行相關優化。下一步將著重從GPU、多線程技術展開進一步的優化工作。