光流法(Optical Flow)及OpenCV实现

Optical Flow

Optical flow 有两个假设:

  1. 亮度恒定:在相邻连续两帧中一个目标的像素强度不会变化。
  2. 空间一致性:周围像素有类似运行。
  3. 时间规律:相邻帧时间足够短,以至于在考虑运行变化时可以忽略它们之间的差异。

假设在第一帧中像素 I ( x , y , t ) I(x,y,t) I(x,y,t) d t dt dt时间后,在下一帧中它运动了 ( d x , d y ) (dx, dy) (dx,dy)。因为像素相同,强度没有变化。

I ( x , y , t ) = I ( x + d x , y + d y , t + d t ) I(x,y,t) = I(x+dx, y+dy, t+dt) I(x,y,t)=I(x+dx,y+dy,t+dt)

根据Taylor 级数得到:

I ( x + d x , y + d y , t + d t ) = I ( x , y , t ) + ∂ I ∂ x Δ x + ∂ I ∂ y Δ y + ∂ I ∂ t Δ t I(x+dx, y+dy, t+dt) = 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 I(x+dx,y+dy,t+dt)=I(x,y,t)+xIΔx+yIΔy+tIΔt

因此:

∂ I ∂ x Δ x + ∂ I ∂ y Δ y + ∂ I ∂ t Δ t = 0 \frac{\partial I}{\partial x}\Delta x + \frac{\partial I}{\partial y}\Delta y + \frac{\partial I}{\partial t}\Delta t = 0 xIΔx+yIΔy+tIΔt=0

或者

∂ I ∂ x Δ x Δ t + ∂ I ∂ y Δ y Δ t + ∂ I ∂ t Δ t Δ 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 xIΔtΔx+yIΔtΔy+tIΔtΔt=0

用速度来代替后:

∂ I ∂ x V x + ∂ I ∂ y V y + ∂ I ∂ t = 0 \frac{\partial I}{\partial x} V_x + \frac{\partial I}{\partial y} V_y + \frac{\partial I}{\partial t} = 0 xIVx+yIVy+tI=0

Lucas-Kanade method

此方法取点周围 3x3 的patch 。9个点有相同的运动。
推导过程 :

I ( x , y , t ) = I ( x + d x , y + d y , t + d t ) I(x,y,t) = I(x+dx, y+dy, t+dt) I(x,y,t)=I(x+dx,y+dy,t+dt)

f x u + f y v + f t = 0 f_x u + f_y v + f_t = 0 fxu+fyv+ft=0

f x = ∂ f ∂ x f_x = \frac{\partial f}{\partial x} fx=xf
f y = ∂ f ∂ y f_y = \frac{\partial f}{\partial y} fy=yf
u = d x d t u = \frac{dx}{dt} u=dtdx
v = d y d t v = \frac{dy}{dt} v=dtdy

[ u v ] = [ Σ i f x i 2 Σ i f x i f y i Σ i f x i f y i Σ i f x i 2 ] − 1 [ − Σ i f x i f t i − Σ i f y i f t i ] \begin{bmatrix} u\\ v \end{bmatrix} = \begin{bmatrix} \Sigma_i f_{x_i} ^ 2 & \Sigma_i f_{x_i} f_{y_i} \\ \Sigma_i f_{x_i} f_{y_i} & \Sigma_i f_{x_i} ^ 2 \end{bmatrix}^{-1} \begin{bmatrix} -\Sigma_i f_{x_i} f_{t_i}\\ -\Sigma_i f_{y_i} f_{t_i} \end{bmatrix} [uv]=[Σifxi2ΣifxifyiΣifxifyiΣifxi2]1[ΣifxiftiΣifyifti]

当有大的运动时会失败。所以我们使用金字塔,大的运动也可以变成小运动。

Lucas-Kanade Optical Flow in OpenCV

(1) cv2.goodFeaturesToTrack() 获取点
(2) cv2.calcOpticalFlowPyrLK() 传递连续三帧。

Dense Optical Flow in OpenCV

Lucas-Kanade 方法只计算一个稀疏的特征集(角点)。OpenCV 提供了另一种方法来发现稠密光流。它计算所有点的光流。

参考资料:

光流Optical Flow介绍与OpenCV实现

OpenCv Optical Flow

维基百科 光流法

《Python计算机视觉编程》

代码

#encoding:utf-8
'''
Lucas-Kanade tracker
====================
Lucas-Kanade sparse optical flow demo. Uses goodFeaturesToTrack
for track initialization and back-tracking for match verification
between frames.
Usage
-----
lk_track.py []
Keys
----
ESC - exit
'''
 
import numpy as np
import cv2
#from common import anorm2, draw_str
from time import clock
 
lk_params = dict( winSize  = (15, 15), 
                  maxLevel = 2, 
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))    
 
feature_params = dict( maxCorners = 500, 
                       qualityLevel = 0.3,
                       minDistance = 7,
                       blockSize = 7 )
 
class App:
    def __init__(self, video_src):#构造方法,初始化一些参数和视频路径
        self.track_len = 10
        self.detect_interval = 5
        self.tracks = []
        self.cam = cv2.VideoCapture(video_src)
        self.frame_idx = 0
 
    def run(self):#光流运行方法
        while True:
            ret, frame = self.cam.read()#读取视频帧
            if frame is None:
                break
            h, w = frame.shape[:2]
            frame = cv2.resize(frame, (w//2, h//2))
            frame = np.rot90(frame)
            if not ret:
                continue
            
            frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)#转化为灰度虚图像
            vis = frame.copy()

            if len(self.tracks) > 0:#检测到角点后进行光流跟踪
                img0, img1 = self.prev_gray, frame_gray
                p0 = np.float32([tr[-1] for tr in self.tracks]).reshape(-1, 1, 2)
                p1, st, err = cv2.calcOpticalFlowPyrLK(img0, img1, p0, None, **lk_params)#前一帧的角点和当前帧的图像作为输入来得到角点在当前帧的位置
                p0r, st, err = cv2.calcOpticalFlowPyrLK(img1, img0, p1, None, **lk_params)#当前帧跟踪到的角点及图像和前一帧的图像作为输入来找到前一帧的角点位置
                d = abs(p0-p0r).reshape(-1, 2).max(-1)#得到角点回溯与前一帧实际角点的位置变化关系
                good = d < 1#判断d内的值是否小于1,大于1跟踪被认为是错误的跟踪点
                new_tracks = []
                for tr, (x, y), good_flag in zip(self.tracks, p1.reshape(-1, 2), good):#将跟踪正确的点列入成功跟踪点
                    if not good_flag:
                        continue
                    tr.append((x, y))
                    if len(tr) > self.track_len:
                        del tr[0]
                    new_tracks.append(tr)
                    cv2.circle(vis, (x, y), 2, (0, 255, 0), -1)
                self.tracks = new_tracks
                cv2.polylines(vis, [np.int32(tr) for tr in self.tracks], False, (0, 255, 0))#以上一振角点为初始点,当前帧跟踪到的点为终点划线
                #draw_str(vis, (20, 20), 'track count: %d' % len(self.tracks))

            if self.frame_idx % self.detect_interval == 0:#每5帧检测一次特征点
                mask = np.zeros_like(frame_gray)#初始化和视频大小相同的图像
                mask[:] = 255#将mask赋值255也就是算全部图像的角点
                for x, y in [np.int32(tr[-1]) for tr in self.tracks]:#跟踪的角点画圆
                    cv2.circle(mask, (x, y), 5, 0, -1)
                p = cv2.goodFeaturesToTrack(frame_gray, mask = mask, **feature_params)#像素级别角点检测
                if p is not None:
                    for x, y in np.float32(p).reshape(-1, 2):
                        self.tracks.append([(x, y)])#将检测到的角点放在待跟踪序列中

            self.frame_idx += 1
            self.prev_gray = frame_gray
            cv2.imshow('lk_track', vis)
 
            ch = 0xFF & cv2.waitKey(30)
            if ch == 27:
                break
 
def main():
    import sys
    try: video_src = sys.argv[1]
    except: video_src = "slow.flv"
 
    print(__doc__)
    App(video_src).run()
    cv2.destroyAllWindows()             
 
if __name__ == '__main__':
    main()

你可能感兴趣的:(计算机视觉)