訂閱
糾錯
加入自媒體

使用U-Net方法對航空圖像進行語義分割

在機器學習中,模型是在各種應用中訓練的,特別是在深度學習和圖像數據集上;诰矸e運算的方法在許多領域都進行了研究,尤其是手臂檢測、自動駕駛汽車、無人機航拍圖像、戰(zhàn)爭技術。人眼能夠很容易地對所看到的進行分類和區(qū)分。然而,在人工智能技術中,這種能力的等價物,即理解圖像的問題,屬于計算機視覺領域。顧名思義,計算機視覺是以計算機可以理解的方式引入(分類)圖像,下一步是使用不同的方法對這些圖像進行操作。本文介紹了一種分割方法,即U-Net體系結構,該體系結構是為生物醫(yī)學圖像分割而開發(fā)的,并包括一個實際項目,該項目使用U-Net對無人機捕獲的航空圖像進行分割。

目錄

1.語義分割

2.U-Net架構

3.教程

3.1. 數據預處理

3.2. 基于U-Net的語義分割

3.3. 基于遷移學習的U-Net語義分割

4.結論

5.參考文獻

語義分割

圖像是由數字組成的像素矩陣。在圖像處理技術中,對這些數字進行一些調整,然后以不同的方式表示圖像,并使其適合相關研究或解釋。

卷積過程是一種基本的數學像素運算,它提供了從不同角度評估圖像的機會。

例如,可以使用濾波器進行圖像的邊緣檢測,也可以通過將圖像從RGB格式轉換為灰度,從不同角度解釋和使用圖像。

基于深度學習模型和卷積層,人們對圖像內容進行了更全面的研究,如特征提取和分類。

如上圖所示,使用邊界框檢測圖像內容中的對象稱為對象檢測。

語義分割是一種逐像素的標記操作,它用一個標簽來顯示圖片中相同類型的對象(天空、貓、狗、人、路、車、山、海等),即顏色。

即時分割是指每個實例都被單獨標記,通過以不同的顏色顯示來分隔每個對象。

如上所述,在這些操作的背景下,針對不同的用途開發(fā)了各種彼此不同的CNN模型和復雜模型。PSPNet、DeepLab、LinkNet、U-Net、Mask R-CNN就是其中的一些模型。我們可以說,分割過程是基于機器學習的應用(如自動駕駛汽車)項目的核心。

總之,計算機視覺中的語義分割是一種基于像素的標記方法。如果相同類型的對象用單一顏色表示,則稱為語義分割;如果每個對象用唯一的顏色(標簽)表示,則稱為實例分割。

U-Net體系結構

U-Net是一種特定類型的卷積神經網絡架構,2015年在德國弗萊堡大學計算機科學系和生物信號研究中心為生物醫(yī)學圖像(計算機斷層掃描、顯微圖像、MRI掃描等)開發(fā)。

當我們考慮技術思想時,該模型由編碼器和解碼器組成,編碼器(收縮)是下采樣(主要是遷移學習中的預訓練權重),解碼器(提。┦巧喜蓸硬糠,它被命名為U-Net,因為它的方案是U形的,如下圖所示。該模型可根據不同的研究進行配置。

在以下教程中,為航空圖像的語義分割配置了U-Net模型,如下所示:

# -*- coding: utf-8 -*-

"""

@author: Ibrahim Kovan

https://ibrahimkovan.medium.com/

"""

from tensorflow.keras.models import Model

from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, concatenate, Conv2DTranspose, BatchNormalization, Dropout, Lambda

from tensorflow.keras import backend as K

def multiclass_unet_architecture(n_classes=2, height=256, width=256, channels=3):

   inputs = Input((height, width, channels))

   #Contraction path

   conv_1 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(inputs)

   conv_1 = Dropout(0.1)(conv_1)  

   conv_1 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_1)

   pool_1 = MaxPooling2D((2, 2))(conv_1)
   

   conv_2 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(pool_1)

   conv_2 = Dropout(0.1)(conv_2)  

   conv_2 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_2)

   pool_2 = MaxPooling2D((2, 2))(conv_2)
   

   conv_3 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(pool_2)

   conv_3 = Dropout(0.1)(conv_3)

   conv_3 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_3)

   pool_3 = MaxPooling2D((2, 2))(conv_3)
   

   conv_4 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(pool_3)

   conv_4 = Dropout(0.1)(conv_4)

   conv_4 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_4)

   pool_4 = MaxPooling2D(pool_size=(2, 2))(conv_4)
   

   conv_5 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(pool_4)

   conv_5 = Dropout(0.2)(conv_5)

   conv_5 = Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_5)
   

   #Expansive path

   u6 = Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(conv_5)

   u6 = concatenate([u6, conv_4])

   conv_6 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u6)

   conv_6 = Dropout(0.2)(conv_6)

   conv_6 = Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_6)
   

   u7 = Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(conv_6)

   u7 = concatenate([u7, conv_3])

   conv_7 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u7)

   conv_7 = Dropout(0.1)(conv_7)

   conv_7 = Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_7)
   

   u8 = Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(conv_7)

   u8 = concatenate([u8, conv_2])

   conv_8 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u8)

   conv_8 = Dropout(0.2)(conv_8)  

   conv_8 = Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_8)
   

   u9 = Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same')(conv_8)

   u9 = concatenate([u9, conv_1], axis=3)

   conv_9 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u9)

   conv_9 = Dropout(0.1)(conv_9)

   conv_9 = Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(conv_9)
   

   outputs = Conv2D(n_classes, (1, 1), activation='softmax')(conv_9)
   

   model = Model(inputs=[inputs], outputs=[outputs])

   model.summary()

   return model


def jacard(y_true, y_pred):

   y_true_c = K.flatten(y_true)

   y_pred_c = K.flatten(y_pred)

   intersection = K.sum(y_true_c * y_pred_c)

   return (intersection + 1.0) / (K.sum(y_true_c) + K.sum(y_pred_c) - intersection + 1.0)


def jacard_loss(y_true, y_pred):

   return -jacard(y_true,y_pred)

如果我們采用上述代碼塊:

1、輸入定義為256x256x3。

2、使用16個濾波器的conv_1,可獲得256x256x16的輸出。使用pool_1中的Maxpooling,它將減少到128x128x16。

3、使用32個濾波器的conv_2,可獲得256x256x32的輸出。使用pool_2,可獲得64x64x32的輸出。

4、使用64個濾波器的conv_3,可獲得64x64x64的輸出。使用pool_3,可獲得32x32x64的輸出。

5、使用128個濾波器的conv_4,可獲得32x32x128的輸出。使用pool_4,可獲得16x16x128的輸出。

6、使用256個濾波器的conv_5,可獲得16x16x256的輸出,并從此點開始進行上采樣。在濾波器數量為128和(2x2)的u6中,conv_5通過Conv2DTranspose和級聯(lián)得到32x32x128的輸出,級聯(lián)通過u6、conv_4執(zhí)行。因此,u6輸出為32x32x256。使用帶有128個濾波器的conv_6,它將變?yōu)?2x32x128。

7、濾波器數量為64且(2x2)的u7通過應用于conv_6并將u7與conv_3串聯(lián),變?yōu)?4x64x64。此操作的結果是,u7被定義為64x64x128,并在conv_7中變?yōu)?4x64x64。

8、濾波器數量為32和(2x2)的u8通過應用于conv_7并將u7與conv_2串聯(lián),變?yōu)?28x128x32。此操作的結果是,u8被定義為128x128x64,并使用conv_8變?yōu)?28x128x32。

9、通過應用于conv_8并將u9與conv_1串聯(lián),濾波器數量為16和(2x2)的u9變?yōu)?56x256x16。此操作的結果是,u9被定義為256x256x32,并通過conv_9變?yōu)?56x256x16。

10、輸出使用softmax激活完成分類過程,最終輸出采用256x256x1的形式。

使用不同速率的dropout來防止過擬合。

教程

在編碼部分,可以使用不同的方法對數據集進行訓練。

在本研究中,RGB(原始圖像)數據集定義為x,并且該模型是通過使用真實標簽(分割標記圖像)作為y來訓練的。在未來的文章中,還將討論使用掩碼數據集的方法。

RGB圖像和標簽如下圖所示。該研究旨在用這種方法訓練數據集,并使外部呈現的圖像能夠像訓練數據一樣進行分割。

它關注的是編碼體系結構部分,而不是實現高性能。這是由于使用圖像數據集時涉及的計算復雜性。

例如,雖然原始圖像為6000x4000像素,但已將其轉換為256x256像素以避免計算復雜性。通過這些操作,目的是通過放棄準確性來確保編碼體系結構正常工作。

數據預處理

# -*- coding: utf-8 -*-

"""

@author: Ibrahim Kovan

https://ibrahimkovan.medium.com/

dataset: http://www.dronedataset.icg.tugraz.at/

dataset link: https://www.kaggle.com/awsaf49/semantic-drone-dataset

License: CC0: Public Domain

"""

#%% Libraries

"""1"""

from architecture import multiclass_unet_architecture, jacard, jacard_loss

from tensorflow.keras.utils import normalize

import os

import glob

import cv2

import numpy as np

from matplotlib import pyplot as plt

import random

from skimage.io import imshow

from PIL import Image

import pandas as pd

from sklearn.preprocessing import MinMaxScaler, StandardScaler

from tensorflow.keras.utils import to_categorical

from sklearn.model_selection import train_test_split

import segmentation_models as sm

from tensorflow.keras.metrics import MeanIoU

#%% Import train and mask dataset

"""2"""

train_path = r"C:UsersibrahDesktopU-Netdataset raining_setimages.jpg"

def importing_data(path):

   sample = []

   for filename in glob.glob(path):

       img = Image.open(filename,'r')

       img = img.resize((256,256))

       img = np.array(img)

       sample.append(img)  

   return sample


data_train   = importing_data(train_path)

data_train = np.asarray(data_train)

mask_path = r"C:UsersibrahDesktopU-Netdataset raining_setgtsemanticlabel_images.png"

def importing_data(path):

   sample = []

   for filename in glob.glob(path):

       img = Image.open(filename,'r')

       img = img.resize((256,256))

       img = np.array(img)

       sample.append(img)  

   return sample

data_mask   = importing_data(mask_path)

data_mask  = np.asarray(data_mask)

#%% Random visualization

x = random.randint(0, len(data_train))

plt.figure(figsize=(24,18))

plt.subplot(1,2,1)

imshow(data_train[x])

plt.subplot(1,2,2)

imshow(data_mask[x])

plt.show()

#%% Normalization

"""3"""

scaler = MinMaxScaler()

nsamples, nx, ny, nz = data_train.shape

d2_data_train = data_train.reshape((nsamples,nx*ny*nz))

train_images = scaler.fit_transform(d2_data_train)

train_images = train_images.reshape(400,256,256,3)


#%% Labels of the masks

"""4"""

labels = pd.read_csv(r"C:UsersibrahDesktopU-Netdataset raining_setgtsemantic/class_dict.csv")

labels = labels.drop(['name'],axis = 1)

labels = np.array(labels)

def image_labels(label):

   image_labels = np.zeros(label.shape, dtype=np.uint8)

   for i in range(24):

       image_labels [np.all(label == labels[i,:],axis=-1)] = i

   image_labels = image_labels[:,:,0]

   return image_labels


label_final = []

for i in range(data_mask.shape[0]):

   label = image_labels(data_mask[i])

   label_final.append(label)    

label_final = np.array(label_final)  

#%% train_test

"""5"""

n_classes = len(np.unique(label_final))

labels_cat = to_categorical(label_final, num_classes=n_classes)

x_train, x_test, y_train, y_test = train_test_split(train_images, labels_cat, test_size = 0.20, random_state = 42)

1-導入庫。從architecture import multiclass_unet_architecture中,定義了jacard,jacard_loss,并從上述部分導入。

2-6000x4000像素的RGB原始圖像和相應標簽的大小調整為256x256像素。

3-MinMaxScaler用于縮放RGB圖像。

4-導入真實標簽。數據集中有23個標簽,并根據像素值將標簽分配給圖像。

5-標簽數據集是用于分類的one-h(huán)ot編碼數據集,數據被分離為訓練集和測試集。

使用U-Net的語義分割(從頭開始)

#%% U-Net

"""6"""

img_height = x_train.shape[1]

img_width  = x_train.shape[2]

img_channels = x_train.shape[3]

metrics=['accuracy', jacard]

def get_model():

   return multiclass_unet_architecture(n_classes=n_classes, height=img_height,
                          width=img_width, channels=img_channels)

model = get_model()

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=metrics)

model.summary()

history = model.fit(x_train, y_train,
                   batch_size = 16,
                   verbose=1,
                   epochs=100,
                   validation_data=(x_test, y_test),
                   shuffle=False)

#%%

"""7"""

loss = history.history['loss']

val_loss = history.history['val_loss']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'y', label='Training loss')

plt.plot(epochs, val_loss, 'r', label='Validation loss')

plt.title('Training and validation loss')

plt.xlabel('Epochs')

plt.ylabel('Loss')

plt.legend()

plt.show()

acc = history.history['jacard']

val_acc = history.history['val_jacard']

plt.plot(epochs, acc, 'y', label='Training Jaccard')

plt.plot(epochs, val_acc, 'r', label='Validation Jaccard')

plt.title('Training and validation Jacard')

plt.xlabel('Epochs')

plt.ylabel('Jaccard')

plt.legend()

plt.show()

#%%

"""8"""

y_pred=model.predict(x_test)

y_pred_argmax=np.argmax(y_pred, axis=3)

y_test_argmax=np.argmax(y_test, axis=3)


test_jacard = jacard(y_test,y_pred)

print(test_jacard)

#%%

"""9"""

fig, ax = plt.subplots(5, 3, figsize = (12,18))

for i in range(0,5):

   test_img_number = random.randint(0, len(x_test))

   test_img = x_test[test_img_number]

   ground_truth=y(tǒng)_test_argmax[test_img_number]

   test_img_input=np.expand_dims(test_img, 0)

   prediction = (model.predict(test_img_input))

   predicted_img=np.argmax(prediction, axis=3)[0,:,:]    
   

   ax[i,0].imshow(test_img)

   ax[i,0].set_title("RGB Image",fontsize=16)

   ax[i,1].imshow(ground_truth)

   ax[i,1].set_title("Ground Truth",fontsize=16)

   ax[i,2].imshow(predicted_img)

   ax[i,2].set_title("Prediction",fontsize=16)

   i+=i
   

plt.show()

6-在訓練過程中使用準確度和Jaccard指數。優(yōu)化器設置為“adam”,損失設置為“categorical_crossentropy”,因為它只是一個復雜的分類問題。該模型配備了這些設置。

7-val_jaccard和訓練過程的損失是可視化的。下圖顯示了val_jaccard。

8-測試數據集的Jaccard系數計算為0.5532。

9-從測試數據集中選擇5幅隨機圖像,用訓練好的算法進行預測,結果如下圖所示。

基于遷移學習的U-Net語義分割

#%% pre-trained model

"""10"""

BACKBONE = 'resnet34'

preprocess_input = sm.get_preprocessing(BACKBONE)

# preprocess input

x_train_new = preprocess_input(x_train)

x_test_new = preprocess_input(x_test)

# define model

model_resnet_backbone = sm.Unet(BACKBONE, encoder_weights='imagenet', classes=n_classes, activation='softmax')

metrics=['accuracy', jacard]

# compile keras model with defined optimozer, loss and metrics

#model_resnet_backbone.compile(optimizer='adam', loss=focal_loss, metrics=metrics)

model_resnet_backbone.compile(optimizer='adam', loss='categorical_crossentropy', metrics=metrics)

print(model_resnet_backbone.summary())

history_tf=model_resnet_backbone.fit(x_train_new,

         y_train,

         batch_size=16,

         epochs=100,

         verbose=1,

         validation_data=(x_test_new, y_test))

#%%

"""11"""

history = history_tf

loss = history.history['loss']

val_loss = history.history['val_loss']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'y', label='Training loss')

plt.plot(epochs, val_loss, 'r', label='Validation loss')

plt.title('Training and validation loss')

plt.xlabel('Epochs')

plt.ylabel('Loss')

plt.legend()

plt.show()

acc = history.history['jacard']

val_acc = history.history['val_jacard']

plt.plot(epochs, acc, 'y', label='Training IoU')

plt.plot(epochs, val_acc, 'r', label='Validation IoU')

plt.title('Training and validation Jaccard')

plt.xlabel('Epochs')

plt.ylabel('Jaccard')

plt.legend()

plt.show()


#%%

"""12"""

y_pred_tf=model_resnet_backbone.predict(x_test)

y_pred_argmax_tf=np.argmax(y_pred_tf, axis=3)

y_test_argmax_tf=np.argmax(y_test, axis=3)

test_jacard = jacard(y_test,y_pred_tf)

print(test_jacard)

#%%

"""13"""

fig, ax = plt.subplots(5, 3, figsize = (12,18))

for i in range(0,5):

   test_img_number = random.randint(0, len(x_test))

   test_img_tf = x_test_new[test_img_number]

   ground_truth_tf=y(tǒng)_test_argmax_tf[test_img_number]

   test_img_input_tf=np.expand_dims(test_img_tf, 0)

   prediction_tf = (model_resnet_backbone.predict(test_img_input_tf))

   predicted_img_transfer_learning=np.argmax(prediction_tf, axis=3)[0,:,:]  
   

   ax[i,0].imshow(test_img_tf)

   ax[i,0].set_title("RGB Image",fontsize=16)

   ax[i,1].imshow(ground_truth_tf)

   ax[i,1].set_title("Ground Truth",fontsize=16

   ax[i,2].imshow(predicted_img_transfer_learning)

   ax[i,2].set_title("Prediction(Transfer Learning)",fontsize=16)

   i+=i
   

plt.show()

10-使用resnet34重新準備數據集。將“Adam”設置為優(yōu)化器,“categorical_crossentropy”設置為損失函數,并對模型進行訓練。

11-val_jaccard和訓練過程的丟失是可視化的。下圖展示了val_jaccard。

12-測試數據集的Jaccard索引值計算為0.6545。

13-從測試數據集中選擇5幅隨機圖像,用訓練好的算法進行預測,結果如下圖所示。

結論

本文提出了一種基于U-Net的衛(wèi)星圖像語義分割方法,該方法是為生物醫(yī)學圖像分割而開發(fā)的。

本研究考慮了兩種主要方法。第一種方法涉及使用從頭開始的實現來訓練配置的u-net模型。第二種方法涉及使用遷移學習技術訓練模型,即預訓練權重。

在實現部分,對相應的帶有真實標簽的圖像進行one-h(huán)ot編碼,并對模型進行分類訓練。Jaccard系數作為度量。

"""14"""

fig, ax = plt.subplots(5, 4, figsize = (16,20))

for i in range(0,5):

   test_img_number = random.randint(0, len(x_test))
   

   test_img = x_test[test_img_number]

   ground_truth=y(tǒng)_test_argmax[test_img_number]

   test_img_input=np.expand_dims(test_img, 0)

   prediction = (model.predict(test_img_input))

   predicted_img=np.argmax(prediction, axis=3)[0,:,:]    
   

   test_img_tf = x_test_new[test_img_number]

   ground_truth_tf=y(tǒng)_test_argmax_tf[test_img_number]

   test_img_input_tf=np.expand_dims(test_img_tf, 0)

   prediction_tf = (model_resnet_backbone.predict(test_img_input_tf))

   predicted_img_transfer_learning=np.argmax(prediction_tf, axis=3)[0,:,:]  
   

   ax[i,0].imshow(test_img_tf)

   ax[i,0].set_title("RGB Image",fontsize=16)

   ax[i,1].imshow(ground_truth_tf)

   ax[i,1].set_title("Ground Truth",fontsize=16)

   ax[i,2].imshow(predicted_img)

   ax[i,2].set_title("Prediction",fontsize=16)

   ax[i,3].imshow(predicted_img_transfer_learning)

   ax[i,3].set_title("Prediction Transfer Learning",fontsize=16)
   

   i+=i  

plt.show()

調整大小并不推薦,因為分割操作中的大小更改會出現不希望的偏移,但由于計算復雜性,數據集從6000x4000調整為256x256。因此,模型的成功率極低。防止這種情況的一些主要措施是使用高分辨率數據集和/或使用修補。

使用調整大小的數據集,評估了兩種不同的方法,結果如圖8所示。從Jaccard索引值來看,遷移學習方法得到的值為0.6545,而scratch構建模型得到的值為0.5532?梢钥闯,使用預訓練模型得到的分割過程更為成功。

在以后的文章中,不同的編碼方法將涵蓋不同的方法。

參考引用

O. Ronneberger, P. Fischer, and T. Brox, “LNCS 9351 — U-Net: Convolutional Networks for Biomedical Image Segmentation,” 2015, doi: 10.1007/978–3–319–24574–4_28.

A. Arnab et al., “Conditional Random Fields Meet Deep Neural Networks for Semantic Segmentation,” IEEE Signal Process. Mag., vol. XX, 2018.

J. Y. C. Chen, G. F. Eds, and G. Goos, 2020_Book_VirtualAugmentedAndMixedReality. 2020.

J. Maurya, R. Hebbalaguppe, and P. Gupta, “Real-Time Hand Segmentation on Frugal Head-mounted Device for Gestural Interface,” Proc. — Int. Conf. Image Process. ICIP, pp. 4023–4027, 2018, doi: 10.1109/ICIP.2018.8451213.


       原文標題 : 使用U-Net方法對航空圖像進行語義分割

聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關注公眾號
OFweek人工智能網
獲取更多精彩內容
文章糾錯
x
*文字標題:
*糾錯內容:
聯(lián)系郵箱:
*驗 證 碼:

粵公網安備 44030502002758號