| |
| import copy |
| import warnings |
| from pathlib import Path |
| from typing import Optional, Sequence, Union |
|
|
| import numpy as np |
| import torch |
| import torch.nn as nn |
| from mmcv.ops import RoIPool |
| from mmcv.transforms import Compose |
| from mmengine.config import Config |
| from mmengine.dataset import default_collate |
| from mmengine.model.utils import revert_sync_batchnorm |
| from mmengine.registry import init_default_scope |
| from mmengine.runner import load_checkpoint |
|
|
| from mmdet.registry import DATASETS |
| from mmdet.utils import ConfigType |
| from ..evaluation import get_classes |
| from ..registry import MODELS |
| from ..structures import DetDataSample, SampleList |
| from ..utils import get_test_pipeline_cfg |
|
|
|
|
| def init_detector( |
| config: Union[str, Path, Config], |
| checkpoint: Optional[str] = None, |
| palette: str = 'none', |
| device: str = 'cuda:0', |
| cfg_options: Optional[dict] = None, |
| ) -> nn.Module: |
| """Initialize a detector from config file. |
| |
| Args: |
| config (str, :obj:`Path`, or :obj:`mmengine.Config`): Config file path, |
| :obj:`Path`, or the config object. |
| checkpoint (str, optional): Checkpoint path. If left as None, the model |
| will not load any weights. |
| palette (str): Color palette used for visualization. If palette |
| is stored in checkpoint, use checkpoint's palette first, otherwise |
| use externally passed palette. Currently, supports 'coco', 'voc', |
| 'citys' and 'random'. Defaults to none. |
| device (str): The device where the anchors will be put on. |
| Defaults to cuda:0. |
| cfg_options (dict, optional): Options to override some settings in |
| the used config. |
| |
| Returns: |
| nn.Module: The constructed detector. |
| """ |
| if isinstance(config, (str, Path)): |
| config = Config.fromfile(config) |
| elif not isinstance(config, Config): |
| raise TypeError('config must be a filename or Config object, ' |
| f'but got {type(config)}') |
| if cfg_options is not None: |
| config.merge_from_dict(cfg_options) |
| elif 'init_cfg' in config.model.backbone: |
| config.model.backbone.init_cfg = None |
|
|
| scope = config.get('default_scope', 'mmdet') |
| if scope is not None: |
| init_default_scope(config.get('default_scope', 'mmdet')) |
|
|
| model = MODELS.build(config.model) |
| model = revert_sync_batchnorm(model) |
| if checkpoint is None: |
| warnings.simplefilter('once') |
| warnings.warn('checkpoint is None, use COCO classes by default.') |
| model.dataset_meta = {'classes': get_classes('coco')} |
| else: |
| checkpoint = load_checkpoint(model, checkpoint, map_location='cpu') |
| |
| checkpoint_meta = checkpoint.get('meta', {}) |
|
|
| |
| if 'dataset_meta' in checkpoint_meta: |
| |
| model.dataset_meta = { |
| k.lower(): v |
| for k, v in checkpoint_meta['dataset_meta'].items() |
| } |
| elif 'CLASSES' in checkpoint_meta: |
| |
| classes = checkpoint_meta['CLASSES'] |
| model.dataset_meta = {'classes': classes} |
| else: |
| warnings.simplefilter('once') |
| warnings.warn( |
| 'dataset_meta or class names are not saved in the ' |
| 'checkpoint\'s meta data, use COCO classes by default.') |
| model.dataset_meta = {'classes': get_classes('coco')} |
|
|
| |
| if palette != 'none': |
| model.dataset_meta['palette'] = palette |
| else: |
| test_dataset_cfg = copy.deepcopy(config.test_dataloader.dataset) |
| |
| test_dataset_cfg['lazy_init'] = True |
| metainfo = DATASETS.build(test_dataset_cfg).metainfo |
| cfg_palette = metainfo.get('palette', None) |
| if cfg_palette is not None: |
| model.dataset_meta['palette'] = cfg_palette |
| else: |
| if 'palette' not in model.dataset_meta: |
| warnings.warn( |
| 'palette does not exist, random is used by default. ' |
| 'You can also set the palette to customize.') |
| model.dataset_meta['palette'] = 'random' |
|
|
| model.cfg = config |
| model.to(device) |
| model.eval() |
| return model |
|
|
|
|
| ImagesType = Union[str, np.ndarray, Sequence[str], Sequence[np.ndarray]] |
|
|
|
|
| def inference_detector( |
| model: nn.Module, |
| imgs: ImagesType, |
| test_pipeline: Optional[Compose] = None, |
| text_prompt: Optional[str] = None, |
| custom_entities: bool = False, |
| ) -> Union[DetDataSample, SampleList]: |
| """Inference image(s) with the detector. |
| |
| Args: |
| model (nn.Module): The loaded detector. |
| imgs (str, ndarray, Sequence[str/ndarray]): |
| Either image files or loaded images. |
| test_pipeline (:obj:`Compose`): Test pipeline. |
| |
| Returns: |
| :obj:`DetDataSample` or list[:obj:`DetDataSample`]: |
| If imgs is a list or tuple, the same length list type results |
| will be returned, otherwise return the detection results directly. |
| """ |
|
|
| if isinstance(imgs, (list, tuple)): |
| is_batch = True |
| else: |
| imgs = [imgs] |
| is_batch = False |
|
|
| cfg = model.cfg |
|
|
| if test_pipeline is None: |
| cfg = cfg.copy() |
| test_pipeline = get_test_pipeline_cfg(cfg) |
| if isinstance(imgs[0], np.ndarray): |
| |
| |
| test_pipeline[0].type = 'mmdet.LoadImageFromNDArray' |
|
|
| test_pipeline = Compose(test_pipeline) |
|
|
| if model.data_preprocessor.device.type == 'cpu': |
| for m in model.modules(): |
| assert not isinstance( |
| m, RoIPool |
| ), 'CPU inference with RoIPool is not supported currently.' |
|
|
| result_list = [] |
| for i, img in enumerate(imgs): |
| |
| if isinstance(img, np.ndarray): |
| |
| data_ = dict(img=img, img_id=0) |
| else: |
| |
| data_ = dict(img_path=img, img_id=0) |
|
|
| if text_prompt: |
| data_['text'] = text_prompt |
| data_['custom_entities'] = custom_entities |
|
|
| |
| data_ = test_pipeline(data_) |
|
|
| data_['inputs'] = [data_['inputs']] |
| data_['data_samples'] = [data_['data_samples']] |
|
|
| |
| with torch.no_grad(): |
| results = model.test_step(data_)[0] |
|
|
| result_list.append(results) |
|
|
| if not is_batch: |
| return result_list[0] |
| else: |
| return result_list |
|
|
|
|
| |
| async def async_inference_detector(model, imgs): |
| """Async inference image(s) with the detector. |
| |
| Args: |
| model (nn.Module): The loaded detector. |
| img (str | ndarray): Either image files or loaded images. |
| |
| Returns: |
| Awaitable detection results. |
| """ |
| if not isinstance(imgs, (list, tuple)): |
| imgs = [imgs] |
|
|
| cfg = model.cfg |
|
|
| if isinstance(imgs[0], np.ndarray): |
| cfg = cfg.copy() |
| |
| cfg.data.test.pipeline[0].type = 'LoadImageFromNDArray' |
|
|
| |
| test_pipeline = Compose(cfg.data.test.pipeline) |
|
|
| datas = [] |
| for img in imgs: |
| |
| if isinstance(img, np.ndarray): |
| |
| data = dict(img=img) |
| else: |
| |
| data = dict(img_info=dict(filename=img), img_prefix=None) |
| |
| data = test_pipeline(data) |
| datas.append(data) |
|
|
| for m in model.modules(): |
| assert not isinstance( |
| m, |
| RoIPool), 'CPU inference with RoIPool is not supported currently.' |
|
|
| |
| |
| torch.set_grad_enabled(False) |
| results = await model.aforward_test(data, rescale=True) |
| return results |
|
|
|
|
| def build_test_pipeline(cfg: ConfigType) -> ConfigType: |
| """Build test_pipeline for mot/vis demo. In mot/vis infer, original |
| test_pipeline should remove the "LoadImageFromFile" and |
| "LoadTrackAnnotations". |
| |
| Args: |
| cfg (ConfigDict): The loaded config. |
| Returns: |
| ConfigType: new test_pipeline |
| """ |
| |
| transform_broadcaster = cfg.test_dataloader.dataset.pipeline[0].copy() |
| for transform in transform_broadcaster['transforms']: |
| if transform['type'] == 'Resize': |
| transform_broadcaster['transforms'] = transform |
| pack_track_inputs = cfg.test_dataloader.dataset.pipeline[-1].copy() |
| test_pipeline = Compose([transform_broadcaster, pack_track_inputs]) |
|
|
| return test_pipeline |
|
|
|
|
| def inference_mot(model: nn.Module, img: np.ndarray, frame_id: int, |
| video_len: int) -> SampleList: |
| """Inference image(s) with the mot model. |
| |
| Args: |
| model (nn.Module): The loaded mot model. |
| img (np.ndarray): Loaded image. |
| frame_id (int): frame id. |
| video_len (int): demo video length |
| Returns: |
| SampleList: The tracking data samples. |
| """ |
| cfg = model.cfg |
| data = dict( |
| img=[img.astype(np.float32)], |
| frame_id=[frame_id], |
| ori_shape=[img.shape[:2]], |
| img_id=[frame_id + 1], |
| ori_video_length=[video_len]) |
|
|
| test_pipeline = build_test_pipeline(cfg) |
| data = test_pipeline(data) |
|
|
| if not next(model.parameters()).is_cuda: |
| for m in model.modules(): |
| assert not isinstance( |
| m, RoIPool |
| ), 'CPU inference with RoIPool is not supported currently.' |
|
|
| |
| with torch.no_grad(): |
| data = default_collate([data]) |
| result = model.test_step(data)[0] |
| return result |
|
|
|
|
| def init_track_model(config: Union[str, Config], |
| checkpoint: Optional[str] = None, |
| detector: Optional[str] = None, |
| reid: Optional[str] = None, |
| device: str = 'cuda:0', |
| cfg_options: Optional[dict] = None) -> nn.Module: |
| """Initialize a model from config file. |
| |
| Args: |
| config (str or :obj:`mmengine.Config`): Config file path or the config |
| object. |
| checkpoint (Optional[str], optional): Checkpoint path. Defaults to |
| None. |
| detector (Optional[str], optional): Detector Checkpoint path, use in |
| some tracking algorithms like sort. Defaults to None. |
| reid (Optional[str], optional): Reid checkpoint path. use in |
| some tracking algorithms like sort. Defaults to None. |
| device (str, optional): The device that the model inferences on. |
| Defaults to `cuda:0`. |
| cfg_options (Optional[dict], optional): Options to override some |
| settings in the used config. Defaults to None. |
| |
| Returns: |
| nn.Module: The constructed model. |
| """ |
| if isinstance(config, str): |
| config = Config.fromfile(config) |
| elif not isinstance(config, Config): |
| raise TypeError('config must be a filename or Config object, ' |
| f'but got {type(config)}') |
| if cfg_options is not None: |
| config.merge_from_dict(cfg_options) |
|
|
| model = MODELS.build(config.model) |
|
|
| if checkpoint is not None: |
| checkpoint = load_checkpoint(model, checkpoint, map_location='cpu') |
| |
| checkpoint_meta = checkpoint.get('meta', {}) |
| |
| if 'dataset_meta' in checkpoint_meta: |
| if 'CLASSES' in checkpoint_meta['dataset_meta']: |
| value = checkpoint_meta['dataset_meta'].pop('CLASSES') |
| checkpoint_meta['dataset_meta']['classes'] = value |
| model.dataset_meta = checkpoint_meta['dataset_meta'] |
|
|
| if detector is not None: |
| assert not (checkpoint and detector), \ |
| 'Error: checkpoint and detector checkpoint cannot both exist' |
| load_checkpoint(model.detector, detector, map_location='cpu') |
|
|
| if reid is not None: |
| assert not (checkpoint and reid), \ |
| 'Error: checkpoint and reid checkpoint cannot both exist' |
| load_checkpoint(model.reid, reid, map_location='cpu') |
|
|
| |
| |
| |
| if not hasattr(model, 'dataset_meta'): |
| warnings.warn('dataset_meta or class names are missed, ' |
| 'use None by default.') |
| model.dataset_meta = {'classes': None} |
|
|
| model.cfg = config |
| model.to(device) |
| model.eval() |
| return model |
|
|