NVIDIA / NVIDIA/DALI

Sampling Random Frames from Short Video Clips

Open
#1,388 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

question
Dominant language
C++
Stars
5.8k
Forks
678
Avg merge
3d 1h
Merged PRs (30d)
27

Description

I am trying to use the VideoLoader to extract individual frames from video clips (~10 sec each). I was previously carrying out this task by extracting frames and saving them before training. I need to be able to augment these frames as well. Is there currently a way to do this?

I used the ElementExtract operator to turn the sequence into a single frame, but the training process was extremely slow (don't have a metric but it took ~3 seconds for a single batch of size 64). In addition, changing the step parameter in the VideoReader changes the epoch_size parameter of my pipeline. Lastly, the iterator has to be reset after ~12 iterations for some reason, so each epoch only runs 12 batches. Not sure what's going on, but it would be great to get some guidance on what might be happening here. Attaching my code as well.

import os
import numpy as np

from nvidia.dali.pipeline import Pipeline
import nvidia.dali.ops as ops
import nvidia.dali.types as types
from nvidia.dali.plugin.pytorch import DALIGenericIterator, TorchPythonFunction

def reshape(input_seq):
    return input_seq.view(-1, input_seq.size()[-3], input_seq.size()[-2], input_seq.size()[-1])

class Training_Pipeline(Pipeline):
    def __init__(self, num_threads, device_id, annotation_path, shuffle, sequence_length=1, batch_size=64):
        super(Training_Pipeline, self).__init__(batch_size, num_threads, device_id)
        self.sequence_length = sequence_length

        #TODO figure out these parameters
        self.input = ops.VideoReader(device='gpu', file_list=annotation_path, step = 10, stride=10,
                sequence_length=self.sequence_length, image_type=types.RGB, shard_id=0,
                num_shards=1, random_shuffle=shuffle, initial_fill=1, normalized=True)

        self.small_rotation = ops.Uniform(range=(-10.0, 10.0))
        self.brightness_change = ops.Uniform(range=(0.8, 1.2))
        
        self.rotate = ops.Rotate(device = 'gpu')
        self.brightness = ops.Brightness(device = 'gpu')
        self.resize = ops.Resize(device = "gpu", image_type = types.RGB,
                interp_type = types.INTERP_LINEAR, resize_x = 224, resize_y = 224)
        self.element_extract = ops.ElementExtract(element_map=0, device='gpu')

        #self.reshape = TorchPythonFunction(reshape, device='cpu')

    def transform(self, inputs):
        # remove arbitrary 2nd dimension (we are only selecting 1 frame per sequence)
        images = self.element_extract(inputs)
        images = self.resize(images)
        if np.random.randint(0, 2):
            images = ops.Flip(device='gpu', horizontal = 0, vertical = 1)(images)

        if np.random.randint(0, 2):
            images = ops.Flip(device='gpu', horizontal = 1, vertical = 0)(images)

        images = ops.Rotate(device = 'gpu', angle = 90 * np.random.randint(0, 4))(images)
        images = self.rotate(images, angle = self.small_rotation())
        images = self.brightness(images, brightness = self.brightness_change())

        return images

    def define_graph(self):
        images, labels = self.input(name="Reader")
        images = self.transform(images)

        return images, labels

class DALILoader():
    def __init__(self, num_threads, device_id, batch_size, annotation_path, shuffle):
        self.pipeline = Training_Pipeline(batch_size=batch_size, num_threads=num_threads,
                device_id=device_id, annotation_path=annotation_path, shuffle=shuffle)

        self.pipeline.build()
        self.epoch_size = self.pipeline.epoch_size("Reader")
        self.dali_iterator = DALIGenericIterator(self.pipeline, ["data", "label"], self.epoch_size,
             auto_reset=True)

    def __len__(self):
        return int(self.epoch_size)

    def __iter__(self):
        return self.dali_iterator.__iter__()

# wrap dataset in dataloader
def prepare_dataset(args):
    #TODO figure out num_threads parameter
    if args.cuda:
        kwargs = {'num_threads': 16, 'device_id': 0, 'annotation_path': os.path.join(args.annotation_path,
            "train_annots.txt"), 'shuffle': True, 'batch_size': args.batch_size}
        val_kwargs = {'num_threads': 16, 'device_id': 0, 'annotation_path': args.test_annotation_path,
                'shuffle': True, 'batch_size': args.val_batch_size}
        test_kwargs = {'num_threads': 8, 'device_id': 0, 'annotation_path': args.test_annotation_path,
                'shuffle': True, 'batch_size': args.test_batch_size}

    else:
        kwargs = {'num_threads': 2, 'device_id': 0, 'annotation_path': args.annotation_path,
                'shuffle': True, 'batch_size': args.batch_size}
        val_kwargs = {'num_threads': 8, 'device_id': 0, 'annotation_path': args.test_annotation_path,
                'shuffle': True, 'batch_size': args.val_batch_size}
        test_kwargs = {'num_threads': 8, 'device_id': 0, 'annotation_path': args.test_annotation_path,
                'shuffle': True, 'batch_size': args.test_batch_size}

    train_loader, val_loader, test_loaders = None, None, None

    if not args.test_only:
        #train_pipe = Training_Pipeline(**kwargs)
        #train_pipe.build()
        train_loader = DALILoader(**kwargs)
        #train_loader = DALIGenericIterator(train_pipe, ['data', 'label'], train_pipe.epoch_size("Reader"))

    #val_dataset = DatasetClass(args.val_data_dir, datasets = args.datasets, categories = args.categories,
    #        extra_channel = args.extra_channel, transform = transforms.Compose(resize_normalize_only))

    #val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=args.test_batch_size, shuffle=False, **test_kwargs)

    #test_dataset = DatasetClass(args.test_data_dir, datasets = args.datasets,
    #        categories = args.categories, extra_channel = args.extra_channel,
    #        transform = transforms.Compose(resize_normalize_only))

    #if args.tta:
    #    test_dataset_augmented = DatasetClass(args.test_data_dir, datasets = args.datasets,
    #            categories = args.categories, extra_channel = args.extra_channel,
    #            transform = transforms.Compose(test_composed))

    #    test_dataset2_augmented = DatasetClass(args.test_data_dir, datasets = args.datasets,
    #            categories = args.categories, extra_channel = args.extra_channel,
    #            transform = transforms.Compose(test_composed))

    #    test_loaders = [torch.utils.data.DataLoader(test_dataset, batch_size=args.test_batch_size,
    #            shuffle=False, **test_kwargs),
    #            torch.utils.data.DataLoader(test_dataset_augmented, batch_size=args.test_batch_size,
    #                shuffle=False, **test_kwargs),
    #            torch.utils.data.DataLoader(test_dataset2_augmented, batch_size=args.test_batch_size,
    #                shuffle=False, **test_kwargs)]

    #else:
    #    test_loaders = [torch.utils.data.DataLoader(test_dataset, batch_size=args.test_batch_size,
    #            shuffle=False, **test_kwargs)]

    return train_loader, val_loader, test_loaders

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Training_Pipeline.define_graph and DALILoader code shown in the issue, focusing on VideoReader, ElementExtract, and DALIGenericIterator behavior. Reproduce the short-clip sampling, epoch-size, throughput, and iterator-reset observations; done means documenting or implementing a supported approach for random frame sampling with augmentation and correct epoch iteration.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, pytorch
Domain
data, machine-learning
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.