飞桨常规赛:遥感影像地块分割 - 12月第9名方案

转自AI Studio,原文链接:飞桨常规赛:遥感影像地块分割 - 12月第9名方案 - 飞桨AI Studio

引言

(1) 项目简介

为了对遥感影像进行像素级内容解析,对遥感影像中感兴趣的类别进行提取和分类,考虑使用飞桨提供的PaddleX产品解决该问题(PaddleX官方参考文档),该产品在数据标注、图像处理、模型训练等方面简单实用。本项目首先安装了PaddleX以及项目中所用到的相关包。然后,进行数据处理,主要包括数据解压操作,图像数据增强操作,划分数据集操作以及数据准备操作。进而,进行模型训练,模型评估以及模型预测。最后对本项目的中的经验以及接下来的改进方向做出总结。

(2) 比赛介绍

本赛题由 2020 CCF BDCI 遥感影像地块分割 初赛赛题改编而来。遥感影像地块分割, 旨在对遥感影像进行像素级内容解析,对遥感影像中感兴趣的类别进行提取和分类,在城乡规划、防汛救灾等领域具有很高的实用价值,在工业界也受到了广泛关注。现有的遥感影像地块分割数据处理方法局限于特定的场景和特定的数据来源,且精度无法满足需求。因此在实际应用中,仍然大量依赖于人工处理,需要消耗大量的人力、物力、财力。本赛题旨在衡量遥感影像地块分割模型在多个类别(如建筑、道路、林地等)上的效果,利用人工智能技术,对多来源、多场景的异构遥感影像数据进行充分挖掘,打造高效、实用的算法,提高遥感影像的分析提取能力。 赛题任务 本赛题旨在对遥感影像进行像素级内容解析,并对遥感影像中感兴趣的类别进行提取和分类,以衡量遥感影像地块分割模型在多个类别(如建筑、道路、林地等)上的效果。

(3) 比赛链接

常规赛:遥感影像地块分割

(4) 数据介绍

本赛题提供了多个地区已脱敏的遥感影像数据,各参赛选手可以基于这些数据构建自己的地块分割模型。

训练数据集

样例图片及其标注如下图所示:

飞桨常规赛:遥感影像地块分割 - 12月第9名方案_第1张图片

 

飞桨常规赛:遥感影像地块分割 - 12月第9名方案_第2张图片

 

训练数据集文件名称:train_and_label.zip

包含2个子文件,分别为:训练数据集(原始图片)文件、训练数据集(标注图片)文件,详细介绍如下:

  • 训练数据集(原始图片)文件名称:img_train

    包含66,653张分辨率为2m/pixel,尺寸为256 * 256的JPG图片,每张图片的名称形如T000123.jpg。

  • 训练数据集(标注图片)文件名称:lab_train

    包含66,653张分辨率为2m/pixel,尺寸为256 * 256的PNG图片,每张图片的名称形如T000123.png。

  • 备注: 全部PNG图片共包括4种分类,像素值分别为0、1、2、3。此外,像素值255为未标注区域,表示对应区域的所属类别并不确定,在评测中也不会考虑这部分区域。

测试数据集

测试数据集文件名称:img_test.zip,详细介绍如下:

包含4,609张分辨率为2m/pixel,尺寸为256 * 256的JPG图片,文件名称形如123.jpg。、

数据增强工具

PaTTA:由第三方开发者组织AgentMaker维护的Test-Time Augmentation库,可在测试时通过数据增强方式产生额外的推理结果,在此基础上进行投票即可获得更稳定的成绩表现。 https://github.com/AgentMaker/PaTTA

RIFLE:由第三方开发者对ICML 2020中的《RIFLE: Backpropagation in Depth for Deep Transfer Learning through Re-Initializing the Fully-connected LayEr》论文所提供的封装版本,其通过对输出层多次重新初始化来使得深层backbone得到更充分的更新。 https://github.com/GT-ZhangAcer/RIFLE_Module

提交内容及格式

  • 以zip压缩包形式提交结果文件,文件命名为 result.zip;
  • zip压缩包中的图片格式必须为单通道PNG;
  • PNG文件数需要与测试数据集中的文件数相同,且zip压缩包文件名需要与测试数据集中的文件名一一对应;
  • 单通道PNG图片中的像素值必须介于0~3之间,像素值不能为255。如果存在未标注区域,评测系统会自动忽略对应区域的提交结果。

提交示例

提交文件命名为:result.zip,zip文件的组织方式如下所示:

主目录                                                                        
├── 1.png         #每个结果文件命名为:测试数据集图片名称+.png                      
├── 2.png                                                              
├── 3.png                                                    
├── ...     

备注: 主目录中必须包含与测试数据集相同数目、名称相对应的单通道PNG图片,且每张单通道PNG图片中的像素值必须介于0~3之间,像素值不能为255。

(5) 赛题重点难点剖析

  • 本次比赛的重点是考验选手对PaddleSeg或PaddleX等工具的使用,本项目使用的是PaddleX,需要对API接口和模型库较熟练掌握。

  • 本次比赛的难点在于数据增强的参数设定和训练时的参数优化,想要获得理想的分数,需要对参数的调整有自己的见解和方法。

(6) 个人方案亮点

  • 本方案使用较为全面的数据增强方式
  • 本方案使用PaddleX进行构建
  • 本方案进行了合理的参数调优

一、环境安装

1.1 安装Paddlex及相关环境

  • 注意numpy和paddlex的版本兼容问题,应选择合适的版本进行安装

In [ ]

# 安装paddlex
# 需要注意paddlex1对于版本有所要求,所以最好更新对应的包版本
!pip install "numpy<=1.19.5" -i https://mirror.baidu.com/pypi/simple
!pip install "paddlex<2.0.0" -i https://mirror.baidu.com/pypi/simple

In [ ]

!pip install imgaug

1.2 导入相关包

In [3]

# 设置使用0号GPU卡(如无GPU,执行此代码后仍然会使用CPU训练模型)
import matplotlib
import os
import paddlex as pdx

os.environ['CUDA_VISIBLE_DEVICES'] = '0'

二、数据处理

2.1 解压数据集

In [3]

!unzip -q data/data80164/train_and_label.zip
!unzip -q data/data80164/img_test.zip

2.2 数据增广

项目使用PaddleX进行数据处理和训练,详情请查看PaddleX的API说明文档

对用于分割任务的数据进行操作。可以利用paddlex.seg.transforms中的Compose类将图像预处理/增强操作进行组合。

  • RandomHorizontalFlip 以一定的概率对图像进行水平翻转。
  • Resize 调整图像大小,本项目的插值方式选用默认的 “LINEAR”,作为模型训练时的数据增强操作。
  • RandomPaddingCrop 对图像和标注图进行随机裁剪,当所需要的裁剪尺寸大于原图时,则进行padding操作,模型训练时的数据增强操作。
  • RandomBlur 以一定的概率对图像进行高斯模糊。
  • RandomRotate 对图像进行随机旋转,模型训练时的数据增强操作。目前支持多通道的RGB图像,例如支持多张RGB图像沿通道轴做concatenate后的图像数据,不支持通道数量不是3的倍数的图像数据。
  • Normalize 对图像进行标准化。

详情请参考数据增强文档

In [7]

from paddlex.seg import transforms
import imgaug.augmenters as iaa

# 定义训练和验证时的transforms
train_transforms = transforms.Compose([
    transforms.RandomHorizontalFlip(),
    transforms.Resize(target_size=300),
    transforms.RandomPaddingCrop(crop_size=256),
    transforms.RandomBlur(prob=0.1),
    transforms.RandomRotate(rotate_range=15),
    # transforms.RandomDistort(brightness_range=0.5),
    transforms.Normalize()
])
eval_transforms = transforms.Compose([
    transforms.Resize(256),
    transforms.Normalize()
])

2.3 数据准备

  • 训练前的一些准备
  • 将训练集的图像集和标签路径写入datas中

In [ ]

import numpy as np

datas = []
image_base = 'img_train'   # 训练集原图路径
annos_base = 'lab_train'   # 训练集标签路径

# 读取原图文件名
ids_ = [v.split('.')[0] for v in os.listdir(image_base)]

# 将训练集的图像集和标签路径写入datas中
for id_ in ids_:
    img_pt0 = os.path.join(image_base, '{}.jpg'.format(id_))
    img_pt1 = os.path.join(annos_base, '{}.png'.format(id_))
    datas.append((img_pt0.replace('/home/aistudio', ''), img_pt1.replace('/home/aistudio', '')))
    if os.path.exists(img_pt0) and os.path.exists(img_pt1):
        pass
    else:
        raise "path invalid!"

# 打印datas的长度和具体存储例子
print('total:', len(datas))
print(datas[0][0])
print(datas[0][1])
print(datas[10][:])

2.4 划分数据集

  • 以0.05的比率划分训练集和验证集,其中95%作为训练集,5%作为验证集

In [ ]

import numpy as np

# 四类标签,这里用处不大,比赛评测是以0、1、2、3类来对比评测的
labels = ['建筑', '耕地', '林地',  '其他']

# 将labels写入标签文件
with open('labels.txt', 'w') as f:
    for v in labels:
        f.write(v+'\n')

# 随机打乱datas
np.random.seed(5)
np.random.shuffle(datas)

# 验证集与训练集的划分,0.05表示5%为验证集,95%为训练集
split_num = int(0.05*len(datas))

# 划分训练集和验证集
train_data = datas[:-split_num]
valid_data = datas[-split_num:]

# 写入训练集list
with open('train_list.txt', 'w') as f:
    for img, lbl in train_data:
        f.write(img + ' ' + lbl + '\n')

# 写入验证集list
with open('valid_list.txt', 'w') as f:
    for img, lbl in valid_data:
        f.write(img + ' ' + lbl + '\n')

# 打印训练集和测试集大小
print('train:', len(train_data))
print('valid:', len(valid_data))

读取语义分割任务数据集,可使用paddlex.datasets.SegDataset

paddlex.datasets.SegDataset参数说明:

  • data_dir (str): 数据集所在的目录路径。
  • file_list (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对data_dir的相对路径)。
  • label_list (str): 描述数据集包含的类别信息文件路径。
  • transforms (paddlex.seg.transforms): 数据集中每个样本的预处理/增强算子,详见paddlex.seg.transforms。
  • num_workers (int|str):数据集中样本在预处理过程中的线程或进程数。默认为’auto’。当设为’auto’时,根据系统的实际CPU核数设置num_workers: 如果CPU核数的一半大于8,则num_workers为8,否则为CPU核数的一半。
  • buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
  • parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持’thread’线程和’process’进程两种方式。默认为’process’(Windows和Mac下会强制使用thread,该参数无效)。
  • shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。

In [ ]

data_dir = './'

# 定义训练和验证数据集
train_dataset = pdx.datasets.SegDataset(
    data_dir=data_dir,                # 数据集路径
    file_list='train_list.txt',       # 训练集图片文件list路径
    label_list='labels.txt',          # 训练集标签文件list路径
    transforms=train_transforms,      # train_transforms
    shuffle=True)                     # 数据集是否打乱
    
eval_dataset = pdx.datasets.SegDataset(
    data_dir=data_dir,                # 数据集路径
    file_list='valid_list.txt',       # 验证集图片文件list路径
    label_list='labels.txt',          # 验证集标签文件list路径
    transforms=eval_transforms)       # eval_transforms

三、模型训练、评估及预测

利用paddlex.seg.DeepLabv3p构建DeepLabv3p分割器。

paddlex.seg.DeepLabv3p参数说明:

  • num_classes (int): 类别数。
  • backbone (str): DeepLabv3+的backbone网络,实现特征图的计算,取值范围为[’Xception65’, ‘Xception41’, ‘MobileNetV2_x0.25’, ‘MobileNetV2_x0.5’, ‘MobileNetV2_x1.0’, ‘MobileNetV2_x1.5’, ‘MobileNetV2_x2.0’, ‘MobileNetV3_large_x1_0_ssld’],默认值为’MobileNetV2_x1.0’。
  • output_stride (int): backbone 输出特征图相对于输入的下采样倍数,一般取值为8或16。默认16。
  • aspp_with_sep_conv (bool): aspp模块是否采用separable convolutions。默认True。
  • decoder_use_sep_conv (bool): decoder模块是否采用separable convolutions。默认True。
  • encoder_with_aspp (bool): 是否在encoder阶段采用aspp模块。默认True。
  • enable_decoder (bool): 是否使用decoder模块。默认True。
  • use_bce_loss (bool): 是否使用bce loss作为网络的损失函数,只能用于两类分割。可与dice loss同时使用。默认False。
  • use_dice_loss (bool): 是否使用dice loss作为网络的损失函数,只能用于两类分割,可与bce loss同时使用,当use_bce_loss和use_dice_loss都为False时,使用交叉熵损失函数。默认False。
  • class_weight (list/str): 交叉熵损失函数各类损失的权重。当class_weight为list的时候,长度应为num_classes。当class_weight为str时, weight.lower()应为’dynamic’,这时会根据每一轮各类像素的比重自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1,即平时使用的交叉熵损失函数。
  • ignore_index (int): label上忽略的值,label为ignore_index的像素不参与损失函数的计算。默认255。
  • pooling_crop_size (int):当backbone为MobileNetV3_large_x1_0_ssld时,需设置为训练过程中模型输入大小,格式为[W, H]。例如模型输入大小为[512, 512], 则pooling_crop_size应该设置为[512, 512]。在encoder模块中获取图像平均值时被用到,若为None,则直接求平均值;若为模型输入大小,则使用avg_pool算子得到平均值。默认值None。
  • input_channel (int): 输入图像通道数。默认值3。

DeepLabv3p模型的训练接口,函数内置了polynomial学习率衰减策略和momentum优化器。

train参数说明:

  • num_epochs (int): 训练迭代轮数。
  • train_dataset (paddlex.datasets): 训练数据读取器。
  • train_batch_size (int): 训练数据batch大小。同时作为验证数据batch大小。默认2。
  • eval_dataset (paddlex.datasets): 评估数据读取器。
  • save_interval_epochs (int): 模型保存间隔(单位:迭代轮数)。默认为1。
  • log_interval_steps (int): 训练日志输出间隔(单位:迭代次数)。默认为2。
  • save_dir (str): 模型保存路径。默认’output’
  • pretrain_weights (str): 若指定为路径时,则加载路径下预训练模型;若为字符串’IMAGENET’,则自动下载在ImageNet图片数据上预训练的模型权重;若为字符串’COCO’,则自动下载在COCO数据集上预训练的模型权重(注意:暂未提供Xception41、MobileNetV2_x0.25、MobileNetV2_x0.5、MobileNetV2_x1.5、MobileNetV2_x2.0的COCO预训练模型);若为字符串’CITYSCAPES’,则自动下载在CITYSCAPES数据集上预训练的模型权重(注意:暂未提供Xception41、MobileNetV2_x0.25、MobileNetV2_x0.5、MobileNetV2_x1.5、MobileNetV2_x2.0的CITYSCAPES预训练模型);若为None,则不使用预训练模型。默认’IMAGENET’。
  • optimizer (paddle.fluid.optimizer): 优化器。当该参数为None时,使用默认的优化器:使用fluid.optimizer.Momentum优化方法,polynomial的学习率衰减策略。
  • learning_rate (float): 默认优化器的初始学习率。默认0.01。
  • lr_decay_power (float): 默认优化器学习率衰减指数。默认0.9。
  • use_vdl (bool): 是否使用VisualDL进行可视化。默认False。
  • sensitivities_file (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串’DEFAULT’,则自动下载在Cityscapes图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
  • eval_metric_loss (float): 可容忍的精度损失。默认为0.05。
  • early_stop (bool): 是否使用提前终止训练策略。默认值为False。
  • early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在early_stop_patience个epoch内连续下降或持平,则终止训练。默认值为5。
  • resume_checkpoint (str): 恢复训练时指定上次训练保存的模型路径。若为None,则不会恢复训练。默认值为None。

3.1 模型训练

  • 本方案训练使用学习率为0.0001,训练轮数为35轮(Epoch1到Epoch35)
  • 根据GPU显存设定训练时批处理图片数为8,太大容易爆显存
  • 模型保存在output/deeplab文件下,设定为每进行一轮保存一次

In [ ]

# 分割类别数
num_classes = len(train_dataset.labels)

# 构建DeepLabv3p分割器
model = pdx.seg.DeepLabv3p(
    num_classes=num_classes,  backbone='Xception65', use_bce_loss=False
)

# 模型训练
model.train(
    num_epochs=35,                 # 训练迭代轮数
    train_dataset=train_dataset,  # 训练集读取
    train_batch_size=8,           # 训练时批处理图片数
    eval_dataset=eval_dataset,    # 验证集读取
    learning_rate=0.0001,         # 学习率
    save_interval_epochs=1,       # 保存模型间隔轮次  
    save_dir='output/deeplab',    # 模型保存路径
    log_interval_steps=200,       # 日志打印间隔
    pretrain_weights='output/deeplab/best_model')  #加载预训练模型

3.2 模型评估

  • eval_dataset (paddlex.datasets): 评估数据读取器。
  • batch_size (int): 评估时的batch大小。默认1。
  • epoch_id (int): 当前评估模型所在的训练轮数。
  • return_details (bool): 是否返回详细信息。默认False。
  • 最终提交结果的checkpoint使用的是/output/deeplab/路径下的best_model
  • 使用以下语句进行模型评估

In [ ]

# 加载模型
model = pdx.load_model('./output/deeplab/best_model')

# 模型评估
model.evaluate(eval_dataset, batch_size=1, epoch_id=None, return_details=False)

3.3 模型预测

DeepLabv3p模型预测接口。需要注意的是,只有在训练过程中定义了eval_dataset,模型在保存时才会将预测时的图像处理流程保存在DeepLabv3p.test_transforms和DeepLabv3p.eval_transforms中。如未在训练时定义eval_dataset,那在调用预测predict接口时,用户需要再重新定义test_transforms传入给predict接口。

  • img_file (str|np.ndarray): 预测图像路径或numpy数组(HWC排列,BGR格式)。
  • transforms (paddlex.seg.transforms): 数据预处理操作。
  • 将预测结果保存在/home/aistudio/路径下的result文件中

In [ ]

from tqdm import tqdm
import cv2

test_base = 'img_testA/'    # 测试集路径
out_base = 'result/'        # 预测结果保存路径

# 是否存在结果保存路径,如不存在,则创建该路径
if not os.path.exists(out_base):
    os.makedirs(out_base)

# 模型预测并保存预测图片
for im in tqdm(os.listdir(test_base)):
    if not im.endswith('.jpg'):
        continue
    pt = test_base + im
    result = model.predict(pt)
    cv2.imwrite(out_base+im.replace('jpg', 'png'), result['label_map'])

In [ ]

# 由预测结果生成提交文件
!zip -r result.zip result/

四、总结与展望

  • 可以尝试按照其他比率划分数据集
  • 尝试丰富数据增强操作来改进
  • 尝试从增加网络深度的角度来改进模型
  • 考虑结合一些自适应算法来调节参数

五、给其他选手的建议

  • 有一定经验的小伙伴可以从竞赛入手锻炼自己的能力,在学习中可以多查阅Paddle官方API文档或者教程,有助于快速解决问题。

  • 另外,可以多学习他人分享的项目,从中学习一些思路和调参经验。

  • 对于没有经验的小伙伴,可以报名飞桨训练营和相关课程,可以很好的打下基础。

  • 总之,要多学和多练相结合可以提升自我。

参考资料

常规赛:遥感影像地块分割baseline

使用 VisualDL 助力遥感影像地块分割(PaddleX 篇)

你可能感兴趣的:(深度学习,机器学习,计算机视觉)