OpenCV-Python系列之稀疏光流
之前介紹的兩種演算法對於視訊中的跟蹤而言仍然有一定的侷限性。這次我們來討論一種光流估計的方法用於進行目標跟蹤。
光流是物體或者攝像頭的運動導致的兩個連續幀之間的影象物件的視覺運動的模式。它是一個向量場,每個向量是一個位移向量,顯示了從第一幀到第二幀的點的移動,如圖:
它顯示了一個球在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()
結果示例:
天道酬勤 循序漸進 技壓群雄