复现RSE顶刊3D-CNN模型

复现顶刊RSE的3D-CNN

AI在遥感领域的应用已经成熟,并且大大推动了传统遥感的创新和发展。根据最近发表在《Remote Sensing of Environment》上的一篇文章,研究人员利用三维卷积神经网络(3D-CNN)结合机载高光谱和LiDAR数据,对树种进行了精确分类。这种方法不仅显著提高了树种分类的准确性,而且展示了AI技术在处理复杂高光谱数据方面的强大能力。

通过3D-CNN,研究人员能够自动提取数据中的空间和光谱特征,这比传统的机器学习方法如支持向量机和随机森林更加高效。研究结果表明,3D-CNN模型在区分针叶树种方面尤其有效,总体准确率达到了87%。此外,这种方法还可以生成详细的树种分布图,有助于可持续林业和生物多样性保护。

由此可见,AI技术在遥感中的应用不仅提升了数据处理效率,还带来了新的研究思路和创新方法。这样的研究成果为我们提供了新的视角,让我们对遥感技术的未来充满期待。赶紧复现这个idea,看看效果如何吧!

数据

通常日常生活中遇到的图像包含 3 个波段,即 RGB。卫星图像包含三个以上的波段,下图显示了一个包含 R行、C列和 B个波段的卫星图像立方体。sentinel-2 具有不同波长和分辨率的沿海气溶胶、近红外 (NIR)、短波红外 (SWIR) 和 RGB 波段组成。

alt

以Sundarbans地区为例(是世界上最大的红树林区之一),特别适合做分类任务。

alt

上面的地图显示了 Sundarbans 地区。我们使用 2020 年 1 月 27 日Sentinel-2卫星数据。

波段信息如图:

Band No Name Cental Wavelenth(micro meters) Resolution(meters)
1 Coastal aerosol 0.443 60
2 Blue 0.490 10
3 Green 0.560 10
4 Red 0.665 10
5 Vegetation Red Edge 0.405 20
6 Vegetation Red Edge 0.705 20
7 Vegetation Red Edge 0.740 20
8 NIR 0.842 10
8A Vegetation Red Edge 0.965 20
9 Water Vapour 0.945 60
10 SWIR-Cirrus 1.375 60
11 SWIR 1.610 20
12 SWIR 2.190 20

接下来读取数据

rasterio并使用 方法将它们堆叠成一个 n 维数组numpy.stack()。堆叠后的结果数据具有形状 (12, 954, 298)。使用scipy.io包中的loadmat方法读取卫星图像的地面实况。地面实况有 6 个类别,包括水、植物、树木、裸地等

from glob import glob
import numpy as np
from scipy.io import loadmat
import rasterio as rio

S_sentinel_bands = glob("/content/drive/MyDrive/Satellite_data/sundarbans_data/*B?*.tiff")
S_sentinel_bands.sort()

l = []
for i in S_sentinel_bands:
  with rio.open(i, 'r') as f:
    l.append(f.read(1))

# Data
arr_st = np.stack(l)

# Ground Truth
y_data = loadmat('Sundarbands_gt.mat')['gt']

这些 Sundarbans 数据有多个波段,包含从可见光到红外的数据。因此,肉眼很难看到这些数据。借助 RGB 合成图像,可以更轻松地有效理解这些数据。

要绘制 RGB 合成图像,使用红色、绿色和蓝色波段(波段 4、3 和 2),我们可以可视化合成图像。

使用EarthPy包中的方法plot_rgb对卫星图像的合成图像进行可视化。

ep.plot_rgb(
    arr_st,
    rgb=(3, 2, 1),
    stretch=True,
    str_clip=0.02,
    figsize=(12, 16),
    # title="RGB Composite Image with Stretch Applied",
)

plt.show()
# Visualize Groundtruth

ep.plot_bands(y_data, 
              cmap=ListedColormap(['darkgreen''green''black'
                                   '#CA6F1E''navy''forestgreen']))
plt.show()
alt

数据包含 12 个波段。让我们使用 EarhPy 包可视化每个波段。该方法plot_bands()采用波段自定义标题

ep.plot_bands(arr_st, 
              cmap = 'gist_earth'
              figsize = (20, 12), 
              cols = 6, 
              cbar = False)
plt.show()
image-20240808234310950
image-20240808234310950

3D CNN

3D CNN 需要三维数据作为输入,因此我们需要将卫星图像分解成块,每个块都有一个类。块的类标签已定义为块中心像素的类。每个块都有尺寸 (W、W、B)。其中 W 和 B 是窗口大小和波段数。下图描绘了卫星图像中的块。

image-20240808234333109
image-20240808234333109

让我们通过对数据应用主成分分析 (PCA) 来创建 Sundarbans 卫星图像的三维块。以下代码用于创建实现 PCA、创建 3D 块以及将数据按 7:3 的比例拆分为训练数据和测试数据

def applyPCA(X, numComponents=75):
    newX = np.reshape(X, (-1, X.shape[2]))
    pca = PCA(n_components=numComponents, whiten=True)
    newX = pca.fit_transform(newX)
    newX = np.reshape(newX, (X.shape[0],X.shape[1], numComponents))
    return newX, pca

def padWithZeros(X, margin=2):
    newX = np.zeros((X.shape[0] + 2 * margin, X.shape[1] + 2* margin, X.shape[2]))
    x_offset = margin
    y_offset = margin
    newX[x_offset:X.shape[0] + x_offset, y_offset:X.shape[1] + y_offset, :] = X
    return newX

def createImageCubes(X, y, windowSize=5, removeZeroLabels = False):
    margin = int((windowSize - 1) / 2)
    zeroPaddedX = padWithZeros(X, margin=margin)
    # split patches
    patchesData = np.zeros((X.shape[0] * X.shape[1], windowSize, windowSize, X.shape[2]))
    patchesLabels = np.zeros((X.shape[0] * X.shape[1]))
    patchIndex = 0
    for r in range(margin, zeroPaddedX.shape[0] - margin):
        for c in range(margin, zeroPaddedX.shape[1] - margin):
            patch = zeroPaddedX[r - margin:r + margin + 1, c - margin:c + margin + 1]   
            patchesData[patchIndex, :, :, :] = patch
            patchesLabels[patchIndex] = y[r-margin, c-margin]
            patchIndex = patchIndex + 1
    if removeZeroLabels:
        patchesData = patchesData[patchesLabels>0,:,:,:]
        patchesLabels = patchesLabels[patchesLabels>0]
        patchesLabels -= 1
    return patchesData, patchesLabels

def splitTrainTestSet(X, y, testRatio, randomState=42):
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=testRatio, random_state=randomState, stratify=y)
    return X_train, X_test, y_train, y_test
  
## GLOBAL VARIABLES
dataset = 'SB'
test_size = 0.30
windowSize = 15
MODEL_NAME = 'Sundarbans'
path = '/content/drive/MyDrive/Sundarbans/'

X_data = np.moveaxis(arr_st, 0, -1)
y_data = loadmat('Sundarbands_gt.mat')['gt']

# Apply PCA
K = 5
X,pca = applyPCA(X_data,numComponents=K)

print(f'Data After PCA: {X.shape}')

# Create 3D Patches
X, y = createImageCubes(X, y_data, windowSize=windowSize)
print(f'Patch size: {X.shape}')

# Split train and test
X_train, X_test, y_train, y_test = splitTrainTestSet(X, y, testRatio = test_size)

X_train = X_train.reshape(-1, windowSize, windowSize, K, 1)
X_test = X_test.reshape(-1, windowSize, windowSize, K, 1)

# One Hot Encoding
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

print(f'Train: {X_train.shape}\nTest: {X_test.shape}\nTrain Labels: {y_train.shape}\nTest Labels: {y_test.shape}')

让我们构建一个具有多个层的三维 CNN,例如卷积层、Dropout 层和密集层。以下代码用于使用 TensorFlow 创建用于土地覆盖分类的 3D-CNN。

S = windowSize
L = K
output_units = y_train.shape[1]

## input layer
input_layer = Input((S, S, L, 1))

## convolutional layers
conv_layer1 = Conv3D(filters=16, kernel_size=(2, 2, 3), activation='relu')(input_layer)
conv_layer2 = Conv3D(filters=32, kernel_size=(2, 2, 3), activation='relu')(conv_layer1)
conv2d_shape = conv_layer2.shape
conv_layer3 = Reshape((conv2d_shape[1], conv2d_shape[2], conv2d_shape[3]*conv2d_shape[4]))(conv_layer2)
conv_layer4 = Conv2D(filters=64, kernel_size=(2,2), activation='relu')(conv_layer3)

flatten_layer = Flatten()(conv_layer4)

## fully connected layers
dense_layer1 = Dense(128, activation='relu')(flatten_layer)
dense_layer1 = Dropout(0.4)(dense_layer1)
dense_layer2 = Dense(64, activation='relu')(dense_layer1)
dense_layer2 = Dropout(0.4)(dense_layer2)
dense_layer3 = Dense(20, activation='relu')(dense_layer2)
dense_layer3 = Dropout(0.4)(dense_layer3)
output_layer = Dense(units=output_units, activation='softmax')(dense_layer3)
# define the model with input layer and output layer
model = Model(name = dataset+'_Model' , inputs=input_layer, outputs=output_layer)

model.summary()

3D-CNN 模型共有 1,204,098 个可训练参数。下图显示了开发的 3D-CNN 模型的摘要。

alt

训练模型

使用了Adam优化器、分类交叉熵、准确度作为指标

以下代码用于编译和训练模型。

EarlyStopping:减少神经网络过度拟合的一种方法是使用 Early Stopping。如果模型没有真正学到任何东西,Early Stopping 会终止训练过程

ModelCheckpoint:此回调将在每次成功执行 epoch 后将您的模型作为检查点文件(hdf5 or h5格式)保存到电脑

TensorBoard:此回调在每epoch训练后记录

# Compile
model.compile(optimizer = 'adam', loss = 'categorical_crossentropy',
              metrics = ['accuracy'])

# Callbacks
logdir = path+"logs/" +model.name+'_'+datetime.now().strftime("%d:%m:%Y-%H:%M:%S")

tensorboard_callback = TensorBoard(log_dir=logdir)

es = EarlyStopping(monitor = 'val_loss',
                   min_delta = 0,
                   patience = 1,
                   verbose = 1,
                   restore_best_weights = True)

checkpoint = ModelCheckpoint(filepath = 'Pavia_University_Model.h5'
                             monitor = 'val_loss'
                             mode ='min'
                             save_best_only = True,
                             verbose = 1)
# Fit
history = model.fit(x=X_train, y=y_train, 
                    batch_size=1024*6, epochs=6, 
                    validation_data=(X_test, y_test), callbacks = [tensorboard_callback, es, checkpoint])

训练的准确率和损失图如下所示

import pandas as pd
history = pd.DataFrame(history.history)

plt.figure(figsize = (12, 6))
plt.plot(range(len(history['accuracy'].values.tolist())), history['accuracy'].values.tolist(), label = 'Train_Accuracy')
plt.plot(range(len(history['loss'].values.tolist())), history['loss'].values.tolist(), label = 'Train_Loss')
plt.plot(range(len(history['val_accuracy'].values.tolist())), history['val_accuracy'].values.tolist(), label = 'Test_Accuracy')
plt.plot(range(len(history['val_loss'].values.tolist())), history['val_loss'].values.tolist(), label = 'Test_Loss')
plt.xlabel('Epochs')
plt.ylabel('Value')
plt.legend()
plt.show()
alt

结果

训练后的 CNN 模型准确率为95.60%,让我们看一下混淆矩阵

pred = model.predict(X_test, batch_size=1204*6, verbose=1)

plt.figure(figsize = (10,7))

classes = [f'Class-{i}' for i in range(1, 7)]

mat = confusion_matrix(np.argmax(y_test, 1),
                            np.argmax(pred, 1))

df_cm = pd.DataFrame(mat, index = classes, columns = classes)

sns.heatmap(df_cm, annot=True, fmt='d')

plt.show()
alt

分类报告显示准确率以及类别精度、召回率、f1 分数和支持度。输出如下所示:

# Classification Report
print(classification_report(np.argmax(y_test, 1),
                            np.argmax(pred, 1),
      target_names = [f'Class-{i}' for i in range(1, 7)]))
alt

最后,让我们可视化 Sundarbans 卫星的分类图。以下代码用于创建分类图。该方法perdict用于预测整个数据的标签,并将其重塑为原始大小,并使用该方法进行可视化plot_bands

pred_t = model.predict(X.reshape(-1, windowSize, windowSize, K, 1),
                       batch_size=1204*6, verbose=1)
# Visualize Groundtruth

ep.plot_bands(np.argmax(pred_t, axis=1).reshape(954, 298), 
              cmap=ListedColormap(['darkgreen''green''black'
                                   '#CA6F1E''navy''forestgreen']))
plt.show()
alt

结论

回顾本文基本复现了RSE的方法和各种图,准确度优于原文章的同时,也提供了一种新的方法。收藏点在看学起来,你也能发表一区TOP论文。

猜你喜欢

转载自blog.csdn.net/wlh2067/article/details/143063996