opencv+python 稀疏光流估計算法 代碼實現 目標跟蹤

光流估計算法

光流的概念是Gibson在1950年首先提出來的。它是空間運動物體在觀察成像平面上的像素運動的瞬時速度,是利用圖像序列中像素在時間域上的變化以及相鄰幀之間的相關性來找到上一幀跟當前幀之間存在的對應關係,從而計算出相鄰幀之間物體的運動信息的一種方法。一般而言,光流是由於場景中前景目標本身的移動、相機的運動,或者兩者的共同運動所產生的。

假設條件

(1)亮度恆定,就是同一點隨着時間的變化,其亮度不會發生改變。這是基本光流法的假定(所有光流法變種都必須滿足),用於得到光流法基本方程;

(2)小運動,這個也必須滿足,就是時間的變化不會引起位置的劇烈變化,這樣灰度才能對位置求偏導(換句話說,小運動情況下我們才能用前後幀之間單位位置變化引起的灰度變化去近似灰度對位置的偏導數),這也是光流法不可或缺的假定;

(3)空間一致,一個場景上鄰近的點投影到圖像上也是鄰近點,且鄰近點速度一致。這是Lucas-Kanade光流法特有的假定,因爲光流法基本方程約束只有一個,而要求x,y方向的速度,有兩個未知變量。我們假定特徵點鄰域內做相似運動,就可以連立n多個方程求取x,y方向的速度(n爲特徵點鄰域總點數,包括該特徵點)。

研究光流場的目的就是爲了從圖片序列中近似得到不能直接得到的運動場。運動場,其實就是物體在三維真實世界中的運動在圖像上的二位速度矢量投影;光流場,是三維物體的運動在二維圖像平面上(人的眼睛或者攝像頭)亮度模式的投影。

那通俗的講就是通過一個圖片序列,把每張圖像中每個像素的運動速度和運動方向找出來就是光流場。那怎麼找呢?咱們直觀理解肯定是:第t幀的時候A點的位置是(x1, y1),那麼我們在第t+1幀的時候再找到A點,假如它的位置是(x2,y2),那麼我們就可以確定A點的運動了:(ux, vy) = (x2, y2) - (x1,y1)。

那怎麼知道第t+1幀的時候A點的位置呢? 這就存在很多的光流計算方法了。

1981年,Horn和Schunck創造性地將二維速度場與灰度相聯繫,引入光流約束方程,得到光流計算的基本算法。人們基於不同的理論基礎提出各種光流計算方法,算法性能各有不同。Barron等人對多種光流計算技術進行了總結,按照理論基礎與數學方法的區別把它們分成四種:基於梯度的方法、基於匹配的方法、基於能量的方法、基於相位的方法。近年來神經動力學方法也頗受學者重視。

L-K光流估計方法的基本原理:

L-K光流估計方法屬於一種基於匹配的光流計算方法,其包括基於特徵和區域的兩種。基於特徵的方法不斷地對目標主要特徵進行定位和跟蹤,對目標大的運動和亮度變化具有魯棒性(robustness)。存在的問題是光流通常很稀疏,而且特徵提取和精確匹配也十分困難。基於區域的方法先對類似的區域進行定位,然後通過相似區域的位移計算光流。這種方法在視頻編碼中得到了廣泛的應用。然而,它計算的光流仍不稠密。另外,這兩種方法估計亞像素精度的光流也有困難,計算量很大。在考慮光流精度和稠密性時,基於匹配的方法適用。

根據上面的光流估計基本模型得出方程IxΔx+IyΔy=−It,任務:求(u,v)=(Δx,Δy)

困難:一個方程包含兩個未知數。

Lucas-Kanade方法採用基於領域的計算方法,假設在一個小方格里的所有像素位移相同。則會得到一組光流估計方程。用矩陣表示如下:

輸入圖片說明

既:

輸入圖片說明

將上述問題轉化爲最優化問題(超定方程求解)

輸入圖片說明

最小二乘解:

輸入圖片說明

區域像素只有2個時,就是2元1次方程組求解!

多個像素,比如3*3時,則是求上述最小二乘解
進一步,L-K方法假定在一個小的圖像鄰域內速度近似一致

約束:

輸入圖片說明

對應:

輸入圖片說明

類似前述求解,可得

輸入圖片說明

可信度判斷:矩陣求逆是否能實現?

輸入圖片說明

我們在計算光流的時候,我們要求圖像對應位置灰度變化充分(具有充分特徵),假如位置灰度變化平緩,那麼沿x和y方向的偏倒就可能爲0,那麼上述式子不可求逆,也就無法計算光流。

通過特徵值判斷是否計算可信
在數學上我們要判斷矩陣是否可逆,我們可以通過計算特徵值來判斷。如果兩個特徵值遠大於0,那麼矩陣求逆是可靠的。假如有一個特徵值接近於0的話,那麼矩陣求逆是不可靠的。

opencv代碼實現

import numpy as np
import cv2
import time
import datetime

cap = cv2.VideoCapture("vtest.avi")#打開一個視頻

fourcc = cv2.VideoWriter_fourcc(*'XVID')#設置保存圖片格式
out = cv2.VideoWriter(datetime.datetime.now().strftime("%A_%d_%B_%Y_%I_%M_%S%p")+'.avi',fourcc, 10.0, (768,576))#分辨率要和原視頻對應

# 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()#多維數據轉一維,將座標轉換後賦值給a,b
        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)  #顯示圖像

    out.write(img)#保存每一幀畫面

    k = cv2.waitKey(30) & 0xff #按Esc退出檢測
    if k == 27:
        break

    # 更新上一幀的圖像和追蹤點
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)


out.release()#釋放文件
cap.release()
cv2.destoryAllWindows()#關閉所有窗口

輸入圖片說明

檢測效果視頻:稀疏光流估計算法的視頻展示-點這裏!

光流估計函數詳解:

void calcOpticalFlowPyrLK(
InputArray prevImg, InputArray nextImg, InputArray prevPts, InputOutputArray nextPts, 
OutputArray status, OutputArray err, 
Size winSize=Size(21,21), int maxLevel=3, 
TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), 
int flags=0, double minEigThreshold=1e-4 
)
  • prevImg:第一幅8位輸入圖像 或 由buildOpticalFlowPyramid()構造的金字塔。
  • nextImg:第二幅與preImg大小和類型相同的輸入圖像或金字塔。
  • prevPts:光流法需要找到的二維點的vector。點座標必須是單精度浮點數。
  • nextPts:包含輸入特徵在第二幅圖像中計算出的新位置的二維點(單精度浮點座標)的輸出vector。當使用OPTFLOW_USE_INITIAL_FLOW 標誌時,nextPts的vector必須與input的大小相同。
  • status:輸出狀態vector(類型:unsigned chars)。如果找到了對應特徵的流,則將向量的每個元素設置爲1;否則,置0。
  • err:誤差輸出vector。vector的每個元素被設置爲對應特徵的誤差,可以在flags參數中設置誤差度量的類型;如果沒有找到流,則未定義誤差(使用
  • status:參數來查找此類情況)。
  • winSize:每級金字塔的搜索窗口大小。
  • maxLevel:基於最大金字塔層次數。如果設置爲0,則不使用金字塔(單級);如果設置爲1,則使用兩個級別,等等。如果金字塔被傳遞到input,那麼算法使用的級別與金字塔同級別但不大於MaxLevel。
  • criteria:指定迭代搜索算法的終止準則(在指定的最大迭代次數標準值(criteria.maxCount)之後,或者當搜索窗口移動小於criteria.epsilon。)
  • flags:操作標誌,可選參數:
  • OPTFLOW_USE_INITIAL_FLOW:使用初始估計,存儲在nextPts中;如果未設置標誌,則將prevPts複製到nextPts並被視爲初始估計。
  • OPTFLOW_LK_GET_MIN_EIGENVALS:使用最小本徵值作爲誤差度量(見minEigThreshold描述);如果未設置標誌,則將原始周圍的一小部分和移動的點之間的 L1 距離除以窗口中的像素數,作爲誤差度量。
  • minEigThreshol:算法所計算的光流方程的2x2標準矩陣的最小本徵值(該矩陣稱爲[Bouguet00]中的空間梯度矩陣)÷ 窗口中的像素數。如果該值小於MinEigThreshold,則過濾掉相應的特徵,相應的流也不進行處理。因此可以移除不好的點並提升性能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章