Farneback 光流演算法詳解與 calcOpticalFlowFarneback 原始碼分析
光流基礎概念
真實的三維空間中,描述物體運動狀態的物理概念是運動場。三維空間中的每一個點,經過某段時間的運動之後會到達一個新的位置,而這個位移過程可以用運動場來描述。
而在計算機視覺的空間中,計算機所接收到的訊號往往是二維圖片資訊。由於缺少了一個維度的資訊,所以其不再適用以運動場描述。光流場(optical flow)就是用於描述三維空間中的運動物體表現到二維影象中,所反映出的畫素點的運動向量場。
- 當描述部分畫素時,稱為:稀疏光流
- 當描述全部畫素時,稱為:稠密光流
光流法是利用影象序列中的畫素在時間域上的變化、相鄰幀之間的相關性來找到的上一幀跟當前幀間存在的對應關係,計算出相鄰幀之間物體的運動資訊的一種方法。光流法理解的關鍵點有:
- 核心問題:同一個空間中的點,在下一幀即將出現的位置
- 重要假設:光流的變化(向量場)幾乎是光滑
- 角點處的光流能夠通過角點的鄰域完全確定下來,因此角點處的運動資訊最為可靠;其次是邊界的資訊
光流法有著各種各樣的分支,本文介紹的則是一種被廣泛使用的經典稠密光流演算法:Farneback 光流演算法
Farneback 光流演算法
- 作者:Gunnar Farneback
- 參考資料
- Two-Frame Motion Estimation Based on Polynomial Expansion,提綱閱讀
- Polynomial Expansion for Orientation and Motion Estimation,作者博士畢業論文,深讀(以下簡稱:博士論文
OpenCV 主函式原始碼:
void calcOpticalFlowFarneback( const Mat& prev0, const Mat& next0, Mat& flow0,
double pyr_scale, int levels,
int winsize, int iterations, int poly_n, double poly_sigma, int flags )
{
……
for( k = 0, scale = 1; k < levels; k++ )
{
scale *= pyr_scale;
if ( prev0.cols*scale < min_size || prev0.rows*scale < min_size )
break;
}
levels = k;
for( k = levels; k >= 0; k-- )
{
for( i = 0, scale = 1; i < k; i++ )
scale *= pyr_scale;
……
Mat R[2], I, M;
for( i = 0; i < 2; i++ )
{
img[i]->convertTo(fimg, CV_32F);
GaussianBlur(fimg, fimg, Size(smooth_sz, smooth_sz), sigma, sigma);
resize( fimg, I, Size(width, height), CV_INTER_LINEAR );
FarnebackPolyExp( I, R[i], poly_n, poly_sigma );
}
FarnebackUpdateMatrices( R[0], R[1], flow, M, 0, flow.rows );
for( i = 0; i < iterations; i++ )
{
if( flags & OPTFLOW_FARNEBACK_GAUSSIAN )
FarnebackUpdateFlow_GaussianBlur( R[0], R[1], flow, M, winsize, i < iterations - 1 );
else
FarnebackUpdateFlow_Blur( R[0], R[1], flow, M, winsize, i < iterations - 1 );
}
prevFlow = flow;
}
}
原始碼中為了解決孔徑問題,主函式中引入了影象金字塔。
孔徑問題(Aperture Problem):
- http://blog.csdn.net/hankai1024/article/details/23433157
- 形象理解:從小孔中觀察一塊移動的黑色幕布觀察不到任何變化。但實際情況是幕布一直在移動中
- 解決方案:從不同尺度(影象金字塔)上對影象進行觀察,由高到低逐層利用上一層已求得資訊來計算下一層資訊
主函式 calcOpticalFlowFarneback 中需要的變數引數包括:
1. _prev0:輸入前一幀影象
2. _next0:輸入後一幀影象
3. _flow0:輸出的光流
4. pyr_scale:金字塔上下兩層之間的尺度關係
5. levels:金字塔層數
6. winsize:均值視窗大小,越大越能 denoise 並且能夠檢測快速移動目標,但會引起模糊運動區域
7. iterations:迭代次數
8. poly_n:畫素鄰域範圍大小,一般為 5、7 等
9. poly_sigma:高斯標準差,一般為 1~1.5(函式處理中需要高斯分佈權重)
10. flags:計算方法,包括 OPTFLOW_USE_INITIAL_FLOW 和 OPTFLOW_FARNEBACK_GAUSSIAN
實際的 Farneback 演算法在每一層金字塔上的處理過程為:
Mat R[2], I, M;
for( i = 0; i < 2; i++ )
{
img[i]->convertTo(fimg, CV_32F);
GaussianBlur(fimg, fimg, Size(smooth_sz, smooth_sz), sigma, sigma);
resize( fimg, I, Size(width, height), CV_INTER_LINEAR );
FarnebackPolyExp( I, R[i], poly_n, poly_sigma );
}
FarnebackUpdateMatrices( R[0], R[1], flow, M, 0, flow.rows );
for( i = 0; i < iterations; i++ )
{
if( flags & OPTFLOW_FARNEBACK_GAUSSIAN )
FarnebackUpdateFlow_GaussianBlur( R[0], R[1], flow, M, winsize, i < iterations - 1 );
else
FarnebackUpdateFlow_Blur( R[0], R[1], flow, M, winsize, i < iterations - 1 );
}
輸入的影象預設為灰度圖片,預設只有亮度值
影象輸入與輸出時進行的高斯模糊操作(Gaussian Blur)操作都是使得光流場結果平滑,從而滿足假設:光流的變化幾乎是光滑的
所以需要關注的子函式有 4 個:
1. FarnebackPolyExp
2. FarnebackUpdateMatrices
3. FarnebackUpdateFlow_GaussianBlur
4. FarnebackUpdateFlow_Blur
OpenCV 子函式 FarnebackPolyExp:
static void FarnebackPolyExp( const Mat& src, Mat& dst, int n, double sigma )
理論基礎
影象建模
將影象視為二維訊號的函式(輸出影象是灰度影象),因變數是二維座標位置
其中,
因此係數化之後,以上公式等號右側可以寫為:
求解空間轉換
如果將原有(笛卡爾座標系)影象的二維訊號空間,轉換到以
Farneback 演算法對於每幀影象中的每個畫素點周圍設定一個鄰域
在一個鄰域內灰度值的
此處博士論文中有舉例說明,非常便於理解,詳見博士論文 3.4 小節
權重分配
利用最小二乘法求解時,並非是鄰域內每個畫素點樣本誤差都對中心點有著同樣的影響力,函式中利用二維高斯分佈將影響力賦予權重。
在