使用 PaddleRS 进行变化检测全流程开发

本项目旨在作为案例,演示如何使用飞桨遥感全流程工具PaddleRS进行变化检测全流程开发。

1 遥感影像变化检测任务介绍

我们生活在一个变化的世界。无论是地表生态系统还是人类社会活动,都是不断演进和动态发展的过程。实时,准确地获取地表的变化信息,对于研究自然环境、人类活动以及二者之间的相互作用具有十分重要的意义。得益于卫星传感器技术的发展,人类可以通过遥感手段对地表实施大范围、长时间的监测。而遥感影像的变化检测,正是通过对同一地区不同时间的重复观测来分析区域内地物状态变化的技术

“The only thing that never changes is that everything changes.” - Louis L’Amour

如下图所示,变化检测任务的输入一般是多时相遥感影像。对于二值变化检测任务(即,只关注『变化』与『不变』两个类别)而言,输出是二值变化图,其中标示了变化发生的位置。例如图中用白色标记发生变化的像素,黑色标记未发生变化的像素。

变化检测任务的基本流程如下图所示:

在该流程中,最核心的环节是从预处理好的影像得到差值图或变化概率图的『变化检测』部分,而基于深度学习的变化检测算法也主要应用在这一环节。

变化检测在实际生产生活中已经得到了十分广泛的应用,例如用于生态系统监测、城市发展规划、农业用地研究、受灾情况评估等等。本项目主要涉及的建筑物变化检测是变化检测的分支之一,指的是仅关注建筑物类别的变化检测。

2 环境配置

首先从GitHub下载PaddleRS并切换到指定commit:

!git clone https://github.com/PaddlePaddle/PaddleRS.git
!cd PaddleRS && git checkout c5b8244a7b3c6d7b5e00464fb2fc4258bb21dfbb

安装相关依赖。之后,安装PaddleRS。

!pip install -r PaddleRS/requirements.txt
!pip install -e PaddleRS/

因为sys.path可能没有及时更新,手动将PaddleRS的安装目录添加到sys.path

import sys
sys.path.append("/home/aistudio/PaddleRS")

3 数据准备

本项目以建筑物变化检测数据集LEVIR-CD为例进行演示。

LEVIR-CD数据集共包含637对分辨率为0.5米的遥感影像对以及对应的建筑物变化标签。数据集官方提供了标准的训练集/验证集/测试集划分。需要指出的是,LEVIR-CD数据集中的样本已经经过影像配准、辐射校正等预处理,因此在本项目中无需重复执行这些操作。

首先解压数据集:

!unzip -oq data/data136610/LEVIR-CD.zip -d data/data136610/

下图展示了LEVIR-CD数据集中的一个样本,从左往右依次为第一时相影像、第二时相影像以及二值变化标签。

由于影像的原始尺寸较大(1024x1024),使用PaddleRS提供的数据集预处理脚本将原始影像切分为256x256大小的影像块以用于模型训练。影像块之间无重叠部分。

!python PaddleRS/tools/prepare_dataset/prepare_levircd.py --in_dataset_dir data/data136610/LEVIR-CD --out_dataset_dir data/data136610/levircd --crop_size 256 --crop_stride 256

经过预处理后,训练集、验证集和测试集分别包含7120、1024和2048对影像块以及对应的变化标签。

最后,生成PaddleRS训练模型所需的file lists。

4 模型训练

4.1 BIT模型简介

Bitemporal image transformer(BIT)是最早使用Transformer架构用于变化检测的工作之一。BIT模型的整体结构如下图所示:

BIT的核心思想在于使用双时相Transformer优化CNN提取的特征,并使CNN和Transformer能够针对变化检测任务进行协同训练,从而在保持轻量模型结构的同时得到准确的变化检测结果。

Hao Chen, Zipeng Qi, and Zhenwei Shi. Remote Sensing Image Change Detection with Transformers. IEEE Transactions on Geoscience and Remote Sensing.

4.2 构建模型

# 导入PaddleRS库
import paddlers as pdrs

# 调用PaddleRS API一键构建模型
model = pdrs.tasks.cd.BIT(
    # 模型输出类别数
    num_classes=2,
    # 是否使用混合损失函数,默认使用交叉熵损失函数训练
    use_mixed_loss=False,
    # 模型输入通道数
    in_channels=3,
    # 模型使用的骨干网络,支持'resnet18'或'resnet34'
    backbone='resnet18',
    # 骨干网络中的resnet stage数量
    n_stages=4,
    # 是否使用tokenizer获取语义token
    use_tokenizer=True,
    # token的长度
    token_len=4,
    # 若不使用tokenizer,则使用池化方式获取token。此参数设置池化模式,有'max'和'avg'两种选项,分别对应最大池化与平均池化
    pool_mode='max',
    # 池化操作输出特征图的宽和高(池化方式得到的token的长度为pool_size的平方)
    pool_size=2,
    # 是否在Transformer编码器中加入位置编码(positional embedding)
    enc_with_pos=True,
    # Transformer编码器使用的注意力模块(attention block)个数
    enc_depth=1,
    # Transformer编码器中每个注意力头的嵌入维度(embedding dimension)
    enc_head_dim=64,
    # Transformer解码器使用的注意力模块个数
    dec_depth=8,
    # Transformer解码器中每个注意力头的嵌入维度
    dec_head_dim=8
)
# 查看组网信息
# 在PaddleRS中,可通过ChangeDetector对象的net属性获取paddle.nn.Layer类型组网
model.net

4.3 构建数据集

# 构建需要使用的数据变换(数据增强、预处理)

import paddlers.transforms as T

# 使用Compose组合多种变换方式。Compose中包含的变换将按顺序串行执行
train_transforms = T.Compose([
    # 读取数据
    T.DecodeImg(),
    # 随机裁剪
    T.RandomCrop(
        # 裁剪区域将被缩放到256x256
        crop_size=256,
        # 将裁剪区域的横纵比固定为1
        aspect_ratio=[1.0, 1.0],
        # 裁剪区域相对原始影像长宽比例在一定范围内变动,最小不低于原始长宽的0.8
        scaling=[0.8, 1.0]
    ),
    # 以50%的概率实施随机水平翻转
    T.RandomHorizontalFlip(prob=0.5),
    # 以50%的概率实施随机垂直翻转
    T.RandomVerticalFlip(prob=0.5),
    # 数据归一化到[-1,1]
    T.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    ),
    # 挑选训练过程中需要用到的数据,并按照指定顺序排列
    T.ArrangeChangeDetector('train')
])
eval_transforms = T.Compose([
    T.DecodeImg(),
    # 在验证阶段,输入原始尺寸影像,对输入影像仅进行归一化处理
    # 验证阶段与训练阶段的数据归一化方式必须相同
    T.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    ),
    # 挑选验证过程中需要用到的数据,并按照指定顺序排列
    T.ArrangeChangeDetector('eval')
])

# 实例化数据集

import os.path as osp

# 处理后数据集所在目录
DATA_DIR = "/home/aistudio/data/data136610/levircd/"

train_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=osp.join(DATA_DIR, 'train.txt'),
    transforms=train_transforms,
    label_list=None,
    # 使用4个辅助进程加载数据
    num_workers=4,
    shuffle=True,
    # 将取值为{0,255}的标签自动归一化到{0,1}
    binarize_labels=True
)
val_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=osp.join(DATA_DIR, 'val.txt'),
    transforms=eval_transforms,
    label_list=None,
    num_workers=0,
    shuffle=False,
    binarize_labels=True
)

4.4 构建优化器

import paddle

# 制定定步长学习率衰减策略
lr_scheduler = paddle.optimizer.lr.StepDecay(
    0.001,
    step_size=5000,
    # 学习率衰减系数,这里指定每次减半
    gamma=0.5
)

# 构造AdamW优化器
optimizer = paddle.optimizer.AdamW(
    learning_rate=lr_scheduler,
    parameters=model.net.parameters()
)

4.5 执行训练

模型、数据集、优化器等组件均构建完毕后,通过调用PaddleRS API实现模型的一键训练。

# 执行模型训练
model.train(
    num_epochs=20,
    train_dataset=train_dataset,
    train_batch_size=8,
    eval_dataset=val_dataset,
    optimizer=optimizer,
    save_interval_epochs=10,
    # 每多少次迭代记录一次日志
    log_interval_steps=200,
    save_dir='exp/',
    # 是否使用early stopping策略,当精度不再改善时提前终止训练
    early_stop=False,
    # 是否启用VisualDL日志功能
    use_vdl=True,
    # 指定从某个检查点继续训练
    resume_checkpoint=None
)

4.6 执行验证

训练完毕后,可以在测试集上验证模型的效果。

对于二值变化检测任务,常用的评价指标为F1分数和交并比(intersection over union, IoU)等。F1分数与IoU指标的计算公式分别如下:

I o U = T P F N + F P + T P IoU=\frac{TP}{FN+FP+TP} IoU=FN+FP+TPTP
F 1 = 2 ⋅ T P 2 ⋅ T P + F N + F P F1=\frac{2 \cdot TP}{2 \cdot TP + FN + FP} F1=2TP+FN+FP2TP

式中, T P TP TP表示预测为变化且实际为变化的样本数, T N TN TN表示预测为不变且实际为不变的样本数, F P FP FP表示预测为变化但实际为不变的样本数, F N FN FN表示预测为不变但实际为变化的样本数。

from paddlers.tasks import load_model

# 构建测试集
test_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=osp.join(DATA_DIR, 'test.txt'),
    transforms=eval_transforms,
    label_list=None,
    num_workers=0,
    shuffle=False,
    binarize_labels=True
)

# 加载验证集上最佳模型
model = load_model("exp/best_model")

# 验证模型在测试集上效果
res = model.evaluate(test_dataset)

# 打印评价指标
print(f"\nIoU={res['iou']*100:.2f}%, F1={res['f1']*100:.2f}%")
2022-11-04 17:26:50 [INFO]	2048 samples in file /home/aistudio/data/data136610/levircd/test.txt
2022-11-04 17:26:51 [INFO]	Model[BIT] loaded.
2022-11-04 17:26:51 [INFO]	Start to evaluate(total_samples=2048, total_steps=2048)...

5 模型导出与部署

在实际应用中,由于训练阶段使用模型的动态图版本,而将动态图模型直接用于部署往往效率不高,因此常需要将模型导出为专用的部署格式,在部署阶段使用静态图版本的模型以达到更高的推理效率。

5.1 导出部署格式模型

使用PaddleRS提供的export_model.py脚本进行模型导出。通过--fixed_input_shape选项指定输入张量形状。

!python PaddleRS/deploy/export/export_model.py --model_dir=exp/best_model --save_dir=exp/best_model/static --fixed_input_shape [-1,3,256,256]

5.2 对模型进行Python部署

得到导出的静态图模型后,可以利用PaddleRS提供的API,基于Paddle Inference库将模型部署到X86 CPU、Nvidia GPU(含Jetson系列)、飞腾/鲲鹏、申威、兆芯、龙芯、AMD GPU,海光DCU,昆仑XPU,昇腾910NPU,Graphcore IPU 等不同硬件上。本小节主要演示使用PaddleRS Python API进行部署的步骤。关于使用Paddle Infernce C++ API进行部署的细节,请参考相关文档

# 构建预测器,指定使用GPU进行推理
predictor = pdrs.deploy.Predictor('exp/best_model/static', use_gpu=True)
# 获取推理结果

T1_PATH = "data/data136610/levircd/LEVIR-CD/test/A/test_1/test_1_3.png"
T2_PATH = "data/data136610/levircd/LEVIR-CD/test/B/test_1/test_1_3.png"
GT_PATH = "data/data136610/levircd/LEVIR-CD/test/label/test_1/test_1_3.png"


test_transforms = T.Compose([
    T.DecodeImg(),
    T.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    ),
    T.ArrangeChangeDetector('test')
])

pred = predictor.predict(
    # 以二元组形式输入两个时相的影像
    (T1_PATH, T2_PATH), 
    transforms=test_transforms
)
# 可视化推理结果

%matplotlib inline

import numpy as np
from PIL import Image
from matplotlib import pyplot as plt


# 从左到右依次显示:第一时相影像、第二时相影像、模型推理结果以及真值标签
plt.figure(figsize=(9, 3), constrained_layout=True)
plt.subplot(141)
plt.imshow(Image.open(T1_PATH))
plt.gca().set_axis_off()
plt.title("Image1")
plt.subplot(142)
plt.imshow(Image.open(T2_PATH))
plt.gca().set_axis_off()
plt.title("Image2")
plt.subplot(143)
plt.imshow((pred['label_map']*255).astype('uint8'))
plt.gca().set_axis_off()
plt.title("Pred")
plt.subplot(144)
plt.imshow((np.asarray(Image.open(GT_PATH))*255).astype('uint8'))
plt.gca().set_axis_off()
plt.title("GT")

plt.show()

在这里插入图片描述
此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐