使用稀疏光流跟蹤算法,可以實現關鍵點跟蹤。大概流程為:首先在上一幀圖像中檢測出關鍵點,然后在下一幀圖像中評估關鍵點位移量。如果下一幀圖像以統一的方向偏移,則可以剔除該偏移以達到視頻穩像效果。
關鍵點檢測是稀疏光流跟蹤基礎,一般情況下,關鍵點為角點,邊緣上的點由於孔徑問題難以在系數光流中實現跟蹤。使用稠密光流算法可以描述所有點(包括邊緣上點)的運動情況,這在某些邊緣較多的場景中,稠密光流算法更加便於描述物體運動。
Faerneback 算法使用多項式擬合平面方式評估圖像中各個點的運動情況,其擬合平面方程為:。
當圖像發生平移后,平移前后擬合的二次多項式也同樣存在一個平移關系:,由於
已知,則可以求解出
。
在一維圖像下,可使用下圖表示:
上圖中,擬合前后兩幀圖像相同位置附近區間的二次曲線,通過模型 可求解偏移量 d。
以上跟蹤精度取決於擬合函數精度,當擬合區間越小,更細小的變化可以被二次函數表現處理。但過小的擬合區間可能受到噪聲影響,故需要選擇一個合適的擬合區間。同時,可以使用多次擬合的方式來提升跟蹤精度,第一次擬合時,下一幀圖像擬合中心與上一幀圖像擬合中心保持一致;當通過第一次擬合求解出偏移量后,使用偏移后位置作為下一幀圖像擬合中心,可以求解更精確擬合函數,從而得到更精確跟蹤精度。
通過金字塔分層方式可以跟蹤到大偏移。首先使用較低分辨率跟蹤各點偏移量,再以已經得到偏移量作為擬合中心,使用更高分辨率圖像求解更精確偏移量,即可跟蹤較大偏移。
在 opencv 中,使用 cv::calcOpticalFlowFarneback() 函數實現基於二次多項式擬合的稠密光流計算,其函數原型如下:
void cv::calcOpticalFlowFarneback(cv::InputArray preImg, cv::InputArray nextImg, cv::InputOutputArray flow,
double pyrScale, int levels, int winsize, int iterations, int polyN, double polySigma, int flags);
preImg, nextImg: 傳入圖像序列中前一幀與后一幀圖像,圖像格式均為8位單通道,且尺寸應該保持一致。
flow: 函數記錄各點運動向量,圖像格式位32位浮點雙通道(CV_32FC2),圖像尺寸與 preImg, nextImg 保持一致。
pyrScale: 表示金字塔各層縮減系數,一般使用 .5。
levels: 表示金字塔層數。
winsize: 在光流跟蹤前,使用 winsize 窗口大小對圖像平滑,避免噪聲干擾;可將 flags 設置為 cv::OPTFLOW_FARNEBACK_GAUSSIAN 以使用 Gaussian 平滑。
iterations: 通過控制各層上迭代擬合次數控制跟蹤精度。
polyN: 控制二次多項式擬合區間大小。
polySigma: polyN = 5, polySigma = 1.1; polyN = 7, polySigma = 1.5。
flags: 當使用 cv::OPTFLOW_USE_INITIAL_FLOW 時,flow 被同時當作輸入參數,表示初始光流速度場;通常在序列視頻中,相鄰幀運動方向具有相似性,故可以使用上幀速度場作為初始猜測。
以下代碼計算兩幀圖像間的速度場:
1 cv::Mat img0 = cv::imread("prev.bmp", cv::IMREAD_GRAYSCALE); 2 cv::Mat img1 = cv::imread("next.bmp", cv::IMREAD_GRAYSCALE); 3 cv::Mat flow(img0.size(), CV_32FC2); 4 cv::calcOpticalFlowFarneback(img0, 5 img1, 6 flow, 7 .5, 8 3, 9 5, 10 1, 11 7, 12 1.5, 13 cv::OPTFLOW_FARNEBACK_GAUSSIAN); 14 15 cv::Mat flow2(flow.size(), CV_8UC1); 16 for (int y = 0; y < flow.rows; ++y) 17 { 18 float* data1 = flow.ptr<float>(y); 19 uchar* data2 = flow2.ptr<uchar>(y); 20 for (int x = 0; x < flow.cols; ++x) 21 { 22 float mx = data1[x * 2]; 23 float my = data1[x * 2 + 1]; 24 float mv = sqrt(mx * mx + my * my); 25 data2[x] = cv::saturate_cast<uchar>(mv * 10); 26 } 27 } 28 cv::imwrite("Farneback.bmp", flow2);
執行效果如下:
參考資料: Learning Opencv 3 Adrian Kaehler & Gary Bradski