天天看點

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

下面進行Prewitt的NEON加速設計,将實作的具體思路描述一下。

S0. Prewitt的C語言實作

我把Prewitt算子的計算過程按下圖重新進行表示:

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

可以看到該算法主要分為了三個步驟去實作:

1. 像素灰階化,将源圖像數像素點的彩色圖像轉換為灰階值,使用常用的公式:GRAY=(R*77+G*151+G*28)/256
2. x、y方向一階梯度計算
3. 兩個方向梯度均方根計算
           

C代碼:

unsigned char pixel_rgb2gray(unsigned char * rgb)
{
    unsigned char gray;
    unsigned int y = (rgb[] * ) + (rgb[] * ) + (rgb[] * );
    gray = (y >> );
    return gray;
}

int prewitt_c_perfect_loop(IMG_SRC_MAT src, IMG_DST_MAT dst, unsigned int uiParam2, unsigned int uiParam3)
{
    unsigned int i,j;
    unsigned int gx,gy,g;
    unsigned int sum_check=;
    unsigned int array[FILTER_WINDOW][FILTER_WINDOW];
    //perfect loop
    for(i=; i<(IMG_ROWS-); i++){
        for(j=; j<(IMG_COLS-); j++){
            array[][]=pixel_rgb2gray((*src)[i-][j-]);
            array[][]=pixel_rgb2gray((*src)[i-][j]);
            array[][]=pixel_rgb2gray((*src)[i-][j+]);
            array[][]=pixel_rgb2gray((*src)[i][j-]);
            array[][]=pixel_rgb2gray((*src)[i][j]);
            array[][]=pixel_rgb2gray((*src)[i][j+]);
            array[][]=pixel_rgb2gray((*src)[i+][j-]);
            array[][]=pixel_rgb2gray((*src)[i+][j]);
            array[][]=pixel_rgb2gray((*src)[i+][j+]);
            gx = (array[][] + array[][] + array[][]) - (array[][] + array[][] + array[][]);
            gy = (array[][] + array[][] + array[][]) - (array[][] + array[][] + array[][]);
            g = sqrt(gx*gx + gy*gy);
            if (g > ) g = ;
            //else if (g < 0) g = 0;
            (*dst)[i][j][] = g;
            (*dst)[i][j][] = g;
            (*dst)[i][j][] = g;
            sum_check += g;
        }
    }
    return sum_check;
}
           

可見,針對輸出圖像的每個像素點都要進行單獨的Prewitt計算,整體計算的循環次數的數值是比較大的,而且在RGB轉換灰階、均方根計算時用到了乘法和平方根的計算,這對于CPU來說占用大量的計算時間。具體的計算時間就不在這裡說了,總之要達到60fps(16.667ms)的速率是達不到的,實作不了Prewitt的實時計算。

S1. Prewitt的NEON加速方案

Prewitt同衆多濾波算法類似,采用的是3*3滑動視窗逐個掃描整幅輸入圖像,然後進行加權重、平方根等計算得到最終一個像素的結果。這些算法有一個很明顯的特點就是計算濾波結果時,前一個像素濾波與接下來的一個像素會共用到6個同樣的原始像素點,即兩次的3*3視窗是有重疊部分的。如下圖所示,第一次的視窗為紅框,第二次為綠框,它們有6個共用的原始像素點。

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

因連續的兩個像素濾波會共用原始像素點資料,若在計算時能夠進行結果緩存或者其他一些處理,将能夠大大降低算法對從DDR讀取源資料、灰階變換等計算過程的CPU處理時間。

NEON有并行計算的能力,可以同時針對多種資料格式進行并行的加、減、乘、移位、平方根倒數等計算。而NEON并行計算一般情況下是針對8個元素的向量進行計算的。根據這個特點,我們将Prewitt算法一次進行3*3資料讀取的方式改變,每次直接對8*3的原始資料進行處理。而8*3的原始資料實際進行中,隻能夠産生6個準确的計算結果,如下圖加深的6個像素點對應的位置可以獲得計算結果。

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

上面這個可以說是這個加速設計中最為重要的一環!!!精華所在

是我自己想了一段時間想出來的,但我感覺肯定有别人想到的比我更早~~我可沒有抄襲哦

如果有人受到這個的啟發,歡迎交流哦,微信:vacajk

接着說——是以,我們将對Prewitt算法進行NEON并行方式的加速,每次讀取8*3個原始像素點(針對RGB原始資料格式,每個像素點内有R、G、B三個顔色通道,資料量為3倍:24*3位元組),進行處理後得到6個濾波結果進行儲存,接着向右步進6個像素進行下一次的處理。如下圖,紅色虛線框内為第一次8*3的原始像素點,綠色虛線框内為第二次的8*3原始像素點。每次計算使用并行處理方式實作6個像素點的濾波結果計算,即圖中最底下的0~5像素點。

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

針對設計要求,我們本需要進行640*360=230,400次循環(去除邊界,實際為638*358=228,404次),但是使用上面的加速方案,可以将循環減少為原來的1/6:638/6 * 358=107*358=38306次循環。

NEON加速的循環次數僅為之前次數的16.77%,可見在循環次數這一項上,借助NEON的并行計算能力,就能夠使得速度有很大的提升。而在這實作後,我們還可以進行預加載、dual-issue功能的優化。最終速度肯定還能進一步的提升!

S2. Prewitt的NEON實作

下面用圖檔的方式來介紹Prewitt在NEON中加速的實作過程,在這之前需要你了解基本的NEON彙編指令及其用法,以及準備一份手冊:DUI0204IC_rvct_assembler_guide

8像素的RGB2GRAY

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

8*3視窗的RGB2GRAY

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

x方向一階梯度

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

y方向一階梯度

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

兩個方向梯度均方根

-04-實時Prewitt邊緣檢測,第二步:Prewitt的NEON加速實作【ARM NEON加速】

上面的左邊是資料的處理與變化格式,右邊含有對應的NEON彙編代碼。仔細研究應該可以看懂的吧,我就不再詳細說明了。要注意的是x方向和y方向一階梯度的計算是根據Prewitt算子公式的特點進行設計的,是以計算步驟不太相同。

在XC7Z010中裸奔的計算速度對比:

C代碼(-O0): 635.9ms

原始彙編代碼:9.66ms

調整彙編指令順序(dual issue):8.62ms

增加預加載指令(pld):6.97ms

這一篇介紹了Prewitt的NEON加速設計思路與具體的實作方式,彙編源碼的裸奔測試也OK。下一篇介紹如何将彙編代碼加入整個ZYBO HDMI Demo工程中,實作HDMI視訊輸入、實時Prewitt的NEON加速計算、VGA視訊輸出,也就是系統總體功能的內建。

繼續閱讀