基於 keras的全卷積網絡u-net端到端醫學圖像多類型圖像分割(一)

有醫院的朋友,需要幫忙完成一個圖像分割的任務,提供了一些數據,看了下數據,灰度圖,覺得設計特徵再做分割太麻煩。直接整神經網絡吧。不用費神設計特徵,省事,畢竟只是幫個忙而已。

1. 查找方案

顯然,這個任務,早有前人做過無數次了,這麼熱點的領域,簡直一搜一大把。搜索結果,是用 u-net 做醫學圖像分割的較多,於是決定使用u-net。關於FCN的介紹,看這個博客吧,本文着重於代碼實現!

FCN介紹

考慮到任務的價值和擼代碼的便利性,決定使用keras,畢竟這只是一個任務。

使用kears 做圖像分割,CSDN 有一篇很容易搜到的文章(文章鏈接在本文末尾),還附了github地址,簡直得來全不費工夫,立馬下下來,準備直接換數據跑完代碼收工。顯然… 我還是太年輕。

2. 坑

原作者的代碼的測試是二分類的,但我要跑的數據與標記如下:
注:左邊原圖,右邊mask,三類,mask=0,128,255 各爲一類。
左邊原圖,右邊mask
數據是多分類的,從此埋下了深深地禍根!
先來一個個看吧。

  • ValueError: Error when checking target: expected conv2d_24 to have 4 dimensions, but got array with shape (2, 65536, 3)

這個問題其實見得比較多了,神經網絡圖像初學時比較容易出現類似的問題,於是檢查代碼,根據提示定位到如下代碼段:

def adjustData(img,mask,flag_multi_class,num_class):
    if(flag_multi_class):
        img = img / 255
        mask = mask[:,:,:,0] if(len(mask.shape) == 4) else mask[:,:,0]
        new_mask = np.zeros(mask.shape + (num_class,))
        for i in range(num_class):
            #for one pixel in the image, find the class in mask and convert it into one-hot vector
            #index = np.where(mask == i)
            #index_mask = (index[0],index[1],index[2],np.zeros(len(index[0]),dtype = np.int64) + i) if (len(mask.shape) == 4) else (index[0],index[1],np.zeros(len(index[0]),dtype = np.int64) + i)
            #new_mask[index_mask] = 1
            new_mask[mask == i,i] = 1
        new_mask = np.reshape(new_mask,(new_mask.shape[0],new_mask.shape[1]*new_mask.shape[2],new_mask.shape[3])) if flag_multi_class else np.reshape(new_mask,(new_mask.shape[0]*new_mask.shape[1],new_mask.shape[2]))
        mask = new_mask
    elif(np.max(img) > 1):
        img = img / 255
        mask = mask /255
        mask[mask > 0.5] = 1
        mask[mask <= 0.5] = 0
    return (img,mask)

由於是多分類,設置flag_multi_class=True,num_class=3,可以看到代碼將走向前段,這樣mask將會被reshape成(65536,3),至於前面的2 是 batch_size,此時明確了label的形狀,就證明網絡輸出層與label不匹配導致錯誤,於是查看模型代碼。

    conv10 = Conv2D(1, 1, activation='sigmoid')(conv9) #其實conv2d_24 就是這裏的conv10
    model = Model(input=inputs, output=conv10)
    model.compile(optimizer=Adam(lr=1e-4), loss='binary_crossentropy', metrics=['accuracy'])

根據model的輸出形狀可推出 label形狀應該是(2,256,256,1)。而我們提供的mask是(2,256*256,3),所以報錯。
明確了錯誤就很好修改了!

  • 後面又其實前段代碼還是有健壯性的問題(鑑於作者寫的時候應該還沒畢業,默默的原諒了)
        for i in range(num_class):
            new_mask[mask == i,i] = 1 #去掉了註釋

這段代碼是指mask中有與類型相等的值時,添加爲這層的label。 我的mask怎麼會是0,1,2這種呢,而且一般的mask都是0,128,256這種易於區分的值啊…尷尬
自己擼代碼:

def adjustData(img,mask,flag_multi_class,num_class):
    if(flag_multi_class):
        img = img / 255.
        mask = mask[:,:,:,0] if(len(mask.shape) == 4) else mask[:,:,0]
        new_mask = np.zeros(mask.shape + (num_class,))
        new_mask[mask == 0,0] = 1
        new_mask[mask == 128, 1] = 1
        new_mask[mask > 200, 2] = 1
        mask = new_mask
        # print('new 0 :',np.sum(mask==1))
        # print('new 128 :', np.sum(mask ==2))
        # print('new 255 :', np.sum(mask == 3))
        # print('new sum :',np.sum(mask==1)+np.sum(mask==2)+np.sum(mask==3))
    elif(np.max(img) > 1):
        img = img / 255.
        mask = mask /255.
        mask[mask > 0.5] = 1
        mask[mask <= 0.5] = 0
    return (img,mask)

這個修改不再reshape mask,並且將mask處理成one-hot編碼。即按照數據集中mask的值 0,128,256 進行分類疊加。如果mask圖像相應的值是 0
,那麼,處理後的值爲[1,0,0],當mask=128時爲[0,1,0],255時爲[0,0,1]。這樣就完成了標記數據的轉換。處理後的label shape 爲(2,256,256,3),與網絡輸出還有區別網絡爲(2,256,256,1)所以我們要對網絡再進行改進。

    conv10 = Conv2D(3, 1, activation='sigmoid')(conv9) #修改原來的1爲3,此時網絡有3通道輸出。
    model = Model(input=inputs, output=conv10)
    model.compile(optimizer=Adam(lr=1e-4), loss='binary_crossentropy', metrics=['accuracy'])

這樣就能完成多分類的訓練了。
後面發現,訓練了多個epoch(40次左右),輸出圖像仍爲純白,再次回顧網絡結構,與loss函數。發現居然用的binary_crossentropy,坑了個爹的,只能用於二分類。修改成categorical_crossentropy 又不收斂,唉,本來不想花時間的東西,居然已經弄了幾個小時。
至此,已經發現要改的東西比較多。老老實實再繼續吧弄吧。

3. 目標很明確,端到端多類型圖像分割!

後面借鑑github的代碼做了端到端的多分類,將在(二)裏面介紹,考慮用u-net做多分類的可以看下,github地址:Keras-u-net,歡迎star。

注:分析代碼原文地址:文章鏈接
端到端多分類代碼地址:Keras-u-net

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章