概述

?? 光流

是由對象或相機的移動引起的兩個連續幀之間的圖像對象的明顯運動的模式.它是2D矢量場,其中每個矢量是位移矢量,表示從第一幀到第二幀的點的移動。

如下圖所示,是一個球在連續的5幀圖像中的運動,箭頭顯示其位移矢量.

位移矢量

光流有很多應用場景如下:

  • 運動恢復結構
  • 視頻壓縮
  • 視頻防抖動等

光流法的工作原理基於如下假設:

  • 連續的兩幀圖像之間,目標像素灰度

    值不變
  • 相鄰的像素之間有相似的運動

數學原理

?? 第一幀的像素 I(x,y,t) ,表示在 t 時刻的像素值,那麼經過 d_t 時間後,像素在下一幀移動的距離為 (d_x,d_y) 。基於像素相同,亮度不變,可得:

I(x, y, t)=Ileft(x+d_{x}, y+d_{y}, t+d_{t}
ight)

假設移動很小,使用泰勒公式得:

I(x+Delta x, y+Delta y, t+Delta t)=I(x, y, t)+frac{partial I}{partial x} Delta x+frac{partial I}{partial y} Delta y+frac{partial I}{partial t} Delta t + varepsilon

其中 varepsilon 為無窮小,由第一個假設得:

frac{partial I}{partial x} Delta x+frac{partial I}{partial y} Delta y+frac{partial I}{partial t} Delta t=0  <==>  frac{partial I}{partial x} frac{Delta x}{Delta t}+frac{partial I}{partial y} frac{Delta y}{Delta t}+frac{partial I}{partial t} frac{Delta t}{Delta t}=0

 frac{partial I}{partial x}=f_{x} , frac{partial I}{partial y}=f_{y} , frac{partial I}{partial t}=f_{t} , frac{Delta x}{Delta t}=u ; frac{Delta y}{Delta t}=v ,則有得到光流方程為:

f_{x} u+f_{y} v+f_{t}=0

其中, f_xf_y 分別是圖像梯度, f_t 是圖像沿著時間的梯度。

?? 為了求解未知的 u, v ,採用Lucas-Kanada 方法解決,這個演算法最早是有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 函數計算出兩幀圖像中興趣點的移動情況。
  • 刪除未移動的興趣點。
  • 在兩次移動的點之間繪製一條線段。

示例代碼

1. 刪除靜止點的光流分析:

import numpy as np
import cv2 as cv
import math
cap = cv.VideoCapture(car_flow.mp4)

#角點檢測參數
feature_params = dict(maxCorners=100, qualityLevel=0.1, minDistance=7, blockSize=7)

#KLT光流參數
lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.02))

# 隨機顏色
color = np.random.randint(0,255,(100,3))

# 讀取第一幀
ret, old_frame = cap.read()
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params,useHarrisDetector=False,k=0.04)
good_ini=p0.copy()

def caldist(a,b,c,d):
return abs(a-c)+abs(b-d)

mask = np.zeros_like(old_frame)
# 光流跟蹤
while True:
ret, frame = cap.read()
if ret is False:
break
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# 計算光流
p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 根據狀態選擇

good_new = p1[st == 1]
good_old = p0[st == 1]
#刪除靜止點
k=0
for i, (new0, old0) in enumerate(zip(good_new,good_old)):
a0,b0 = new0.ravel()
c0,d0 = old0.ravel()
dist=caldist(a0,b0,c0,d0)
if dist>2:
good_new[k]=good_new[i]
good_old[k]=good_old[i]
good_ini[k]=good_ini[i]
k=k+1

# 提取動態點
good_ini=good_ini[:k]
good_new=good_new[:k]
good_old=good_old[:k]

# 繪製跟蹤線
for i, (new, old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
mask = cv.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv.circle(frame,(a,b),5,color[i].tolist(),-1)
cv.imshow(frame,cv.add(frame,mask))
k = cv.waitKey(30) & 0xff
if k == 27:
cv.imwrite("flow.jpg", cv.add(frame,mask))
break

# 更新
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)

if good_ini.shape[0]<40:
p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
good_ini=p0.copy()

cv.destroyAllWindows()
cap.release()

輸出

2. 反向檢測的光流分析:

import numpy as np
import cv2 as cv
import math
cap = cv.VideoCapture(car_flow.mp4)

#角點檢測參數
feature_params = dict(maxCorners=100, qualityLevel=0.1, minDistance=7, blockSize=7)

#KLT光流參數
lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.02))

height = cap.get(cv.CAP_PROP_FRAME_HEIGHT)
width = cap.get(cv.CAP_PROP_FRAME_WIDTH)
fps = cap.get(cv.CAP_PROP_FPS)
#out = cv.VideoWriter("reslut.avi", cv.VideoWriter_fourcc(D, I, V, X), fps,
#(np.int(width), np.int(height)), True)

tracks = []
track_len = 15
frame_idx = 0
detect_interval = 5
while True:

ret, frame = cap.read()
if ret:
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
vis = frame.copy()

if len(tracks)>0:
img0 ,img1 = prev_gray, frame_gray
p0 = np.float32([tr[-1] for tr in tracks]).reshape(-1,1,2)
# 上一幀的角點和當前幀的圖像作為輸入來得到角點在當前幀的位置
p1, st, err = cv.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)

# 反向檢查,當前幀跟蹤到的角點及圖像和前一幀的圖像作為輸入來找到前一幀的角點位置
p0r, _, _ = cv.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params)

# 得到角點回溯與前一幀實際角點的位置變化關係
d = abs(p0-p0r).reshape(-1,2).max(-1)

#判斷d內的值是否小於1,大於1跟蹤被認為是錯誤的跟蹤點
good = d < 1

new_tracks = []

for i, (tr, (x, y), flag) in enumerate(zip(tracks, p1.reshape(-1, 2), good)):

# 判斷是否為正確的跟蹤點
if not flag:
continue

# 存儲動態的角點
tr.append((x, y))

# 只保留track_len長度的數據,消除掉前面的超出的軌跡
if len(tr) > track_len:
del tr[0]
# 保存在新的list中
new_tracks.append(tr)

cv.circle(vis, (x, y), 2, (0, 255, 0), -1)

# 更新特徵點
tracks = new_tracks

# #以上一振角點為初始點,當前幀跟蹤到的點為終點,畫出運動軌跡
cv.polylines(vis, [np.int32(tr) for tr in tracks], False, (0, 255, 0), 1)

# 每隔 detect_interval 時間檢測一次特徵點
if frame_idx % detect_interval==0:
mask = np.zeros_like(frame_gray)
mask[:] = 255

if frame_idx !=0:
for x,y in [np.int32(tr[-1]) for tr in tracks]:
cv.circle(mask, (x, y), 5, 0, -1)

p = cv.goodFeaturesToTrack(frame_gray, mask=mask, **feature_params)
if p is not None:
for x, y in np.float32(p).reshape(-1,2):
tracks.append([(x, y)])

frame_idx += 1
prev_gray = frame_gray

cv.imshow(track, vis)
#out.write(vis)
ch = cv.waitKey(1)
if ch ==27:
cv.imwrite(track.jpg, vis)
break
else:
break

cv.destroyAllWindows()
cap.release()

輸出

視頻封面

00:05光流分析

稠密光流

概述

?? Lucas-Kanade方法計算稀疏特徵集的光流,OpenCV提供了另一種演算法來查找密集的光流,它計算幀中所有點的光流。它基於Gunner Farneback於2003年的《Two-Frame Motion Estimation Based on Polynomial Expansion》。

函數

?? 我們可以通過Opencv的函數cv2.calcOpticalFlowFarneback尋找稠密光流,我們得到的一個兩個通道的向量(u,v)。得到的該向量的大小和方向。用不同的顏色編碼來使其可視化。

?? 方向與Hue值相關,大小與Value值相關。

flow=cv.calcOpticalFlowFarneback(
prev,
next, flow,
pyr_scale, levels, winsize,
iterations, poly_n, poly_sigma, flags)

輸入

  • prev--> 前一幀圖片
  • next--> 下一幀圖片,格式與prev相同
  • flow--> 與返回值相同,得到一個CV_32FC2格式的光流圖
  • pyr_scale--> 構建圖像金字塔尺度
  • levels--> 圖像金字塔層數
  • winsize--> 窗口尺寸,值越大探測高速運動的物體越容易,但是越模糊,同時對雜訊的容錯性越強
  • iterations--> 對每層金字塔的迭代次數
  • poly_n--> 每個像素中找到多項式展開的鄰域像素的大小。n越大越光滑,也越穩定
  • poly_sigma--> 高斯標準差,用來平滑倒數,n越大,sigma應該適當增加
  • flags--> 光流的方式,有OPTFLOW_USE_INITIAL_FLOW 和OPTFLOW_FARNEBACK_GAUSSIAN 兩種

- 輸出

  • 一個兩通道的光流向量,實際上是每個點的像素位移值

示例代碼

import cv2 as cv
import numpy as np
cap = cv.VideoCapture("vtest.avi")
ret, frame1 = cap.read()
prvs = cv.cvtColor(frame1,cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255
while(1):
ret, frame2 = cap.read()
next = cv.cvtColor(frame2,cv.COLOR_BGR2GRAY)

# 返回一個兩通道的光流向量,實際上是每個點的像素位移值
flow = cv.calcOpticalFlowFarneback(prvs,next, None, 0.5, 3, 15, 3, 5, 1.2, 0)

# 笛卡爾坐標轉換為極坐標,獲得極軸和極角
mag, ang = cv.cartToPolar(flow[...,0], flow[...,1])

hsv[...,0] = ang*180/np.pi/2 #角度
hsv[...,2] = cv.normalize(mag,None,0,255,cv.NORM_MINMAX)
bgr = cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
cv.imshow(frame2,bgr)
cv.imshow("frame1", frame2)
k = cv.waitKey(30) & 0xff
if k == 27:
break
elif k == ord(s):
cv.imwrite(opticalfb.png,frame2)
cv.imwrite(opticalhsv.png,bgr)
prvs = next
cap.release()
cv.destroyAllWindows()

原圖

稠密

------------------------------------------可愛の分割線------------------------------------------

更多Opencv教程可以 Follow github的opencv教程,中文&English??????歡迎Star??????

https://github.com/JimmyHHua/opencv_tutorials?

github.com

參考

??????- 機器視覺 OpenCV—python目標跟蹤(光流)

??????- opencv python 光流法

??????- OpenCV Tutorial 官網


推薦閱讀:
相关文章