Общая цель метода оптического потока состоит в том, чтобы найти компонент скорости каждого пикселя (если он плотный) или каждой характерной точки (если он разреженный) между двумя изображениями (или видеокадрами, как правило). Идея состоит в том, что пиксели в кадре 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) здесь. Массивы индексируются в порядке (строка, столбец), что означает, что сначала идет компонент y, а затем компонент x. Обратите особое внимание на то, что flow[y, x]
— это вектор, где первый элемент — это координата x, а второй — координата y — поэтому я добавил y + flow[y, x, 1]
и x + flow[y, x, 0]
. Вы увидите то же самое в документации для calcOpticalFlowFarneback()
а>:
Функция находит оптический поток для каждого предыдущего пикселя с помощью алгоритма Фарнебака, так что
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')
И результирующее видео.
Однако то, что вы хотите сделать, это интерполировать между кадрами. Это немного сбивает с толку, потому что лучше всего это сделать с помощью 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()
И вот результат. В оригинале на каждый кадр приходится 4 кадра, поэтому он замедлен в 4 раза. Конечно, будут появляться черные краевые пиксели, поэтому при этом вы, вероятно, захотите либо выполнить интерполяцию границ ваших кадров (вы можете использовать cv2.copyMakeBorder()
), чтобы повторить похожие краевые пиксели, и/или обрезать окончательный результат. немного, чтобы избавиться от этого. Обратите внимание, что большинство алгоритмов стабилизации видео обрезают изображение по тем же причинам. Это одна из причин, почему, когда вы переключаете камеру телефона на видео, вы заметите большее фокусное расстояние (оно выглядит немного увеличенным).
person
alkasm
schedule
10.12.2017
prvs
поnext
конечно! Вы также можете убедиться в этом, просто взглянув на значения. Пиксель вprvs[y, x]
переместится наflow[y, x]
пикселя, чтобы добраться доnext[y', x']
. Другими словами,next[flow[y, x]] = prvs[y, x]
. (Это просто пример, здесь вам нужно позаботиться о порядке индексации). - person alkasm   schedule 10.12.2017prvs
точно в промежуточную точку междуprvs
иnext
, что мне следует учитывать?prvs[x,y] + flow[x,y]
илиprvs[x,y] - flow[x,y]
? @Александр Рейнольдс - person   schedule 10.12.2017[y,x]
вместо[x,y]
? Элементы вектора потока изменились? Сначала компонент y, а затем компонент x? @Александр Рейнольдс - person   schedule 10.12.2017[y, x]
, потому что это изображения, которые являются массивами и, как таковые, индексируются с помощью(row, col)
, т.е.(y, x)
.prvs[y, x]
— это пиксель,flow[y, x]
— это вектор, их не следует добавлять. Технически это должно бытьnext[ [y, x] + flow[y, x][::-1] ] = prvs[y, x]
,[::-1]
там, потому что я думаю, чтоflow[y, x]
даст вам координаты в порядке (x, y), поэтому[::-1]
меняет их на[y, x]
для индексации. Я могу попробовать и дать вам лучший ответ позже, когда я дома. - person alkasm   schedule 10.12.2017