一般的矩陣旋轉操作都是對矩陣中的元素逐個操作,假設矩陣大小為m*n,那么時間復雜度就是o(mn)。如果使用了arm公司提供的neon加速技術,則可以並行的讀取多個元素,對多個元素進行操作,雖然時間復雜度還是o(mn),但是常數因子會變小,並且在寄存器里的操作比在普通內存中還要快一些,所以會帶來一定的性能提升。
在實際應用中,我需要對一個矩陣進行順時針旋轉90度,網上這方面的資料很少,於是自己研究了一下,利用neon給出的一些加速指令,設計了一個簡單的neon矩陣旋轉算法。
1.目標:將輸入矩陣順時針旋轉90度,如下圖所示:
輸入矩陣 輸出矩陣

2.一般做法:
一般的做法是,將輸入矩陣中的每個元素,根據旋轉的角度,計算出其在旋轉后矩陣中的位置,並填充該值。
3.利用NEON的做法:
考慮將一個矩陣划分成若干子矩陣,例如:一個128×256大小的矩陣可以划分為16×32個8×8大小的矩陣。分別對每個8x8的子矩陣進行旋轉,再將其復制到輸出矩陣中正確的坐標上即可。可以總結為2步:
循環執行以下步驟,直到所有子矩陣均被處理過
1.旋轉當前子矩陣
2.將旋轉后的子矩陣復制到輸出矩陣中
其中最關鍵的就是第一步,詳細講一下利用neon技術如何做到:
以byte數組為例(因為android中獲取的yuv數據就是byte型,將一個矩形按行連成了一個大一維數組),假設圖像的長寬都可以被8整除。首先利用2個uint8x8x4_t型數組,將8×8大小的子矩陣讀入
mat1.val[0]=vld1_u8(srcImg+i*width+j); mat1.val[1]=vld1_u8(srcImg+(i+1)*width+j); mat1.val[2]=vld1_u8(srcImg+(i+2)*width+j); mat1.val[3]=vld1_u8(srcImg+(i+3)*width+j); mat2.val[0]=vld1_u8(srcImg+(i+4)*width+j); mat2.val[1]=vld1_u8(srcImg+(i+5)*width+j); mat2.val[2]=vld1_u8(srcImg+(i+6)*width+j); mat2.val[3]=vld1_u8(srcImg+(i+7)*width+j);
接着,對兩兩相鄰的寄存器做基於uint8_t類型的專置操作,即:mat1和mat2中的0和1,2和3寄存器分別做轉置,得到4個uint8x8x2_t類型數組
vtrn操作示意圖如下:

temp1=vtrn_u8(mat1.val[1],mat1.val[0]); temp2=vtrn_u8(mat1.val[3],mat1.val[2]); temp3=vtrn_u8(mat2.val[1],mat2.val[0]); temp4=vtrn_u8(mat2.val[3],mat2.val[2]);
注意,vtrn_8里面兩個寄存器的順序不能顛倒
然后,對這四個數組的類型進行轉換,將uint8x8_t轉換成uint16x4_t
temp5.val[0]= vreinterpret_u16_u8(temp1.val[0]); temp5.val[1]= vreinterpret_u16_u8(temp1.val[1]); temp6.val[0]= vreinterpret_u16_u8(temp2.val[0]); temp6.val[1]= vreinterpret_u16_u8(temp2.val[1]); temp7.val[0]= vreinterpret_u16_u8(temp3.val[0]); temp7.val[1]= vreinterpret_u16_u8(temp3.val[1]); temp8.val[0]= vreinterpret_u16_u8(temp4.val[0]); temp8.val[1]= vreinterpret_u16_u8(temp4.val[1]);
接下來的做法和上面的這一套很像,繼續對這些uint16x4_t數據進行轉置,這次的順序和上次有所不同,相鄰的奇偶序號寄存器之間進行專置即:0和2,1和3,4和6,5和7
temp9=vtrn_u16(temp6.val[0],temp5.val[0]); temp10=vtrn_u16(temp6.val[1],temp5.val[1]); temp11=vtrn_u16(temp8.val[0],temp7.val[0]); temp12=vtrn_u16(temp8.val[1],temp7.val[1]);
然后,繼續對這四個數組的類型進行轉換,將uint16x4_t轉換成uint32x2_t
temp13.val[0]= vreinterpret_u32_u16(temp9.val[0]); temp13.val[1]= vreinterpret_u32_u16(temp9.val[1]); temp14.val[0]= vreinterpret_u32_u16(temp10.val[0]); temp14.val[1]= vreinterpret_u32_u16(temp10.val[1]); temp15.val[0]= vreinterpret_u32_u16(temp11.val[0]); temp15.val[1]= vreinterpret_u32_u16(temp11.val[1]); temp16.val[0]= vreinterpret_u32_u16(temp12.val[0]); temp16.val[1]= vreinterpret_u32_u16(temp12.val[1]);
最后,再做一次基於uint32x2_t的轉置
temp17=vtrn_u32(temp15.val[0],temp13.val[0]); temp18=vtrn_u32(temp15.val[1],temp13.val[1]); temp19=vtrn_u32(temp16.val[0],temp14.val[0]); temp20=vtrn_u32(temp16.val[1],temp14.val[1]);
最后的最后,還需要對各個寄存器中存儲的值重新排列一遍,並轉換回最初的uint8x8_t
temp1.val[0]= vreinterpret_u8_u32(temp17.val[0]); temp1.val[1]= vreinterpret_u8_u32(temp19.val[0]); temp2.val[0]= vreinterpret_u8_u32(temp18.val[0]); temp2.val[1]= vreinterpret_u8_u32(temp20.val[0]); temp3.val[0]= vreinterpret_u8_u32(temp17.val[1]); temp3.val[1]= vreinterpret_u8_u32(temp19.val[1]); temp4.val[0]= vreinterpret_u8_u32(temp18.val[1]); temp4.val[1]= vreinterpret_u8_u32(temp20.val[1]);
大功告成!此時的子矩陣已經被順時針旋轉了90度,接下來,只要將其復制到輸出矩陣的正確位置即可。
幾點說明
1.為什么這么做可以旋轉矩陣:
NEON提供的專置函數相當於對2×2的小矩陣進行專置,因此利用這個特性,可以對更大的矩陣進行旋轉。其實自己按照我說的步驟,畫個矩陣,自己做一下,就明白了。也不一定用8×8的,4×4的就能說明問題,當然4×4比8×8的要簡單。
2.怎樣得到正確位置:
這個還是自己思考一下吧,不難,假設某元素在輸入矩陣的位置是(i,j),那么在輸出的旋轉90度的矩陣中的位置和i,j是相關的。
