NEON優化——OpenCV WarpAffine最近鄰
warpAffine一種典型的應用場景是將camera拍到的影象中人臉摳出來,通過變換輸出一張正臉,然後再做一些人臉識別,關鍵點檢測之類的運算。所以通常是輸入尺寸固定,輸出尺寸也固定,變的是轉換矩陣。
最近鄰的優勢是計算量小,速度較快,問題是放大的情況下鋸齒較明顯,對於通常是縮小的應用場景最近鄰是比較合適的,如果希望在所有的場景下有相對更好的效果,可以考慮雙線性。
演算法要點
warpAffine和resize其實比較類似,都是從源影象對映到目標影象,只不過resize只是單純的縮放,而warpAffine還另外涉及到旋轉,平移,對映關係更復雜一點。總體流程上還是大同小異,都是依次掃描目標影象,對於目標影象的每個點,都找到原始影象的對應點,然後拷貝畫素即可。為了描述這種對應關係,resize只是單純的乘以一個縮放係數,x和y是相互獨立的,而warpAffine需要一個變換矩陣,目標影象的x和y對於原始影象的xy都有影響。
A0X + B0Y + C0 = X0
A1X + B1Y + C1 = Y0
(X,Y)是目標影象的座標,(X0,Y0)是原始影象的座標,可見對於X0 ,目標影象的XY都會參與計算。
對於最近鄰,這裡算出來的(X0,Y0)如果不是整數,則向下取整。
由於是對目標影象逐行掃描,在y一定的情況下,x不斷變化,因此上述矩陣運算中可以將與Y無關的部分單獨抽出來,每一行都可以複用。
先給出原始的C實現,再來討論怎麼優化,以下程式碼只處理8UC4,adelta和bdelta分別是目標影象的x在原始影象對應座標的x和y上的分量。offsets則是目標影象的y在原始影象對應座標的x和y上的分量。
class Warpaffine_8UC4 : public ParallelLoopBody { public: Warpaffine_8UC4(Mat src, Mat dst, int *adelta, int *bdelta, int *offsets) { mSrc = src; mDst = dst; mAdelta = adelta; mBdelta = bdelta; mOffsets = offsets; } virtual void operator()(const Range &range) const { int cols = mDst.cols; for (int iy = range.start; iy < range.end; ++iy) { int *pDstData = (int *) mDst.ptr(iy); int x0 = mOffsets[2 * iy]; int y0 = mOffsets[2 * iy + 1]; for (int jx = 0; jx < cols; ++jx) { int x = (mAdelta[jx] + x0) >> AB_BITS; if (((uint32_t) x < mSrc.cols)) { int y = (mBdelta[jx] + y0) >> AB_BITS; if ((uint32_t) y < mSrc.rows) { int *pSrcData = (int *) (mSrc.ptr(y)); *(pDstData + jx) = *(pSrcData + x); } } } } } private: Mat mSrc; Mat mDst; int *mAdelta; int *mBdelta; int *mOffsets; };
優化要點
接下來討論怎麼優化上述的C程式碼,主要圍繞兩點,
- 怎麼去掉for迴圈中的if
- 怎麼加速for迴圈
關於第一點,思路是計算好偏移,非法情況時指向某個常量即可。關於第二點,當if去掉之後,剩下的邏輯就只是拷貝畫素點了,用neon加速很容易。
要注意的問題:
- 非法情況指向什麼樣的常量,可以自己建立一塊buffer,非法時指向該buffer。但是計算偏移時要注意,偏移是該buffer的地址減去影象記憶體的地址,要考慮32位還是64位的問題。
- 載入畫素資料時可能越界,可以將影象最後兩個點的資料拷貝到buffer,算偏移時如果發現對應到原始影象的最後兩個點,則重定向到buffer。
- 這種偏移的思路只適合非ROI的情況
測試結果
8UC4
D: warpAffine H/W 1280/960 -> 128/128: opencv takes 0.280ms, neon takes 0.104ms, time reduce 62%
D: warpAffine H/W 1280/960 -> 256/256: opencv takes 0.847ms, neon takes 0.461ms, time reduce 45%
D: warpAffine H/W 1440/1080 -> 128/128: opencv takes 0.419ms, neon takes 0.132ms, time reduce 68%
D: warpAffine H/W 1440/1080 -> 256/256: opencv takes 1.357ms, neon takes 0.514ms, time reduce 62%
8UC3
D: warpAffine H/W 1280/960 -> 128/128: opencv takes 0.205ms, neon takes 0.096ms, time reduce 52%
D: warpAffine H/W 1280/960 -> 256/256: opencv takes 0.652ms, neon takes 0.401ms, time reduce 38%
D: warpAffine H/W 1440/1080 -> 128/128: opencv takes 0.269ms, neon takes 0.139ms, time reduce 48%
D: warpAffine H/W 1440/1080 -> 256/256: opencv takes 0.775ms, neon takes 0.474ms, time reduce 38%