之前介紹的兩種算法對於視頻中的跟蹤而言仍然有一定的局限性。這次我們來討論一種光流估計的方法用於進行目標跟蹤。
光流是物體或者攝像頭的運動導致的兩個連續幀之間的圖像對象的視覺運動的模式。它是一個向量場,每個向量是一個位移矢量,顯示了從第一幀到第二幀的點的移動,如圖:

它顯示了一個球在5個連續幀里的移動。箭頭顯示了它的位移矢量。光流在很多領域有應用:
·移動構建
·視頻壓縮
·視頻穩定
光流在很多假設下有效:
1.物體像素強度在連續幀之間不變化
2.鄰居像素有相似運動
考慮第一幀里的一個像素I(x,y,t)(檢查新的維度,時間)。它在dt時間后的下一幀移動了(dx, dy)。所以因為那些像素都一樣,強度也不變化。我們可以認為:

然后對右邊做泰勒級數近似。除以dt得到下面的等式:

其中:

上面的等式被叫做光流等式,我們可以找到fx和fy,他們是圖像梯度。類似的ft 是沿時間的梯度。但是(u, v)是未知的。我們無法解出這個等式。所以有一些方法來提供解決這個問題,其中一個是Lucas-Kanade。是由Bruce D. Lucas and Takeo Kanade兩位作者提出來的,所以又被稱為KLT。
KLT算法工作有三個假設前提條件:
· 亮度恆定
· 短距離移動
· 空間一致性
Opencv中使用cv2.calcOpticalFlowPyrLK()函數計算一個稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法。來看函數原型:
nextPts,status,err = cv2.calcOpticalFlowPyrLK(prevImg, #上一幀圖片
nextImg, #當前幀圖片
prevPts, #上一幀找到的特征點向量
nextPts #與返回值中的nextPtrs相同
[, status[, err[, winSize
[, maxLevel[, criteria
[, flags[, minEigThreshold]]]]]]])
參數解釋:
prevImg--> 上一幀圖片;
nextImg--> 當前幀圖片;
prevPts--> 上一幀找到的特征點向量;
nextPts--> 與返回值中的nextPtrs相同;
status--> 與返回的status相同;
err--> 與返回的err相同;
winSize--> 在計算局部連續運動的窗口尺寸(在圖像金字塔中),default=Size(21, 21);
maxLevel--> 圖像金字塔層數,0表示不使用金字塔, default=3;
criteria--> 尋找光流迭代終止的條件;
flags--> 有兩個宏,表示兩種計算方法,分別是OPTFLOW_USE_INITIAL_FLOW表示使用估計值作為尋找到的初始光流,OPTFLOW_LK_GET_MIN_EIGENVALS表示使用最小特征值作為誤差測量,default=0;
minEigThreshold--> 該算法計算光流方程的2×2規范化矩陣的最小特征值,除以窗口中的像素數; 如果此值小於minEigThreshold,則會過濾掉相應的功能並且不會處理該光流,因此它允許刪除壞點並獲得性能提升, default=1e-4.
返回值:
nextPtrs--> 輸出一個二維點的向量,這個向量可以是用來作為光流算法的輸入特征點,也是光流算法在當前幀找到特征點的新位置(浮點數);
status--> 標志,在當前幀當中發現的特征點標志status==1,否則為0;
err--> 向量中的每個特征對應的錯誤率.
實現原理: 在第一幀圖像中檢測Shi-Tomasi角點,使用LK算法來迭代的跟蹤這些特征點。迭代的方式就是不斷向cv2.calcOpticalFlowPyrLK()中傳入上一幀圖片的特征點以及當前幀的圖片。函數會返回當前幀的點,這些點帶有狀態1或者0,如果在當前幀找到了上一幀中的點,那么這個點的狀態就是1,否則就是0。
實現流程:
· 加載視頻。
· 調用cv2.GoodFeaturesToTrack 函數尋找興趣點(關鍵點)。
· 調用cv2.CalcOpticalFlowPyrLK 函數計算出兩幀圖像中興趣點的移動情況。
· 刪除未移動的興趣點。
· 在兩次移動的點之間繪制一條線段。
我們仍然使用之前的示例視頻。
來看代碼:
import numpy as np
import cv2
cap = cv2.VideoCapture("test.avi")
# ShiTomasi 角點檢測參數
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7)
# lucas kanade光流法參數
lk_params = dict(winSize=(15, 15),
maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
# 創建隨機顏色
color = np.random.randint(0, 255, (100, 3))
# 獲取第一幀,找到角點
ret, old_frame = cap.read()
# 找到原始灰度圖
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
# 獲取圖像中的角點,返回到p0中
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# 創建一個蒙版用來畫軌跡
mask = np.zeros_like(old_frame)
while (1):
ret, frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 計算光流
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 選取好的跟蹤點
good_new = p1[st == 1]
good_old = p0[st == 1]
# 畫出軌跡
for i, (new, old) in enumerate(zip(good_new, good_old)):
a, b = new.ravel()
c, d = old.ravel()
mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
frame = cv2.circle(frame, (a, b), 5, color[i].tolist(), -1)
img = cv2.add(frame, mask)
cv2.imshow('frame', img)
k = cv2.waitKey(100) & 0xff
if k == 27:
break
# 更新上一幀的圖像和追蹤點
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
cv2.destroyAllWindows()
cap.release()
結果示例:



