mmsegmentation自定义数据集的准备,配置文件编写以及训练,测试

一、前提

确认已经安装好了mmsegmentation的环境。具体安装方法,请看官方的get_started.md

二、数据准备

首先是结合官方的customize_datasets.md,如图:
在这里插入图片描述

其中img_dir中存放的就是原图,ann_dir中就是放的对应图像的"mask"。

我的数据是用labelme标注的,出来就是一张图对应一个json文件,它是只标注了每个目标物的外轮廓,大概这个样子:
在这里插入图片描述

然后接下来就是按照官方的说法,制作每张图对应的ann:

import os
import json
import glob
import shutil
import tqdm
import cv2
import numpy as np
from PIL import Image
from sklearn.model_selection import train_test_split

np.random.seed(0)
w, h = (1280, 1024)   # 这是我的图的大小,你看你来改


def labelme2seg(json_files: list, output_path: str):
    for json_file in tqdm.tqdm(json_files, desc="transforming:"):
        img_data = np.ones((h, w), dtype=np.uint8) * 27   # 我一共27类,你目标物多少类这里就写多少类,
        with open(json_file, encoding="utf-8") as f:
            json_data = json.load(f)
        labels_data = json_data["shapes"]
        # 将目标物区域像素填充为对应ID号
        for label_data in labels_data:
            # 下面这行,你的label不是数字的话,是汉字或者其它,自己记得稍微改一下,映射成数字,从0开始
            goods_id = int(label_data["label"])
            location = np.asarray(label_data["points"], dtype=np.int32)
            cv2.fillPoly(img_data, [location], color=(goods_id, goods_id, goods_id))

        res_img = Image.fromarray(img_data, mode="P")
        res_img_name = os.path.basename(json_file).replace(".json", ".png")
        res_img.save(os.path.join(output_path, res_img_name))
    return


if __name__ == '__main__':
    labelme_path = r"上面方.jpg和json文件的路径"
    save_path = r"最终保存的路径/my_dataset"

    img_dir_train = os.path.join(save_path, "img_dir", "train")
    img_dir_val = os.path.join(save_path, "img_dir", "val")
    img_dir_test = os.path.join(save_path, "img_dir", "test")

    ann_dit_train = os.path.join(save_path, "ann_dir", "train")
    ann_dir_val = os.path.join(save_path, "ann_dir", "val")
    ann_dir_test = os.path.join(save_path, "ann_dir", "test")

    if not os.path.exists(img_dir_train):
        os.makedirs(img_dir_train)
    if not os.path.exists(img_dir_val):
        os.makedirs(img_dir_val)
    if not os.path.exists(img_dir_test):
        os.makedirs(img_dir_test)

    if not os.path.exists(ann_dit_train):
        os.makedirs(ann_dit_train)
    if not os.path.exists(ann_dir_val):
        os.makedirs(ann_dir_val)
    if not os.path.exists(ann_dir_test):
        os.makedirs(ann_dir_test)

    json_list_path = glob.glob(labelme_path + "/*.json")
    train_path, test_val_path = train_test_split(json_list_path, test_size=0.15)
    test_path, val_path = train_test_split(test_val_path, test_size=0.15)

    # 制作mask:
    labelme2seg(train_path, ann_dit_train)
    labelme2seg(val_path, ann_dir_val)
    labelme2seg(test_path, ann_dir_test)

    # 图复制进对应位置
    for file in tqdm.tqdm(train_path, desc="copy train_img"):
        shutil.copy(file.replace(".json", ".jpg"), img_dir_train)
    for file in tqdm.tqdm(val_path, desc="copy val_img"):
        shutil.copy(file.replace(".json", ".jpg"), img_dir_val)
    for file in tqdm.tqdm(test_path, desc="copy test_img"):
        shutil.copy(file.replace(".json", ".jpg"), img_dir_test)

然后等你制作好了数据集,就把这个my_dataset文件夹放在mmsegmentation的data目录下。
特别注意:一定要看以上代码,我写的3处注释,根据你的实际情况来改,特别是类别名映射成[0, class_num-1]。除非极度巧合,不然代码肯定的是会报错的。

三、配置文件

自定义数据类

在mmseg/datasets新建一个文件my_dataset.py仿造其它的数据集写下如下内容:

from .builder import DATASETS
from .custom import CustomDataset


@DATASETS.register_module()
class MyDataset(CustomDataset):
    # 写你实际的类别名就好了,最后再加上一个background
    CLASSES = (
        'T01', 'T02', 'T03', 'T04', 'T05', 'T06', 'T07', 'T08', 'T09', 'T10', 'T11', 'T12', 'T13',
        'T14', 'T15', 'T16', 'T17', 'T18', 'T19', 'T20', 'T21', 'T22', 'T23', 'T24', 'T25','T26', 'background'
    )
    # 这个数量与上面个数对应就好了
    PALETTE = [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50],
               [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255],
               [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7],
               [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82],
               [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3],
               [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255],
               [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220]]

    def __init__(self, **kwargs):
        super(MyDataset, self).__init__(
            **kwargs
        )

然后在mmseg/datasets/__init__.py中把自己的数据集添加进去(主要是添加以下两行):

from .my_dataset import MyDataset
# 在 __all__中添加自己的类名
__all__ = [
    '......', 'LoveDADataset', 'MyDataset'       # 最后添加这个
]

自定义配置文件

首先说明,这是仿照configs/pspnet/pspnet_r50-d8_512x512_160k_ade20k.py这一配置文件来写的,其它的大同小异,把pspnet_r50-d8_512x512_160k_ade20k.py配置文件中的是用到的几个配置文件都写进了一个文件里my_psp.py,放在configs/pspnet下,内容如下:

# 参考 pspnet_r50-d8_512x512_160k_ade20k 来做的

# 1.model:根据'../_base_/models/pspnet_r50-d8.py'
# model settings
norm_cfg = dict(type='SyncBN', requires_grad=True)
model = dict(
    type='EncoderDecoder',
    pretrained='open-mmlab://resnet50_v1c',
    backbone=dict(
        type='ResNetV1c',
        depth=50,
        num_stages=4,
        out_indices=(0, 1, 2, 3),
        dilations=(1, 1, 2, 4),
        strides=(1, 2, 1, 1),
        norm_cfg=norm_cfg,
        norm_eval=False,
        style='pytorch',
        contract_dilation=True),
    decode_head=dict(
        type='PSPHead',
        in_channels=2048,
        in_index=3,
        channels=512,
        pool_scales=(1, 2, 3, 6),
        dropout_ratio=0.1,
        num_classes=28,   # 注意改这个类别数,我的是27类+最后一个background
        norm_cfg=norm_cfg,
        align_corners=False,
        loss_decode=dict(
            type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)),
    auxiliary_head=dict(
        type='FCNHead',
        in_channels=1024,
        in_index=2,
        channels=256,
        num_convs=1,
        concat_input=False,
        dropout_ratio=0.1,
        num_classes=28,       # 注意改这个值,
        norm_cfg=norm_cfg,
        align_corners=False,
        loss_decode=dict(
            type='CrossEntropyLoss', use_sigmoid=False, loss_weight=0.4)),
    # model training and testing settings
    train_cfg=dict(),
    test_cfg=dict(mode='whole'))


# 2. data:根据'../_base_/datasets/ade20k.py'
# dataset settings
dataset_type = 'MyDataset'   # 自己mmseg/datasets/my_dataset.py中的类名
data_root = 'data/my_dataset'
img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
crop_size = (512, 512)
train_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(type='LoadAnnotations', reduce_zero_label=True),
    dict(type='Resize', img_scale=(1280, 1024), ratio_range=(0.5, 2.0)),   # img_scale我还是就用的我原图的大小,你可以改成你的大小,其他地方同步改
    dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),
    dict(type='RandomFlip', prob=0.5),
    dict(type='PhotoMetricDistortion'),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='Pad', size=crop_size, pad_val=0, seg_pad_val=255),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img', 'gt_semantic_seg']),
]
test_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(
        type='MultiScaleFlipAug',
        img_scale=(1280, 1024),
        # img_ratios=[0.5, 0.75, 1.0, 1.25, 1.5, 1.75],
        flip=False,
        transforms=[
            dict(type='Resize', keep_ratio=True),
            dict(type='RandomFlip'),
            dict(type='Normalize', **img_norm_cfg),
            dict(type='ImageToTensor', keys=['img']),
            dict(type='Collect', keys=['img']),
        ])
]
data = dict(
    samples_per_gpu=4,   # 显存不够,来把这改小
    workers_per_gpu=4,    
    train=dict(
        type=dataset_type,
        data_root=data_root,
        img_dir='img_dir/train',
        ann_dir='ann_dir/train',
        pipeline=train_pipeline),
    val=dict(
        type=dataset_type,
        data_root=data_root,
        img_dir='img_dir/val',
        ann_dir='ann_dir/val',
        pipeline=test_pipeline),
    test=dict(
        type=dataset_type,
        data_root=data_root,
        img_dir='img_dir/test',
        ann_dir='ann_dir/test',
        pipeline=test_pipeline))


# 3. 根据:'../_base_/default_runtime.py'
# yapf:disable
log_config = dict(
    interval=50,
    hooks=[
        dict(type='TextLoggerHook', by_epoch=False),
        # dict(type='TensorboardLoggerHook')
    ])
# yapf:enable
dist_params = dict(backend='nccl')
log_level = 'INFO'
load_from = None
resume_from = None
workflow = [('train', 1)]
cudnn_benchmark = True


# 4. schedules:根据'../_base_/schedules/schedule_160k.py'
# optimizer
optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005)   # 根据自己情况改学习率吧
optimizer_config = dict()
# learning policy
lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
# runtime settings
runner = dict(type='IterBasedRunner', max_iters=160000)
checkpoint_config = dict(by_epoch=False, interval=16000)
evaluation = dict(interval=16000, metric='mIoU', pre_eval=True)

训练

在mmsegmentation目录下,激活你的虚拟环境,然后执行:

单张GPU:python tools/train.py configs/pspnet/my_psp.py

两张GPU:./tools/dist_train.sh configs/pspnet/my_psp.py 2

然后你应该就能看到这个效果:
在这里插入图片描述
然后就大功告成。

四、结果测试

把测试集的结果保存起来:

python tools/test.py configs/pspnet/my_psp.py work_dirs/my_psp/latest.pth --show-dir ./mypsp_output

如果想像官方demo展示一样的话,就还需要修改文件mmseg/core/evaluation/class_names.py:

仿照里面已有的数据写,添加(与上面保持一致)

# for myself dataset
def mydata_classes():    
    return [
        'T01', 'T02_1', 'T02_2', 'T02_3', 'T03_1', 'T03_2', 'T03_3', 'T04', 'T05', 'T06', 'T07', 'T08', 'T09', 'T10',
        'T11', 'T12', 'T13', 'T14', 'T15', 'T16', 'T17', 'T18', 'T19', 'T20', 'T21', 'T22', 'T26', 'background'
    ]

def mydata_palette():
    return [[120, 120, 120], [180, 120, 120], [6, 230, 230], [80, 50, 50],
            [4, 200, 3], [120, 120, 80], [140, 140, 140], [204, 5, 255],
            [230, 230, 230], [4, 250, 7], [224, 5, 255], [235, 255, 7],
            [150, 5, 61], [120, 120, 70], [8, 255, 51], [255, 6, 82],
            [143, 255, 140], [204, 255, 4], [255, 51, 7], [204, 70, 3],
            [0, 102, 200], [61, 230, 250], [255, 6, 51], [11, 102, 255],
            [255, 7, 71], [255, 9, 224], [9, 7, 230], [220, 220, 220]]

这里面还有一处需要修改:

dataset_aliases = {
    'cityscapes': ['cityscapes'],
    'ade': ['ade', 'ade20k'],
    'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'],
    'mydata': ['mydata']     # 这处是自己添加的
}

Tips:函数名这些别写错了,想要改成别的也一定要全部一起改。

然后可以用官方展示demo的方式来展示你的结果了:

python demo/image_demo.py data/my_dataset/img_dir/test/10740.jpg work_dirs/my_psp/my_psp.py work_dirs/my_psp/latest.pth --device cuda:0 --palette mydata

再次说明,一定要看我代码里写的注释说明,根据你的实际情况来改,除非极度巧合,不然直接拿来就跑,肯定是会出错的。

我这里都是自己成功跑通了的,也是学习摸索中,如果哪里有什么问题,还请交流、雅正。

希望能帮到你~