医学图像分割——Unet
這是一個學習記錄博~可能有錯,歡迎討論
P.S. 本文所用的unet源碼來自Unet源碼。
目標
實現胃部超聲圖像的病灶分割
醫學數據以及預處理簡介
醫學圖像的數據格式十分復雜,數據形式有什么CT圖像,MRI圖像(目前還沒接觸過~),超聲圖像等(詳細信息可以參考:醫學圖像了解。),而數據格式上有諸如dicom(.dcm,.IMA,常用的python處理庫有:SimpleITK, pydicom庫),Nifit(.nii或.nii.gz,常用的python處理庫有:SimpleITK, nibabel庫),NRRD(.nrrd,常用的python處理庫有:SimpleITK,pynrrd庫)等。
這里再插一句,對于CT圖像呢,有圖像值與HU值這個概念,CT值屬于醫學領域的概念,通常稱亨氏單位(hounsfield unit ,HU),反映了組織對X射線吸收程度,范圍一般是[-1024,3071]。圖像值屬于計算機領域的概念,例如灰度圖像的范圍一般是[0,255]。
在網上有看到這樣的解釋(CT圖像之Hu值變換與窗寬窗位調整):
對于nii格式的圖片,經過測試,nibabel, simpleitk常用的api接口,都會自動的進行上述轉化過程,即取出來的值已經是Hu了。(除非專門用nib.load('xx').dataobj.get_unscaled()或者itk.ReadImage('xx').GetPixel(x,y,z)才能取得原始數據)對于dcm格式的圖片,經過測試,simpleitk, pydicom常用的api接口都不會將原始數據自動轉化為Hu!!(itk snap軟件讀入dcm或nii都不會對數據進行scale操作)但我認為這個解釋不是很對,因為我用simpleItk讀取dcm格式的數據時,得出來的值就是Hu值(我也不太明白為什么),所以我覺得大家還是把數據打印出來看看,自己判斷一下是不是需要轉Hu值吧。
對CT圖像,在Hu值這個問題解決好之后,就要進行窗寬窗位的預處理了(窗寬窗位的解釋參考來自CT圖像之Hu值變換與窗寬窗位調整)。窗寬(window width)和窗位(window center),是用于選擇感興趣的CT值范圍的,因為各種組織結構或病變具有不同的CT值,因此想要獲得某一組織結構的最佳顯示時,要選擇適合觀察該組織或病變的窗寬和窗位。
舉個例子,CT原圖可能是這樣的:
而在選取了窗寬窗位并對原圖進行了clip之后的圖是這樣的:
是不是清晰了很多呢。
本實驗數據及預處理
實驗數據
實驗使用的數據為胃部超聲圖像,這里使用的超聲數據是一般導出jpg格式的(一般會轉成nrrd或者dcm來勾畫病灶)。
數據預處理
增強對比度
超聲圖像有的會比較暗,需要增加一下對比度的話可以使用如python+opencv直方圖均衡化,以及tensorflow中常見的圖片預處理操作等方法。
數據增強
數據量不夠的情況下需要進行數據增強,可以使用keras中的Data generator來進行操作,具體做法可以參考Unet源碼。
提取包含病灶的slices
CT圖像一般都很多的slices組成,很多時候病灶不會在全部的slices上出現,所以需要我們提取一下包含有病灶的slices,雖然本實驗不涉及這個問題,但還是介紹一下吧。這個過程呢,就是檢測一下mask數據,看一下在該層slice上是否有病灶出現,有就保留該slice,沒有就舍棄。
def getRangImageDepth(image):"""args:image ndarray of shape (depth, height, weight)"""# 得到軸向上出現過目標(label>=1)的切片### image.shape為(depth,height,width),否則要改axis的值z = np.any(image, axis=(1,2)) # z.shape:(depth,)### np.where(z)[0] 輸出滿足條件,即true的元素位置,返回為一個列表,用0,-1取出第一個位置和最后一個位置的值startposition,endposition = np.where(z)[0][[0,-1]]return startposition, endpositionUnet訓練中需要注意的幾個點
這個Unet源碼是用keras寫的,代碼比較簡單易懂,所以這里不對代碼做過多的介紹,如有問題,可以下面留言,這里只記錄一下容易踩坑的點~
圖像的位深
如果采用的數據是jpg格式的,可能會有圖像位深問題的存在,從而導致預測失敗,具體可以參考Unet訓練自己的數據集,預測結果全黑。
檢查數據類型
在這個Unet源碼的data.py文件中有對圖像做一些預處理,比如reshape成網絡所需的4維張量,以及對圖像/255以將uint8轉為網絡所需的float型,這個根據自己的數據可能需要對代碼進行一些修改,所以不要忘記檢查這部分。
輸入圖像大小的設置
unet中會下采樣4次,并且還要在上采樣時還要與下采樣中的特征圖進行concatenate,因此,當圖片的size在下采樣時不能一直維持在偶數時,上采樣進行特征結合就會出現問題。例如,37x37的下采樣變成了18x18,而18x18在上采樣時會變成36x36,在進行concatenate時,36x36和37x37就會出現大小不匹配的問題。
對于這個問題,簡單一點的解決方法就是把圖片設置成每次下采樣都能滿足偶數的大小,如256x256;如果不想這樣的話,我們也可以用tf.pad對圖像張量進行填充,舉例:
### 判斷concatenate的特征圖大小是否一致 if drop4.get_shape().as_list() != up6.get_shape().as_list():# _,height, width, depth = up6.shape# 只要你使用Model,就必須保證該函數內全為layer,不能有其他函數,如果有其他函數必須用Lambda封裝為layerup6_padded = Lambda(lambda x: tf.pad(x, [[0, 0], [1, 0], [0, 0], [0, 0]], 'REFLECT'))(up6)merge6 = concatenate([drop4, up6_padded], axis=3) else:merge6 = concatenate([drop4, up6], axis=3)這里對tf.pad進行一下簡要說明,
tf.pad(tensor,paddings,mode='CONSTANT',name=None )- tensor是待填充的張量
- paddings指出要給tensor的哪個維度進行填充,以及填充方式,要注意的是paddings的rank必須和tensor的rank相同
- mode指出用什么進行填充,可以取三個值,分別是"CONSTANT" ,“REFLECT”,“SYMMETRIC,mode=“CONSTANT” 填充0;mode="REFLECT"映射填充,按邊緣翻且不復制邊緣;mode="SYMMETRIC"對稱填充,從對稱軸開始復制,把邊緣也復制了。
- name是這個節點的名字
在本實驗中,我們的tensor是一個四維張量, [[0, 0], [1, 0], [0, 0], [0, 0]]分別對應[,height,width,depth],0表示不進行填充, [1, 0]表示對在圖片上邊填充一行,下邊不填充,如果設置為[1,1]的話就代表在圖片上下各填充1行;同樣的,如果width為[2,3]就表示在圖片的左邊填充2列,右邊填充3列。
更換loss函數
Unet源碼中采用的是交叉熵損失來進行訓練的,對于醫學圖像而言,有時候需要分割的病灶很小,這時候使用交叉熵損失有訓練失敗的可能,需要更換loss函數,這里有一個講解的比較詳細的博客,可以參考一下,從loss處理圖像分割中類別極度不均衡的狀況—keras。
例如將交叉熵損失換成Dice loss,具體做法為:
def dice_coef(y_true, y_pred, smooth=1):intersection = K.sum(y_true * y_pred, axis=[1,2,3])union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0)def dice_coef_loss(y_true, y_pred):return 1 - dice_coef(y_true, y_pred, smooth=1)然后將modle.compile改一下:
model.compile(optimizer = Adam(lr = 1e-5), loss = dice_coef_loss, metrics = [dice_coef])更改學習率、batchsize和epoch
學習率不對也可能會導致預測失敗,所以需要自己調整,然后在測試看看。
還有batchsize和epoch也要根據自己的數據集的情況進行調整。
emmm…暫時就這么多啦~
總結
以上是生活随笔為你收集整理的医学图像分割——Unet的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 教育行业是永恒不过时的常青藤行业!
- 下一篇: Dense biased network