OpenCV 的 calcOpticalFlowFarneback 的未知输出

Unknown output of OpenCV's calcOpticalFlowFarneback

我一直想知道光流矩阵,即 OpenCV 的 calcOpticalFlowFarneback 函数 returns,告诉我们什么。如果我计算此 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 的向量?

谢谢。

光流法的一般目的是找到两个图像(通常是视频帧)之间每个像素(如果密集)或每个特征点(如果稀疏)的速度分量。这个想法是 N-1 帧中的像素移动到 N 帧中的新位置,这些像素的 location 的差异就像一个速度向量。这意味着前一帧中位置 (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():

The function finds an optical flow for each prev pixel using the Farneback algorithm so that

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

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

这是一个完整的例子。我也将从 this short timelapse that I shot in Vancouver earlier this year. I'll create a function which ascribes the direction of the flow for each pixel with a color, and the magnitude of the flow with the brightness of that color. That means brighter pixels will correspond to higher flows, and the color corresponds to the direction. This is what they do in the last example on the 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() 函数的详尽解释,请参阅我的回答

所以我在这里创建了一个函数 interpolate_frames(),它将根据您希望从流中插入的帧数进行插值。这与我们在评论中讨论的完全一样,但请注意 calcOpticalFlowFarneback().

currprev 的翻转顺序

上面的延时摄影视频不太适合,因为帧间移动非常高。相反,我将在与输入相同的位置使用 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())来重复相似的边缘像素,and/or 稍微裁剪最终输出以摆脱它。请注意,出于类似原因,大多数视频稳定算法 裁剪图像。这就是为什么当您将 phone 相机切换为视频时,您会注意到焦距变长了(看起来放大了一点)的部分原因。