mirror of
https://github.com/modelscope/modelscope.git
synced 2026-07-11 04:50:39 +02:00
334 lines
12 KiB
Python
334 lines
12 KiB
Python
import math
|
|
import random
|
|
|
|
import numpy as np
|
|
import torch
|
|
import torch.utils.data
|
|
import torch.utils.dlpack as dlpack
|
|
import torchvision.transforms._transforms_video as transforms
|
|
from decord import VideoReader
|
|
from torchvision.transforms import Compose
|
|
|
|
from modelscope.metainfo import Preprocessors
|
|
from modelscope.utils.constant import Fields, ModeKeys
|
|
from modelscope.utils.type_assert import type_assert
|
|
from .base import Preprocessor
|
|
from .builder import PREPROCESSORS
|
|
|
|
|
|
def ReadVideoData(cfg,
|
|
video_path,
|
|
num_spatial_crops_override=None,
|
|
num_temporal_views_override=None):
|
|
""" simple interface to load video frames from file
|
|
|
|
Args:
|
|
cfg (Config): The global config object.
|
|
video_path (str): video file path
|
|
num_spatial_crops_override (int): the spatial crops per clip
|
|
num_temporal_views_override (int): the temporal clips per video
|
|
Returns:
|
|
data (Tensor): the normalized video clips for model inputs
|
|
"""
|
|
data = _decode_video(cfg, video_path, num_temporal_views_override)
|
|
if num_spatial_crops_override is not None:
|
|
num_spatial_crops = num_spatial_crops_override
|
|
transform = kinetics400_tranform(cfg, num_spatial_crops_override)
|
|
else:
|
|
num_spatial_crops = cfg.TEST.NUM_SPATIAL_CROPS
|
|
transform = kinetics400_tranform(cfg, cfg.TEST.NUM_SPATIAL_CROPS)
|
|
data_list = []
|
|
for i in range(data.size(0)):
|
|
for j in range(num_spatial_crops):
|
|
transform.transforms[1].set_spatial_index(j)
|
|
data_list.append(transform(data[i]))
|
|
return torch.stack(data_list, dim=0)
|
|
|
|
|
|
def kinetics400_tranform(cfg, num_spatial_crops):
|
|
"""
|
|
Configs the transform for the kinetics-400 dataset.
|
|
We apply controlled spatial cropping and normalization.
|
|
Args:
|
|
cfg (Config): The global config object.
|
|
num_spatial_crops (int): the spatial crops per clip
|
|
Returns:
|
|
transform_function (Compose): the transform function for input clips
|
|
"""
|
|
resize_video = KineticsResizedCrop(
|
|
short_side_range=[cfg.DATA.TEST_SCALE, cfg.DATA.TEST_SCALE],
|
|
crop_size=cfg.DATA.TEST_CROP_SIZE,
|
|
num_spatial_crops=num_spatial_crops)
|
|
std_transform_list = [
|
|
transforms.ToTensorVideo(), resize_video,
|
|
transforms.NormalizeVideo(
|
|
mean=cfg.DATA.MEAN, std=cfg.DATA.STD, inplace=True)
|
|
]
|
|
return Compose(std_transform_list)
|
|
|
|
|
|
def _interval_based_sampling(vid_length, vid_fps, target_fps, clip_idx,
|
|
num_clips, num_frames, interval, minus_interval):
|
|
"""
|
|
Generates the frame index list using interval based sampling.
|
|
Args:
|
|
vid_length (int): the length of the whole video (valid selection range).
|
|
vid_fps (int): the original video fps
|
|
target_fps (int): the normalized video fps
|
|
clip_idx (int): -1 for random temporal sampling, and positive values for sampling specific
|
|
clip from the video
|
|
num_clips (int): the total clips to be sampled from each video.
|
|
combined with clip_idx, the sampled video is the "clip_idx-th" video from
|
|
"num_clips" videos.
|
|
num_frames (int): number of frames in each sampled clips.
|
|
interval (int): the interval to sample each frame.
|
|
minus_interval (bool): control the end index
|
|
Returns:
|
|
index (tensor): the sampled frame indexes
|
|
"""
|
|
if num_frames == 1:
|
|
index = [random.randint(0, vid_length - 1)]
|
|
else:
|
|
# transform FPS
|
|
clip_length = num_frames * interval * vid_fps / target_fps
|
|
|
|
max_idx = max(vid_length - clip_length, 0)
|
|
if num_clips == 1:
|
|
start_idx = max_idx / 2
|
|
else:
|
|
start_idx = clip_idx * math.floor(max_idx / (num_clips - 1))
|
|
if minus_interval:
|
|
end_idx = start_idx + clip_length - interval
|
|
else:
|
|
end_idx = start_idx + clip_length - 1
|
|
|
|
index = torch.linspace(start_idx, end_idx, num_frames)
|
|
index = torch.clamp(index, 0, vid_length - 1).long()
|
|
|
|
return index
|
|
|
|
|
|
def _decode_video_frames_list(cfg,
|
|
frames_list,
|
|
vid_fps,
|
|
num_temporal_views_override=None):
|
|
"""
|
|
Decodes the video given the numpy frames.
|
|
Args:
|
|
cfg (Config): The global config object.
|
|
frames_list (list): all frames for a video, the frames should be numpy array.
|
|
vid_fps (int): the fps of this video.
|
|
num_temporal_views_override (int): the temporal clips per video
|
|
Returns:
|
|
frames (Tensor): video tensor data
|
|
"""
|
|
assert isinstance(frames_list, list)
|
|
if num_temporal_views_override is not None:
|
|
num_clips_per_video = num_temporal_views_override
|
|
else:
|
|
num_clips_per_video = cfg.TEST.NUM_ENSEMBLE_VIEWS
|
|
|
|
frame_list = []
|
|
for clip_idx in range(num_clips_per_video):
|
|
# for each clip in the video,
|
|
# a list is generated before decoding the specified frames from the video
|
|
list_ = _interval_based_sampling(
|
|
len(frames_list),
|
|
vid_fps,
|
|
cfg.DATA.TARGET_FPS,
|
|
clip_idx,
|
|
num_clips_per_video,
|
|
cfg.DATA.NUM_INPUT_FRAMES,
|
|
cfg.DATA.SAMPLING_RATE,
|
|
cfg.DATA.MINUS_INTERVAL,
|
|
)
|
|
frames = None
|
|
frames = torch.from_numpy(
|
|
np.stack([frames_list[index] for index in list_.tolist()], axis=0))
|
|
frame_list.append(frames)
|
|
frames = torch.stack(frame_list)
|
|
del vr
|
|
return frames
|
|
|
|
|
|
def _decode_video(cfg, path, num_temporal_views_override=None):
|
|
"""
|
|
Decodes the video given the numpy frames.
|
|
Args:
|
|
cfg (Config): The global config object.
|
|
path (str): video file path.
|
|
num_temporal_views_override (int): the temporal clips per video
|
|
Returns:
|
|
frames (Tensor): video tensor data
|
|
"""
|
|
vr = VideoReader(path)
|
|
if num_temporal_views_override is not None:
|
|
num_clips_per_video = num_temporal_views_override
|
|
else:
|
|
num_clips_per_video = cfg.TEST.NUM_ENSEMBLE_VIEWS
|
|
|
|
frame_list = []
|
|
for clip_idx in range(num_clips_per_video):
|
|
# for each clip in the video,
|
|
# a list is generated before decoding the specified frames from the video
|
|
list_ = _interval_based_sampling(
|
|
len(vr),
|
|
vr.get_avg_fps(),
|
|
cfg.DATA.TARGET_FPS,
|
|
clip_idx,
|
|
num_clips_per_video,
|
|
cfg.DATA.NUM_INPUT_FRAMES,
|
|
cfg.DATA.SAMPLING_RATE,
|
|
cfg.DATA.MINUS_INTERVAL,
|
|
)
|
|
frames = None
|
|
if path.endswith('.avi'):
|
|
append_list = torch.arange(0, list_[0], 4)
|
|
frames = dlpack.from_dlpack(
|
|
vr.get_batch(torch.cat([append_list,
|
|
list_])).to_dlpack()).clone()
|
|
frames = frames[append_list.shape[0]:]
|
|
else:
|
|
frames = dlpack.from_dlpack(
|
|
vr.get_batch(list_).to_dlpack()).clone()
|
|
frame_list.append(frames)
|
|
frames = torch.stack(frame_list)
|
|
del vr
|
|
return frames
|
|
|
|
|
|
class KineticsResizedCrop(object):
|
|
"""Perform resize and crop for kinetics-400 dataset
|
|
Args:
|
|
short_side_range (list): The length of short side range. In inference, this shoudle be [256, 256]
|
|
crop_size (int): The cropped size for frames.
|
|
num_spatial_crops (int): The number of the cropped spatial regions in each video.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
short_side_range,
|
|
crop_size,
|
|
num_spatial_crops=1,
|
|
):
|
|
self.idx = -1
|
|
self.short_side_range = short_side_range
|
|
self.crop_size = int(crop_size)
|
|
self.num_spatial_crops = num_spatial_crops
|
|
|
|
def _get_controlled_crop(self, clip):
|
|
"""Perform controlled crop for video tensor.
|
|
Args:
|
|
clip (Tensor): the video data, the shape is [T, C, H, W]
|
|
"""
|
|
_, _, clip_height, clip_width = clip.shape
|
|
|
|
length = self.short_side_range[0]
|
|
|
|
if clip_height < clip_width:
|
|
new_clip_height = int(length)
|
|
new_clip_width = int(clip_width / clip_height * new_clip_height)
|
|
new_clip = torch.nn.functional.interpolate(
|
|
clip, size=(new_clip_height, new_clip_width), mode='bilinear')
|
|
else:
|
|
new_clip_width = int(length)
|
|
new_clip_height = int(clip_height / clip_width * new_clip_width)
|
|
new_clip = torch.nn.functional.interpolate(
|
|
clip, size=(new_clip_height, new_clip_width), mode='bilinear')
|
|
x_max = int(new_clip_width - self.crop_size)
|
|
y_max = int(new_clip_height - self.crop_size)
|
|
if self.num_spatial_crops == 1:
|
|
x = x_max // 2
|
|
y = y_max // 2
|
|
elif self.num_spatial_crops == 3:
|
|
if self.idx == 0:
|
|
if new_clip_width == length:
|
|
x = x_max // 2
|
|
y = 0
|
|
elif new_clip_height == length:
|
|
x = 0
|
|
y = y_max // 2
|
|
elif self.idx == 1:
|
|
x = x_max // 2
|
|
y = y_max // 2
|
|
elif self.idx == 2:
|
|
if new_clip_width == length:
|
|
x = x_max // 2
|
|
y = y_max
|
|
elif new_clip_height == length:
|
|
x = x_max
|
|
y = y_max // 2
|
|
return new_clip[:, :, y:y + self.crop_size, x:x + self.crop_size]
|
|
|
|
def _get_random_crop(self, clip):
|
|
_, _, clip_height, clip_width = clip.shape
|
|
|
|
short_side = min(clip_height, clip_width)
|
|
long_side = max(clip_height, clip_width)
|
|
new_short_side = int(random.uniform(*self.short_side_range))
|
|
new_long_side = int(long_side / short_side * new_short_side)
|
|
if clip_height < clip_width:
|
|
new_clip_height = new_short_side
|
|
new_clip_width = new_long_side
|
|
else:
|
|
new_clip_height = new_long_side
|
|
new_clip_width = new_short_side
|
|
|
|
new_clip = torch.nn.functional.interpolate(
|
|
clip, size=(new_clip_height, new_clip_width), mode='bilinear')
|
|
|
|
x_max = int(new_clip_width - self.crop_size)
|
|
y_max = int(new_clip_height - self.crop_size)
|
|
x = int(random.uniform(0, x_max))
|
|
y = int(random.uniform(0, y_max))
|
|
return new_clip[:, :, y:y + self.crop_size, x:x + self.crop_size]
|
|
|
|
def set_spatial_index(self, idx):
|
|
"""Set the spatial cropping index for controlled cropping..
|
|
Args:
|
|
idx (int): the spatial index. The value should be in [0, 1, 2], means [left, center, right], respectively.
|
|
"""
|
|
self.idx = idx
|
|
|
|
def __call__(self, clip):
|
|
return self._get_controlled_crop(clip)
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.cv, module_name=Preprocessors.movie_scene_segmentation_preprocessor)
|
|
class MovieSceneSegmentationPreprocessor(Preprocessor):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""
|
|
movie scene segmentation preprocessor
|
|
"""
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.is_train = kwargs.pop('is_train', True)
|
|
self.preprocessor_train_cfg = kwargs.pop(ModeKeys.TRAIN, None)
|
|
self.preprocessor_test_cfg = kwargs.pop(ModeKeys.EVAL, None)
|
|
self.num_keyframe = kwargs.pop('num_keyframe', 3)
|
|
|
|
from .movie_scene_segmentation import get_transform
|
|
self.train_transform = get_transform(self.preprocessor_train_cfg)
|
|
self.test_transform = get_transform(self.preprocessor_test_cfg)
|
|
|
|
def train(self):
|
|
self.is_train = True
|
|
return
|
|
|
|
def eval(self):
|
|
self.is_train = False
|
|
return
|
|
|
|
@type_assert(object, object)
|
|
def __call__(self, results):
|
|
if self.is_train:
|
|
transforms = self.train_transform
|
|
else:
|
|
transforms = self.test_transform
|
|
|
|
results = torch.stack(transforms(results), dim=0)
|
|
results = results.view(-1, self.num_keyframe, 3, 224, 224)
|
|
return results
|