基于U-Net的的图像分割代码详解及应用实现
摘要
U-Net是基于卷積神經(jīng)網(wǎng)絡(CNN)體系結構設計而成的,由Olaf Ronneberger,Phillip Fischer和Thomas Brox于2015年首次提出應用于計算機視覺領域完成語義分割任務。(其發(fā)表U-Net的作者建議該模型能夠很好的應用于生物醫(yī)學圖像分割)。而本文將從U-Net的應用領域、模型原理、具體應用三個方面具體概述U-Net神經(jīng)網(wǎng)絡的實現(xiàn)方式及應用技術。最后,本文基于keras神經(jīng)網(wǎng)絡框架設計并實現(xiàn)U-Net模型在EM神經(jīng)元數(shù)據(jù)集上的 分割應用,其實現(xiàn)結果現(xiàn)實,U-Net在EM 堆棧神經(jīng)元的分割任務上具有較好的實驗結果。
1.計算機視覺任務概述
1.1 語義分割解析(Semantic Segmentation)
1.1.1 定義
圖像語義分割(Semantic Segmentation)是圖像處理和是機器視覺技術中關于圖像理解的重要一環(huán),也是 AI 領域中一個重要的分支。語義分割即是對圖像中每一個像素點進行分類,確定每個點的類別(如屬于背景、人或車等),從而進行區(qū)域劃分。目前,語義分割已經(jīng)被廣泛應用于自動駕駛、無人機落點判定等場景中。
1.1.2 實例解釋
圖像分類
舉個例子進行描述:
在圖像領域,我們常見的任務是對圖像的分類任務,該任務是較為粗粒度的來識別和理解圖像。即是我們給定一張圖像,我們期望模型輸出該張圖像所屬的類別(離散標簽),而在圖像分類任務中,我們假設圖像中只有一個(而不是多個)對象。描述如下圖:
?但是,語義分割則在圖像分類任務上更為細化。其需要經(jīng)歷三個過程:圖像定位、目標檢測、語義分割(及實例分割)。其中,目標檢測( Object detection)不僅需要提供圖像中物體的類別,還需要提供物體的位置(bounding box)。語義分割( Semantic segmentation)需要預測出輸入圖像的每一個像素點屬于哪一類的標簽。實例分割( instance segmentation)在語義分割的基礎上,還需要區(qū)分出同一類不同的個體。
圖像定位
在與離散標簽一起輸出時(完成分類任務過程中),我們還期望模型能夠準確定位圖像中存在該物體的位置。這種定位通常使用邊界框來實現(xiàn),邊界框可以通過一些關于圖像邊界的數(shù)值參數(shù)來識別。其如下圖所示:
目標檢測
與圖像定位不同的是,目標檢測需要判斷圖像中物體的類別,還判斷指出提供圖像中物體的具體位置(bounding box)。即是現(xiàn)在圖像不再局限于只有一個對象,而是可以包含多個對象。任務是對圖像中的所有對象進行分類和定位。這里再次使用邊界框的概念進行定位。其如下圖所示:
語義分割
語義分割通過對每個像素進行密集的預測、推斷標簽來實現(xiàn)細粒度的推理,從而使每個像素都被標記為其封閉對象礦石區(qū)域的類別。即是需要判斷圖像每個像素點的類別,進行精確分割。圖像語義分割是像素級別的!因為我們要對圖像中的每個像素進行預測,所以這個任務通常被稱為密集預測。其如下圖所示:
實例分割
實例分割比語義分割是更為細致的下游任務,其中與像素級的分類任務處理方式一樣,我們希望模型分別對類的每個實例進行分類。例如,圖中有 3 個人,嚴格來說是“Person”類的 3 個實例。所有這3個分別分類(以不同的顏色)。其如下圖所示:
?目標檢測、語義分割、實例分割三個像素級別的分類任務,其區(qū)別如下圖所示。
1.2 應用領域
1.2.1 自動駕駛汽車
自動駕駛是一項極為復雜的深度學習應用任務,其需要模型在不斷變化的環(huán)境中進行感知、規(guī)劃和執(zhí)行相關任務(既是汽車避免路障等問題)。因為人生安全是最重要的,所以這項任務需要最為精準的模型運行效果。語義分割可提供有關道路上的障礙信息、檢測路況標記和交通標志等信息。
1.2.2?生物醫(yī)學影像診斷(精準醫(yī)療)
這就是本文將要實現(xiàn)的具體應用了。其實現(xiàn)的最終模型可以減少放射科醫(yī)生的分析時間,為相關疾病的診療提供輔助和技術支持。
1.2.3?地理傳感
語義分割問題也可以被視為分類問題(其最終的解決方式),其中每個像素被歸類為一系列對象類別中的一個。因此,可用于衛(wèi)星圖像的土地使用制圖。例如監(jiān)測森林砍伐和城市化區(qū)域。道路和建筑物檢測也是交通管理、城市規(guī)劃和道路監(jiān)測的重要研究課題等。
1.2.4 精準農業(yè)
農業(yè)機器人可以減少田間需要噴灑的除草劑次數(shù)和劑量,通過對作物和雜草進行語義分割,實時協(xié)助它們觸發(fā)除草動作。
2. U-Net模型解釋
2.1 U-Net模型概述
U-Net的U形結構如圖所示。網(wǎng)絡是一個經(jīng)典的全卷積網(wǎng)絡(即網(wǎng)絡中沒有全連接操作)。網(wǎng)絡的輸入是一張?572*572?的邊緣經(jīng)過鏡像操作的圖片(input image tile),關于“鏡像操作“會在接下來進行詳細分析,網(wǎng)絡的左側(紅色虛線)是由卷積和Max Pooling構成的一系列降采樣操作,論文中將這一部分叫做壓縮路徑(contracting path)。壓縮路徑由4個block組成,每個block使用了3個有效卷積和1個Max Pooling降采樣,每次降采樣之后Feature Map的個數(shù)乘2,因此有了圖中所示的Feature Map尺寸變化。最終得到了尺寸為 32*32的Feature Map。
? 網(wǎng)絡的右側部分(綠色虛線)在論文中叫做擴展路徑(expansive path)。同樣由4個block組成,每個block開始之前通過反卷積將Feature Map的尺寸乘2,同時將其個數(shù)減半(最后一層略有不同),然后和左側對稱的壓縮路徑的Feature Map合并,由于左側壓縮路徑和右側擴展路徑的Feature Map的尺寸不一樣,U-Net是通過將壓縮路徑的Feature Map裁剪到和擴展路徑相同尺寸的Feature Map進行歸一化的(即圖1中左側虛線部分)。擴展路徑的卷積操作依舊使用的是有效卷積操作,最終得到的Feature Map的尺寸是 388*388。由于該任務是一個二分類任務,所以網(wǎng)絡有兩個輸出Feature Map。
?
首先,數(shù)據(jù)集我們的原始圖像的尺寸都是 512*512?的。為了能更好的處理圖像的邊界像素,U-Net使用了鏡像操作(Overlay-tile Strategy)來解決該問題。鏡像操作即是給輸入圖像加入一個對稱的邊(圖2),那么邊的寬度是多少呢?一個比較好的策略是通過感受野確定。因為有效卷積是會降低Feature Map分辨率的,但是我們希望 515*512的圖像的邊界點能夠保留到最后一層Feature Map。所以我們需要通過加邊的操作增加圖像的分辨率,增加的尺寸即是感受野的大小,也就是說每條邊界增加感受野的一半作為鏡像邊。
2.2?U-Net 和 autoencoder 架構的區(qū)別
U-Net模型與傳統(tǒng)的圖像分割方法(自動編碼器體系結構)有所區(qū)別。傳統(tǒng)的經(jīng)典圖像分割方法是最初輸入信息的大小隨著層數(shù)的增加其特征信息在不斷減少,至此,自動編碼器體系結構的編碼器部分完成,開始解碼器部分。而U-Net模型學習線性特征表示,其特征大小在逐漸增大,其最后的輸出大小等于輸入大小。
既是整個Auto-Encoder有兩個吸引人的應用:1)利用前半部分做特征提取;2)利用后半部分做圖像生成。
然而,U-Net結構和Auto-Encoder的傳統(tǒng)結構十分相似,但是它獨特的前傳結構讓網(wǎng)絡能夠capture到很多空間的信息,U-Net的直接前傳可以保留很多空間信息,既其前半部分是降采樣,下半部分是升采樣。
2.3?U-Net模型代碼詳解
在這部分代碼實現(xiàn)過程中一定要注意調整卷積核的大小和padding的大小,這樣才可以在最后保證圖片的尺寸恢復到和原來一樣。
inputs = Input(input_size)conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(inputs)conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1)pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1)conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2)pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2)conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3)pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3)conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4)drop4 = Dropout(0.5)(conv4)pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4)conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5)drop5 = Dropout(0.5)(conv5)up6 = Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(drop5))merge6 = concatenate([drop4,up6], axis = 3)conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6)conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6)up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv6))merge7 = concatenate([conv3,up7], axis = 3)conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7)conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7)up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv7))merge8 = concatenate([conv2,up8], axis = 3)conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8)conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8)up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))merge9 = concatenate([conv1,up9], axis = 3)conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9)conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)conv9 = Conv2D(2, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)conv10 = Conv2D(1, 1, activation = 'sigmoid')(conv9)model = Model(input = inputs, output = conv10)from keras.utils.vis_utils import plot_modelplot_model(model, to_file='model1.png',show_shapes=True)3. 應用實現(xiàn)
3.1 數(shù)據(jù)集介紹
數(shù)據(jù)來自于 ISBI 挑戰(zhàn)的數(shù)據(jù)集。該數(shù)據(jù)集包含30張訓練圖、30張對應的標簽。30張測試圖片。數(shù)據(jù)是來自果蠅一齡幼蟲腹神經(jīng)索 (VNC) 的串行部分透射電子顯微鏡 (ssTEM)的圖像,其分辨率為4x4x50 nm /像素。
3.2 模型設計與實現(xiàn)
實驗環(huán)境:python3.6.5;tensorflow==1.12;keras==2.2.4等;
上述2.3實現(xiàn)的 U-Net模型其參數(shù)過多,在自己電腦上運行過慢,為了快速建立模型并實現(xiàn)應用,我們設計了更小是U-Net模型,其實現(xiàn)后的結構如下所示:
其模型的訓練采用如下代碼進行實現(xiàn)(添加了數(shù)據(jù)增強):
ata_gen_args = dict(rotation_range=0.2,width_shift_range=0.05,height_shift_range=0.05,shear_range=0.05,zoom_range=0.05,horizontal_flip=True,fill_mode='nearest') myGene = trainGenerator(2,'data/membrane/train','image','label',data_gen_args,save_to_dir = None)model = unet() model_checkpoint = ModelCheckpoint('unet_membrane.hdf5', monitor='loss',verbose=1, save_best_only=True) model.fit_generator(myGene,steps_per_epoch=3,epochs=5,callbacks=[model_checkpoint],validation_data=())其實驗結果的準確率曲線與模型訓練次數(shù)的曲線如下所示:
?從圖中我們發(fā)現(xiàn) U-Net模型在經(jīng)過簡化之后,其在細胞分割任務中已經(jīng)達到了不錯的訓練效果結果,其準確率高達78%,為了進一步提高模型準確率,我們需要在模型設計環(huán)節(jié)設計完整的 U-Net模型,并加大模型的訓練次數(shù),這樣就能使模型很輕松的達到95%左右。
3.3 結果展示
輸入原始圖像:
??
模型輸出圖像:?
4.結論
本文主要介紹了U-net模型在細胞分割中的相關基本概念及一個具體應用,下一步,將進一步探究將每兩個相鄰的卷積層替換為殘差結構,并在收縮路徑和擴張路徑中間加入并聯(lián)在一起的位置注意力模塊和通道注意力模塊以進一步提高U-net模型的應用效果。
?
?
總結
以上是生活随笔為你收集整理的基于U-Net的的图像分割代码详解及应用实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue 商城浏览足迹_vue 移动端记录
- 下一篇: Unity5.联机笔记