Opencv----Optical Flow

2019-11-26 16:10:12 浏览数 (1)

目标

  1. 理解光流的概念和lucas-kanade估计算法
  2. 我们将使用cv.calcOpticalFlowPyrLK() 函数来跟踪视频中的特征点
  3. 我们将使用cv.calcOpticalFlowFarneback()函数来创建一个密集的光流场

光流

光流是相机或物体运动引起的两连续帧图像中物体的运动模式, 是一个二维的位移向量场, 每一个向量表示第一个点到第二个点之间的位移

imageimage

图片展示了在五个连续帧中球的移动, 箭头表示位移向量, 光流应用在许多领域:

  • 运动结构(structure from motion)
  • 视频压缩
  • 视频稳定

光流基于几个基本假设:

  1. 物体的像素强度在连续帧中不改变
  2. 相邻像素具有相似的运动

考虑一个像素I(x_0, y_0, t_0)(这里增加了一个时间维度, 前期只用图像, 时间维度暂时不用), 下一帧图像移动了(dx, dy)的距离, 耗费dt的时间, 这些像素相同并且强度不变, 因此

I(x_0,y_0,t_0)=I(x_0 dx,y_0 dy,t_0 dt)

右边使用泰勒级数展开

I(x_0,y_0,t_0)=I(x_0,y_0,t_0) (dx frac{partial}{partial x} dy frac{partial}{partial y} dt frac{partial}{partial t})I(x_0,y_0,t_0) frac{1}{2!} (dx frac{partial}{partial x} dy frac{partial}{partial y} dt frac{partial}{partial t})^2 I(x_0,y_0,t_0) cdots

忽略二阶及以上的高阶小量, 并对t求导

(frac{dx}{dt} frac{partial}{partial x} frac{dy}{dt} frac{partial}{partial y} frac{partial}{partial t})I(x_0,y_0,t_0) = 0

将上式写成下面的形式, 称为光流方程式, 如何写成的请自己脑补

f_x u f_y v f_t = 0

不难得出f_xf_y是图像梯度, f_t是时间梯度, 这些都是已知量, uv都是未知的

我们无法直接求解此方程

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

0 人点赞