资讯详情

第十一届 “中国软件杯”百度遥感赛项:遥感变化检测预选赛0.87方案

1 第十一届 百度遥感赛中国软件杯

1.1 比赛介绍


中国软件杯大学生软件设计大赛是面向中国学生的公益性大赛,是2021年全国大学生大赛榜单内的大赛。竞赛由工业和信息化部、教育部、江苏省人民政府联合主办,致力于正确引导中国学生积极参与软件研究活动,有效提高自我创新能力和实践能力,为中国软件和信息技术服务业培养更高端、优秀的人才。2022年,百度飞桨承办了A组和B组两个赛道,本赛题为A组。

官网链接

1.2 赛题背景

掌握土地资源利用和土地覆盖的类型是地理国情普查和监测的重要组成部分。有效获取准确、客观的土地利用,监测土地变化,可以为国家和地方政府提供地理国情信息决策支持。随着遥感和传感器技术的发展,特别是遥感图像数据的普及,我们可以掌握世界上任何表面的细微变化,而开家。

目前,我国遥感领域已进入高分辨率图像快车道,对遥感数据分析应用服务的需求也日益增加。高分辨率卫星遥感图像的传统方法描绘能力差,依赖人工经验工作量大。随着人工智能技术的兴起,特别是基于深度学习的图像识别方法,相关技术也促进了遥感领域的变化。与传统的基于人海战术的视觉解译方法相比,基于深度学习的遥感图像识别技术可以自动分析图像中的地物类型,在准确性和效率方面具有巨大的潜力。

本次赛题由百度飞桨和北航组成LEVIR团队 共同设置要求玩家使用百度AI Studio基于国产化人工智能框架的百度飞桨平台进行培训PaddlePaddle框架进行开发,设计并开发一个可以通过深度学习技术实现对遥感图像自动解译的WEB系统。

1.3 任务说明

变更检测部分要求参与者使用提供的培训数据来实现多时相图像中的建筑变更检测。具体来说,多时相遥感图像建筑变更检测任务是给定两个不同时间拍摄的相同位置(地理准确)的遥感图像,需要定位建筑变更区域。

参考链接:遥感图像变化检测是什么?

1.4 数据集介绍

参见数据集链接和赛题描述。

2 比赛方案

  • 方案主要分为数据处理和模型选择
    • 数据处理:切割扩展数据集和快速傅里叶转换
    • 模型选择:主要采用DSIFN模型,后期融合DSAMNet模型涨点
    • 数据增强:在Baseline增加了基础RansomBlur、RandomDistort和RandomSwap

2.1 数据预处理

# 安装第三方库 !pip install scikit-image > /dev/null !pip install matplotlib==3.4 > /dev/null  # 安装PaddleRS(AI Studio上缓存版) !unzip -o -d /home/aistudio/ /home/aistudio/data/data135375/PaddleRS-develop.zip > /dev/null !mv /home/aistudio/PaddleRS-develop /home/aistudio/PaddleRS !pip install -e /home/aistudio/PaddleRS > /dev/null # 因为`sys.path`可能没有及时更新,这里选择手动更新 import sys sys.path.append('/home/aistudio/PaddleRS') 
  • 该操作涉及大量文件IO,可能需要一些时间
!unzip -o -d /home/aistudio/data/data134796/dataset /home/aistudio/data/data134796/train_data.zip > /dev/null !unzip -o -d /home/aistudio/data/data134796/dataset /home/aistudio/data/data134796/test_data.zip > /dev/null
# 导入一些需要用到的库
import cv2
from tqdm import tqdm
import random
import os.path as osp
from glob import glob
from PIL import Image
import os
import numpy as np

import os.path as osp
from copy import deepcopy
from functools import partial

import paddle
# import paddlers as pdrs
# from paddlers import transforms as T
# from skimage.io import imread, imsave
from matplotlib import pyplot as plt

2.1.1 快速fft变换

  • 由于两时相遥感数据受传感器、天气等原因导致的光谱差异可能会对检测产生影响,所以采用快速fft变换统一A时相影像与B时相影像光谱特征
# 对影像A进行快速fft变换,并保存至B_fft文件夹中,大约需要10分钟

def style_transfer(source_image, target_image):
    # 快速fft变换
    h, w, c = source_image.shape
    out = []
    for i in range(c):
        source_image_f = np.fft.fft2(source_image[:,:,i])
        source_image_fshift = np.fft.fftshift(source_image_f)
        target_image_f = np.fft.fft2(target_image[:,:,i])
        target_image_fshift = np.fft.fftshift(target_image_f)
        
        change_length = 1
        source_image_fshift[int(h/2)-change_length:int(h/2)+change_length, 
                            int(h/2)-change_length:int(h/2)+change_length] = \
            target_image_fshift[int(h/2)-change_length:int(h/2)+change_length,
                                int(h/2)-change_length:int(h/2)+change_length]
            
        source_image_ifshift = np.fft.ifftshift(source_image_fshift)
        source_image_if = np.fft.ifft2(source_image_ifshift)
        source_image_if = np.abs(source_image_if)
        
        source_image_if[source_image_if>255] = np.max(source_image[:,:,i])
        out.append(source_image_if)
    out = np.array(out)
    out = out.swapaxes(1,0).swapaxes(1,2)
    
    out = out.astype(np.uint8)
    return out

def fft_save(data_path):
    img_path_A = os.path.join(data_path,'A')
    img_path_B = os.path.join(data_path,'B')
    img_B_save = os.path.join(data_path,'A_fft')
    names = os.listdir(img_path_A)
    if not os.path.exists(img_B_save):
        os.mkdir(img_B_save)
    for name in tqdm(names):
        img_A = cv2.imread(os.path.join(img_path_A,name))
        img_B = cv2.imread(os.path.join(img_path_B,name))
        img_B_fft = style_transfer(source_image=img_A,target_image=img_B)
        cv2.imwrite(os.path.join(img_B_save,name),img_B_fft)

data_path = '/home/aistudio/data/data134796/dataset/train'
test_path = '/home/aistudio/data/data134796/dataset/test'

fft_save(data_path)
fft_save(test_path)

2.1.3 数据裁剪

  • 原始数据大小为1024*1024,为充分利用数据集,将每张影像裁剪成16张256影像进行模型训练。
# 把image_A 、 image_B 、 label 裁剪成256*256并保存 差不多需要8分钟

CROP_SIZE = 256
train_dir = '/home/aistudio/data/data134796/dataset/train'
def crop_images(data_dir):
    img_path_A = os.path.join(data_dir,'A_fft')
    img_path_B = os.path.join(data_dir,'B')
    label = os.path.join(data_dir,'label')
    A_crop_path = os.path.join(data_dir,'A_fft_crop')
    B_crop_path = os.path.join(data_dir,'B_crop')
    label_crop_path = os.path.join(data_dir,'label_crop')
    if not os.path.exists(A_crop_path):
        os.mkdir(A_crop_path)
    if not os.path.exists(B_crop_path):
        os.mkdir(B_crop_path)
    if not os.path.exists(label_crop_path):
        os.mkdir(label_crop_path)

    names = os.listdir(label)
    for name in tqdm(names):
        label_ = np.array(Image.open(os.path.join(label,name)))
        img_A_ = np.array(Image.open(os.path.join(img_path_A,name)))
        img_B_ = np.array(Image.open(os.path.join(img_path_B,name)))

        h,w = label_.shape[0],label_.shape[1]
        count = 0
        for i in range(h//CROP_SIZE):
            for j in range(w//CROP_SIZE):
                label_crop = label_[i*CROP_SIZE:(i+1)*CROP_SIZE,j*CROP_SIZE:(j+1)*CROP_SIZE]
                img_A_crop = img_A_[i*CROP_SIZE:(i+1)*CROP_SIZE,j*CROP_SIZE:(j+1)*CROP_SIZE,:]
                img_B_crop = img_B_[i*CROP_SIZE:(i+1)*CROP_SIZE,j*CROP_SIZE:(j+1)*CROP_SIZE,:]
                Image.fromarray(img_A_crop).save(os.path.join(A_crop_path,name.split('.')[0]+'_'+str(count)+'.png'))
                Image.fromarray(img_B_crop).save(os.path.join(B_crop_path,name.split('.')[0]+'_'+str(count)+'.png'))
                Image.fromarray(label_crop).save(os.path.join(label_crop_path,name.split('.')[0]+'_'+str(count)+'.png'))
                count += 1


crop_images(train_dir)
100%|██████████| 637/637 [08:58<00:00,  1.18it/s]

2.2.3 划分数据集

  • 按照8:2划分训练集/验证集,并生成文件名列表
# 划分训练集/验证集,并生成文件名列表

# 随机数生成器种子
RNG_SEED = 114514
# 调节此参数控制训练集数据的占比
TRAIN_RATIO = 0.95
# 数据集路径
DATA_DIR = '/home/aistudio/data/data134796/dataset/'


def write_rel_paths(phase, names, out_dir, prefix=''):
    """将文件相对路径存储在txt格式文件中"""
    with open(osp.join(out_dir, phase+'.txt'), 'w') as f:
        for name in names:
            if prefix == 'test':
                f.write(
                    ' '.join([
                        osp.join(prefix, 'A_fft', name),
                        osp.join(prefix, 'B', name),
                        osp.join(prefix, 'label', name)
                    ])
                )
            else:
                f.write(
                    ' '.join([
                        osp.join(prefix, 'A_fft_crop', name),
                        osp.join(prefix, 'B_crop', name),
                        osp.join(prefix, 'label_crop', name)
                    ])
                )
            
            f.write('\n')


random.seed(RNG_SEED)

# 随机划分训练集/验证集
names = list(map(osp.basename, glob(osp.join(DATA_DIR, 'train', 'label_crop', '*.png'))))
# 对文件名进行排序,以确保多次运行结果一致
names.sort()
random.shuffle(names)
len_train = int(len(names)*TRAIN_RATIO) # 向下取整
write_rel_paths('train', names[:len_train], DATA_DIR, prefix='train')
write_rel_paths('val', names[len_train:], DATA_DIR, prefix='train')

# 处理测试集
test_names = map(osp.basename, glob(osp.join(DATA_DIR, 'test', 'A_fft', '*.png')))
test_names = sorted(test_names)
write_rel_paths(
    'test', 
    test_names, 
    DATA_DIR,
    prefix='test'
)

print("数据集划分已完成。")

数据集划分已完成。

2.2 模型选择


  • 本方案使用DSIFN模型进行打榜,后期将DSAMNet模型与之融合

2.2.1 超参数设置

# 可在此处调整实验所用超参数

# 随机种子
SEED = 1919810

# 数据集路径
DATA_DIR = '/home/aistudio/data/data134796/dataset/'
# 实验路径。实验目录下保存输出的模型权重和结果
EXP_DIR = '/home/aistudio/exp/'
# 保存最佳模型的路径
BEST_CKP_PATH = osp.join(EXP_DIR, 'best_model', 'model.pdparams')

# 训练的epoch数
NUM_EPOCHS = 100
# 每多少个epoch保存一次模型权重参数
SAVE_INTERVAL_EPOCHS = 10
# 初始学习率
LR = 0.001
# 学习率衰减步长(注意,单位为迭代次数而非epoch数),即每多少次迭代将学习率衰减一半
DECAY_STEP = 10000
# 训练阶段 batch size
TRAIN_BATCH_SIZE = 8
# 推理阶段 batch size
INFER_BATCH_SIZE = 8
# 加载数据所使用的进程数
NUM_WORKERS = 4
# 裁块大小
CROP_SIZE = 256
# 模型推理阶段使用的滑窗步长
STRIDE = 64
# 影像原始大小
ORIGINAL_SIZE = (256, 256)
# 固定随机种子,尽可能使实验结果可复现

random.seed(SEED)
np.random.seed(SEED)
paddle.seed(SEED)
# 定义一些辅助函数
def info(msg, **kwargs):
    print(msg, **kwargs)


def warn(msg, **kwargs):
    print('\033[0;31m'+msg, **kwargs)


def quantize(arr):
    return (arr*255).astype('uint8')

2.2.2 模型构建

  • model1是BIT模型,用作Baseline,仅做比较,没有实际用到

  • model2是DSIFN模型(backbone换成了Resnet34)

  • model3是DSAMNet模型

    最终将model2和model3进行模型融合

# BIT模型
# 调用PaddleRS API一键构建模型
model1 = pdrs.tasks.BIT(
    # 模型输出类别数
    num_classes=2,
    # 是否使用混合损失函数,默认使用交叉熵损失函数训练
    use_mixed_loss=True,
    # 模型输入通道数
    in_channels=3,
    # 模型使用的骨干网络,支持'resnet18'或'resnet34'
    backbone='resnet34',
    # 骨干网络中的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
)
W0511 15:55:28.340306   549 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1
W0511 15:55:28.345744   549 device_context.cc:465] device: 0, cuDNN Version: 7.6.
100%|██████████| 128669/128669 [00:03<00:00, 33477.57it/s]
# DSIFN
# 调用PaddleRS API一键构建模型
model2 = pdrs.tasks.DSIFN(
    # 模型输出类别数
    num_classes=2,
    # 不使用dropout
    use_dropout=False,
    # 是否使用混合损失函数,默认使用交叉熵损失函数训练
    use_mixed_loss=False,
    backbone='resnet34',
    features_bool = True,
)
W0526 22:17:37.634099   166 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0526 22:17:37.638391   166 device_context.cc:465] device: 0, cuDNN Version: 7.6.
100%|██████████| 128669/128669 [00:02<00:00, 50442.02it/s]
# DSAMNet
# 调用PaddleRS API一键构建模型
model3 = pdrs.tasks.DSAMNet(
    # 模型输出类别数
    in_channels=3,
    num_classes=2,
)
100%|██████████| 69183/69183 [00:01<00:00, 35346.46it/s]

2.2.3 数据增强

  • 在Baseline的基础上尤其考虑到光谱特征的影响,加入了RandomBlur和RandomDistort,后考虑变化检测的多样性,加入了常见的RandomSwap和MixupImage数据增强策略
# 构建需要使用的数据变换(数据增强、预处理)
# 使用Compose组合多种变换方式。Compose中包含的变换将按顺序串行执行
train_transforms = T.Compose([
    # 随机裁剪
    T.RandomCrop(
        # 裁剪区域将被缩放到此大小(裁剪成256小图后CROP_SIZE=IMAGE_SIZE)
        crop_size=CROP_SIZE,
        # 将裁剪区域的横纵比固定为1
        aspect_ratio=[1.0, 1.0],
        # 裁剪区域相对原始影像长宽比例在一定范围内变动,最小不低于原始长宽的1/5
       scaling=[0.2, 1.0]
    ),
    # 以50%的概率实施随机水平翻转
    T.RandomHorizontalFlip(prob=0.5),
    # 以50%的概率实施随机垂直翻转
    T.RandomVerticalFlip(prob=0.5),
    T.RandomBlur(prob=0.5),
    T.RandomDistort(
        brightness_prob=0.5,
        contrast_prob=0.5,
        saturation_prob=0.5),
    T.RandomSwap(prob=0.2),
    T.MixupImage(alpha=1.5,beta=1.5),
    # 数据归一化到[-1,1]
    T.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])
eval_transforms = T.Compose([
    # 在验证阶段,输入原始尺寸影像,对输入影像仅进行归一化处理
    # 验证阶段与训练阶段的数据归一化方式必须相同
    T.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])

# 实例化数据集
train_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=osp.join(DATA_DIR, 'train.txt'),
    label_list=None,
    transforms=train_transforms,
    num_workers=NUM_WORKERS,
    shuffle=True,
    binarize_labels=True
)
eval_dataset = pdrs.datasets.CDDataset(
    data_dir=DATA_DIR,
    file_list=osp.join(DATA_DIR, 'val.txt'),
    label_list=None,
    transforms=eval_transforms,
    num_workers=0,
    shuffle=False,
    binarize_labels=True
)
2022-05-26 22:17:45 [INFO]	9682 samples in f

标签: fn7325力传感器fn3060力传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台