MMDetection中的数据处理

CustomDataset

代码路径:mmdet/datasets/custom.py
mmdet中的CustomDataset继承自torch的Dataset,因此,对应的需要实现3个虚函数,接下来我们首先看看这3个重要函数的实现。

构造函数

CLASSES = None			# static变量

def __init__(self,
             ann_file,					# 标签文件路径
             pipeline,					# 数据处理pipeline
             classes=None,				# 数据集类别
             data_root=None,			# 数据集主路径
             img_prefix='',				# 图像前缀
             seg_prefix=None,			# 
             proposal_file=None,
             test_mode=False,			# 测试模式,该模式下标签文件不会被夹在
             filter_empty_gt=True):		# 是否过滤掉没有目标的背景图像!!!!
    self.ann_file = ann_file
    self.data_root = data_root
    self.img_prefix = img_prefix
    self.seg_prefix = seg_prefix
    self.proposal_file = proposal_file
    self.test_mode = test_mode
    self.filter_empty_gt = filter_empty_gt
    # 获取数据集的类别, 如果传入的classes为空, 则使用CLASSES
    # classes不为空
    # 1. 传入文本文件
    # 2. 列表
    self.CLASSES = self.get_classes(classes)

    # join paths if data_root is specified
    # 拼凑文件路径
    if self.data_root is not None:
        if not osp.isabs(self.ann_file):
            self.ann_file = osp.join(self.data_root, self.ann_file)
        if not (self.img_prefix is None or osp.isabs(self.img_prefix)):
            self.img_prefix = osp.join(self.data_root, self.img_prefix)
        if not (self.seg_prefix is None or osp.isabs(self.seg_prefix)):
            self.seg_prefix = osp.join(self.data_root, self.seg_prefix)
        if not (self.proposal_file is None
                or osp.isabs(self.proposal_file)):
            self.proposal_file = osp.join(self.data_root,
                                          self.proposal_file)
    # load annotations (and proposals)
    # 调用load_annotations函数加载标签内容
    self.data_infos = self.load_annotations(self.ann_file)

    if self.proposal_file is not None:
        self.proposals = self.load_proposals(self.proposal_file)
    else:
        self.proposals = None

    # filter images too small and containing no annotations
    # _filter_imgs函数中过滤掉尺寸比较小的图像,以及不包含目标的背景图像
    if not test_mode:
        valid_inds = self._filter_imgs()
        self.data_infos = [self.data_infos[i] for i in valid_inds]
        if self.proposals is not None:
            self.proposals = [self.proposals[i] for i in valid_inds]
        # set group flag for the sampler
        # 根据图像的宽高比,将图像分配到不同组中,用于后续dataloader的数据采样
        # 宽高比小于1的分配到组0,宽高比大于1的分配到组1
        self._set_group_flag()

    # processing pipeline
    # 数据集处理pipeline构建
    self.pipeline = Compose(pipeline)

getitem函数

def __getitem__(self, idx):
    # 根据idx获取到对应的图像和标签数据
    if self.test_mode:
        return self.prepare_test_img(idx)
    while True:
        data = self.prepare_train_img(idx)
        if data is None:		# 如果数据为空,则重新随机采样
            idx = self._rand_another(idx)
            continue
        return data

len函数

返回数据集的大小

    def __len__(self):
        """Total number of samples of data."""
        return len(self.data_infos)

CocoDataset

CocoDataset类继承自CustomDataset,分别实现了如下的方法:

  • load_annotations:加载标签文件内容
    def load_annotations(self, ann_file):
        self.coco = COCO(ann_file)				# coco对象
        self.cat_ids = self.coco.get_cat_ids(cat_names=self.CLASSES)	# 获取数据集类别ID
        self.cat2label = {cat_id: i for i, cat_id in enumerate(self.cat_ids)}	# 类别ID和标签之间的映射
        self.img_ids = self.coco.get_img_ids()		# 获取到数据集全部image ID
        data_infos = []
        for i in self.img_ids:		
            info = self.coco.load_imgs([i])[0]		# 根据image ID获取到images字段内容
            info['filename'] = info['file_name']	
            data_infos.append(info)					
        return data_infos
  • get_ann_info:获取单个目标信息
    def get_ann_info(self, idx):
        """Get COCO annotation by index.

        Args:
            idx (int): Index of data.

        Returns:
            dict: Annotation info of specified index.
        """
    	# 根据idx获取得到image ID
        img_id = self.data_infos[idx]['id']
        # 根据image ID找到对应的标签ID
        ann_ids = self.coco.get_ann_ids(img_ids=[img_id])
        # 根据标签ID获取到对应的annotations字段内容
        ann_info = self.coco.load_anns(ann_ids)
        # 对annotations字段内容进行解析
        return self._parse_ann_info(self.data_infos[idx], ann_info)
  • get_cat_ids:获取目标类别ID
  • _filter_imgs:过滤图像数据
    def _filter_imgs(self, min_size=32):
        """Filter images too small or without ground truths."""
        valid_inds = []
        # obtain images that contain annotation
        ids_with_ann = set(_['image_id'] for _ in self.coco.anns.values())
        # obtain images that contain annotations of the required categories
        ids_in_cat = set()
        for i, class_id in enumerate(self.cat_ids):
            ids_in_cat |= set(self.coco.cat_img_map[class_id])
        # merge the image id sets of the two conditions and use the merged set
        # to filter out images if self.filter_empty_gt=True
        ids_in_cat &= ids_with_ann

        valid_img_ids = []
        for i, img_info in enumerate(self.data_infos):
            img_id = self.img_ids[i]
            # 背景图像过滤
            if self.filter_empty_gt and img_id not in ids_in_cat:
                continue
            # 图像尺寸最小边过滤
            if min(img_info['width'], img_info['height']) >= min_size:
                valid_inds.append(i)
                valid_img_ids.append(img_id)
        self.img_ids = valid_img_ids
        return valid_inds
  • _parse_ann_info:解析annotations字段内容
    def _parse_ann_info(self, img_info, ann_info):
        """Parse bbox and mask annotation.

        Args:
            ann_info (list[dict]): Annotation info of an image.
            with_mask (bool): Whether to parse mask annotations.

        Returns:
            dict: A dict containing the following keys: bboxes, bboxes_ignore,\
                labels, masks, seg_map. "masks" are raw annotations and not \
                decoded into binary masks.
        """
        gt_bboxes = []
        gt_labels = []
        gt_bboxes_ignore = []
        gt_masks_ann = []
        for i, ann in enumerate(ann_info):
            if ann.get('ignore', False):
                continue
            x1, y1, w, h = ann['bbox']		# 获取目标框信息
            # 判断边界条件
            inter_w = max(0, min(x1 + w, img_info['width']) - max(x1, 0))
            inter_h = max(0, min(y1 + h, img_info['height']) - max(y1, 0))
            # 目标框宽高存在0
            if inter_w * inter_h == 0:
                continue
            # 目标框面积判断,宽高判断
            if ann['area'] <= 0 or w < 1 or h < 1:
                continue
            # 类别ID判断
            if ann['category_id'] not in self.cat_ids:
                continue
            
            bbox = [x1, y1, x1 + w, y1 + h]
            if ann.get('iscrowd', False):
                gt_bboxes_ignore.append(bbox)
            else:
                gt_bboxes.append(bbox)
                # 获取到对应的类别字符串
                gt_labels.append(self.cat2label[ann['category_id']])
                gt_masks_ann.append(ann.get('segmentation', None))

        if gt_bboxes:
            gt_bboxes = np.array(gt_bboxes, dtype=np.float32)
            gt_labels = np.array(gt_labels, dtype=np.int64)
        else:
            gt_bboxes = np.zeros((0, 4), dtype=np.float32)
            gt_labels = np.array([], dtype=np.int64)

        if gt_bboxes_ignore:
            gt_bboxes_ignore = np.array(gt_bboxes_ignore, dtype=np.float32)
        else:
            gt_bboxes_ignore = np.zeros((0, 4), dtype=np.float32)
    	
        seg_map = img_info['filename'].replace('jpg', 'png')

        ann = dict(
            bboxes=gt_bboxes,
            labels=gt_labels,
            bboxes_ignore=gt_bboxes_ignore,
            masks=gt_masks_ann,
            seg_map=seg_map)

        return ann
  • evaluate:评估数据集结果

DataLoader

首先,我们看一下coco数据集对应的config文件内容,具体如下:

dataset_type = 'CocoDataset'
data_root = 'data/coco/'
img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(type='LoadAnnotations', with_bbox=True),
    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
    dict(type='RandomFlip', flip_ratio=0.5),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='Pad', size_divisor=32),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
]
test_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(
        type='MultiScaleFlipAug',
        img_scale=(1333, 800),
        flip=False,
        transforms=[
            dict(type='Resize', keep_ratio=True),
            dict(type='RandomFlip'),
            dict(type='Normalize', **img_norm_cfg),
            dict(type='Pad', size_divisor=32),
            dict(type='ImageToTensor', keys=['img']),
            dict(type='Collect', keys=['img']),
        ])
]
data = dict(			# >>>>>>>>>> 构建dataloader对象
    samples_per_gpu=2,	
    workers_per_gpu=2,
    train=dict(			# >>>>>>>>>> 构建datasets对象
        type=dataset_type,
        ann_file=data_root + 'annotations/instances_train2017.json',
        img_prefix=data_root + 'train2017/',
        pipeline=train_pipeline),
    val=dict(			# >>>>>>>>>> 构建datasets对象
        type=dataset_type,
        ann_file=data_root + 'annotations/instances_val2017.json',
        img_prefix=data_root + 'val2017/',
        pipeline=test_pipeline),
    test=dict(			# >>>>>>>>>> 构建datasets对象
        type=dataset_type,
        ann_file=data_root + 'annotations/instances_val2017.json',
        img_prefix=data_root + 'val2017/',
        pipeline=test_pipeline))
evaluation = dict(interval=1, metric='bbox')

根据上述config文件进行数据处理的过程如下:

  1. 构建数据集datasets
datasets = [build_dataset(cfg.data.train)]
  1. 基于数据集构建dataloader
data_loaders = [
    build_dataloader(
        ds,
        cfg.data.samples_per_gpu,
        cfg.data.workers_per_gpu,
        # cfg.gpus will be ignored if distributed
        len(cfg.gpu_ids),
        dist=distributed,
        seed=cfg.seed) for ds in dataset
]

而dataloader定义在mmdet/dataset/builder.py中。

from torch.utils.data import DataLoader


def build_dataloader(dataset,samples_per_gpu,workers_per_gpu,
                     num_gpus=1,shuffle=True,seed=None,**kwargs):

    sampler = GroupSampler(dataset, samples_per_gpu) if shuffle else None # shuffle为True的情况下
    # 对图片的长宽比进行分组,ratio大于1为分组1,小于1为分组0,目的是为了尽可能减少pad
    # 后面文章会具体介绍

    batch_size = num_gpus * samples_per_gpu # 之前介绍过,mmdetection支持多GPU分布式训练
    num_workers = num_gpus * workers_per_gpu # io的进程数

    init_fn = partial(worker_init_fn, num_workers=num_workers, rank=0,seed=seed) if seed is not None else None
    # 如果给定seed,将固定随机数,否则为None,随机数不固定

    data_loader = DataLoader(
        dataset,
        batch_size=batch_size,
        sampler=sampler, # 需要实现__len__和__iter__方法,每次返回一个整数下标索引
        num_workers=num_workers,
        collate_fn=partial(collate, samples_per_gpu=samples_per_gpu), # collate就是上面mmcv实现的函数
        pin_memory=False,
        worker_init_fn=init_fn,
        **kwargs)

    return data_loader

def worker_init_fn(worker_id, num_workers, rank, seed):
    # pytorch的dataloader在新建时会调用该函数,功能是让随机数固定!传递的第一个参数为进程编号
    worker_seed = num_workers * rank + worker_id + seed
    np.random.seed(worker_seed)
    random.seed(worker_seed)

数据增强pipeline

mmdet中的数据增强是在datasets中完成的,具体的代码如下:

  1. 构建pipeline
self.pipeline = Compose(pipeline)
  1. 使用pipeline
#!!! 在getitem函数中会调用prepare_train_img函数
def pre_pipeline(self, results):
    """Prepare results dict for pipeline."""
    results['img_prefix'] = self.img_prefix
    results['seg_prefix'] = self.seg_prefix
    results['proposal_file'] = self.proposal_file
    results['bbox_fields'] = []
    results['mask_fields'] = []
    results['seg_fields'] = []
def prepare_train_img(self, idx):
    """Get training data and annotations after pipeline.

    Args:
        idx (int): Index of data.

    Returns:
        dict: Training data and annotation after pipeline with new keys \
            introduced by pipeline.
    """

    img_info = self.data_infos[idx]			# 图像信息
    ann_info = self.get_ann_info(idx)		# 标签信息
    results = dict(img_info=img_info, ann_info=ann_info)
    if self.proposals is not None:
        results['proposals'] = self.proposals[idx]
    self.pre_pipeline(results)
    return self.pipeline(results)	# >>>>>>>> 进行数据增强

接下来,让我们看看传入到数据增强pipeline中的results字典最开始包含哪些内容。

img_prefix
img_info
    ● height
    ● width
    ● id
    ● filename(file_name)
anno_info
    ● bboxes
    ● labels
    ● bboxes_ignore
    ● masks
    ● seg_map
seg_prefix
proposal_file
bbox_fields
mask_fields
seg_fields

之后进行各种数据处理,results字典内容的变换如下
image.png
我自己简单调试画出来如下的字典内容变化图像。
image.png

LoadImageFromFile

从文件中加载图像数据。

  • to_float32:是否将加载的图像数据转换为float32的numpy格式
  • color_type:默认为color
  • file_client_args:FileClient

Resize

保持宽高比缩放图像

    dict(type='Resize', img_scale=(1300, 1300), keep_ratio=True),
        if ratio_range is not None:
            # scale 不变 ratio发生变化
            # mode 1: given a scale and a range of image ratio
            assert len(self.img_scale) == 1
        else:
            # scale 变化 ratio不变
            # dict(img_scale=[(1333, 800), (512, 512)], multiscale_mode='value')
            # mode 2: given multiple scales or a range of scales
            # range: 从multiscale区间中随机采样一个scale系数
            # value: 从给定的multiscale值中随机采样一个
            assert multiscale_mode in ['value', 'range']

RandomFlip

进行水平、竖直和沿对角线翻转,对图像和bbox进行翻转。有三种方式:

  • flip_ratio为float,direction为string,例如flip_ratio=0.5,direction=‘horizontal’,则表示图像将以0.5的概率进行水平翻转;

  • flip_ratio为float,direction为string列表,例如flip_ratio=0.5,direction=[‘horizontal’, ‘vertical’],则表示图像将以flip_ratio/len(direction)的概率分别进行水平和竖着翻转;

  • flip_ratio为float列表,direction为string列表,例如flip_ratio=[0.3, 0.5],direction=[‘horizontal’,

      'vertical'],则图像将以0.3的概率进行水平翻转,0.5的概率进行竖直翻转。
    

进行翻转的方向direction有3种选择:‘horizontal’, ‘vertical’, ‘diagonal’,默认为’horizontal’。

    dict(type='RandomFlip', flip_ratio=0.75, direction=['horizontal', 'vertical', 'diagonal']),  # do all flip.

Normalize

对图像数据进行归一化

    dict(type='Normalize', **img_norm_cfg),

Pad

两种pad模式:

  1. pad到固定大小
  2. pad之后的图像能被整除(比如resnet要求输入能被32整除)
    dict(type='Pad', size_divisor=32),
    def _pad_img(self, results):
        """Pad images according to ``self.size``."""
        for key in results.get('img_fields', ['img']):
            # pad填充的都是右边和下标 原点不变
            if self.size is not None:                       # pad到固定大小
                padded_img = mmcv.impad(
                    results[key], shape=self.size, pad_val=self.pad_val)
            elif self.size_divisor is not None:             # pad到除数的最小整数倍
                padded_img = mmcv.impad_to_multiple(
                    results[key], self.size_divisor, pad_val=self.pad_val)
            results[key] = padded_img

        if self.need_depth_img:
            assert self.size_divisor is not None
            depth_padded_img = mmcv.impad_to_multiple(results['depth_img'], self.size_divisor, pad_val=self.pad_val)
            results['depth_img'] = depth_padded_img

        results['pad_shape'] = padded_img.shape
        results['pad_fixed_size'] = self.size
        results['pad_size_divisor'] = self.size_divisor

DefaultFormatBundle

将img、bboxes、labels转换为tensor,再转换为DataContainer。

    dict(type='DefaultFormatBundle'),

Collect

将一些标注信息插入到results[‘img_metas’]中

    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),

MultiScaleFlipAug

在测试集上测试时,**必须使用MultiScaleFlipAug!即使不采用多尺度测试,也要使用MultiScaleFlipAug。**因为aseDetector.forward_test只接收list。
若采用了多尺度训练,即num_augs大于1,那么batch必须为1;若num_augs等于1时,batch可以大于1.

test_pipeline = [
    # dict(type='LoadImageFromFile')
    dict(type='LoadImageFromFile', to_float32=True),
    dict(
        type='MultiScaleFlipAug',
        img_scale=(1300, 1300),
        flip=False,
        transforms=[
            dict(type='Resize', keep_ratio=True),
            dict(type='RandomFlip'),
            dict(type='Normalize', **img_norm_cfg),
            dict(type='Pad', size_divisor=32),
            dict(type='ImageToTensor', keys=['img']),
            dict(type='Collect', keys=['img']),
        ])
]

DataContainer

数据容器,定义在mmcv中,主要时为了解决不同size的图像要stack到同一个batch中。其主要时维护stacked、data、pad、pad_dim、padding_value和cpu_only值。

import functools
import torch

class DataContainer:
    '''
    对于img_meta:cpu_only = True
    对于img tensor:cpu_only = False, stack = True
    对于gt_bboxes:cpu_only = False, stack = False
    '''
    def __init__(self,data,stack=False,padding_value=0,cpu_only=False,pad_dims=2):
        self._data = data # 数据本身 任何数据类型都可以
        self._cpu_only = cpu_only # 为True,表示只在cpu上,如图片大小scale,是否翻转flip等不需要放到GPU上的信息
        self._stack = stack # 是否要堆叠,若为img,则为True,需要对图片进行stack
        self._padding_value = padding_value # pad操作的填充值,默认0
        assert pad_dims in [None, 1, 2, 3] 
        self._pad_dims = pad_dims
        # pad_dims表示需要填充的倒数n个dim,以图片为例,BCHW需要填充是HW最后2维

    def __len__(self):
        return len(self._data)

    @property
    def data(self):
        return self._data	# 返回数据本身

    @property
    def datatype(self):
        if isinstance(self.data, torch.Tensor):
            return self.data.type()
        else:
            return type(self.data)

    @property
    def cpu_only(self):
        return self._cpu_only		# cpu上

    @property
    def stack(self):
        return self._stack

    @property
    def padding_value(self):
        return self._padding_value

    @property
    def pad_dims(self):
        return self._pad_dims		# padding的维度

数据分发collate

首先得到batch个Dataset.__getitem__的结果:[(img1(1, C, H, W), bboxes1),…],将其输入到collate函数中,转换为[img(batch, C, H, W), [bboxs1, bboxes2…])。
collate实现在mmcv中,源码如下:

from collections.abc import Mapping, Sequence
import torch
import torch.nn.functional as F
from torch.utils.data.dataloader import default_collate

def collate(batch, samples_per_gpu):
    '''
    batch:由Batch个Dataset.__getitem__返回结果(如collect_results)组成的list
    # 为了防止混淆!!小写batch表示输入的参数,大写Batch表示len(batch)即一批量中样本个数

    samples_per_gpu:int,每块GPU上的样本个数
    # 有意思,mmdetection是能支持多GPU的,这意味着,在数据分发时,我们应该把数据分为num_gpus份
    # 做个简单的数学运算Batch = num_gpus * sampler_per_gpu!!!,当是单GPU时,Batch = sampler_per_gpu
    '''
    if not isinstance(batch, Sequence):
        raise TypeError(f'{batch.dtype} is not supported.')

    if isinstance(batch[0], DataContainer): 
        stacked = []
        if batch[0].cpu_only:
            for i in range(0, len(batch), samples_per_gpu):
                stacked.append([sample.data for sample in batch[i:i + samples_per_gpu]])
            return DataContainer(stacked, batch[0].stack, batch[0].padding_value, cpu_only=True)
        elif batch[0].stack:
            for i in range(0, len(batch), samples_per_gpu):
                assert isinstance(batch[i].data, torch.Tensor)

                if batch[i].pad_dims is not None:
                    ndim = batch[i].dim()
                    assert ndim > batch[i].pad_dims
                    max_shape = [0 for _ in range(batch[i].pad_dims)] 

                    for sample in batch[i:i + samples_per_gpu]:
                        for dim in range(0, ndim - batch[i].pad_dims):
                            assert batch[i].size(dim) == sample.size(dim) # 除了需要pad的维度,其他维度要一致
                        for dim in range(1, batch[i].pad_dims + 1): 
                        # 遍历所有维度求max,因为要stack,所以维度要一样,用pad
                            max_shape[dim - 1] = max(max_shape[dim - 1],sample.size(-dim))
                    padded_samples = []
                    for sample in batch[i:i + samples_per_gpu]:
                        pad = [0 for _ in range(batch[i].pad_dims * 2)]
                        for dim in range(1, batch[i].pad_dims + 1):
                            pad[2 * dim - 1] = max_shape[dim - 1] - sample.size(-dim)
                            # bbox不需要偏移
                        padded_samples.append(F.pad(sample.data, pad, value=sample.padding_value))
                    stacked.append(default_collate(padded_samples)) 
                    # default_collate是pytorch实现的,就是做stack,[(1,C,H,W),(1,C,H,W)……]变为(B,C,H,W)
                elif batch[i].pad_dims is None:
                    stacked.append(default_collate([sample.data for sample in batch[i:i + samples_per_gpu]]))
                else:
                    raise ValueError('pad_dims should be either None or integers (1-3)')
        else:
            # 非stack,返回list
            for i in range(0, len(batch), samples_per_gpu):
                stacked.append([sample.data for sample in batch[i:i + samples_per_gpu]])
        return DataContainer(stacked, batch[0].stack, batch[0].padding_value)

    elif isinstance(batch[0], Sequence): 
        transposed = zip(*batch)
        return [collate(samples, samples_per_gpu) for samples in transposed]

    elif isinstance(batch[0], Mapping): 
        return {
            key: collate([d[key] for d in batch], samples_per_gpu) # 递归调用
            for key in batch[0]
        }
    else:
        return default_collate(batch)

collate对图像进行数据分发时,会把不同size的图像进行pad。这里的pad操作会把图片从左上角对齐,即填充右边和下边。

GroupSampler

在collate中需要将不同size的图片padding到一样的大小,为了尽可能减少pad面积,节省算力,Groupsampler类应运而生。通过将未处理图片根据ratio分为两组,ratio大于1分为一组,ratio小于1的分为另一组。相似ratio图片的输入能有效减少pad面积
通过dataset得到数据集的index,每个图像对应一个index,sampler指的是采样方法,指的是每个batch采样哪些index,collate_fn是对采样的图像进行处理,最后得到batch数据用于训练测试。

class GroupSampler(Sampler):

    def __init__(self, dataset, samples_per_gpu=1):
        assert hasattr(dataset, 'flag')
        self.dataset = dataset
        self.samples_per_gpu = samples_per_gpu
        self.flag = dataset.flag.astype(np.int64)  
        # flag标志由dataset在初始化时确定,详见customdataset
        # flag只有两个取值,根据ratio是否大于1,分为两组

        self.group_sizes = np.bincount(self.flag)  # 对每组的数量进行计数,详见bincount的使用方法
        self.num_samples = 0 # 作为__len__的返回值
        
        for i, size in enumerate(self.group_sizes):
            self.num_samples += int(np.ceil(size / self.samples_per_gpu)) * self.samples_per_gpu
        # group_size不一定能确保被samples_per_gpu整除,因此需要向上取整
        # 比如分组0的数量是100个,分组1的数量是200个,samples_per_gpu为29
        # 那么num_samples = 116+203 = 319

    def __iter__(self): # 返回迭代器,每次迭代返回一个整数索引
        indices = []
        for i, size in enumerate(self.group_sizes):
            if size == 0:
                continue
            indice = np.where(self.flag == i)[0] # 获得同组的图片下标
            assert len(indice) == size
            np.random.shuffle(indice) # 打乱
            num_extra = int(np.ceil(size / self.samples_per_gpu)) * self.samples_per_gpu - len(indice)
            indice = np.concatenate([indice, np.random.choice(indice, num_extra)])
            indices.append(indice)
        # 还是以"分组0的数量是100个,分组1的数量是200个,samples_per_gpu为29"举例,num_samples = 116+203 = 319        
        # 116大于100,203大于200,所以我们需要还额外增加下标
        # 最后得到319个下标,其中前116个是分组0,后203个是分组1,确保每samples_per_gpu都是同一ratio

        indices = np.concatenate(indices)
       
        indices = [
            indices[i * self.samples_per_gpu:(i + 1) * self.samples_per_gpu]
            for i in np.random.permutation(range(len(indices) // self.samples_per_gpu))
        ]
        indices = np.concatenate(indices)
        indices = indices.astype(np.int64).tolist()
        assert len(indices) == self.num_samples
        return iter(indices)

    def __len__(self):
        return self.num_samples

总结

在本文中,介绍了mmdet框架下数据处理的流程,将数据增强、数据集构建和dataloader构建的顺序和关系理顺,并简单介绍了常见数据增强内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/272095.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于勒索软件盛行,LockBit网络攻击者气焰嚣张的动态情报

一、基本内容 在过去一周内&#xff0c;LockBit团队颇为高调&#xff0c;他们对关键组织、政府和企业发动了一系列的攻击。该勒索软件团队发动的攻击数量远远超过其他所有的勒索团队&#xff0c;CSEM事件为近期勒索事件画上了句号 二、相关发声情况 2023年8月30日&#xff0…

数据库原理知识点清晰总结

数据库基础知识 数据库系统结构 数据库管理系统 数据库设计 关系数据库 关系数据库标准语言SQL 关系数据库规范化理论 数据库保护技术 视图 存储过程 触发器 数据的锁定

[uniapp] 文件查找失败:‘./iconfont.woff?t=1703574566334‘

uniapp 报错 文件查找失败&#xff1a;‘./iconfont.woff?t1703574566334’ uniapp引入图标的时候如果没有处理,就会报这个错误,这个错误是因为字体图标用的相对路径,uniapp不允许, 所以要解决这个错误,只需要我们去到iconfont.css文件内将相对路径改为绝对路径就好 改成 …

【模型部署入门 一】:Pytorch图像分类模型转换ImageNet1000类预训练模型转ONNX

入门学习主要是跟随同济子豪兄&#xff0c;很感谢该博主&#xff0c;本博客代码主体是子豪兄的&#xff0c;我只是总结加工整理记录。 子豪兄对应学习视频链接地址为&#xff1a;ImageNet1000类预训练模型转ONNX 我自己总结的教程中代码使用任何python编译环境都可以直接打开…

Linux系统--账号和权限管理

目录 一、Linux安全模型 二、用户账号和组账号 2.1 用户帐号类型 2.2 组账号 2.3 用户和组的关系 三、两个重要文件夹 3.1 用户账号文件/etc/passwd 3.2 /etc/shadow 四、用户命令 4.1 useradd命令——添加用户 4.2 passwd命令——密码管理 4.3 usermod命令—…

北科智慧智能手提箱旗舰版惊艳亮相2023(香港)智能科技展

2023年12月8日&#xff0c;北科智慧团队在粤港澳大湾区创新发展峰会暨侨交会2023&#xff08;香港&#xff09;智能科技展上举办新品发布&#xff0c;推出三款自主研发科技产品&#xff1a;智能手提箱高级版和旗舰版&#xff0c;以及智能呈批夹。新品一经推出&#xff0c;便受到…

PMP®项目管理,2024年1月4日开课啦~想了解的提前查看!

PMP项目管理认证 1&#x1f237;4日开课~ 想报名的提前预约啦 &#x1f447;&#x1f447;&#x1f447; &#x1f446;&#xff08;以上是PMP课程内容&#xff09; 课程介绍 PMP&#xff08;Project Management Professional&#xff09;是由美国项目管理协会&#xff08;…

Kubernetes的理论基础

k8s:kubernetes:8个字母省略&#xff0c;就是k8s。 自动部署&#xff0c;自动扩展和管理容器化部署的应用程序的一个开源系统。k8s是负责自动化运维管理多个容器化程序的集群&#xff0c;是一个功能强大的容器编排工具。分布式和集群化的方式进行容器管理。 1.15 1.18 1.20 1…

vue 基础学习 一

1. vue 使用快速入门三步走 (1) 新建 HTML 页面&#xff0c;引入 Vue.js文件 1 2 3 4 5 6 7 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>Vue.js 入门示例</title> <script src"https://cdn.j…

Python如何实现邮件的发送

python笔记- 发送邮件 依赖&#xff1a; Python代码实现发送邮件&#xff0c;使用的模块是smtplib、MIMEText&#xff0c;实现代码之前需要导入包&#xff1a; import smtplib from email.mime.text import MIMEText 使用163邮件发送邮件&#xff0c;具体代码实现如下&#x…

计算机毕业设计选题推荐,ssm诗词打卡微信小程序 44669(赠送源码数据库 )上万套实战教程手把手教学JAVA、PHP,node.js,C++、python、数据可视化等

诗词打卡微信小程序 摘要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;诗词打卡微信小程序被用户普遍使用&…

Cron介绍,以及常见的cron表达式

目录 一.cron介绍 1.什么是Cron&#xff1f; 2.Cron语法 时间字段的取值范围如下&#xff1a; 时间字段支持以下特殊字符&#xff1a; 下面是一些示例&#xff1a; 3.虚拟机安装cron(centos7展示) 二.常见的cron表达式 一.cron介绍 1.什么是Cron&#xff1f; Cron是一个…

视频物体对象追踪AI技术模型——Tracking Any Object Amodally

项目地址&#xff1a;https://tao-amodal.github.io 论文&#xff1a;https://arxiv.org/abs/2312.12433 GitHub&#xff1a;GitHub - WesleyHsieh0806/TAO-Amodal: Official Code for Tracking Any Object Amodally AIGC专区&#xff1a;aigc 更多消息&#xff1a;AI人工智能行…

Linux之vim编辑器

目录 vim编辑器 vim编辑器指令 命令模式指令 光标相关 移动光标相关 文本操作 底行模式指令 插入模式 vim配置 vimforcpp 面试官&#xff1a;小伙子&#xff0c;你是用什么环境编写代码的&#xff1f; 小明&#xff1a;vs2019 面试官&#xff1a;小伙子&#xff0c…

亚马逊云科技 re:Invent 2023 产品体验:亚马逊云科技产品应用实践 王炸产品 Amazon Q,你的 AI 助手

意料之中 2023年9月25日&#xff0c;亚马逊宣布与 Anthropic 正式展开战略合作&#xff0c;结合双方在更安全的生成式 AI 领域的先进技术和专业知识&#xff0c;加速 Anthropic 未来基础模型的开发&#xff0c;并将其广泛提供给亚马逊云科技的客户使用。 亚马逊云科技开发者社…

工业信息采集平台的五大核心优势

关键字&#xff1a;工业信息采集平台,蓝鹏数据采集系统,蓝鹏测控系统, 生产管控系统, 生产数据处理平台,MES系统数据采集, 蓝鹏数据采集平台通过实现和构成其他工业数据信息平台的一级设备进行通讯&#xff0c;从而完成平台之间的无缝对接。这里我们采用的最多的方式是和PLC进行…

vue3 vue-cropper实现图片裁剪+上传功能(组件封装使用)

效果&#xff08;CV即用&#xff09; 1.安装引入vue-cropper&#xff08;官网&#xff09;官网地址 npm install vue-croppernextimport vue-cropper/dist/index.cssimport { VueCropper } from "vue-cropper";2. 全局引入 import VueCropper from vue-cropper; i…

链接世界与中国时尚文化,积萨伯爵国际时尚品牌在中国大放异彩

时尚的历史是一部文化发展的历史。从中国古代到现代西方&#xff0c;每个时代的时尚都有其独特的文化背景和历史意义。自丝绸之路开启了古代中国与罗马帝国之间的贸易&#xff0c;时尚的不断创新和变革&#xff0c;是文化变迁和时代精神的反映。时尚的变化&#xff0c;也引领着…

关于Axios发送Get请求无法添加Content-Type

在拦截器中尝试给headers添加Content-Type&#xff1a; request.interceptors.request.use(config > {if (!config.headers[Content-Type]) {config.headers[Content-Type] application/json;}return config;},error > {return Promise.reject(error)} )如果是GET请求&…