2 python opencv opticalflow computer-vision python-2.7
我一直想知道OpenCV的calcOpticalFlowFarneback函数返回的光流矩阵告诉了什么。如果我计算此Python行:
flow = cv2.calcOpticalFlowFarneback(cv2.UMat(prvs),cv2.UMat(next), None, 0.5, 3, 15, 3, 5, 1.2, 0)
Run Code Online (Sandbox Code Playgroud)
我将得到一个矩阵,其大小与prvs和next框架相同,每个位置包含两个元素(x,y)的向量。我的问题是...这矢量是矢量从prvs到next或next到prvs?
谢谢。
光流方法的一般目的是找到两个图像(通常是视频帧)之间每个像素(如果密集)或每个特征点(如果稀疏)的速度分量。这个想法是第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]]
Run Code Online (Sandbox Code Playgroud)
注意此处(x,y)的相反顺序。数组以(行,列)顺序索引,这意味着y分量首先出现,然后是x分量出现。请特别注意,这flow[y, x]是一个向量,其中第一个元素是x坐标,第二个元素是y坐标,因此我加了y + flow[y, x, 1]和x + flow[y, x, 0]。您会在文档中calcOpticalFlowFarneback()看到以下内容:
该函数使用Farneback算法为每个上一个像素找到光流,因此
Run Code Online (Sandbox Code Playgroud)prev(y,x) ~ next(y + flow(y,x)[1], x + flow(y,x)[0])
密集的光流算法期望像素离它们的起点不远,因此它们通常用于视频-每帧的变化不大。如果每个帧之间都存在巨大差异,则可能无法获得正确的估计。当然,金字塔分辨率模型的目的是帮助更大的跳跃,但是您需要谨慎选择合适的分辨率范围。
这是一个完整的例子。我将从今年早些时候在温哥华拍摄的短暂间隔开始。我将创建一个函数,该函数用一种颜色为每个像素分配流的方向,并用该颜色的亮度确定流的大小。这意味着较亮的像素将对应较高的流量,而颜色则对应于方向。这也是他们在OpenCV光流教程的最后一个示例中所做的。
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')
Run Code Online (Sandbox Code Playgroud)
这是生成的视频。
但是,您要在帧之间进行插值。这有点令人困惑,因为执行此操作的最佳方法是,cv2.remap()但是此功能的作用与我们想要的方向相反。光流告诉我们像素的位置,但是remap()想知道像素的来源。因此,实际上,我们需要将光流计算的顺序交换为remap。有关此功能的详尽说明,请参见此处的答案remap()。
因此,在这里我创建了一个函数interpolate_frames(),该函数将对流中想要的许多帧进行插值。这个作品完全一样,我们在评论中讨论,但注意的翻转排序curr和prev里面calcOpticalFlowFarneback()。
由于帧间运动非常高,因此上面的延时视频是一个不好的选择。取而代之的是,我将使用与输入位置相同的另一个视频片段中的简短剪辑。
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()
Run Code Online (Sandbox Code Playgroud)
这是输出。原稿中的每一帧都有4帧,因此速度降低了4倍。当然,会有黑色边缘像素进入,因此执行此操作时,您可能想要对帧进行某种形式的边界插值(可以使用cv2.copyMakeBorder())来重复相似的边缘像素,和/或裁剪最终输出一点点摆脱它。请注意,出于类似的原因,大多数视频稳定算法都会裁切图像。这就是为什么在将手机摄像头切换为视频时会注意到更大的焦距(看起来有点放大)的部分原因。
| 归档时间: |
|
| 查看次数: |
1342 次 |
| 最近记录: |