# Copyright (c) OpenMMLab. All rights reserved. import argparse import os import os.path as osp import cv2 import numpy as np def flow_to_img(raw_flow, bound=20.): """Convert flow to gray image. Args: raw_flow (np.ndarray[float]): Estimated flow with the shape (w, h). bound (float): Bound for the flow-to-image normalization. Default: 20. Returns: np.ndarray[uint8]: The result list of np.ndarray[uint8], with shape (w, h). """ flow = np.clip(raw_flow, -bound, bound) flow += bound flow *= (255 / float(2 * bound)) flow = flow.astype(np.uint8) return flow def generate_flow(frames, method='tvl1'): """Estimate flow with given frames. Args: frames (list[np.ndarray[uint8]]): List of rgb frames, with shape (w, h, 3). method (str): Use which method to generate flow. Options are 'tvl1' and 'farneback'. Default: 'tvl1'. Returns: list[np.ndarray[float]]: The result list of np.ndarray[float], with shape (w, h, 2). """ assert method in ['tvl1', 'farneback'] gray_frames = [cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) for frame in frames] if method == 'tvl1': tvl1 = cv2.optflow.DualTVL1OpticalFlow_create() def op(x, y): return tvl1.calc(x, y, None) elif method == 'farneback': def op(x, y): return cv2.calcOpticalFlowFarneback(x, y, None, 0.5, 3, 15, 3, 5, 1.2, 0) gray_st = gray_frames[:-1] gray_ed = gray_frames[1:] flow = [op(x, y) for x, y in zip(gray_st, gray_ed)] return flow def extract_dense_flow(path, dest, bound=20., save_rgb=False, start_idx=0, rgb_tmpl='img_{:05d}.jpg', flow_tmpl='{}_{:05d}.jpg', method='tvl1'): """Extract dense flow given video or frames, save them as gray-scale images. Args: path (str): Location of the input video. dest (str): The directory to store the extracted flow images. bound (float): Bound for the flow-to-image normalization. Default: 20. save_rgb (bool): Save extracted RGB frames. Default: False. start_idx (int): The starting frame index if use frames as input, the first image is path.format(start_idx). Default: 0. rgb_tmpl (str): The template of RGB frame names, Default: 'img_{:05d}.jpg'. flow_tmpl (str): The template of Flow frame names, Default: '{}_{:05d}.jpg'. method (str): Use which method to generate flow. Options are 'tvl1' and 'farneback'. Default: 'tvl1'. """ frames = [] assert osp.exists(path) video = cv2.VideoCapture(path) flag, f = video.read() while flag: frames.append(f) flag, f = video.read() flow = generate_flow(frames, method=method) flow_x = [flow_to_img(x[:, :, 0], bound) for x in flow] flow_y = [flow_to_img(x[:, :, 1], bound) for x in flow] if not osp.exists(dest): os.system('mkdir -p ' + dest) flow_x_names = [ osp.join(dest, flow_tmpl.format('x', ind + start_idx)) for ind in range(len(flow_x)) ] flow_y_names = [ osp.join(dest, flow_tmpl.format('y', ind + start_idx)) for ind in range(len(flow_y)) ] num_frames = len(flow) for i in range(num_frames): cv2.imwrite(flow_x_names[i], flow_x[i]) cv2.imwrite(flow_y_names[i], flow_y[i]) if save_rgb: img_names = [ osp.join(dest, rgb_tmpl.format(ind + start_idx)) for ind in range(len(frames)) ] for frame, name in zip(frames, img_names): cv2.imwrite(name, frame) def parse_args(): parser = argparse.ArgumentParser(description='Extract flow and RGB images') parser.add_argument( '--input', help='videos for frame extraction, can be' 'single video or a video list, the video list should be a txt file ' 'and just consists of filenames without directories') parser.add_argument( '--prefix', default='', help='the prefix of input ' 'videos, used when input is a video list') parser.add_argument( '--dest', default='', help='the destination to save ' 'extracted frames') parser.add_argument( '--save-rgb', action='store_true', help='also save ' 'rgb frames') parser.add_argument( '--rgb-tmpl', default='img_{:05d}.jpg', help='template filename of rgb frames') parser.add_argument( '--flow-tmpl', default='{}_{:05d}.jpg', help='template filename of flow frames') parser.add_argument( '--start-idx', type=int, default=1, help='the start ' 'index of extracted frames') parser.add_argument( '--method', default='tvl1', help='use which method to ' 'generate flow') parser.add_argument( '--bound', type=float, default=20, help='maximum of ' 'optical flow') args = parser.parse_args() return args if __name__ == '__main__': args = parse_args() if args.input.endswith('.txt'): lines = open(args.input).readlines() lines = [x.strip() for x in lines] videos = [osp.join(args.prefix, x) for x in lines] dests = [osp.join(args.dest, x.split('.')[0]) for x in lines] for video, dest in zip(videos, dests): extract_dense_flow(video, dest, args.bound, args.save_rgb, args.start_idx, args.rgb_tmpl, args.flow_tmpl, args.method) else: extract_dense_flow(args.input, args.dest, args.bound, args.save_rgb, args.start_idx, args.rgb_tmpl, args.flow_tmpl, args.method)