天天看點

OpenCV中單目标跟蹤算法DaSiamRPN的C++實作

單目标跟蹤是計算機視覺中的一個基本問題。在如今深度學習時代也湧現出多種基于深度學習的單目标跟蹤算法,其中基于SiamFC(雙流網絡)的單目标跟蹤算法占據了半壁江山。DaSiamRPN就是典型的基于SiamFC的方法。

GSoC 2020為OpenCV增加了Python版本的DaSiamRPN,後來發現大家對C++版本的需求挺強烈的,于是我就為OpenCV貢獻了一個C++版本的DaSiamRPN。

對于DaSiamRPN,在inference階段,模型的主要結構如下圖所示:

OpenCV中單目标跟蹤算法DaSiamRPN的C++實作

其中黃色的CNN網絡和橙色的Conv網絡是模型檔案,DaSiamRPN最後的輸出有兩個分别為分類分支:17 x 17 x 2k 和 回歸分支:17 x 17 x 4k。

在實際代碼中我們将上圖中的模型整合成三個模型:siamRPN,siamKernelCL1,siamKernelR1。其中siamRPN是主要模型,會在每一幀的跟蹤中用到,而siamKernelCL1和siamKernelR1僅會在設定模闆參數時用到。注意:圖中的兩個黃色的CNN是公用參數的,詳細介紹請看原文[1]。

C++版本使用了與Python版本同樣的邏輯和模型檔案,下面簡單介紹一下實作的主要邏輯。

在單目标跟蹤中,首先需要設定模闆參數,如以下代碼所示:

siamRPN.setInput(blob); // blob 為輸入的template
Mat out1;
siamRPN.forward(out1, "63"); // 63層的輸出為文中描述的黃色CNN的輸出
siamKernelCL1.setInput(out1); // 分别作為回歸和分類分支的輸入
siamKernelR1.setInput(out1);

Mat cls1 = siamKernelCL1.forward(); // 擷取模闆分類分支的特征
Mat r1 = siamKernelR1.forward();  // 擷取模闆回歸分支的特征
std::vector<int> r1_shape = { 20, 256, 4, 4 }, cls1_shape = { 10, 256, 4, 4 };

siamRPN.setParam(siamRPN.getLayerId("65"), 0, r1.reshape(0, r1_shape)); // 将擷取到的參數寫入主模型
siamRPN.setParam(siamRPN.getLayerId("68"), 0, cls1.reshape(0, cls1_shape));      

設定模闆參數之後進入跟蹤主循環:

// 主循環
for (int count = 0; ; ++count)
{
    cap >> image;
    // ...
    float score = trackerEval(image, trackState, siamRPN); // 每一幀的跟蹤計算

    // 繪制圖像
    Rect rect = {
        int(trackState.targetBox.x - int(trackState.targetBox.width / 2)),
        int(trackState.targetBox.y - int(trackState.targetBox.height / 2)),
        int(trackState.targetBox.width),
        int(trackState.targetBox.height)
    };

    Mat render_image = image.clone();
    rectangle(render_image, rect, Scalar(0, 255, 0), 2);
    // ...
    imshow(winName, render_image);

    int c = waitKey(1);
    if (c == 27 /*ESC*/)
        break;
}      

其中上述的​

​trackerEval​

​函數即為跟蹤目标的計算,主體如下所示:

float trackerEval(Mat img, trackerConfig& trackState, Net& siamRPN)
{
    // 第一步:确認搜尋區域。跟蹤算法根據前一幀中目标位置确定搜尋區域
    float searchSize = float((trackState.instanceSize - trackState.exemplarSize) / 2);
    float pad = searchSize / scaleZ;
    float sx = sz + 2 * pad;

    Mat xCrop = getSubwindow(img, targetBox, (float)cvRound(sx), trackState.avgChans);

    static Mat blob;
    std::vector<Mat> outs;
    std::vector<String> outNames;
    Mat delta, score;
    Mat sc, rc, penalty, pscore;

    // 第二步:用siamRPN網絡推理
    blobFromImage(xCrop, blob, 1.0, Size(trackState.instanceSize, trackState.instanceSize), Scalar(), trackState.swapRB, false, CV_32F);

    siamRPN.setInput(blob);

    outNames = siamRPN.getUnconnectedOutLayersNames();
    siamRPN.forward(outs, outNames);

    delta = outs[0];
    score = outs[1];
    // score 和 delta即為文章開頭結構圖中的兩個輸出矩陣
    score = score.reshape(0, { 2, trackState.anchorNum, trackState.scoreSize, trackState.scoreSize });
    delta = delta.reshape(0, { 4, trackState.anchorNum, trackState.scoreSize, trackState.scoreSize });
    // 第三步:後處理
    // ...太長,這裡省略
    return score.at<float>(bestID); // 傳回最好的跟蹤框
}      

運作效果:

算法存在的問題:模型訓練集中包含的小物體較少,該算法在目标為小物體情況下的性能較弱,隻能重新訓練解決這個問題。

全部代碼請參考:

​​https://github.com/opencv/opencv/blob/master/samples/dnn/dasiamrpn_tracker.cpp​​

參考資料:

1. 原始論文:https://arxiv.org/abs/1808.06048

2. 原始PyTorch實作:https://github.com/foolwood/DaSiamRPN

3. OpenCV中Python實作:https://github.com/opencv/opencv/blob/master/samples/dnn/dasiamrpn_tracker.py

繼續閱讀