之前我們討論過LK算法,其本質來講屬於稀疏光流算法,我們在OpenCV中所用的函數為:calcOpticalFlowPyrLK。這次來介紹一種稠密光流算法(即圖像上所有像素點的光流都計算出來),它由Gunnar Farneback 所提出。
光流是由物體或相機的運動引起的圖像對象在兩個連續幀之間的視在運動模式.光流方法計算在t和 t+Δt時刻拍攝的兩個圖像幀之間的每個像素的運動位置。這些方法被稱為差分,因為它們基於圖像信號的局部泰勒級數近似; 也就是說,它們使用關於空間和時間坐標的偏導數。
和稀疏光流相比,稠密光流不僅僅是選取圖像中的某些特征點(一般用角點)進行計算;而是對圖像進行逐點匹配,計算所有點的偏移量,得到光流場,從而進行配准.因此其計算量會顯著大於稀疏光流,但效果一般優於稀疏光流。
Farneback算法原理
非常簡單,總共兩步。
1、將圖像視為二維信號的函數(輸出圖像是灰度圖像),因變量是二維坐標位置
,並且利用二次多項式對於圖像進行近似建模的話,會得到:

其中,
· A 是一個2×2的對稱矩陣(是通過像素的鄰域信息的最小二乘加權擬合得到的,權重系數與鄰域的像素大小和位置有關)
· b是一個2×1的矩陣向量;
· c為標量;
因此系數化之后,以上公式等號右側可以寫為:

2、如果將原有(笛卡爾坐標系)圖像的二維信號空間,轉換到以
作為基函數的空間,則表示圖像需要一個六維向量作為系數,代入不同像素點的位置 x,y求出不同像素點的灰度值。
OpenCV中的使用
函數API:
def calcOpticalFlowFarneback(prev, next, flow, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags)
使用Gunnar Farneback算法計算密集光流。
相關參數:
prev 輸入前一幀圖像(8位單通道);
next 輸入后一幀圖像(與prev大小和類型相同);
flow 計算的流量圖像具有與prev相同的大小並為CV_32FC2類型;
pyr_scale 指定圖像比例(\ <1)為每個圖像構建金字塔; pyr_scale = 0.5意味着一個古典金字塔,其中每個下一層比前一層小兩倍。
levels 金字塔層數包括初始圖像; levels = 1意味着不會創建額外的圖層,只會使用原始圖像。
winsize 平均窗口大小;較大的值會增加算法對圖像噪聲的魯棒性,並可以檢測更快速的運動,但會產生更模糊的運動場。
iterations 每個金字塔等級上執行迭代算法的迭代次數。用於在每個像素中查找多項式展開的像素鄰域;
poly_n大小;較大的值意味着圖像將近似於更光滑的表面,產生更穩健的算法和更模糊的運動場,一般取poly_n = 5或7。
poly_sigma用於平滑導數的高斯的標准偏差,用作多項式展開的基礎;對於poly_n = 5,可以設置poly_sigma = 1.1,對於poly_n = 7,可以設置poly_sigma = 1.5;
flags 操作標志,可取計算方法有:
OPTFLOW_USE_INITIAL_FLOW 使用輸入流作為初始流近似。
OPTFLOW_FARNEBACK_GAUSSIAN 使用Gaussian winsize×winsiz過濾器代替光流估計的相同大小的盒子過濾器;通常情況下,這個選項可以比使用箱式過濾器提供更精確的流量,代價是速度更低;通常,應將高斯窗口的勝利設置為更大的值以實現相同的穩健性水平。
我們同樣使用之前的視頻做實驗,來看代碼:
def Farne(cap):
# 獲取第一幀
ret, frame1 = cap.read()
prvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
# 遍歷每一行的第1列
hsv[..., 1] = 255
while (1):
ret, frame2 = cap.read()
next = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
# 返回一個兩通道的光流向量,實際上是每個點的像素位移值
flow = cv2.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
# print(flow.shape)
print(flow)
# 笛卡爾坐標轉換為極坐標,獲得極軸和極角
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
hsv[..., 0] = ang * 180 / np.pi / 2
hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
cv2.imshow('frame2', rgb)
k = cv2.waitKey(30) & 0xff
if k == 27 & k == 0xff:
break
prvs = next
cap.release()
cv2.destroyAllWindows()
來看部分演示的結果:



