1. 程式人生 > >NEON優化——OpenCV WarpAffine最近鄰

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加速很容易。

要注意的問題:

  1. 非法情況指向什麼樣的常量,可以自己建立一塊buffer,非法時指向該buffer。但是計算偏移時要注意,偏移是該buffer的地址減去影象記憶體的地址,要考慮32位還是64位的問題。
  2. 載入畫素資料時可能越界,可以將影象最後兩個點的資料拷貝到buffer,算偏移時如果發現對應到原始影象的最後兩個點,則重定向到buffer。
  3. 這種偏移的思路只適合非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%