大家好,又見面了,我是你們的朋友全棧君。
憨批的語義分割重制版5——Keras 搭建自己的Unet語義分割平台
- 注意事項
- 學習前言
- 什麼是Unet模型
- 代碼下載下傳
- Unet實作思路
-
- 一、預測部分
-
- 1、主幹網絡介紹
- 2、加強特征提取結構
- 3、利用特征獲得預測結果
- 二、訓練部分
-
- 1、訓練檔案詳解
- 2、LOSS解析
- 訓練自己的Unet模型
-
- 一、資料集的準備
- 二、資料集的處理
- 三、開始網絡訓練
- 四、訓練結果預測
注意事項
這是重新建構了的Unet語義分割網絡,主要是檔案架構上的建構,還有代碼的實作,和之前的語義分割網絡相比,更加完整也更清晰一些。建議還是學習這個版本的Unet。
學習前言
重置一下我最喜歡的Unet。
什麼是Unet模型
Unet是一個優秀的語義分割模型,其主要執行過程與其它語義分割模型類似。
Unet可以分為三個部分,如下圖所示:
第一部分是主幹特征提取部分,我們可以利用主幹部分獲得一個又一個的特征層,Unet的主幹特征提取部分與VGG相似,為卷積和最大池化的堆疊。利用主幹特征提取部分我們可以獲得五個初步有效特征層,在第二步中,我們會利用這五個有效特征層可以進行特征融合。
第二部分是加強特征提取部分,我們可以利用主幹部分擷取到的五個初步有效特征層進行上采樣,并且進行特征融合,獲得一個最終的,融合了所有特征的有效特征層。
第三部分是預測部分,我們會利用最終獲得的最後一個有效特征層對每一個特征點進行分類,相當于對每一個像素點進行分類。
代碼下載下傳
Github源碼下載下傳位址為:
https://github.com/bubbliiiing/unet-keras
Unet實作思路
一、預測部分
1、主幹網絡介紹
Unet的主幹特征提取部分由卷積+最大池化組成,整體結構與VGG類似。
本文所采用的主幹特征提取網絡為VGG16,這樣也友善使用imagnet上的預訓練權重。
VGG是由Simonyan 和Zisserman在文獻《Very Deep Convolutional Networks for Large Scale Image Recognition》中提出卷積神經網絡模型,其名稱來源于作者所在的牛津大學視覺幾何組(Visual Geometry Group)的縮寫。
該模型參加2014年的 ImageNet圖像分類與定位挑戰賽,取得了優異成績:在分類任務上排名第二,在定位任務上排名第一。
它的結構如下圖所示:
這是一個VGG16被用到爛的圖,但确實很好的反應了VGG16的結構。
當我們使用VGG16作為主幹特征提取網絡的時候,我們隻會用到兩種類型的層,分别是卷積層和最大池化層。
當輸入的圖像大小為512x512x3的時候,具體執行方式如下:
1、conv1:進行兩次[3,3]的64通道的卷積,獲得一個[512,512,64]的初步有效特征層,再進行2X2最大池化,獲得一個[256,256,64]的特征層。
2、conv2:進行兩次[3,3]的128通道的卷積,獲得一個[256,256,128]的初步有效特征層,再進行2X2最大池化,獲得一個[128,128,128]的特征層。
3、conv3:進行三次[3,3]的256通道的卷積,獲得一個[128,128,256]的初步有效特征層,再進行2X2最大池化,獲得一個[64,64,256]的特征層。
4、conv4:進行三次[3,3]的512通道的卷積,獲得一個[64,64,512]的初步有效特征層,再進行2X2最大池化,獲得一個[32,32,512]的特征層。
5、conv5:進行三次[3,3]的512通道的卷積,獲得一個[32,32,512]的初步有效特征層。
from keras import layers
def VGG16(img_input):
# Block 1
x = layers.Conv2D(64, (3, 3),
activation='relu',
padding='same',
name='block1_conv1')(img_input)
x = layers.Conv2D(64, (3, 3),
activation='relu',
padding='same',
name='block1_conv2')(x)
feat1 = x
x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)
# Block 2
x = layers.Conv2D(128, (3, 3),
activation='relu',
padding='same',
name='block2_conv1')(x)
x = layers.Conv2D(128, (3, 3),
activation='relu',
padding='same',
name='block2_conv2')(x)
feat2 = x
x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)
# Block 3
x = layers.Conv2D(256, (3, 3),
activation='relu',
padding='same',
name='block3_conv1')(x)
x = layers.Conv2D(256, (3, 3),
activation='relu',
padding='same',
name='block3_conv2')(x)
x = layers.Conv2D(256, (3, 3),
activation='relu',
padding='same',
name='block3_conv3')(x)
feat3 = x
x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)
# Block 4
x = layers.Conv2D(512, (3, 3),
activation='relu',
padding='same',
name='block4_conv1')(x)
x = layers.Conv2D(512, (3, 3),
activation='relu',
padding='same',
name='block4_conv2')(x)
x = layers.Conv2D(512, (3, 3),
activation='relu',
padding='same',
name='block4_conv3')(x)
feat4 = x
x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x)
# Block 5
x = layers.Conv2D(512, (3, 3),
activation='relu',
padding='same',
name='block5_conv1')(x)
x = layers.Conv2D(512, (3, 3),
activation='relu',
padding='same',
name='block5_conv2')(x)
x = layers.Conv2D(512, (3, 3),
activation='relu',
padding='same',
name='block5_conv3')(x)
feat5 = x
return feat1, feat2, feat3, feat4, feat5
複制
2、加強特征提取結構
Unet所使用的加強特征提取網絡是一個U的形狀。
利用第一步我們可以獲得五個初步的有效特征層,在加強特征提取網絡這裡,我們會利用這五個初步的有效特征層進行特征融合,特征融合的方式就是對特征層進行上采樣并且進行堆疊。
為了友善網絡的建構與更好的通用性,我們的Unet和上圖的Unet結構有些許不同,在上采樣時直接進行兩倍上采樣再進行特征融合,最終獲得的特征層和輸入圖檔的高寬相同。
具體示意圖如下:
import numpy as np
from keras.models import *
from keras.layers import *
from nets.vgg16 import VGG16
def Unet(input_shape=(256,256,3), num_classes=21):
inputs = Input(input_shape)
feat1, feat2, feat3, feat4, feat5 = VGG16(inputs)
channels = [64, 128, 256, 512]
P5_up = UpSampling2D(size=(2, 2))(feat5)
P4 = Concatenate(axis=3)([feat4, P5_up])
P4 = Conv2D(channels[3], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P4)
P4 = Conv2D(channels[3], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P4)
P4_up = UpSampling2D(size=(2, 2))(P4)
P3 = Concatenate(axis=3)([feat3, P4_up])
P3 = Conv2D(channels[2], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P3)
P3 = Conv2D(channels[2], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P3)
P3_up = UpSampling2D(size=(2, 2))(P3)
P2 = Concatenate(axis=3)([feat2, P3_up])
P2 = Conv2D(channels[1], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P2)
P2 = Conv2D(channels[1], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P2)
P2_up = UpSampling2D(size=(2, 2))(P2)
P1 = Concatenate(axis=3)([feat1, P2_up])
P1 = Conv2D(channels[0], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P1)
P1 = Conv2D(channels[0], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P1)
P1 = Conv2D(num_classes, 1, activation="softmax")(P1)
model = Model(inputs=inputs, outputs=P1)
return model
複制
3、利用特征獲得預測結果
利用1、2步,我們可以擷取輸入進來的圖檔的特征,此時,我們需要利用特征獲得預測結果。
利用特征獲得預測結果的過程為:
利用一個1×1卷積進行通道調整,将最終特征層的通道數調整成num_classes。
import numpy as np
from keras.models import *
from keras.layers import *
from nets.vgg16 import VGG16
def Unet(input_shape=(256,256,3), num_classes=21):
inputs = Input(input_shape)
feat1, feat2, feat3, feat4, feat5 = VGG16(inputs)
channels = [64, 128, 256, 512]
P5_up = UpSampling2D(size=(2, 2))(feat5)
P4 = Concatenate(axis=3)([feat4, P5_up])
P4 = Conv2D(channels[3], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P4)
P4 = Conv2D(channels[3], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P4)
P4_up = UpSampling2D(size=(2, 2))(P4)
P3 = Concatenate(axis=3)([feat3, P4_up])
P3 = Conv2D(channels[2], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P3)
P3 = Conv2D(channels[2], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P3)
P3_up = UpSampling2D(size=(2, 2))(P3)
P2 = Concatenate(axis=3)([feat2, P3_up])
P2 = Conv2D(channels[1], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P2)
P2 = Conv2D(channels[1], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P2)
P2_up = UpSampling2D(size=(2, 2))(P2)
P1 = Concatenate(axis=3)([feat1, P2_up])
P1 = Conv2D(channels[0], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P1)
P1 = Conv2D(channels[0], 3, activation='relu', padding='same', kernel_initializer='he_normal')(P1)
P1 = Conv2D(num_classes, 1, activation="softmax")(P1)
model = Model(inputs=inputs, outputs=P1)
return model
複制
二、訓練部分
1、訓練檔案詳解
我們使用的訓練檔案采用VOC的格式。
語義分割模型訓練的檔案分為兩部分。
第一部分是原圖,像這樣:
第二部分标簽,像這樣:
原圖就是普通的RGB圖像,标簽就是灰階圖或者8位彩色圖。
原圖的shape為[height, width, 3],标簽的shape就是[height, width],對于标簽而言,每個像素點的内容是一個數字,比如0、1、2、3、4、5……,代表這個像素點所屬的類别。
語義分割的工作就是對原始的圖檔的每一個像素點進行分類,是以通過預測結果中每個像素點屬于每個類别的機率與标簽對比,可以對網絡進行訓練。
2、LOSS解析
本文所使用的LOSS由兩部分組成:
1、Cross Entropy Loss。
2、Dice Loss。
Cross Entropy Loss就是普通的交叉熵損失,當語義分割平台利用Softmax對像素點進行分類的時候,進行使用。
Dice loss将語義分割的評價名額作為Loss,Dice系數是一種集合相似度度量函數,通常用于計算兩個樣本的相似度,取值範圍在[0,1]。
計算公式如下:
就是預測結果和真實結果的交乘上2,除上預測結果加上真實結果。其值在0-1之間。越大表示預測結果和真實結果重合度越大。是以Dice系數是越大越好。
如果作為LOSS的話是越小越好,是以使得Dice loss = 1 – Dice,就可以将Loss作為語義分割的損失了。
實作代碼如下:
def dice_loss_with_CE(beta=1, smooth = 1e-5, alpha = 0.25, gamma=2.0, threhold=0.5):
def _dice_loss_with_CE(y_true, y_pred):
y_pred = K.clip(y_pred, K.epsilon(), 1.0 - K.epsilon())
CE_loss = - y_true[...,:-1] * K.log(y_pred)
CE_loss = K.mean(K.sum(CE_loss, axis = -1))
tp = K.sum(y_true[...,:-1] * y_pred, axis=[0,1,2])
fp = K.sum(y_pred , axis=[0,1,2]) - tp
fn = K.sum(y_true[...,:-1], axis=[0,1,2]) - tp
score = ((1 + beta ** 2) * tp + smooth) / ((1 + beta ** 2) * tp + beta ** 2 * fn + fp + smooth)
score = tf.reduce_mean(score)
dice_loss = 1 - score
# dice_loss = tf.Print(dice_loss, [dice_loss, focal_loss])
return CE_loss + dice_loss
return _dice_loss_with_CE
複制
訓練自己的Unet模型
首先前往Github下載下傳對應的倉庫,下載下傳完後利用解壓軟體解壓,之後用程式設計軟體打開檔案夾。
注意打開的根目錄必須正确,否則相對目錄不正确的情況下,代碼将無法運作。
一定要注意打開後的根目錄是檔案存放的目錄。
一、資料集的準備
本文使用VOC格式進行訓練,訓練前需要自己制作好資料集,如果沒有自己的資料集,可以通過Github連接配接下載下傳VOC12+07的資料集嘗試下。
訓練前将圖檔檔案放在VOCdevkit檔案夾下的VOC2007檔案夾下的JPEGImages中。
訓練前将标簽檔案放在VOCdevkit檔案夾下的VOC2007檔案夾下的SegmentationClass中。
二、資料集的處理
在完成資料集的擺放之後,我們需要對資料集進行下一步的處理,目的是獲得訓練用的train.txt以及val.txt,需要用到根目錄下的voc_annotation.py。
如果下載下傳的是我上傳的voc資料集,那麼就不需要運作根目錄下的voc_annotation.py。
如果是自己制作的資料集,那麼需要運作根目錄下的voc_annotation.py,進而生成train.txt和val.txt。
三、開始網絡訓練
通過voc_annotation.py我們已經生成了train.txt以及val.txt,此時我們可以開始訓練了。訓練的參數較多,大家可以在下載下傳庫後仔細看注釋,其中最重要的部分依然是train.py裡的num_classes。
num_classes用于指向檢測類别的個數+1!訓練自己的資料集必須要修改!
之後就可以開始訓練了。
四、訓練結果預測
訓練結果預測需要用到兩個檔案,分别是unet.py和predict.py。
我們首先需要去unet.py裡面修改model_path以及num_classes,這兩個參數必須要修改。
model_path指向訓練好的權值檔案,在logs檔案夾裡。
num_classes指向檢測類别的個數+1。
完成修改後就可以運作predict.py進行檢測了。運作後輸入圖檔路徑即可檢測。
釋出者:全棧程式員棧長,轉載請注明出處:https://javaforall.cn/148888.html原文連結:https://javaforall.cn