OpenCV的calcOpticalFlowFarneback输出未知

时间:2017-12-09 19:04:54

标签: python python-2.7 opencv computer-vision opticalflow

我一直想知道OpenCV的calcOpticalFlowFarneback函数返回的光流矩阵是什么。如果我计算这个Python行:

flow = cv2.calcOpticalFlowFarneback(cv2.UMat(prvs),cv2.UMat(next), None, 0.5, 3, 15, 3, 5, 1.2, 0)

我将获得一个矩阵,其大小与prvsnext相同,每个位置包含两个元素(x,y)的向量。我的问题是......该向量是从prvsnext或从nextprvs的向量?

感谢。

1 个答案:

答案 0 :(得分:3)

光流方法的一般目的是找到两个图像(或通常是视频帧)之间的每个像素(如果是密集的)或每个特征点(如果是稀疏的)的速度分量。想法是帧N-1中的像素移动到帧N中的新位置,并且这些像素的位置的差异类似于速度矢量。这意味着前一帧中位置(x,y)处的像素将位于下一帧中的位置(x + v_x,y + v_y)。

对于像素值,这意味着对于给定位置(x,y),prev_frame(x, y)处的像素值与curr_frame(x+v_x, y+v_y)处的像素值相同。或者更具体地说,就实际数组索引而言:

prev_frame[y, x] == curr_frame[y + flow[y, x, 1], x + flow[y, x, 0]]

注意这里(x,y)的反向排序。数组以(row,col)排序为索引,这意味着首先是y组件,然后是x组件。请特别注意flow[y, x]是一个向量,其中第一个元素是 x 坐标,第二个是 y 坐标 - 这就是为什么我添加了y + flow[y, x, 1]x + flow[y, x, 0]。您会在the docs for calcOpticalFlowFarneback()中看到相同的内容:

  

该函数使用Farneback算法为每个prev像素找到光流,以便

prev(y,x) ~ next(y + flow(y,x)[1], x + flow(y,x)[0])

密集光流算法期望像素距离它们开始的位置不远,因此它们通常用于视频---每帧都没有大量的变化。如果每一帧都存在巨大差异,那么您可能无法获得正确的估算。当然,金字塔分辨率模型的目的是帮助进行更大的跳跃,但是你需要注意选择合适的分辨率。

这是一个完整的例子。我将从今年早些时候在温哥华拍摄的this short timelapse开始。我将创建一个函数,该函数为具有颜色的每个像素分配流的方向,并使用该颜色的亮度分配流的幅度。这意味着更亮的像素将对应于更高的流量,并且颜色对应于方向。这也是他们在OpenCV optical flow tutorial上的最后一个例子中所做的。

import cv2
import numpy as np

def flow_to_color(flow, hsv):
    mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
    hsv[..., 0] = ang*180/np.pi/2
    hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
    return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

cap = cv2.VideoCapture('vancouver.mp4')

fps = cap.get(cv2.CAP_PROP_FPS)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('optflow.mp4', fourcc, fps, (w, h))

optflow_params = [0.5, 3, 15, 3, 5, 1.2, 0]

frame_exists, prev_frame = cap.read()
prev = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(prev_frame)
hsv[..., 1] = 255

while(cap.isOpened()):
    frame_exists, curr_frame = cap.read()
    if frame_exists:
        curr = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)
        flow = cv2.calcOpticalFlowFarneback(prev, curr, None, *optflow_params)
        rgb = flow_to_color(flow, hsv)
        out.write(rgb)
        prev = curr
    else:
        break

cap.release()
out.release()
print('done')

here's the resulting video

但是,您要做的是在帧之间进行插值。这有点令人困惑,因为最好的方法是使用cv2.remap()但这个函数在我们想要的相反的方向上工作。光流告诉我们像素去哪里,但是remap()想知道像素来自哪里。实际上,我们需要将光流量计算的顺序交换为remap。有关remap()功能的详尽说明,请参阅我的回答here

所以我在这里创建了一个函数interpolate_frames(),它将在流程中插入你想要的许多帧。这与我们在评论中讨论的完全相同,但请注意currprevcalcOpticalFlowFarneback()的翻转顺序。

由于帧间移动非常高,因此上面的间隔拍摄视频是一个不好的选择。相反,我会在与输入相同的位置使用short clip from another video镜头。

import cv2
import numpy as np


def interpolate_frames(frame, coords, flow, n_frames):
    frames = [frame]
    for f in range(1, n_frames):
        pixel_map = coords + (f/n_frames) * flow
        inter_frame = cv2.remap(frame, pixel_map, None, cv2.INTER_LINEAR)
        frames.append(inter_frame)
    return frames


cap = cv2.VideoCapture('vancouver.mp4')

fps = cap.get(cv2.CAP_PROP_FPS)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter('optflow-inter1a.mp4', fourcc, fps, (w, h))

optflow_params = [0.5, 3, 15, 3, 5, 1.2, 0]

frame_exists, prev_frame = cap.read()
prev = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
y_coords, x_coords = np.mgrid[0:h, 0:w]
coords = np.float32(np.dstack([x_coords, y_coords]))

while(cap.isOpened()):
    frame_exists, curr_frame = cap.read()
    if frame_exists:
        curr = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)
        flow = cv2.calcOpticalFlowFarneback(curr, prev, None, *optflow_params)
        inter_frames = interpolate_frames(prev_frame, coords, flow, 4)
        for frame in inter_frames:
            out.write(frame)
        prev_frame = curr_frame
        prev = curr
    else:
        break

cap.release()
out.release()

here's the output。原始每帧有4帧,所以它减慢了4倍。当然,会出现黑色边缘像素,因此在执行此操作时,您可能要么对帧进行某种边界插值(可以使用cv2.copyMakeBorder())重复相似的边缘像素,并且/或稍微裁剪最终输出以摆脱它。请注意,大多数视频稳定算法 会出于类似原因裁剪图像。这就是为什么当你将手机摄像头切换到视频时,你会注意到更大的焦距(它看起来有点放大)。