Optical flow 有两个假设:
假设在第一帧中像素 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)+∂x∂IΔx+∂y∂IΔy+∂t∂IΔ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 ∂x∂IΔx+∂y∂IΔy+∂t∂IΔ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 ∂x∂IΔtΔx+∂y∂IΔtΔy+∂t∂IΔ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 ∂x∂IVx+∂y∂IVy+∂t∂I=0
此方法取点周围 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=∂x∂f
f y = ∂ f ∂ y f_y = \frac{\partial f}{\partial y} fy=∂y∂f
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]
当有大的运动时会失败。所以我们使用金字塔,大的运动也可以变成小运动。
(1) cv2.goodFeaturesToTrack() 获取点
(2) cv2.calcOpticalFlowPyrLK() 传递连续三帧。
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()