單目标跟蹤是計算機視覺中的一個基本問題。在如今深度學習時代也湧現出多種基于深度學習的單目标跟蹤算法,其中基于SiamFC(雙流網絡)的單目标跟蹤算法占據了半壁江山。DaSiamRPN就是典型的基于SiamFC的方法。
GSoC 2020為OpenCV增加了Python版本的DaSiamRPN,後來發現大家對C++版本的需求挺強烈的,于是我就為OpenCV貢獻了一個C++版本的DaSiamRPN。
對于DaSiamRPN,在inference階段,模型的主要結構如下圖所示:
其中黃色的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