ショートカット

データセットのカスタマイズ

新しいデータ形式のサポート

新しいデータ形式をサポートするには、既存の形式(COCO形式またはPASCAL形式)に変換するか、中間形式に直接変換します。オフライン(スクリプトによるトレーニング前)またはオンライン(新しいデータセットを実装し、トレーニング時に変換)で変換することもできます。MMDetectionでは、データをCOCO形式に変換し、オフラインで変換することを推奨します。そのため、データの変換後、構成ファイルのデータアノテーションパスとクラスのみを変更する必要があります。

新しいデータ形式を既存の形式に再編成する

最も簡単な方法は、データセットを既存のデータセット形式(COCOまたはPASCAL VOC)に変換することです。

COCO形式のアノテーションJSONファイルには、次の必須キーがあります

'images': [
    {
        'file_name': 'COCO_val2014_000000001268.jpg',
        'height': 427,
        'width': 640,
        'id': 1268
    },
    ...
],

'annotations': [
    {
        'segmentation': [[192.81,
            247.09,
            ...
            219.03,
            249.06]],  # If you have mask labels, and it is in polygon XY point coordinate format, you need to ensure that at least 3 point coordinates are included. Otherwise, it is an invalid polygon.
        'area': 1035.749,
        'iscrowd': 0,
        'image_id': 1268,
        'bbox': [192.81, 224.8, 74.73, 33.43],
        'category_id': 16,
        'id': 42986
    },
    ...
],

'categories': [
    {'id': 0, 'name': 'car'},
 ]

JSONファイルには3つの必須キーがあります

  • images: file_nameheightwidthidなどの情報を含む画像のリスト。

  • annotations: インスタンスアノテーションのリストを含みます。

  • categories: カテゴリ名とそのIDのリストを含みます。

データの前処理後、既存の形式(例:COCO形式)でカスタマイズされた新しいデータセットをトレーニングするには、ユーザーが実行する手順が2つあります。

  1. カスタマイズされたデータセットを使用するための構成ファイルを変更します。

  2. カスタマイズされたデータセットのアノテーションを確認します。

以下では、COCO形式の5クラスのカスタマイズされたデータセットを使用して、既存のCascade Mask R-CNN R50-FPN検出器をトレーニングする例を示します。

1. カスタマイズされたデータセットを使用するための構成ファイルの変更

構成ファイルの変更には、2つの側面が関わっています。

  1. data フィールド。具体的には、train_dataloader.datasetval_dataloader.dataset、およびtest_dataloader.datasetmetainfo=dict(classes=classes)フィールドを明示的に追加する必要があります。classesはタプル型である必要があります。

  2. model 部分のnum_classesフィールド。デフォルト値(例:COCOでは80)からクラス数にnum_classesを明示的に上書きします。

configs/my_custom_config.py


# the new config inherits the base configs to highlight the necessary modification
_base_ = './cascade_mask_rcnn_r50_fpn_1x_coco.py'

# 1. dataset settings
dataset_type = 'CocoDataset'
classes = ('a', 'b', 'c', 'd', 'e')
data_root='path/to/your/'

train_dataloader = dict(
    batch_size=2,
    num_workers=2,
    dataset=dict(
        type=dataset_type,
        # explicitly add your class names to the field `metainfo`
        metainfo=dict(classes=classes),
        data_root=data_root,
        ann_file='train/annotation_data',
        data_prefix=dict(img='train/image_data')
        )
    )

val_dataloader = dict(
    batch_size=1,
    num_workers=2,
    dataset=dict(
        type=dataset_type,
        test_mode=True,
        # explicitly add your class names to the field `metainfo`
        metainfo=dict(classes=classes),
        data_root=data_root,
        ann_file='val/annotation_data',
        data_prefix=dict(img='val/image_data')
        )
    )

test_dataloader = dict(
    batch_size=1,
    num_workers=2,
    dataset=dict(
        type=dataset_type,
        test_mode=True,
        # explicitly add your class names to the field `metainfo`
        metainfo=dict(classes=classes),
        data_root=data_root,
        ann_file='test/annotation_data',
        data_prefix=dict(img='test/image_data')
        )
    )

# 2. model settings

# explicitly over-write all the `num_classes` field from default 80 to 5.
model = dict(
    roi_head=dict(
        bbox_head=[
            dict(
                type='Shared2FCBBoxHead',
                # explicitly over-write all the `num_classes` field from default 80 to 5.
                num_classes=5),
            dict(
                type='Shared2FCBBoxHead',
                # explicitly over-write all the `num_classes` field from default 80 to 5.
                num_classes=5),
            dict(
                type='Shared2FCBBoxHead',
                # explicitly over-write all the `num_classes` field from default 80 to 5.
                num_classes=5)],
    # explicitly over-write all the `num_classes` field from default 80 to 5.
    mask_head=dict(num_classes=5)))

2. カスタマイズされたデータセットのアノテーションの確認

カスタマイズされたデータセットがCOCO形式であると仮定して、カスタマイズされたデータセットに正しいアノテーションが含まれていることを確認します。

  1. アノテーションのcategoriesフィールドの長さは、構成ファイルのclassesフィールドのタプル長(つまり、クラス数、この例では5)と完全に一致する必要があります。

  2. 構成ファイルのclassesフィールドには、アノテーションのcategoriesnameと完全に同じ要素が同じ順序で含まれている必要があります。MMDetectionは、categoriesの非連続なidを連続したラベルインデックスに自動的にマッピングするため、categoriesフィールドのnameの文字列順序はラベルインデックスの順序に影響します。同時に、構成ファイルのclassesの文字列順序は、予測されたバウンディングボックスの視覚化中のラベルテキストに影響します。

  3. annotationsフィールドのcategory_idは有効である必要があります。つまり、category_idのすべての値は、categoriesidに属している必要があります。

アノテーションの有効な例を以下に示します。


'annotations': [
    {
        'segmentation': [[192.81,
            247.09,
            ...
            219.03,
            249.06]],  # if you have mask labels
        'area': 1035.749,
        'iscrowd': 0,
        'image_id': 1268,
        'bbox': [192.81, 224.8, 74.73, 33.43],
        'category_id': 16,
        'id': 42986
    },
    ...
],

# MMDetection automatically maps the uncontinuous `id` to the continuous label indices.
'categories': [
    {'id': 1, 'name': 'a'}, {'id': 3, 'name': 'b'}, {'id': 4, 'name': 'c'}, {'id': 16, 'name': 'd'}, {'id': 17, 'name': 'e'},
 ]

CityScapesデータセットのサポートにはこの方法を使用します。スクリプトはcityscapes.pyにあり、ファインチューニング用のconfigsも提供しています。

注記

  1. インスタンスセグメンテーションデータセットの場合、**MMDetectionは現在、COCO形式のデータセットのマスクAPの評価のみをサポートしています**。

  2. トレーニング前にオフラインでデータをコンバートすることをお勧めします。そのため、CocoDatasetを引き続き使用でき、アノテーションのパスとトレーニングクラスのみを変更する必要があります。

新しいデータ形式を中間形式に再編成する

アノテーション形式をCOCO形式またはPASCAL形式に変換したくない場合でも問題ありません。実際、MMEningeのBaseDatasetでシンプルなアノテーション形式を定義しており、既存のすべてのデータセットは、オンラインまたはオフラインのいずれかでこれと互換性を持つように処理されています。

データセットのアノテーションはjsonまたはyamlymlまたはpicklepkl形式である必要があります。アノテーションファイルに格納されている辞書には、metainfodata_listの2つのフィールドが含まれている必要があります。metainfoは辞書で、クラス情報などのデータセットのメタデータを含みます。data_listはリストで、リストの各要素は辞書です。辞書は1つの画像の生データを定義し、各生データには1つまたは複数のトレーニング/テストサンプルが含まれています。

例を以下に示します。

{
    'metainfo':
        {
            'classes': ('person', 'bicycle', 'car', 'motorcycle'),
            ...
        },
    'data_list':
        [
            {
                "img_path": "xxx/xxx_1.jpg",
                "height": 604,
                "width": 640,
                "instances":
                [
                  {
                    "bbox": [0, 0, 10, 20],
                    "bbox_label": 1,
                    "ignore_flag": 0
                  },
                  {
                    "bbox": [10, 10, 110, 120],
                    "bbox_label": 2,
                    "ignore_flag": 0
                  }
                ]
              },
            {
                "img_path": "xxx/xxx_2.jpg",
                "height": 320,
                "width": 460,
                "instances":
                [
                  {
                    "bbox": [10, 0, 20, 20],
                    "bbox_label": 3,
                    "ignore_flag": 1,
                  }
                ]
              },
            ...
        ]
}

一部のデータセットでは、crowd/difficult/ignoredバウンディングボックスなどのアノテーションが提供される場合があります。これらに対応するためにignore_flagを使用します。

上記の標準的なデータアノテーション形式を取得したら、構成でMMDetectionのBaseDetDatasetを直接使用でき、変換は不要です。

カスタマイズされたデータセットの例

アノテーションがテキストファイルの新しい形式であると仮定します。バウンディングボックスのアノテーションは、次のようにテキストファイルannotation.txtに格納されます。

#
000001.jpg
1280 720
2
10 20 40 60 1
20 40 50 60 2
#
000002.jpg
1280 720
3
50 20 40 60 2
20 40 30 45 2
30 40 50 60 3

データをロードするために、mmdet/datasets/my_dataset.pyに新しいデータセットを作成できます。

import mmengine

from mmdet.base_det_dataset import BaseDetDataset
from mmdet.registry import DATASETS


@DATASETS.register_module()
class MyDataset(BaseDetDataset):

    METAINFO = {
       'classes': ('person', 'bicycle', 'car', 'motorcycle'),
        'palette': [(220, 20, 60), (119, 11, 32), (0, 0, 142), (0, 0, 230)]
    }

    def load_data_list(self, ann_file):
        ann_list = mmengine.list_from_file(ann_file)

        data_infos = []
        for i, ann_line in enumerate(ann_list):
            if ann_line != '#':
                continue

            img_shape = ann_list[i + 2].split(' ')
            width = int(img_shape[0])
            height = int(img_shape[1])
            bbox_number = int(ann_list[i + 3])

            instances = []
            for anns in ann_list[i + 4:i + 4 + bbox_number]:
                instance = {}
                instance['bbox'] = [float(ann) for ann in anns.split(' ')[:4]]
                instance['bbox_label']=int(anns[4])
 				instances.append(instance)

            data_infos.append(
                dict(
                    img_path=ann_list[i + 1],
                    img_id=i,
                    width=width,
                    height=height,
                    instances=instances
                ))

        return data_infos

次に、構成でMyDatasetを使用するには、構成を次のように変更できます。

dataset_A_train = dict(
    type='MyDataset',
    ann_file = 'image_list.txt',
    pipeline=train_pipeline
)

データセットラッパーによるデータセットのカスタマイズ

MMEngineは、データセットを混合したり、トレーニングのためのデータセットの分布を変更したりするための多くのデータセットラッパーもサポートしています。現在、以下に示す3つのデータセットラッパーをサポートしています。

  • RepeatDataset: データセット全体を単純に繰り返します。

  • ClassBalancedDataset: クラスバランスの方法でデータセットを繰り返します。

  • ConcatDataset: データセットを連結します。

詳細な使用方法については、MMEngine Dataset Wrapperを参照してください。

データセットクラスの変更

既存のデータセットタイプを使用すると、アノテーションのサブセットをトレーニングするために、そのメタ情報を変更できます。たとえば、現在のデータセットの3つのクラスのみをトレーニングする場合、データセットのクラスを変更できます。データセットは、他のクラスのground truthボックスを自動的にフィルタリングします。

classes = ('person', 'bicycle', 'car')
train_dataloader = dict(
    dataset=dict(
        metainfo=dict(classes=classes))
    )
val_dataloader = dict(
    dataset=dict(
        metainfo=dict(classes=classes))
    )
test_dataloader = dict(
    dataset=dict(
        metainfo=dict(classes=classes))
    )

注記:

  • MMDetection v2.5.0より前では、クラスが設定されていて空のGT画像が存在しない場合、データセットは空のGT画像を自動的にフィルタリングし、設定でそれを無効にする方法はなく、これは望ましくない動作であり、混乱を招きます。なぜなら、クラスが設定されていない場合、データセットはfilter_empty_gt=Trueおよびtest_mode=Falseの場合にのみ空のGT画像をフィルタリングするからです。MMDetection v2.5.0以降、画像フィルタリングプロセスとクラスの変更を切り離しました。つまり、データセットはfilter_cfg=dict(filter_empty_gt=True)およびtest_mode=Falseの場合にのみ空のGT画像をフィルタリングし、クラスが設定されているかどうかに関係なく、設定されたクラスはトレーニングに使用されるクラスのアノテーションのみに影響し、ユーザーは空のGT画像をフィルタリングするかどうかを自分で決定できます。

  • MMEngineのBaseDatasetまたはMMDetectionのBaseDetDatasetを直接使用する場合、設定を変更してもGTのない画像をフィルタリングすることはできませんが、オフラインで解決できます。

  • データセットでclassesを指定する場合は、ヘッドのnum_classesも変更してください。v2.9.0(PR#4508以降)から、数値の一貫性をチェックするNumClassCheckHookを実装しました。

COCOパノプティックデータセット

現在、COCOパノプティックデータセットをサポートしています。パノプティックアノテーションの形式はCOCO形式とは異なります。アノテーションファイルには、前景と背景の両方が存在します。COCOパノプティック形式のアノテーションJSONファイルには、次の必須キーがあります。

'images': [
    {
        'file_name': '000000001268.jpg',
        'height': 427,
        'width': 640,
        'id': 1268
    },
    ...
]

'annotations': [
    {
        'filename': '000000001268.jpg',
        'image_id': 1268,
        'segments_info': [
            {
                'id':8345037,  # One-to-one correspondence with the id in the annotation map.
                'category_id': 51,
                'iscrowd': 0,
                'bbox': (x1, y1, w, h),  # The bbox of the background is the outer rectangle of its mask.
                'area': 24315
            },
            ...
        ]
    },
    ...
]

'categories': [  # including both foreground categories and background categories
    {'id': 0, 'name': 'person'},
    ...
 ]

さらに、segはパノプティックアノテーション画像のパスに設定する必要があります。

dataset_type = 'CocoPanopticDataset'
data_root='path/to/your/'

train_dataloader = dict(
    dataset=dict(
        type=dataset_type,
        data_root=data_root,
        data_prefix=dict(
            img='train/image_data/', seg='train/panoptic/image_annotation_data/')
    )
)
val_dataloader = dict(
    dataset=dict(
        type=dataset_type,
        data_root=data_root,
        data_prefix=dict(
            img='val/image_data/', seg='val/panoptic/image_annotation_data/')
    )
)
test_dataloader = dict(
    dataset=dict(
        type=dataset_type,
        data_root=data_root,
        data_prefix=dict(
            img='test/image_data/', seg='test/panoptic/image_annotation_data/')
    )
)