基于PaddleSeg的遥感建筑变化的解译平台
[BaiduAIStudio项目地址]欢迎大家fork(https://aistudio.baidu.com/aistudio/projectdetail/3665856?contributionType=1)
一、赛题背景
掌握国土资源利用和土地覆盖类型,是地理国情普查与监测的重要内容。高效获取准确、客观的土地利用情况,监测国土变化情况,可以为国家和地方提供地理国情信息决策支撑。随着遥感、传感器技术的发展,特别是多时相高分辨率遥感图像数据的普及,使我们可以足不出户,就能掌握全球任一地表的细微变化
目前,我国遥感领域已步入了高分辨率影像的快车道,对遥感数据的分析应用服务的需求也与日俱增。传统方式对高分辨率卫星遥感图像的对特征刻画能力差且依赖人工经验工作量巨大。随着人工智能技术的兴起,特别是基于深度学习的图像识别方法获得了极大的发展,相关技术也推动了遥感领域的变革。相对于传统基于人海战术的目视解译方法,基于深度学习的遥感图像识别技术可以自动分析图像中的地物类型,在准确率和效率方面展现出极大的潜力。
二、赛题任务
在这套WEB系统中,选手需要实现目标提取、变化检测、目标检测和地物分类四大分析功能,官方将提供每个功能实现所需的训练数据集。
- 目标提取(使用图像分割技术对卫星图像中指定对象完成分割)
- 变化检测(使用图像分割技术对同区域两个时期的卫星图像变化情况完成分析)
- 目标检测(使用目标检测技术对卫星图像中指定对象完成检测)
- 地物分类(使用图像分割技术对卫星图像每个像素完成分类)
三、数据集介绍
本比赛提供所实现遥感图像建筑变化检测数据集,一共包含1000个样本,数据主要来源于北航LEVIR 团队的公开论文 。
其中,包括有标注训练样本637对,无标注的测试样本363对(提供测试图像,但不提供对应标签)。每个样本包括前时向遥感图像,后时相遥感图像以及对应的建筑变化标签图。
示例数据
本数据集中的遥感图像大小为1024×1024,像元分辨率为0.5米。标签图与图像等大,像素值为0或255,其中0代表没有建筑变化,255代表存在建筑变化。提供的训练数据集样例如下图所示,每个样本包括前时向图(Image 1),后时相图(Image 2)和标签图(Ground Truth)。
四、提交实例
基于官方提供的无标注的363对测试样本,输出图像为单通道png格式的灰度图(8bit),并放在以submisson命名的文件夹中,压缩为submisson.zip后提交。提交的输出图像尺寸与输入图相同。
五、评分标准
本比赛采用建筑变化类别的F1得分作为评价指标:
F1=TP/(TP+1/2(FP+FN))
假设正例为变化的建筑像素,负例为非变化像素,记模型检测正确的正例数量为TP,模型检测错误的正例数量为FP,模型漏检的正例数量为FN。TP、FP、FN等通过比对所有N个测试数据上模型的预测结果M_i,i∈{1,…,N}以及对应的真值M_i^*得到:
TP=∑_i▒‖M_i∙M_i^* ‖_0 FP=∑_i▒‖M_i∙〖(1-M〗_i^)‖_0 FN=∑_i▒‖(1-M_i)∙M_i^ ‖_0
六、数据预处理
1.数据集的解压、生成数据列表以及构建数据集
2.安装PaddleSeg
1.安装依赖
安装paddleseg
! pip -q install paddleseg
2.解压数据集
数据集解压
! mkdir -p work/datasets
! mkdir -p work/datasets/train
! mkdir -p work/datasets/test
! mkdir -p datasets/val
! unzip -q /home/aistudio/data/data134156/train.zip -d work/datasets/train
! unzip -q /home/aistudio/data/data134156/test.zip -d work/datasets/test
! unzip -q /home/aistudio/data/data134156/val.zip -d work/datasets/val
生成数据列表 在LEVIR-CD中已经分为了train、test和val三个部分,每个部分有A、B和label三个文件夹分别保存时段一、时段二的图像和对应的建筑变化标签(三个文件夹中对应文件的文件名都相同,这样就可以使用replace只从A中就获取到B和label中的路径),list的每一行由空格隔开
3.数据格式转换
!python work/dataset.py
train_data_list generated
test_data_list generated
val_data_list generated
构建数据集 这里主要是自定义数据Dataset,这里是选择继承paadle.io中的Dataset,而没有使用PaddleSeg中的Dataset,有以下几个问题需要注意:
在初始化中transforms、num_classes和ignore_index需要,避免后面PaddleSeg在Eval时报错
这里使用transforms,因为通道数不为3通道,直接使用PaddleSeg的不行Compose会报错,需要将to_rgb设置为False
如果使用Normalize,需要将参数乘6,例如mean=[0.5]*6
label二值图像中的值为0和255,需要变为0和1,否则啥也学不到,而且计算出来的Kappa系数还为负数
注意图像的组织方式——将两时段的图像在通道层concat起来(22-24行代码)
4.构建数据集
!python work/made_dataset.py
七、模型训练
1 搭建模型及训练
模型:UNet++
损失函数:BCELoss
优化器:Adam
学习率变化:CosineAnnealingDecay
这里需要注意的就是模型的输入,需要6个通道,如果使用PaddleSeg中UNet等需要进行相应的修改,这里UNet++可以直接使用in_channels,因此这里直接使用的UNet++。关于UNet++,相比于原始的Unet网络,为了避免UNet中的纯跳跃连接在语义上的不相似特征的融合,UNet++通过引入嵌套的和密集的跳跃连接进一步加强了这些连接,目的是减少编码器和解码器之间的语义差距
!python work/train.py
八、模型预测
从test_data中测试一组数据,查看训练10论时的best_model有多强的检测能力,这里需要注意的就是数据拆分为[0:3]和[3:6]分别表示两个时段的图像,类型需要转换为int64才能正常显示.
加载模型只用加载模型参数,即.pdparams。
import os
import cv2
import numpy as np
import paddle
from paddle.io import Dataset
from paddleseg.transforms import Compose, Resize
from paddleseg.models import UNetPlusPlus
import matplotlib.pyplot as plt
batch_size = 2 # 批大小
class ChangeDataset(Dataset):
# 这里的transforms、num_classes和ignore_index需要,避免PaddleSeg在Eval时报错
def __init__(self, dataset_path, mode, transforms=[], num_classes=2, ignore_index=255):
list_path = os.path.join(dataset_path, (mode + '_list.txt'))
self.data_list = self.__get_list(list_path)
self.data_num = len(self.data_list)
self.transforms = Compose(transforms, to_rgb=False) # 一定要设置to_rgb为False,否则这里有6个通道会报错
self.is_aug = False if len(transforms) == 0 else True
self.num_classes = num_classes # 分类数
self.ignore_index = ignore_index # 忽视的像素值
def __getitem__(self, index):
A_path, B_path, lab_path = self.data_list[index]
A_img = cv2.cvtColor(cv2.imread(A_path), cv2.COLOR_BGR2RGB)
B_img = cv2.cvtColor(cv2.imread(B_path), cv2.COLOR_BGR2RGB)
image = np.concatenate((A_img, B_img), axis=-1) # 将两个时段的数据concat在通道层
label = cv2.imread(lab_path, cv2.IMREAD_GRAYSCALE)
if self.is_aug:
image, label = self.transforms(im=image, label=label)
image = paddle.to_tensor(image).astype('float32')
else:
image = paddle.to_tensor(image.transpose(2, 0, 1)).astype('float32')
label = label.clip(max=1) # 这里把0-255变为0-1,否则啥也学不到,计算出来的Kappa系数还为负数
label = paddle.to_tensor(label[np.newaxis, :]).astype('int64')
return image, label
def __len__(self):
return self.data_num
# 这个用于把list.txt读取并转为list
def __get_list(self, list_path):
data_list = []
with open(list_path, 'r') as f:
data = f.readlines()
for d in data:
data_list.append(d.replace('\n', '').split(' '))
return data_list
dataset_path = 'work/datasets'
完成三个数据的创建
transforms = [Resize([1024, 1024])]
train_data = ChangeDataset(dataset_path, 'train', transforms)
test_data = ChangeDataset(dataset_path, 'test', transforms)
val_data = ChangeDataset(dataset_path, 'val', transforms)
model_path = 'output/best_model/model.pdparams' # 加载得到的最好参数
model = UNetPlusPlus(in_channels=6, num_classes=2, use_deconv=True)
para_state_dict = paddle.load(model_path)
model.set_dict(para_state_dict)
for idx, (img, lab) in enumerate(test_data): # 从test_data来读取数据
if idx == 2: # 查看第三个
m_img = img.reshape((1, 6, 1024, 1024))
m_pre = model(m_img)
s_img = img.reshape((6, 1024, 1024)).numpy().transpose(1, 2, 0)
# 拆分6通道为两个3通道的不同时段图像
s_A_img = s_img[:,:,0:3]
s_B_img = s_img[:,:,3:6]
lab_img = lab.reshape((1024, 1024)).numpy()
pre_img = paddle.argmax(m_pre[0], axis=1).reshape((1024, 1024)).numpy()
plt.figure(figsize=(10, 10))
plt.subplot(2,2,1);plt.imshow(s_A_img.astype('int64'));plt.title('Time 1')
plt.subplot(2,2,2);plt.imshow(s_B_img.astype('int64'));plt.title('Time 2')
plt.subplot(2,2,3);plt.imshow(lab_img);plt.title('Label')
plt.subplot(2,2,4);plt.imshow(pre_img);plt.title('Change Detection')
plt.show()
break # 只看一个结果就够了
九、项目总结
代码使用PaddleSeg完成基础的训练和提交,包括模型训练和预测步骤。
可以看到训练10轮半小时的结果还是不错的,大概的建筑变化都检测出来了,再调调参数多跑几轮肯定有更好的效果。
除了通道concat的方法,由于这个变化检测也算有时间序列的特点,可能采用LSTM类也能取得好的效果;或者使用传统的方法(如做差、CVA等)处理两张图得到一张图,再送入网络进行学习【使用CVA详情参考项目】,当然哪样好就不知道了。
如果想要获得更好的精度,可以考虑从以下几个方面考虑:
需要训练更长时间,可能需要训练500个Epoch;
需要更加强大的模型。