CNN高分辨率遥感图像场景分类(NWPU-RESISC45, EuroSAT)

引言

随着卫星遥感技术的不断发展,遥感产品的空间分辨率得到了很大的提升,QuickBird、GeoEye-1、高分系列等卫星传感器的空间分辨率达到了米级甚至亚米级。高分辨率遥感影像不仅具有丰富的光谱、形状和纹理特征,还包含了清晰的场景语义信息。与遥感影像的像素分类不同,场景分类是根据遥感图像内容所表达的“场景”,将其标注为某个特定的语义种类。语义类别是对场景内容高层次的知识抽象和概括,它由人工预先定义,如森林、湖泊、桥梁、港口等对象及其组合。

卷积神经网络的主要组成部分包括输入层、卷积层、池化层、‌全连接层和输出层。卷积层通过卷积操作提取图像的底层特征,池化层用于减小数据维度并防止过拟合,全连接层汇总卷积层和池化层的信息,最终通过输出层给出识别结果。卷积操作是通过一个可移动的小窗口(卷积核)在图像上滑动,与图像的每个位置进行逐元素相乘并求和,从而提取图像的局部特征。‌卷积神经网络在图像识别、目标检测、图像生成等领域取得了显著的进展,成为计算机视觉和深度学习研究的重要组成部分。

本文基于pytorch框架构建卷积神经网络,在NWPU-RESISC45和EuroSAT两个遥感图像数据集上测试模型的性能,经过多轮训练和验证,CNN能够较为准确的识别卫星图像中不同的地物场景。

场景分类数据集

NWPU-RESISC45数据集由西北工业大学程塨教授团队发布,该数据集包含31500张图像,涵盖45个场景类别,包括陆地场景和海洋场景,每个类别有700张图像,每张图像大小为256×256。下载地址Gong CHENG (gcheng-nwpu.github.io)

EuroSAT是基于 Sentinel-2 进行土地利用和土地覆盖分类的数据集,覆盖 13 个光谱波段,由 10 个类别组成,总共有 27,000 张标记和地理参考图像,研发团队使用最先进的深度卷积神经网络为该数据集及其光谱波段提供基准。Torchvision.datasets提供了访问EuroSAT数据集的api(可能无法下载),由于我们是进行场景分类,因此下载RGB版本的数据即可。下载地址Land Use and Land Cover Classification with Sentinel-2 (github.com)

数据加载和处理

将下载好的数据集加载进项目中,对图像统一进行数据增强和归一化等操作。由于遥感图像数据集每个类别的样本较少,很容易在训练的过程中出现过拟合,因此图像增强是十分重要的一个步骤,图像增强通常有缩放、裁剪、旋转、色彩变换、扭曲等操作。注:本文只在NWPU-RESISC45数据集中选择了10个类别进行学习,因此NWPU-RESISC45下共包含10个文件夹。

transform = Compose([Resize(64), ToTensor(), RandomRotation(degrees=(60))])
imagedata = datasets.ImageFolder(root='./NWPU-RESISC45', transform=transform)

Resize对图像重设大小,ToTensor将数组转换为Tensor类型并归一化,RandomRotation对图像进行随机旋转,图像增强中的随机方法在不同batch之间是变化的,相同batch内图像的参数都是一致的。只对图像做旋转变换其实是不够的,大家可以尝试更多图像增强操作的组合,如ColorJitter、RandomHorizontalFlip、RandomAffine等。

划分训练集和测试集。

train_size = int(len(imagedata) * 0.8)
train_data, test_data = random_split(imagedata, [train_size, len(imagedata) - train_size])

最后使用DataLoader将训练集和测试集封装起来,得到可迭代的DataLoader对象,以batch的方式进行学习。batch_size设置为16,注:训练集需要shuffle。

train_loader = DataLoader(dataset=train_data, batch_size=batchsize, shuffle=True)
test_loader = DataLoader(dataset=test_data, batch_size=batchsize, shuffle=False)

构建CNN

定义一个卷积神经网络,网络结构可以在训练过程中根据效果适当调整。使用Sequential容器进行构建,这样更加简洁方便。

  • 卷积层:卷积核大小3×3,输入通道数3,输出通道数16,激活函数ReLU。16个feature map,大小62×62。
  • 池化层:池化核大小2×2,最大池化。16个feature map,大小31×31。
  • 卷积层:卷积核大小为3×3,输入通道数为16,输出通道数为32,激活函数ReLU。32个feature map,大小29×29。
  • 池化层:池化核大小2×2,最大池化。32个feature map,大小14×14。
  • Dropout:随机让部分神经元失活,防止过拟合。
  • Flatten:将所有feature map展开连接成一个一维数组。
  • 线性层:输入特征数14×14×32,输出特征数256,激活函数ReLU。
  • 线性层:输入特征数256,输出10种类别。
class ConvNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 16, 3),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(16, 32, 3),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Dropout(0.25),

            nn.Flatten(),
            nn.Linear(14 * 14 * 32, 256),
            nn.ReLU(),
            nn.Linear(256, 10),
        )

    def forward(self, x):
        output = self.model(x)
        return output

创建一个实例,有cuda就用cuda,没有用cpu。

device = 'cpu'
model = ConvNet().to(device)

训练模型

定义训练轮,包括调用实例前向传播、计算损失函数、反向传播更新网络参数、打印loss等步骤。

测试模型

定义测试轮,包括调用实例前向传播、计算损失函数、计算准确率、打印loss和准确率等步骤。

定义epochs,需要训练和测试的轮次。

epochs = 10

构建损失函数和优化器,损失函数选择交叉熵CrossEntropyLoss,优化器选择Adam(SGD感觉效果不是很好),并设置学习率learning_rate。

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

完整代码(NWPU-RESISC45数据集)

from datetime import datetime as dt
import torch
from torch import nn
from torchvision import datasets
from torch.utils.data import DataLoader, random_split
from torchvision.transforms import ToTensor, Compose, Resize, RandomRotation, RandomAffine


def image_load(batchsize):
    transform = Compose([Resize(64), ToTensor(), RandomRotation(degrees=(60))])
    imagedata = datasets.ImageFolder(root='./NWPU-RESISC45', transform=transform)

    train_size = int(len(imagedata) * 0.8)
    train_data, test_data = random_split(imagedata, [train_size, len(imagedata) - train_size])

    train_loader = DataLoader(dataset=train_data, batch_size=batchsize, shuffle=True)
    test_loader = DataLoader(dataset=test_data, batch_size=batchsize, shuffle=False)
    return train_loader, test_loader


class ConvNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 16, 3),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(16, 32, 3),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Dropout(0.25),

            nn.Flatten(),
            nn.Linear(14 * 14 * 32, 256),
            nn.ReLU(),
            nn.Linear(256, 10),
        )

    def forward(self, x):
        output = self.model(x)
        return output


device = 'cpu'
model = ConvNet().to(device)


def train_loop(dataloader, loss_fn, optimizer):
    model.train()
    num_batches = len(dataloader)
    ave_loss = 0
    for batch, (X, Y) in enumerate(dataloader):
        X = X.to(device)
        Y = Y.to(device)
        pred = model(X)
        loss = loss_fn(pred, Y)

        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        ave_loss += loss
        if batch % 50 == 0:
            loss = loss.item()
            print(f"loss: {loss:.4f} ")
    print(f"Avg train loss: {ave_loss / num_batches:.4f}")


def test_loop(dataloader, loss_fn):
    model.eval()
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss, correct = 0, 0

    with torch.no_grad():
        for X, Y in dataloader:
            X = X.to(device)
            Y = Y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, Y).item()
            correct += (pred.argmax(1) == Y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:.4f} \n")


def main():
    batch_size = 16
    epochs = 10
    loss_fn = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
    train_data, test_data = image_load(batch_size)

    for t in range(epochs):
        print(f"Epoch {t + 1}\n-------------------------------")
        train_loop(train_data, loss_fn, optimizer)
        test_loop(test_data, loss_fn)


if __name__ == '__main__':
    dt1 = dt.now()
    main()
    print(f"Duration: {dt.now() - dt1}")

训练EuroSAT数据集时更改需要加载的文件夹名称,batch_size设置为64,Resize也不需要了,因为图像大小都是64×64,其它地方可以不作修改。

结果展示

NWPU-RESISC45数据集

经过15个epoch,测试集loss下降至0.41左右,准确率大约为86%。

EuroSAT数据集

经过15个epoch,测试集loss下降至0.46左右,准确率大约为84%。

尽管2010年以来遥感场景分类数据集的规模在不断增加,PatternNet和NWPU-RESISC45图像总数都达到了3万幅以上,但这些数据集同类别的样本数依然较少,每个场景类别的图像数量普遍在100~800幅之间。如果不做图像增强,使用CNN进行学习会导致明显的过拟合现象,训练集loss会持续降低,而测试集在经过几个epoch后,loss反而不断上升。除了图像增强以外,减小batch_size,适当减小模型的规模,网络结构的空间大小和参数量应与输入图像信息的复杂度相匹配,使用正则化手段都能改善模型的过拟合。

参考文献

Eurosat: A novel dataset and deep learning benchmark for land use and land cover classification. Patrick Helber, Benjamin Bischke, Andreas Dengel, Damian Borth. IEEE Journal of Selected Topics in Applied Earth Observations and Remote Sensing, 2019.

Gong Cheng, Junwei Han, Xiaoqiang Lu. Remote Sensing Image Scene Classification: Benchmark and State of the Art. Proceedings of the IEEE, 105(10): 1865-1883, 2017.

https://www.sohu.com/a/338872015_120316364

https://www.cnblogs.com/yifanrensheng/p/14087871.html

https://blog.csdn.net/weixin_43312470/article/details/123975010

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值