Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/tutorials/load_data/video.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Carregue dados de vídeo

Este tutorial mostra como carregar e pré-processar dados de vídeos AVI usando o dataset de ações humanas UCF101. Depois de pré-processar os dados, usados para tarefas de vídeo como classificação/reconhecimento, legendagem ou clustering. O dataset original contém vídeos de ações realísticas do YouTube com 101 categorias, como tocar violoncelo, escovar os dentes e maquiar os olhos. Você aprenderá a:

  • Carregar os dados a partir de um arquivo zip.

  • Ler sequências de frames dos arquivos de vídeo.

  • Visualizar os dados dos vídeos.

  • Empacotar o tf.data.Dataset gerador de frames.

Este tutorial de carregamento e pré-processamento de vídeos é a primeira parte de uma série de tutoriais do TensorFlow sobre vídeos. Aqui estão os outros três tutoriais:

Configuração

Comece instalando e importando algumas bibliotecas necessárias, incluindo: remotezip para inspecionar o conteúdo de um arquivo ZIP, tqdm para usar uma barra de progresso, OpenCV para processar arquivos de vídeo e tensorflow_docs para incorporar dados em um notebook Jupyter.

# The way this tutorial uses the `TimeDistributed` layer requires TF>=2.10 !pip install -U "tensorflow>=2.10.0"
!pip install remotezip tqdm opencv-python !pip install -q git+https://github.com/tensorflow/docs
import tqdm import random import pathlib import itertools import collections import os import cv2 import numpy as np import remotezip as rz import tensorflow as tf # Some modules to display an animation using imageio. import imageio from IPython import display from urllib import request from tensorflow_docs.vis import embed

Baixe um subconjunto do dataset UCF101

O dataset UCF101 contém 101 categorias de ações diferentes em vídeo, usadas principalmente no reconhecimento de ações. Você usará um subconjunto dessas categorias nesta demonstração.

URL = 'https://storage.googleapis.com/thumos14_files/UCF101_videos.zip'

A URL acima contém um arquivo zip com o dataset UCF 101. Crie uma função que usa a biblioteca remotezip para examinar o conteúdo do arquivo zip nessa URL:

def list_files_from_zip_url(zip_url): """ List the files in each class of the dataset given a URL with the zip file. Args: zip_url: A URL from which the files can be extracted from. Returns: List of files in each of the classes. """ files = [] with rz.RemoteZip(zip_url) as zip: for zip_info in zip.infolist(): files.append(zip_info.filename) return files
files = list_files_from_zip_url(URL) files = [f for f in files if f.endswith('.avi')] files[:10]

Comece com alguns vídeos e um número limitado de classes para treinamento. Depois de executar o bloco de código acima, observe que o nome da classe está incluso no nome de arquivo de cada vídeo.

Defina a função get_class que recupera o nome da classe a partir de um nome de arquivo. Em seguida, crie uma função chamada get_files_per_class que converte a lista de todos os arquivos (files acima) em um dicionário listando os arquivos para cada classe:

def get_class(fname): """ Retrieve the name of the class given a filename. Args: fname: Name of the file in the UCF101 dataset. Returns: Class that the file belongs to. """ return fname.split('_')[-3]
def get_files_per_class(files): """ Retrieve the files that belong to each class. Args: files: List of files in the dataset. Returns: Dictionary of class names (key) and files (values). """ files_for_class = collections.defaultdict(list) for fname in files: class_name = get_class(fname) files_for_class[class_name].append(fname) return files_for_class

Depois de obter a lista de arquivos por classe, você pode escolher o número de classes que quer usar e o número de vídeos desejado por classe para criar seu dataset.

NUM_CLASSES = 10 FILES_PER_CLASS = 50
files_for_class = get_files_per_class(files) classes = list(files_for_class.keys())
print('Num classes:', len(classes)) print('Num videos for class[0]:', len(files_for_class[classes[0]]))

Crie uma nova função chamada select_subset_of_classes que seleciona um subconjunto de classes presentes no dataset e um número específico de arquivos por classe:

def select_subset_of_classes(files_for_class, classes, files_per_class): """ Create a dictionary with the class name and a subset of the files in that class. Args: files_for_class: Dictionary of class names (key) and files (values). classes: List of classes. files_per_class: Number of files per class of interest. Returns: Dictionary with class as key and list of specified number of video files in that class. """ files_subset = dict() for class_name in classes: class_files = files_for_class[class_name] files_subset[class_name] = class_files[:files_per_class] return files_subset
files_subset = select_subset_of_classes(files_for_class, classes[:NUM_CLASSES], FILES_PER_CLASS) list(files_subset.keys())

Defina as funções helper que dividem os vídeos em datasets de treinamento, validação e teste. Os vídeos são baixados de uma URL com o arquivo zip e colocados nos respectivos subdiretórios.

def download_from_zip(zip_url, to_dir, file_names): """ Download the contents of the zip file from the zip URL. Args: zip_url: A URL with a zip file containing data. to_dir: A directory to download data to. file_names: Names of files to download. """ with rz.RemoteZip(zip_url) as zip: for fn in tqdm.tqdm(file_names): class_name = get_class(fn) zip.extract(fn, str(to_dir / class_name)) unzipped_file = to_dir / class_name / fn fn = pathlib.Path(fn).parts[-1] output_file = to_dir / class_name / fn unzipped_file.rename(output_file)

A seguinte função retorna os dados restantes que não foram colocados em um subconjunto de dados. Assim, você pode colocar os dados restantes no próximo subconjunto de dados especificado.

def split_class_lists(files_for_class, count): """ Returns the list of files belonging to a subset of data as well as the remainder of files that need to be downloaded. Args: files_for_class: Files belonging to a particular class of data. count: Number of files to download. Returns: Files belonging to the subset of data and dictionary of the remainder of files that need to be downloaded. """ split_files = [] remainder = {} for cls in files_for_class: split_files.extend(files_for_class[cls][:count]) remainder[cls] = files_for_class[cls][count:] return split_files, remainder

A função download_ucf_101_subset permite que você baixe um subconjunto do dataset UCF101 e o divida em datasets de treinamento, validação e teste. Você pode especificar o número de classes que gostaria de usar. O argumento splits permite que você passe um dicionário em que os valores-chave são o nome do subconjunto (exemplo: "trem") e o número de vídeos desejado por classe.

def download_ucf_101_subset(zip_url, num_classes, splits, download_dir): """ Download a subset of the UCF101 dataset and split them into various parts, such as training, validation, and test. Args: zip_url: A URL with a ZIP file with the data. num_classes: Number of labels. splits: Dictionary specifying the training, validation, test, etc. (key) division of data (value is number of files per split). download_dir: Directory to download data to. Return: Mapping of the directories containing the subsections of data. """ files = list_files_from_zip_url(zip_url) for f in files: path = os.path.normpath(f) tokens = path.split(os.sep) if len(tokens) <= 2: files.remove(f) # Remove that item from the list if it does not have a filename files_for_class = get_files_per_class(files) classes = list(files_for_class.keys())[:num_classes] for cls in classes: random.shuffle(files_for_class[cls]) # Only use the number of classes you want in the dictionary files_for_class = {x: files_for_class[x] for x in classes} dirs = {} for split_name, split_count in splits.items(): print(split_name, ":") split_dir = download_dir / split_name split_files, files_for_class = split_class_lists(files_for_class, split_count) download_from_zip(zip_url, split_dir, split_files) dirs[split_name] = split_dir return dirs
download_dir = pathlib.Path('./UCF101_subset/') subset_paths = download_ucf_101_subset(URL, num_classes = NUM_CLASSES, splits = {"train": 30, "val": 10, "test": 10}, download_dir = download_dir)

Depois de baixar os dados, você terá uma cópia de um subconjunto do dataset UCF101. Execute o código abaixo para imprimir o número total de vídeos entre todos os seus subconjuntos de dados.

video_count_train = len(list(download_dir.glob('train/*/*.avi'))) video_count_val = len(list(download_dir.glob('val/*/*.avi'))) video_count_test = len(list(download_dir.glob('test/*/*.avi'))) video_total = video_count_train + video_count_val + video_count_test print(f"Total videos: {video_total}")

Você também pode visualizar o diretório de arquivos de dados agora.

!find ./UCF101_subset

Crie frames a partir de cada arquivo de vídeo

A função frames_from_video_file divide os vídeos em frames, lê um intervalo de n_frames escolhidos aleatoriamente de um arquivo de vídeo e os retorna como um array do NumPy. Para reduzir a sobrecarga computacional e na memória, escolha um número pequeno de frames. Além disso, escolha o mesmo número de frames de cada vídeo, o que facilita o trabalho com lotes de dados.

def format_frames(frame, output_size): """ Pad and resize an image from a video. Args: frame: Image that needs to resized and padded. output_size: Pixel size of the output frame image. Return: Formatted frame with padding of specified output size. """ frame = tf.image.convert_image_dtype(frame, tf.float32) frame = tf.image.resize_with_pad(frame, *output_size) return frame
def frames_from_video_file(video_path, n_frames, output_size = (224,224), frame_step = 15): """ Creates frames from each video file present for each category. Args: video_path: File path to the video. n_frames: Number of frames to be created per video file. output_size: Pixel size of the output frame image. Return: An NumPy array of frames in the shape of (n_frames, height, width, channels). """ # Read each video frame by frame result = [] src = cv2.VideoCapture(str(video_path)) video_length = src.get(cv2.CAP_PROP_FRAME_COUNT) need_length = 1 + (n_frames - 1) * frame_step if need_length > video_length: start = 0 else: max_start = video_length - need_length start = random.randint(0, max_start + 1) src.set(cv2.CAP_PROP_POS_FRAMES, start) # ret is a boolean indicating whether read was successful, frame is the image itself ret, frame = src.read() result.append(format_frames(frame, output_size)) for _ in range(n_frames - 1): for _ in range(frame_step): ret, frame = src.read() if ret: frame = format_frames(frame, output_size) result.append(frame) else: result.append(np.zeros_like(result[0])) src.release() result = np.array(result)[..., [2, 1, 0]] return result

Visualize os dados de vídeo

A função frames_from_video_file retorna um conjunto de frames como um array do NumPy. Tente usar essa função em um novo vídeo da Wikimedia{:.external}, de Patrick Gillett:

!curl -O https://upload.wikimedia.org/wikipedia/commons/8/86/End_of_a_jam.ogv
video_path = "End_of_a_jam.ogv"
sample_video = frames_from_video_file(video_path, n_frames = 10) sample_video.shape
def to_gif(images): converted_images = np.clip(images * 255, 0, 255).astype(np.uint8) imageio.mimsave('./animation.gif', converted_images, fps=10) return embed.embed_file('./animation.gif')
to_gif(sample_video)

Além de examinar esse vídeo, você também pode mostrar os dados do UCF-101. Para isso, execute o código a seguir:

# docs-infra: no-execute ucf_sample_video = frames_from_video_file(next(subset_paths['train'].glob('*/*.avi')), 50) to_gif(ucf_sample_video)

Em seguida, defina a classe FrameGenerator para criar um objeto iterável que possa alimentar o pipeline de dados do TensorFlow. A função (__call__) gera o array de frames produzido por frames_from_video_file e um vetor de one-hot encoding do rótulo associado ao conjunto de frames.

class FrameGenerator: def __init__(self, path, n_frames, training = False): """ Returns a set of frames with their associated label. Args: path: Video file paths. n_frames: Number of frames. training: Boolean to determine if training dataset is being created. """ self.path = path self.n_frames = n_frames self.training = training self.class_names = sorted(set(p.name for p in self.path.iterdir() if p.is_dir())) self.class_ids_for_name = dict((name, idx) for idx, name in enumerate(self.class_names)) def get_files_and_class_names(self): video_paths = list(self.path.glob('*/*.avi')) classes = [p.parent.name for p in video_paths] return video_paths, classes def __call__(self): video_paths, classes = self.get_files_and_class_names() pairs = list(zip(video_paths, classes)) if self.training: random.shuffle(pairs) for path, name in pairs: video_frames = frames_from_video_file(path, self.n_frames) label = self.class_ids_for_name[name] # Encode labels yield video_frames, label

Teste o objeto FrameGenerator antes de empacotá-lo como um objeto do TensorFlow Dataset. Além disso, para os dados de treinamento, ative o modo de treinamento para que os dados sejam misturados.

fg = FrameGenerator(subset_paths['train'], 10, training=True) frames, label = next(fg()) print(f"Shape: {frames.shape}") print(f"Label: {label}")

Por fim, crie um pipeline de entrada de dados do TensorFlow. Esse pipeline criado a partir do objeto gerador permite que você alimente seu modelo de aprendizado profundo com dados. Nesse pipeline de vídeo, cada elemento é um único conjunto de frames e o rótulo associado a ele.

# Create the training set output_signature = (tf.TensorSpec(shape = (None, None, None, 3), dtype = tf.float32), tf.TensorSpec(shape = (), dtype = tf.int16)) train_ds = tf.data.Dataset.from_generator(FrameGenerator(subset_paths['train'], 10, training=True), output_signature = output_signature)

Confira se os rótulos foram misturados.

for frames, labels in train_ds.take(10): print(labels)
# Create the validation set val_ds = tf.data.Dataset.from_generator(FrameGenerator(subset_paths['val'], 10), output_signature = output_signature)
# Print the shapes of the data train_frames, train_labels = next(iter(train_ds)) print(f'Shape of training set of frames: {train_frames.shape}') print(f'Shape of training labels: {train_labels.shape}') val_frames, val_labels = next(iter(val_ds)) print(f'Shape of validation set of frames: {val_frames.shape}') print(f'Shape of validation labels: {val_labels.shape}')

Configure o dataset para melhor desempenho

Utilize a pré-busca em buffer para gerar dados a partir do disco sem o bloqueio de I/O. Veja duas funções importantes para usar ao carregar os dados:

  • Dataset.cache mantém o conjunto de frames na memória após o carregamento fora do disco durante a primeira época. Essa função garante que o dataset não se torne um gargalo ao treinar seu modelo. Se o dataset for muito grande para a memória, você também pode usar esse método para criar um cache no disco eficaz.

  • Dataset.prefetch: sobrepõe o processamento de dados e a execução do modelo durante o treinamento. Confira mais detalhes no guia Melhor desempenho com o tf.data.

AUTOTUNE = tf.data.AUTOTUNE train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size = AUTOTUNE) val_ds = val_ds.cache().shuffle(1000).prefetch(buffer_size = AUTOTUNE)

Para preparar os dados que alimentarão o modelo, use os lotes conforme mostrado abaixo. Observe que, ao trabalhar com dados de vídeo, como arquivos AVI, eles devem estar no formato de um objeto pentadimensional [batch_size, number_of_frames, height, width, channels]. Em comparação, uma imagem teria quatro dimensões: [batch_size, height, width, channels]. A imagem abaixo é uma ilustração de como o formato dos dados de vídeo é representado.

Formato dos dados de vídeo

train_ds = train_ds.batch(2) val_ds = val_ds.batch(2) train_frames, train_labels = next(iter(train_ds)) print(f'Shape of training set of frames: {train_frames.shape}') print(f'Shape of training labels: {train_labels.shape}') val_frames, val_labels = next(iter(val_ds)) print(f'Shape of validation set of frames: {val_frames.shape}') print(f'Shape of validation labels: {val_labels.shape}')

Próximos passos

Agora que você criou um Dataset do TensorFlow de frames de vídeos com seus rótulos, você pode usá-lo com um modelo de aprendizado profundo. O seguinte modelo de classificação que usa uma EfficientNet{:.external} pré-treinada realiza o treinamento com alta exatidão em alguns minutos:

net = tf.keras.applications.EfficientNetB0(include_top = False) net.trainable = False model = tf.keras.Sequential([ tf.keras.layers.Rescaling(scale=255), tf.keras.layers.TimeDistributed(net), tf.keras.layers.Dense(10), tf.keras.layers.GlobalAveragePooling3D() ]) model.compile(optimizer = 'adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits = True), metrics=['accuracy']) model.fit(train_ds, epochs = 10, validation_data = val_ds, callbacks = tf.keras.callbacks.EarlyStopping(patience = 2, monitor = 'val_loss'))