目标
- 理解光流的概念和lucas-kanade估计算法
- 我们将使用cv.calcOpticalFlowPyrLK() 函数来跟踪视频中的特征点
- 我们将使用cv.calcOpticalFlowFarneback()函数来创建一个密集的光流场
光流
光流是相机或物体运动引起的两连续帧图像中物体的运动模式, 是一个二维的位移向量场, 每一个向量表示第一个点到第二个点之间的位移
图片展示了在五个连续帧中球的移动, 箭头表示位移向量, 光流应用在许多领域:
- 运动结构(structure from motion)
- 视频压缩
- 视频稳定
光流基于几个基本假设:
- 物体的像素强度在连续帧中不改变
- 相邻像素具有相似的运动
考虑一个像素I(x_0, y_0, t_0)(这里增加了一个时间维度, 前期只用图像, 时间维度暂时不用), 下一帧图像移动了(dx, dy)的距离, 耗费dt的时间, 这些像素相同并且强度不变, 因此
右边使用泰勒级数展开
忽略二阶及以上的高阶小量, 并对t求导
将上式写成下面的形式, 称为光流方程式, 如何写成的请自己脑补
不难得出f_x和f_y是图像梯度, f_t是时间梯度, 这些都是已知量, u和v都是未知的
我们无法直接求解此方程
Lucas-Kanade method
我们已经假设相邻的像素点具有相同的运动, Lucas-Kanade method使用该点周围3x3的色块, 这九个点有着相同的运动, 我们可以确定这九个点的f_x, f_y, f_t, 现在我们用九个方程解决两个未知量, 这个方程组是超定的, 最好的办法是使用最小二乘方法求解, 下面是二元二次的求解方案:
观察其逆矩阵与哈里斯角点detector的相似性, 这表明角点是更好的跟踪对象
从用户的视角, 这个想法很简单, 我们对一些点进行跟踪, 然后获得了这些点的光流矢量. 但是仍有一些难题, 直至现在, 我们只能处理一些小位移, 在大位移情况下会失败. 为了解决这个问题, 我们使用了金字塔, 当在上金字塔时, 会移除小位移, 将大位移变为小位移. 这里应用Lucas-kannade方法, 获得了带尺度的光流
Lucas-Kanade optical flow in opencv
所有这些在一个函数中提供: cv.calcOpticalFlowPyrLK(). 这里我们创建了在视频中跟踪几个点的简单应用. 我们使用cv.goodFeatureToTrack()选取跟踪点. 我们选择第一帧, 在其中提取一些托马斯角点, 然后使用Lucas-Kanade光流迭代地跟踪这些点. 对于函数cv.calcOpticalFlowPyrLK()我们传入前一帧, 和前一帧的跟踪点, 及后一帧. 函数返回下一帧的跟踪点和这些点的状态编码, 1表示找到, 0表示未找到. 我们迭代的将下一帧的点作为前一帧的跟踪点传入函数, 代码如下:
代码语言:txt复制import numpy as np
import cv2 as cv
import argparse
import glob
import sys
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
parser = argparse.ArgumentParser(description='This sample demonstrates Lucas-Kanade Optical Flow calculation.')
parser.add_argument('image', type=str, help='path to image file')
args = None
if len(sys.argv) > 1:
args = parser.parse_args()
else:
args = parser.parse_args(glob.glob("../../data/*.mp4"))
config = dict(if_dense=True)
cap = cv.VideoCapture(args.image)
# params for ShiTomasi corner detection
feature_params = dict(maxCorners=100,
qualityLevel=0.3,
minDistance=7,
blockSize=7)
# Parameters for lucas kanade optical flow
lk_params = dict( winSize=(15, 15),
maxLevel=2,
criteria=(cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))
# Create some random colors
color = np.random.randint(0, 255, (100, 3))
# Take first frame and find corners in it
ret, old_frame = cap.read()
old_frame = cv.transpose(old_frame)
old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
p0 = cv.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)
while not config["if_dense"]:
ret, frame = cap.read()
frame = cv.transpose(frame)
if frame is None:
break
frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# calculate optical flow
p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# Select good points
good_new = p1[st == 1]
good_old = p0[st == 1]
# draw the tracks
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)
img = cv.add(frame, mask)
cv.imshow('frame', img)
k = cv.waitKey(30) & 0xff
if k == 27:
break
# Now update the previous frame and previous points
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1, 1, 2)
dense lucas kanade in opencv
Lucas-Kanade方法计算稀疏特征集的光流(在我们的示例中为使用Shi-Tomasi算法检测到的角), OpenCV提供了另一种算法来查找密集的光流, 它计算帧中所有点的光流, 它基于Gunner Farneback的算法, 在2003年Gunner Farneback的“基于多项式展开的两帧运动估计”中对此进行了解释.
下面的例子展示了如何使用上述算法计算稠密光流. 我们得到了双通道的光流矢量(u, v), 我们找到了它们的大小和方向。 我们对结果进行颜色编码,以实现更好的可视化。 方向对应于图像的色相值。 幅度对应于值平面。 请参见下面的代码:
代码语言:txt复制frame1 = old_frame
prvs = cv.cvtColor(frame1, cv.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[..., 1] = 255
while config["if_dense"]:
ret, frame2 = cap.read()
frame2 = cv.transpose(frame2)
next = cv.cvtColor(frame2, cv.COLOR_BGR2GRAY)
logging.info("1")
flow = cv.calcOpticalFlowFarneback(prvs, next, None, 0.5, 3, 15, 3, 5, 1.2, 0)
logging.info("2")
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)
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