如何训练自制数据集?
首先需要在 mmsegmentation/mmseg/datasets 目录下创建一个自制数据集的配置文件,以我的苹果叶片病害分割数据集为例,创建了mmsegmentation/mmseg/datasets/appleleafseg.py
可以看到,这个配置文件主要定义了自制数据集中的 METAINFO , 包括标签的类别,以及对应的 palette 调色板色彩数值,还定义了原始图像和标签图像的文件后缀,分别是 jpg 和 png,以及设置 reduce_zero_label 属性 (是否忽略背景)
from mmseg.registry import DATASETS
from .basesegdataset import BaseSegDataset@DATASETS.register_module()
class AppleLeafSegDataset(BaseSegDataset):METAINFO = dict(classes=('background', 'Alternaria_Boltch', 'Brown_spot', 'Frogeye_leaf_spot', 'Grey_spot', 'Mosaic', 'Powdery_mildew', 'Rust', 'Scab', 'Health'),palette=[[0, 0, 0], [170, 0, 0], [99, 102, 129], [249, 193, 0], [160, 180, 0],[115, 82, 59], [217, 213, 180], [51, 142, 137], [218, 147, 70], [234, 132, 163]])def __init__(self,img_suffix='.jpg',seg_map_suffix='.png',reduce_zero_label=False,# 因为上面METAINFO已经将背景0作为一种类别并且设置掩码色彩为0,0,0所以这里的reduce_zero_label需要设置为false**kwargs) -> None:super().__init__(img_suffix=img_suffix,seg_map_suffix=seg_map_suffix,reduce_zero_label=reduce_zero_label,**kwargs)
然后将 AppleLeafSegDataset 添加到 mmseg/datasets/__init__.py
中的__all__
里
__all__ = ['BaseSegDataset', 'BioMedical3DRandomCrop', 'BioMedical3DRandomFlip','CityscapesDataset', 'PascalVOCDataset', 'ADE20KDataset','PascalContextDataset', 'PascalContextDataset59', 'ChaseDB1Dataset','DRIVEDataset', 'HRFDataset', 'STAREDataset', 'DarkZurichDataset','NightDrivingDataset', 'COCOStuffDataset', 'LoveDADataset','MultiImageMixDataset', 'iSAIDDataset', 'ISPRSDataset', 'PotsdamDataset','LoadAnnotations', 'RandomCrop', 'SegRescale', 'PhotoMetricDistortion','RandomRotate', 'AdjustGamma', 'CLAHE', 'Rerange', 'RGB2Gray','RandomCutOut', 'RandomMosaic', 'PackSegInputs', 'ResizeToMultiple','LoadImageFromNDArray', 'LoadBiomedicalImageFromFile','LoadBiomedicalAnnotation', 'LoadBiomedicalData', 'GenerateEdge','DecathlonDataset', 'LIPDataset', 'ResizeShortestEdge','BioMedicalGaussianNoise', 'BioMedicalGaussianBlur','BioMedicalRandomGamma', 'BioMedical3DPad', 'RandomRotFlip','SynapseDataset', 'REFUGEDataset', 'MapillaryDataset_v1','MapillaryDataset_v2', 'Albu', 'LEVIRCDDataset','LoadMultipleRSImageFromFile', 'LoadSingleRSImageFromFile','ConcatCDInput', 'BaseCDDataset', 'DSDLSegDataset', 'BDD100KDataset','NYUDataset', 'HSIDrive20Dataset', 'AppleLeafSegDataset'
]
接下来,需要在 mmsegmentation/mmseg/utils/class_names.py 中补充数据集元信息
我的苹果树叶病害数据集相关片段如下:
def appleleafdiseases_classes():"""BDD100K class names for external use(the class name is compatible withCityscapes )."""return ['background', 'Alternaria_Boltch', 'Brown_spot', 'Frogeye_leaf_spot', 'Grey_spot', 'Mosaic','Powdery_mildew', 'Rust', 'Scab', 'Health']def appleleafdiseases_palette():"""bdd100k palette for external use(same with cityscapes)"""return [[0, 0, 0], [170, 0, 0], [99, 102, 129], [249, 193, 0], [160, 180, 0],[115, 82, 59], [217, 213, 180], [51, 142, 137], [218, 147, 70], [234, 132, 163]]dataset_aliases = {'cityscapes': ['cityscapes'],'ade': ['ade', 'ade20k'],'voc': ['voc', 'pascal_voc', 'voc12', 'voc12aug'],'pcontext': ['pcontext', 'pascal_context', 'voc2010'],'loveda': ['loveda'],'potsdam': ['potsdam'],'vaihingen': ['vaihingen'],'cocostuff': ['cocostuff', 'cocostuff10k', 'cocostuff164k', 'coco-stuff','coco-stuff10k', 'coco-stuff164k', 'coco_stuff', 'coco_stuff10k','coco_stuff164k'],'isaid': ['isaid', 'iSAID'],'stare': ['stare', 'STARE'],'lip': ['LIP', 'lip'],'mapillary_v1': ['mapillary_v1'],'mapillary_v2': ['mapillary_v2'],'bdd100k': ['bdd100k'],'hsidrive': ['hsidrive', 'HSIDrive', 'HSI-Drive', 'hsidrive20', 'HSIDrive20','HSI-Drive20'],'appleleafdiseases': ['appleleafdiseases']
}
然后,需要在mmsegmentation/configs/_base_/datasets/
目录下创建一个新的数据集配置文件 mmsegmentation/configs/_base_/datasets/apple.py
这个数据集配置文件代码如下,可以看到,主要是告诉模型训练和测试的一些配置信息,包括数据集类和数据集路径,训练,测试的pipiline数据增强,不同的dataloader(训练集,验证集,测试集),验证集测试集的评价指标计算。
# dataset settings
dataset_type = 'AppleLeafSegDataset'
data_root = 'AppleLeafSegDataset/' # 自己数据集所在位置
img_scale = (320, 640) # img_scale是指图像在处理管道中将被调整到的尺寸
crop_size = (160, 320)
train_pipeline = [dict(type='LoadImageFromFile'),dict(type='LoadAnnotations', reduce_zero_label=False), # 不忽略背景dict(type='RandomResize',scale=img_scale,ratio_range=(0.5, 2.0),keep_ratio=True),dict(type='RandomCrop', crop_size=crop_size, cat_max_ratio=0.75),dict(type='RandomFlip', prob=0.5),dict(type='PhotoMetricDistortion'),dict(type='PackSegInputs')
]
test_pipeline = [dict(type='LoadImageFromFile'),dict(type='Resize', scale=img_scale, keep_ratio=True),# add loading annotation after ``Resize`` because ground truth# does not need to do resize data transformdict(type='LoadAnnotations', reduce_zero_label=False),dict(type='PackSegInputs')
]
img_ratios = [0.5, 0.75, 1.0, 1.25, 1.5, 1.75]
# 测试时增强 (TTA) 是一种在测试阶段使用的数据增强策略。它对同一张图片应用不同的增强,例如翻转和缩放,用于模型推理,然后将每个增强后的图像的预测结果合并,以获得更准确的预测结果。
tta_pipeline = [dict(type='LoadImageFromFile', backend_args=None),dict(type='TestTimeAug',transforms=[[dict(type='Resize', scale_factor=r, keep_ratio=True)for r in img_ratios],[dict(type='RandomFlip', prob=0., direction='horizontal'),dict(type='RandomFlip', prob=1., direction='horizontal')], [dict(type='LoadAnnotations')], [dict(type='PackSegInputs')]])
]
train_dataloader = dict(batch_size=4,num_workers=4,persistent_workers=True,sampler=dict(type='InfiniteSampler', shuffle=True),dataset=dict(type=dataset_type,data_root=data_root,data_prefix=dict(img_path='images/training', seg_map_path='annotations/training'),pipeline=train_pipeline))
val_dataloader = dict(batch_size=1,num_workers=4,persistent_workers=True,sampler=dict(type='DefaultSampler', shuffle=False),dataset=dict(type=dataset_type,data_root=data_root,data_prefix=dict(img_path='images/validation',seg_map_path='annotations/validation'),pipeline=test_pipeline))
test_dataloader = val_dataloaderval_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU'])
test_evaluator = val_evaluator
最后,我们需要创建一个总的配置文件,mmsegmentation/configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_appleleafdiseases-320×640.py
这里可以选择mmsegmentation/configs/目录下的不同模型进行实验,这里以unet为例,我创建的这个文件代码如下:
可以看到,_base_
定义了模型配置,数据集配置,调度策略配置,运行时配置。
然后也定义了裁剪大小,数据预处理。
_base_ = ['../_base_/models/apple_deeplabv3_unet_s5-d16.py', '../_base_/datasets/apple.py','../_base_/default_runtime.py', '../_base_/schedules/schedule_40k.py'
]
crop_size = (160, 320)
data_preprocessor = dict(size=crop_size)
model = dict(data_preprocessor=data_preprocessor,test_cfg=dict(crop_size=(160, 320), stride=(85, 85)))
然后,创建一个mmsegmentation/configs/_base_/models/apple_deeplabv3_unet_s5-d16.py
代码如下, 可以看到定义了数据预处理,模型结构,backbone类型,解码器头和辅助解码器头:
# model settings
norm_cfg = dict(type='BN', requires_grad=True)
data_preprocessor = dict(type='SegDataPreProcessor',mean=[123.675, 116.28, 103.53],std=[58.395, 57.12, 57.375],bgr_to_rgb=True,pad_val=0,seg_pad_val=255)
model = dict(type='EncoderDecoder',data_preprocessor=data_preprocessor,pretrained=None,backbone=dict(type='UNet',in_channels=3,base_channels=64,num_stages=5,strides=(1, 1, 1, 1, 1),enc_num_convs=(2, 2, 2, 2, 2),dec_num_convs=(2, 2, 2, 2),downsamples=(True, True, True, True),enc_dilations=(1, 1, 1, 1, 1),dec_dilations=(1, 1, 1, 1),with_cp=False,conv_cfg=None,norm_cfg=norm_cfg,act_cfg=dict(type='ReLU'),upsample_cfg=dict(type='InterpConv'),norm_eval=False),decode_head=dict(type='ASPPHead',in_channels=64,in_index=4,channels=16,dilations=(1, 12, 24, 36),dropout_ratio=0.1,num_classes=10,norm_cfg=norm_cfg,align_corners=False,loss_decode=dict(type='LovaszLoss', reduction='none', loss_weight=1.0)),auxiliary_head=dict(type='FCNHead',in_channels=128,in_index=3,channels=64,num_convs=1,concat_input=False,dropout_ratio=0.1,num_classes=10,norm_cfg=norm_cfg,align_corners=False,loss_decode=dict(type='LovaszLoss', reduction='none', loss_weight=0.4)),# model training and testing settingstrain_cfg=dict(),test_cfg=dict(mode='slide', crop_size=128, stride=85))
然后,重新启动
python setup.py install
pip install -v -e .
开始训练
python tools/train.py configs/unet/unet_s5-d16_deeplabv3_4xb4-40k_appleleafdiseases-320×640.py --work-dir mmseg_log
如何添加训练过程中日志所打印的评价指标?
默认的只打印 IoU ,Acc,mIoU,mAcc,aAcc,如果还想打印 Fscore ,Precision, Recall,mFscore ,mPrecision, mRecall,只需要在数据集配置文件中mmsegmentation/configs/_base_/datasets/apple.py
,在这行代码中添加 mFscore
val_evaluator = dict(type='IoUMetric', iou_metrics=['mIoU', 'mFscore'])
下面的打印信息如何解读?
+-------------------+-------+-------+--------+-----------+--------+
| Class | IoU | Acc | Fscore | Precision | Recall |
+-------------------+-------+-------+--------+-----------+--------+
| background | 94.07 | 96.16 | 96.95 | 97.74 | 96.16 |
| Alternaria_Boltch | 58.0 | 72.75 | 73.42 | 74.1 | 72.75 |
| Brown_spot | 64.19 | 82.51 | 78.19 | 74.29 | 82.51 |
| Frogeye_leaf_spot | 44.46 | 55.28 | 61.55 | 69.43 | 55.28 |
| Grey_spot | 53.24 | 67.83 | 69.49 | 71.23 | 67.83 |
| Mosaic | 50.3 | 81.06 | 66.93 | 56.99 | 81.06 |
| Powdery_mildew | 59.52 | 93.42 | 74.63 | 62.13 | 93.42 |
| Rust | 61.07 | 71.43 | 75.83 | 80.82 | 71.43 |
| Scab | 28.49 | 50.39 | 44.35 | 39.61 | 50.39 |
| Health | 84.84 | 91.07 | 91.8 | 92.55 | 91.07 |
+-------------------+-------+-------+--------+-----------+--------+
07/11 18:55:19 - mmengine - INFO - Iter(val) [169/169] aAcc: 94.1500 mIoU: 59.8200 mAcc: 76.1900 mFscore: 73.3100 mPrecision: 71.8900 mRecall: 76.1900 data_time: 0.0196 time: 0.0292
在这个表格中,每个类别的 Acc
表示的是该类别的分类准确率(Accuracy),也称为像素准确率(PA)。具体来说,Acc
是每个类别中被正确分类的像素数占该类别总像素数的比例。公式如下: Acc = TP TP + FN \text{Acc} = \frac{\text{TP}}{\text{TP} + \text{FN}} Acc=TP+FNTP其中:
- TP \text{TP} TP 是该类别的真阳性像素数(正确分类为该类别的像素数)。
- FN \text{FN} FN 是该类别的假阴性像素数(实际为该类别但被错误分类为其他类别的像素数)。
这个准确率反映了模型在特定类别上的预测准确度,表示模型有多少比例的该类别像素被正确识别。每个类别都有一个单独的 Acc
值,用于衡量该类别的分类性能。
总结:
- 表格中的
Acc
表示的是每个类别的分类准确率,衡量模型在该类别上的预测准确度。
在 mmsegmentation 训练模型时,aAcc 和 mAcc 分别表示:
- aAcc(Overall Accuracy or Pixel Accuracy): 表示所有像素分类正确的比例。它是通过将所有类别的像素预测正确的数量除以总的像素数量来计算的。公式如下:
aAcc = ∑ i = 1 N TP i ∑ i = 1 N ( TP i + FP i + FN i + TN i ) \text{aAcc} = \frac{\sum_{i=1}^{N} \text{TP}_i}{\sum_{i=1}^{N} (\text{TP}_i + \text{FP}_i + \text{FN}_i + \text{TN}_i)} aAcc=∑i=1N(TPi+FPi+FNi+TNi)∑i=1NTPi
其中, TP \text{TP} TP 是真阳性, FP \text{FP} FP 是假阳性, FN \text{FN} FN 是假阴性, TN \text{TN} TN 是真阴性, N N N 是类别数量。
- mAcc(Mean Accuracy): 表示每个类别的平均准确率。它是对每个类别的准确率的平均值。公式如下:
mAcc = 1 N ∑ i = 1 N TP i TP i + FN i \text{mAcc} = \frac{1}{N} \sum_{i=1}^{N} \frac{\text{TP}_i}{\text{TP}_i + \text{FN}_i} mAcc=N1i=1∑NTPi+FNiTPi
其中, TP \text{TP} TP 和 FN \text{FN} FN 是每个类别的真阳性和假阴性, N N N 是类别数量。
总结:
- aAcc 表示所有像素分类正确的总体准确率。
- mAcc 表示每个类别的平均准确率。
怎么使用mmsegmentation的tool/test.py
首先看一下命令行参数部分的代码
def parse_args():parser = argparse.ArgumentParser(description='MMSeg test (and eval) a model')parser.add_argument('config', help='train config file path')parser.add_argument('checkpoint', help='checkpoint file')parser.add_argument('--work-dir', help=('if specified, the evaluation metric results will be dumped into the directory as json'))parser.add_argument('--out', type=str, help='The directory to save output prediction for offline evaluation')parser.add_argument('--show', action='store_true', help='show prediction results')parser.add_argument('--show-dir', help='directory where painted images will be saved. If specified, it will be automatically saved to the work_dir/timestamp/show_dir')parser.add_argument('--wait-time', type=float, default=2, help='the interval of show (s)')parser.add_argument('--cfg-options', nargs='+', action=DictAction, help='override some settings in the used config, the key-value pair in xxx=yyy format will be merged into config file. If the value to be overwritten is a list, it should be like key="[a,b]" or key=a,b It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" Note that the quotation marks are necessary and that no white space is allowed.')parser.add_argument('--launcher', choices=['none', 'pytorch', 'slurm', 'mpi'], default='none', help='job launcher')parser.add_argument('--tta', action='store_true', help='Test time augmentation')parser.add_argument('--local_rank', '--local-rank', type=int, default=0)args = parser.parse_args()if 'LOCAL_RANK' not in os.environ:os.environ['LOCAL_RANK'] = str(args.local_rank)return args
- config:训练配置文件路径。
- checkpoint:模型检查点文件路径。
- work-dir:指定工作目录,评估结果会保存为 JSON。
- out:保存预测结果的目录,用于离线评估。
- show:显示预测结果。
- show-dir:保存绘制图像的目录。
- wait-time:显示间隔时间(秒)。
- cfg-options:覆盖配置文件中的一些设置。
- launcher:选择作业启动器。
- tta:启用测试时增强。
- local_rank:本地排名。
这里的 config 是指模型的整体配置文件,而不是训练结束后保存到 --work-dir 目录下的配置文件。
最终我执行测试的命令是
python tools/test.py
configs/segformer/apple_segformer_mit-b0_8xb2-160k_appleleaf-512x512.py
mmseg_log/iter_20000.pth
--work-dir mmseg_log --out outputs --show-dir outputs
第一个参数是模型配置文件,第二个参数是训练过程保存的权重文件,这里以最后一次iter保存的为例,–work-dir 会将测试集的评估结果和日志保存到我指定的mmseg_log目录下,–out和–show-dir我都指定为outputs目录下,测试结束后,会在outputs目录下得到测试所得到的结果图像,一种是灰度图像,看起来是纯黑色的,另一种是可视化的彩色对比图像,如下图:
左边是Ground Truth,右边是模型预测的结果。
将outputs目录下模型测试所得到的灰度图像转换为伪彩色图像
下面这段代码保存为convert_to_pseudo_color.py
,然后执行python convert_to_pseudo_color.py
后就会将outputs目录下模型测试所得到的灰度图像转换为伪彩色图像并保存到 outputs/Pseudo/
目录下了
import os
from PIL import Image
import numpy as np# METAINFO
METAINFO = dict(classes=('background', 'Alternaria_Boltch', 'Brown_spot', 'Frogeye_leaf_spot', 'Grey_spot', 'Mosaic', 'Powdery_mildew', 'Rust', 'Scab', 'Health'),palette=[[0, 0, 0], [170, 0, 0], [99, 102, 129], [249, 193, 0], [160, 180, 0],[115, 82, 59], [217, 213, 180], [51, 142, 137], [218, 147, 70], [234, 132, 163]]
)# Ensure output directory exists
output_dir = 'outputs/Pseudo/'
os.makedirs(output_dir, exist_ok=True)# Get the palette from METAINFO
palette = METAINFO['palette']
palette = np.array(palette, dtype=np.uint8)def convert_to_pseudo_color(image_path, save_path):"""Convert a grayscale image to pseudo color using the given palette."""gray_image = Image.open(image_path).convert('L')gray_array = np.array(gray_image)color_image = np.zeros((gray_array.shape[0], gray_array.shape[1], 3), dtype=np.uint8)for i in range(len(palette)):color_image[gray_array == i] = palette[i]color_image = Image.fromarray(color_image)color_image.save(save_path)# Process all .png files in the outputs directory
for file_name in os.listdir('outputs'):if file_name.endswith('.png'):file_path = os.path.join('outputs', file_name)save_path = os.path.join(output_dir, file_name)convert_to_pseudo_color(file_path, save_path)print("Pseudo color images have been saved to", output_dir)
怎么打印模型 flops 和参数量?
执行这个命令,第一个参数是模型配置文件,第二个参数–shape是可选项,表示测试的图像大小
python tools/analysis_tools/get_flops.py
configs/segformer/apple_segformer_mit-b0_8xb2-160k_appleleaf-512x512.py
--shape 1024 512
打印输出的结果如下:
==============================
Compute type: direct: randomly generate a picture
Input shape: (1024, 512)
Flops: 17.917G
Params: 3.718M
==============================
怎么打印模型 fps?
怎么按照epoch训练,而非iter?
首先我重新编写了一个../_base_/schedules/schedule_20k_by_epoch.py
可以看到需要将 train_cfg 中的 type 设置为 EpochBasedTrainLoop,下面的默认hooks也需要调整为按epoch方式训练的
# optimizer
optimizer = dict(type='SGD', lr=0.001, momentum=0.9, weight_decay=0.0005)
optim_wrapper = dict(type='OptimWrapper', optimizer=optimizer, clip_grad=None)# learning policy
param_scheduler = [dict(type='PolyLR',eta_min=1e-4,power=0.9,begin=0,end=200,by_epoch=True)
]# training schedule for 20 epochs
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=200, val_interval=1)
val_cfg = dict(type='ValLoop')
test_cfg = dict(type='TestLoop')default_hooks = dict(timer=dict(type='IterTimerHook'),logger=dict(type='LoggerHook', interval=1, log_metric_by_epoch=True),param_scheduler=dict(type='ParamSchedulerHook'),checkpoint=dict(type='CheckpointHook', by_epoch=True, interval=20),sampler_seed=dict(type='DistSamplerSeedHook'),visualization=dict(type='SegVisualizationHook'))
然后需要修改整体配置文件mmsegmentation/configs/segformer/apple_segformer_mit-b0_8xb2-160k_appleleaf-512x512.py
_base_ = ['../_base_/models/apple_segformer_mit-b0.py', '../_base_/datasets/apple.py','../_base_/default_runtime.py', '../_base_/schedules/schedule_20k_by_epoch.py'
]
我将原先的../_base_/schedules/schedule_20k.py
替换成了重新编写的../_base_/schedules/schedule_20k_by_epoch.py
然后还需要将整体配置文件中的param_scheduler进行修改,如下所示,前5个epoch按照LinearLR进行学习率调整,5-200按照PolyLR
param_scheduler = [dict(type='LinearLR', start_factor=0.1, by_epoch=True, begin=0, end=5),dict(type='PolyLR',eta_min=0.0,power=1.0,begin=5,end=200,by_epoch=True,)
]
然后我执行训练命令,发现训练期间,log所打印的仍然是按 iter 训练的信息,问题是../_base_/default_runtime.py
的代码有覆盖,
需要将下面这行代码修改
log_processor = dict(by_epoch=True) # 修改为按 epoch 打印日志
此时遇到了第二个问题,虽然按照epoch打印了,但是在第一个 epoch 陷入了无限循环,解决办法是需要修改数据集配置文件../_base_/datasets/apple.py
,需要将下面这一行代码修改为
sampler=dict(type='DefaultSampler', shuffle=True), # 更改为 DefaultSampler
原先的 InfiniteSampler 导致无限循环
运行segmenter遇到的问题
需要升级 pytorch版本到 1.13.1 cuda117,然后我在虚拟环境升级之后,遇到mmcv版本不匹配的问题,解决办法是卸载 mmcv,然后安装mmcv 次最新版,问题得到解决。