uniformer
              
                
                
            
          copied
				 8 changed files with 268 additions and 1 deletions
			
			
		@ -1,2 +1,106 @@ | 
			
		|||||
# uniformer | 
				 | 
			
		||||
 | 
				# Video Classification with Uniformer | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				*Author: [Xinyu Ge](https://github.com/gexy185)* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<br /> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				## Description | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				A video classification operator generates labels (and corresponding scores) and extracts features for the input video. | 
			
		||||
 | 
				It transforms the video into frames and loads pre-trained models by model names. | 
			
		||||
 | 
				This operator has implemented pre-trained models from [Uniformer](https://arxiv.org/abs/2201.09450) | 
			
		||||
 | 
				and maps vectors with labels provided by datasets used for pre-training. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<br /> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				## Code Example | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				Use the pretrained Uniformer model to classify and generate a vector for the given video path './archery.mp4'  | 
			
		||||
 | 
				([download](https://dl.fbaipublicfiles.com/pytorchvideo/projects/archery.mp4)). | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				 *Write the pipeline in simplified style*: | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				- Predict labels (default): | 
			
		||||
 | 
				```python | 
			
		||||
 | 
				import towhee | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				( | 
			
		||||
 | 
				    towhee.glob('./archery.mp4')  | 
			
		||||
 | 
				          .video_decode.ffmpeg() | 
			
		||||
 | 
				          .action_classification.uniformer( | 
			
		||||
 | 
				            model_name='omnivore_swinT', topk=5) | 
			
		||||
 | 
				          .show() | 
			
		||||
 | 
				) | 
			
		||||
 | 
				``` | 
			
		||||
 | 
				<img src="./result1.png" height="px"/> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				*Write a same pipeline with explicit inputs/outputs name specifications*: | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				```python | 
			
		||||
 | 
				import towhee | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				( | 
			
		||||
 | 
				    towhee.glob['path']('./archery.mp4') | 
			
		||||
 | 
				          .video_decode.ffmpeg['path', 'frames']() | 
			
		||||
 | 
				          .action_classification.uniformer['frames', ('labels', 'scores', 'features')]( | 
			
		||||
 | 
				                model_name='uniformer_k400_s8') | 
			
		||||
 | 
				          .select['path', 'labels', 'scores', 'features']() | 
			
		||||
 | 
				          .show(formatter={'path': 'video_path'}) | 
			
		||||
 | 
				) | 
			
		||||
 | 
				``` | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<img src="./result2.png" height="px"/> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				<br /> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				## Factory Constructor | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				Create the operator via the following factory method | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				***video_classification.uniformer( | 
			
		||||
 | 
				model_name='omnivore_swinT', skip_preprocess=False, classmap=None, topk=5)*** | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				**Parameters:** | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					***model_name***: *str* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					The name of pre-trained uniformer model. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    Supported model names: | 
			
		||||
 | 
				- uniformer_k400_s8 | 
			
		||||
 | 
				- uniformer_k400_s16 | 
			
		||||
 | 
				- uniformer_k400_b8 | 
			
		||||
 | 
				- uniformer_k400_b16 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					***skip_preprocess***: *bool* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					Flag to control whether to skip video transforms, defaults to False. | 
			
		||||
 | 
				If set to True, the step to transform videos will be skipped. | 
			
		||||
 | 
				In this case, the user should guarantee that all the input video frames are already reprocessed properly, | 
			
		||||
 | 
				and thus can be fed to model directly. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					***classmap***: *Dict[str: int]*:  | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					Dictionary that maps class names to one hot vectors. | 
			
		||||
 | 
				If not given, the operator will load the default class map dictionary. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					***topk***: *int* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					The topk labels & scores to present in result. The default value is 5. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				## Interface | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				A video classification operator generates a list of class labels | 
			
		||||
 | 
				and a corresponding vector in numpy.ndarray given a video input data. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				**Parameters:** | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					***video***: *Union[str, numpy.ndarray]* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					Input video data using local path in string or video frames in ndarray. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				**Returns**: *(list, list, torch.Tensor)* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
					A tuple of (labels, scores, features), | 
			
		||||
 | 
				which contains lists of predicted class names and corresponding scores. | 
			
		||||
 | 
			
		|||||
@ -0,0 +1,19 @@ | 
			
		|||||
 | 
				# Copyright 2022 Zilliz. All rights reserved. | 
			
		||||
 | 
				# | 
			
		||||
 | 
				# 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 | 
			
		||||
 | 
				# | 
			
		||||
 | 
				#     http://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. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				from .uniformer import Uniformer | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				def uniformer(**kwargs): | 
			
		||||
 | 
				    return Uniformer(**kwargs) | 
			
		||||
								
									
										File diff suppressed because one or more lines are too long
									
								
							
						
					@ -0,0 +1,131 @@ | 
			
		|||||
 | 
				import logging | 
			
		||||
 | 
				import os | 
			
		||||
 | 
				import json | 
			
		||||
 | 
				from pathlib import Path | 
			
		||||
 | 
				from typing import List | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				import torch | 
			
		||||
 | 
				import numpy | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				from towhee import register | 
			
		||||
 | 
				from towhee.operator.base import NNOperator | 
			
		||||
 | 
				from towhee.types.video_frame import VideoFrame | 
			
		||||
 | 
				from towhee.models.utils.video_transforms import get_configs, transform_video | 
			
		||||
 | 
				from towhee.models.uniformer.uniformer import create_model | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				log = logging.getLogger() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				@register(output_schema=['labels', 'scores', 'features']) | 
			
		||||
 | 
				class Tsm(NNOperator): | 
			
		||||
 | 
				    """ | 
			
		||||
 | 
				    Generate a list of class labels given a video input data. | 
			
		||||
 | 
				    Default labels are from [Kinetics400 Dataset](https://deepmind.com/research/open-source/kinetics). | 
			
		||||
 | 
				    Args: | 
			
		||||
 | 
				        model_name (`str`): | 
			
		||||
 | 
				            Supported model names: | 
			
		||||
 | 
				            - uniformer_k400_s8 | 
			
		||||
 | 
				            - uniformer_k400_s16 | 
			
		||||
 | 
				            - uniformer_k400_b8 | 
			
		||||
 | 
				            - uniformer_k400_b16 | 
			
		||||
 | 
				        skip_preprocess (`str`): | 
			
		||||
 | 
				            Flag to skip video transforms. | 
			
		||||
 | 
				        predict (`bool`): | 
			
		||||
 | 
				            Flag to control whether predict labels. If False, then return video embedding. | 
			
		||||
 | 
				        classmap (`dict=None`): | 
			
		||||
 | 
				            The dictionary maps classes to integers. | 
			
		||||
 | 
				        topk (`int=5`): | 
			
		||||
 | 
				            The number of classification labels to be returned (ordered by possibility from high to low). | 
			
		||||
 | 
				    """ | 
			
		||||
 | 
				    def __init__(self, | 
			
		||||
 | 
				                 model_name: str = 'uniformer_k400_s8', | 
			
		||||
 | 
				                 framework: str = 'pytorch', | 
			
		||||
 | 
				                 skip_preprocess: bool = False, | 
			
		||||
 | 
				                 classmap: dict = None, | 
			
		||||
 | 
				                 topk: int = 5, | 
			
		||||
 | 
				                 ): | 
			
		||||
 | 
				        super().__init__(framework=framework) | 
			
		||||
 | 
				        self.model_name = model_name | 
			
		||||
 | 
				        self.skip_preprocess = skip_preprocess | 
			
		||||
 | 
				        self.topk = topk | 
			
		||||
 | 
				        if 'k400' in model_name: | 
			
		||||
 | 
				            self.dataset_name = 'kinetics_400' | 
			
		||||
 | 
				        if classmap is None: | 
			
		||||
 | 
				            class_file = os.path.join(str(Path(__file__).parent), self.dataset_name+'.json') | 
			
		||||
 | 
				            with open(class_file, "r") as f: | 
			
		||||
 | 
				                kinetics_classes = json.load(f) | 
			
		||||
 | 
				            self.classmap = {} | 
			
		||||
 | 
				            for k, v in kinetics_classes.items(): | 
			
		||||
 | 
				                self.classmap[v] = str(k).replace('"', '') | 
			
		||||
 | 
				        else: | 
			
		||||
 | 
				            self.classmap = classmap | 
			
		||||
 | 
				        self.device = 'cuda' if torch.cuda.is_available() else 'cpu' | 
			
		||||
 | 
				         | 
			
		||||
 | 
				        self.input_mean = [0.45, 0.45, 0.45] | 
			
		||||
 | 
				        self.input_std = [0.225, 0.225, 0.225] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        model_path = str(Path(__file__).parent) | 
			
		||||
 | 
				        model_path = { | 
			
		||||
 | 
				            'uniformer_k400_s8': os.path.join(model_path, 'uniformer_small_k400_8x8.pth'), | 
			
		||||
 | 
				            'uniformer_k400_s16': os.path.join(model_path, 'uniformer_small_k400_16x4.pth'), | 
			
		||||
 | 
				            'uniformer_k400_b8': os.path.join(model_path, 'uniformer_base_k400_8x8.pth'), | 
			
		||||
 | 
				            'uniformer_k400_b16': os.path.join(model_path, 'uniformer_base_k400_16x4.pth'), | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        self.weights_path = model_path[model_name] | 
			
		||||
 | 
				        self.model = create_model(model_name=model_name, pretrained=True, weights_path=self.weights_path, device=self.device) | 
			
		||||
 | 
				        if '8' in model_name: | 
			
		||||
 | 
				            self.transform_cfgs = get_configs( | 
			
		||||
 | 
				                    side_size=256, | 
			
		||||
 | 
				                    crop_size=224, | 
			
		||||
 | 
				                    num_frames=8, | 
			
		||||
 | 
				                    mean=self.input_mean, | 
			
		||||
 | 
				                    std=self.input_std, | 
			
		||||
 | 
				                    ) | 
			
		||||
 | 
				        elif '16' in model_name: | 
			
		||||
 | 
				            self.transform_cfgs = get_configs( | 
			
		||||
 | 
				                    side_size=256, | 
			
		||||
 | 
				                    crop_size=224, | 
			
		||||
 | 
				                    num_frames=16, | 
			
		||||
 | 
				                    mean=self.input_mean, | 
			
		||||
 | 
				                    std=self.input_std, | 
			
		||||
 | 
				                    ) | 
			
		||||
 | 
				        self.model.eval() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    def __call__(self, video: List[VideoFrame]): | 
			
		||||
 | 
				        """ | 
			
		||||
 | 
				        Args: | 
			
		||||
 | 
				            video (`List[VideoFrame]`): | 
			
		||||
 | 
				                Video path in string. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        Returns: | 
			
		||||
 | 
				            (labels, scores) | 
			
		||||
 | 
				                A tuple of lists (labels, scores). | 
			
		||||
 | 
				            OR emb | 
			
		||||
 | 
				                Video embedding. | 
			
		||||
 | 
				        """ | 
			
		||||
 | 
				        # Convert list of towhee.types.Image to numpy.ndarray in float32 | 
			
		||||
 | 
				        video = numpy.stack([img.astype(numpy.float32)/255. for img in video], axis=0) | 
			
		||||
 | 
				        assert len(video.shape) == 4 | 
			
		||||
 | 
				        video = video.transpose(3, 0, 1, 2)  # twhc -> ctwh | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        # Transform video data given configs | 
			
		||||
 | 
				        if self.skip_preprocess: | 
			
		||||
 | 
				            self.transform_cfgs.update(num_frames=None) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        data = transform_video( | 
			
		||||
 | 
				                    video=video, | 
			
		||||
 | 
				                    **self.transform_cfgs | 
			
		||||
 | 
				                    ) | 
			
		||||
 | 
				        inputs = data.to(self.device)[None, ...] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        feats = self.model.forward_features(inputs) | 
			
		||||
 | 
				        features = feats.to('cpu').squeeze(0).detach().numpy() | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        feats = feats.flatten(2).mean(-1) | 
			
		||||
 | 
				        outs = self.model.head(feats) | 
			
		||||
 | 
				        post_act = torch.nn.Softmax(dim=1) | 
			
		||||
 | 
				        preds = post_act(outs) | 
			
		||||
 | 
				        pred_scores, pred_classes = preds.topk(k=self.topk) | 
			
		||||
 | 
				        labels = [self.classmap[int(i)] for i in pred_classes[0]] | 
			
		||||
 | 
				        scores = [round(float(x), 5) for x in pred_scores[0]] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        return labels, scores, features | 
			
		||||
								
									Binary file not shown.
								
							
						
					
								
									Binary file not shown.
								
							
						
					
								
									Binary file not shown.
								
							
						
					
								
									Binary file not shown.
								
							
						
					
					Loading…
					
					
				
		Reference in new issue