OpenCV目標跟蹤-LK光流法
這幾天主要看了光流的有關內容,下面就關於光流的有關內容進行個簡單的總結。
所謂的光流是一種運動模式,這種運動模式即是指一個物體、表面、邊緣在一個視角下由一個觀察者和背景之間形成的明顯移動。在如下的圖中給出了光流的直觀解釋。
這裡的每個畫素都與速度相關聯,這樣得到的即是稠密光流,在光流中主要分為稠密光流和稀疏光流,相對於稠密光流,稀疏光流的計算則需要在跟蹤之前指定一組點,下面我主要介紹下比較流行的基於金字塔的Lucas-Kanade光流演算法。
(1)LK演算法
LK演算法其實是基於三個假設進行的:
a.亮度恆定。影象場景中目標的畫素在幀間運動時外觀上保持不變,對於灰度影象,需要假設畫素被逐幀跟蹤時其亮度不發生變化。這樣的假設,我們可以用下面的數學表示式來表達:
也即是亮度I對時間t的偏導數為0,即:
b.時間連續或者運動是“小運動”。影象的運動隨時間的變化比較緩慢。實際應用中指的是時間變化相對於影象中運動的比例要足夠小,這樣目標在幀間的運動就比較小。
這條假設,就只能針對小運動,但實際上的運動往往是比較大的運動,這時我們就會將現在的LK演算法加以改進,採用基於影象金字塔的LK演算法,這在後面將進一步進行介紹。
針對這條假設,換句話說,可以將運動的變化看成是亮度對時間的導數,此時將f(x,t)用I(x(t),t)替換,應用偏微分的鏈式法則即可得到:
我們接著將從一維的情況過渡到二維的情況下去分析即得到:
c.空間一致性假設。一個場景中同一表面上鄰近的點具有相似的運動,在影象平面上的投影也在鄰近區域。
下面的這幅圖,直觀的給出了上述的三條假設。
但垂直光流由孔徑問題產生,即用小孔或者小視窗去測量運動。這種情況下,我們通常只能觀測到邊緣而觀測不到角點,而只靠邊緣是不足以判斷整個物體是如何運動的,如下圖解釋的那樣:
單畫素是不能解決整個運動的,要想解決這樣的一個問題,需用到光流的最後一個假設。若一個區域性區域的畫素運動是一致的,則可以建立鄰域畫素的系統方程來求解中心畫素的移動。這個約束方程有點複雜,如果感興趣可以去查閱相關文獻。
以上的LK演算法只需要每個感興趣點周圍小視窗的區域性資訊,但是使用小視窗的LK演算法存在著不足之處就是,較大的運動點將點移出這個小視窗,從而造成演算法無法找到這些點。而金字塔的LK演算法可以解決這樣的問題,即從影象金字塔的最高層(細節最少)開始向金字塔的低層(豐富的細節)進行跟蹤。
(2)影象金字塔的LK演算法
在影象金字塔的最高層計算光流,用得到的運動估計結果作為下一層金字塔的起始點,重複這個過程直到達到金字塔的最底層。這樣就將不滿足運動假設的可能性降到最小從而實現對更快和更長的運動的跟蹤,這個演算法就叫做基於金字塔的LK演算法。如圖所示:
在OpenCV中主要有個基於影象金字塔的LK演算法的函式cvCalcOpticalFlowPyrLK(),下面就簡要介紹下這個函式的具體的引數的意義,並嘗試給出完整的示例程式。
void cvCalcOpticalFlowPyrLK(
const CvArr* imgA,//初始影象
const CvArr* imgB,//最終影象
CvArr* pyrA,
CvArr* pyrB,//pyrA,B申請存放兩幅影象金字塔的快取
CvPoint2D32f* featuresA,//存放的是用於尋找運動的點
CvPoint2D32f* featuresB,//存放featuresA中點的新位置
int count,//featuresA中點的數目
CvSize winsize,//定義了計算區域性連續運動的視窗尺寸
int level,//用於設定構建的影象金字塔的棧的層數,若設定為0,則不使用金字塔
char* status,//函式呼叫結束時,status中的每個元素被置1(對應點在第二幅圖中發現)或者0(未發現)
float* track_error,//為可選引數,表示被跟蹤點的原始影象小區域與此點在第二幅影象的小區域間的差的陣列
CvTermCriteria criteria,//迭代終止條件
int flags//標誌位
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
應用上述函式的過程非常簡單:
輸入影象,在featuresA中列出需要跟蹤的點,然後呼叫函式。函式返回後,檢查status陣列以確定哪些點被成功跟蹤,再檢查featuresB得到這些點新的位置。
下面給出基於影象金字塔的LK演算法完整的示例程式:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
const int MAX_CORNERS = 500;
int main()
{
IplImage *imgA = cvLoadImage("OpticalFlow0.jpg", CV_LOAD_IMAGE_GRAYSCALE);
IplImage *imgB = cvLoadImage("OpticalFlow1.jpg", CV_LOAD_IMAGE_GRAYSCALE);
CvSize img_sz = cvGetSize(imgA);
int win_size = 10;
IplImage *imgC = cvLoadImage("OpticalFlow1.jpg", CV_LOAD_IMAGE_UNCHANGED);
IplImage *eig_image = cvCreateImage(img_sz, IPL_DEPTH_32F,1);
IplImage *tmp_image = cvCreateImage(img_sz, IPL_DEPTH_32F, 1);
int corner_count = MAX_CORNERS;
CvPoint2D32f *cornersA = new CvPoint2D32f[MAX_CORNERS];
cvGoodFeaturesToTrack(
imgA,
eig_image,
tmp_image,
cornersA,
&corner_count,
0.01,
5.0,
0,
3,
0,
0.04
);
cvFindCornerSubPix(
imgA,
cornersA,
corner_count,
cvSize(win_size, win_size),
cvSize(-1, -1),
cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03)
);
char features_found[MAX_CORNERS];
float features_errors[MAX_CORNERS];
CvSize pyr_sz = cvSize(imgA->width + 8, imgB->height / 3);
IplImage *pyrA = cvCreateImage(pyr_sz, IPL_DEPTH_32F, 1);
IplImage *pyrB = cvCreateImage(pyr_sz, IPL_DEPTH_32F, 1);
CvPoint2D32f *cornersB = new CvPoint2D32f[MAX_CORNERS];
cvCalcOpticalFlowPyrLK(
imgA,
imgB,
pyrA,
pyrB,
cornersA,
cornersB,
corner_count,
cvSize(win_size, win_size),
5,
features_found,
features_errors,
cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.3),
0
);
for (int i = 0; i < corner_count; ++i)
{
if (features_found[i] == 0 || features_errors[i] > 550)
{
cout << "Error is " << features_errors[i];
continue;
}
cout << "Got it!" << endl;
CvPoint p0 = cvPoint(cvRound(cornersA[i].x), cvRound(cornersA[i].y));
CvPoint p1 = cvPoint(cvRound(cornersB[i].x), cvRound(cornersB[i].y));
cvLine(imgC, p0, p1, CV_RGB(255, 0, 0),2);
}
cvNamedWindow("ImageA", 0);
cvNamedWindow("ImageB", 0);
cvNamedWindow("LKpyr_OpticalFlow", 0);
cvShowImage("ImageA", imgA);
cvShowImage("ImageB", imgB);
cvShowImage("LKpyr_OpticalFlow", imgC);
cvWaitKey(0);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
下面是這個程式執行的結果:
這個程式選擇的是兩幀不同的影象進行跟蹤畫出的光流,大家可以嘗試著把OpenCV中的視訊捕捉功能用起來,用攝像頭捕捉兩幀影象進行基於影象金字塔的LK演算法。