光流的概念:
是Gibson在1950年首先提出來的。它是空間運動物體在觀察成像平面上的像素運動的瞬時速度,是利用圖像序列中像素在時間域上的變化以及相鄰幀之間的相關性來找到上一幀跟當前幀之間存在的對應關系,從而計算出相鄰幀之間物體的運動信息的一種方法。一般而言,光流是由於場景中前景目標本身的移動、相機的運動,或者兩者的共同運動所產生的。
當人的眼睛觀察運動物體時,物體的景象在人眼的視網膜上形成一系列連續變化的圖像,這一系列連續變化的信息不斷“流過”視網膜(即圖像平面),好像一種光的“流”,故稱之為光流(optical flow)。光流表達了圖像的變化,由於它包含了目標運動的信息,因此可被觀察者用來確定目標的運動情況。
看下面的圖,它展示了一個小球在5個連續的幀中的運動,箭頭上的數字代表不同的幀,那個紅色小球的運動構成了光流。
操作:
給你一個圖上的一系列點,在另外一張圖上找到與前面一些列點相同的點。
或者給你圖I1上的點[ux, uy]T,找到I2上的點[ux + δx, uy + δy]T,最小化ε:
上面加入Wx表示一塊區域,一般跟蹤一個區域的點。
在圖形學應用中,在多張圖上跟蹤點(特征)是一項基本的操作:在一張圖上找到一個對象,觀察對象如何移動。
基於特征點的跟蹤算法大致可以分為兩個步驟:
1)探測當前幀的特征點;
2)通過當前幀和下一幀灰度比較,估計當前幀特征點在下一幀的位置;
3)過濾位置不變的特征點,余下的點就是目標了。
特征點可以是Harris角點,也可以是邊緣點等等。
考慮一個像素在第一幀的光強度(這裡增加了一個維度時間,前面的時候我們只是處理圖像,所以沒有必要時間。現在需要增加這個維度)。它移動了 的距離到一下幀,用了時間。因為像素點是一樣的,光強度也沒有發生變化(其實這個光強度沒有改變是很多光流算法的基本假設)。,所以我們可以說:
然後通過泰勒級數近似展開有:
所以:
上面的等式叫做光流等式,偏導數可以求出來,可是 u和v是未知的,所以無法解決上的等式。但是有很多方法可以解決這個問題,其中一個叫做Lucas-Kanade方法。
有這麼一個假定,所有的相鄰像素有相似的行動,Lucas-Kanade方法使用3*3的一塊區域,它假定這9個點有相同的行動,所以現在的問題變為有9個等式,2個未知量,這個問題當然能夠解決。一個好的解決方式是使用最小二乘法。
令n=9,於是便有了9個等式:
其中q1,q2,…,代表像素點, 是偏導,上面的等式可以寫成下面的形式:A v = b,其中:
然後,得到下面的:
最終算出來的兩個未知數的解是:
上面的解決小而連貫的運動,想想剛剛我們的假設是9個像素點速度一致。因為現實中大而連貫的運動是普遍存在的,我們需要大的窗口來捕獲運動,可是大窗口違背了運動連貫的假設,圖像金字塔可以解決這個問題。(圖像金字塔的內容以後本人掌握更多的再補充,現在不敢亂發表)。
返回欄目頁:http://www.bianceng.cn/Programming/cplus/
OpenCV提供了對上面介紹的方法的支持,函數名叫做:cv2.calcOpticalFlowPyrLK(),現在讓我們在視頻中跟蹤一些點。為了決定跟蹤哪些點,使用cv2.goodFeaturesToTrack()。
我們得到第一幀,探測Shi-Tomasi角點,然後我們使用 Lucas-Kanade光流法來跟綜這些點。
01.#include "opencv2/video/tracking.hpp"
02.#include "opencv2/imgproc/imgproc.hpp"
03.#include "opencv2/highgui/highgui.hpp"
04.
05.#include <iostream>
06.#include <ctype.h>
07.
08.using namespace cv;
09.using namespace std;
10.
11.static void help()
12.{
13. // print a welcome message, and the OpenCV version
14. cout << "\nThis is ademo of Lukas-Kanade optical flow lkdemo(),\n"
15. "Using OpenCVversion "<< CV_VERSION << endl;
16. cout << "\nIt usescamera by default, but you can provide a path to video as an argument.\n";
17. cout << "\nHot keys:\n"
18. "\tESC - quitthe program\n"
19. "\tr -auto-initialize tracking\n"
20. "\tc - deleteall the points\n"
21. "\tn - switch the\"night\" mode on/off\n"
22. "To add/removea feature point click it\n" << endl;
23.}
24.
25.Point2f point;
26.bool addRemovePt = false;
27.
28.static void onMouse(int event, int x, int y, int /*flags*/, void* /*param*/)
29.{
30. if (event == CV_EVENT_LBUTTONDOWN)
31. {
32. point = Point2f((float)x, (float)y);
33. addRemovePt = true;
34. }
35.}
36.
37.int main(int argc, char** argv)
38.{
39. help();
40.
41. VideoCapture cap;
42. TermCriteria termcrit(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03);
43. Size subPixWinSize(10, 10), winSize(31, 31);
44.
45. const int MAX_COUNT = 500;
46. bool needToInit = false;
47. bool nightMode = false;
48.
49. /*if (argc == 1 || (argc == 2 && strlen(argv[1])== 1 && isdigit(argv[1][0])))
50. cap.open(argc == 2 ? argv[1][0] - '0' :0);
51. else if (argc == 2)
52. cap.open(argv[1]);*/
53.
54. cap.open("G:\\視頻分析入門練習\\視頻分析入門練習 - 附件\\sample.avi");
55.
56. if (!cap.isOpened())
57. {
58. cout << "Could notinitialize capturing...\n";
59. return 0;
60. }
61.
62. namedWindow("LK", 1);
63. setMouseCallback("LK", onMouse, 0);
64.
65. Mat gray, prevGray, image;
66. vector<Point2f> points[2];
67.
68. for (;;)
69. {
70. Mat frame;
71. cap >> frame;
72. if (frame.empty())
73. break;
74.
75. frame.copyTo(image);
76. cvtColor(image, gray, COLOR_BGR2GRAY);
77.
78. if (nightMode)
79. image = Scalar::all(0);
80.
81. if (needToInit)
82. {
83. // automaticinitialization
84. goodFeaturesToTrack(gray, points[1],100, 0.01, 10, Mat(), 3, 0, 0.04);
85. cornerSubPix(gray, points[1],subPixWinSize, Size(-1, -1), termcrit);
86. addRemovePt = false;
87. }
88. else if(!points[0].empty())
89. {
90. vector<uchar> status;
91. vector<float> err;
92. if (prevGray.empty())
93. gray.copyTo(prevGray);
94. calcOpticalFlowPyrLK(prevGray, gray,points[0], points[1], status, err, winSize,
95. 3, termcrit, 0, 0.001);
96. size_t i, k;
97. for (i = k = 0; i <points[1].size(); i++)
98. {
99. if (addRemovePt)
100. {
101. if (norm(point -points[1][i]) <= 5)
102. {
103. addRemovePt = false;
104. continue;
105. }
106. }
107.
108. if (!status[i])
109. continue;
110.
111. points[1][k++] = points[1][i];
112. circle(image, points[1][i], 3, Scalar(0, 255, 0), -1, 8);
113. }
114. points[1].resize(k);
115. }
116.
117. if (addRemovePt&& points[1].size() < (size_t)MAX_COUNT)
118. {
119. vector<Point2f> tmp;
120. tmp.push_back(point);
121. cornerSubPix(gray, tmp, winSize,cvSize(-1, -1), termcrit);
122. points[1].push_back(tmp[0]);
123. addRemovePt = false;
124. }
125.
126. needToInit = false;
127. imshow("LK", image);
128.
129. char c = (char)waitKey(100);
130. if (c == 27)
131. break;
132. switch (c)
133. {
134. case 'r':
135. needToInit = true;
136. break;
137. case 'c':
138. points[0].clear();
139. points[1].clear();
140. break;
141. case 'n':
142. nightMode = !nightMode;
143. break;
144. }
145.
146. std::swap(points[1], points[0]);
147. cv::swap(prevGray, gray);
148. }
149.
150. return 0;
151.}
結果:隨意取得一些特征點,特征點會隨著車的移動而移動