飞桨学习赛:眼底OCT层分割(GOALS挑战赛任务一)第二名方案

★★★ 本文源自AI Studio社区精品项目,【点击此处】查看更多精品内容 >>>


飞桨学习赛:眼底OCT层分割-2022年12月赛段第2名


眼底OCT层分割(GOALS挑战赛任务一)

赛题介绍

光学相干断层扫描(OCT)因其无接触、非侵入性的特点,已成为眼部疾病诊疗中的常规检查,可为医生提供视网膜结构图像。与只能提供视网膜表面信息的彩色眼底图像相比,OCT图像可以提供视网膜的横断面信息,因此可以更准确地分析视网膜结构。层的分割和厚度量化对许多视网膜和视神经疾病的诊断有帮助,例如青光眼、黄斑变性或糖尿病性视网膜病变。在青光眼的诊断中,使用OCT比使用眼底彩色图像更容易发现早期病例。本学习赛是环扫OCT图像的层分割任务,目的是确定视网膜神经纤维层、神经节细胞丛层和脉络膜层,这有助于青光眼的诊断和区分。

数据集与标签文件


训练集解压文件包含如下:

  • Image 原始图片
  • Layer_Masks 标签
  • Layer_show 可视化图片(训练无需使用)
  • License-GOALS-0606.pdf 使用证书

数据集是一个标准的医学影像数据集,医学影像的特点是数据少。很多时候许多图片还是来自于1个病人,并且附加的病人文本信息也作为1个输入,这样的多模态比赛在其他竞赛平台上很常见。另外1个特点是,医学影像多是灰度图。因为人体内是无法直接获取的。早期的机器学习时期就有在医学影像的应用,深度学习火热后,效果远远超越其他方法,已经被验证为一个有效的策略。

一.环境搭建

import numpy as np
import cv2
import matplotlib.pyplot as plt
# 切换到工作目录
%cd /home/aistudio/work
# 解压下载好的PaddleSeg
!unzip PaddleSeg.zip
!mv PaddleSeg /home/aistudio
# 安装PaddleSeg依赖
%cd PaddleSeg
!pip install -r requirements.txt

二.数据预处理

前面提到医学图像的特点,除了不同仪器产生的多模态,还有就是灰度图。对于OCT图像的像素其实还好,CT的图像分布非常大,没有足够经验的人,根本无法从原图像获取身体的各个部位。这个demo里除了PaddleSeg,主要就是额外曾加了应对这一问题的方法:windowing。同时还有噪声的问题,医学图常常伴有噪声(比如仪器造成的)。如果不做特殊处理,算法有时会把病变位置当作噪声处理掉。这也是为何使用windowing的原因。

windowing是图像常用的处理手段,其作用是通过线性变换调整图像的像素值,最终使得图像易于分辨。windowing有2个参数,分别是窗宽和窗位。
简单来说:

窗宽小,对比度高,界限清晰。

窗宽大,对比度低,界限模糊。

窗位低,亮度高

窗位高,亮度低

# 解压数据集
%cd /home/aistudio/data/data170514

!unzip -o GOALS2022-Train.zip
!unzip -o GOALS2022-Validation.zip

! mv Train/Image /home/aistudio/PaddleSeg/data/mine/
! mv Train/Layer_Masks /home/aistudio/PaddleSeg/data/mine/
! mv GOALS2022-Validation/Image /home/aistudio/PaddleSeg/data/mine/Test

统计所有图像的像素值分布,进而获取原始数据集中的窗宽分布。

datapath='/home/aistudio/PaddleSeg/data/mine/' 
data=cv2.imread(datapath+'Image/0001.png',0)
for i in range(2,101):
        name = str(i).zfill(4)
        image = cv2.imread(datapath+'Image/'+name+'.png',0)
        data=np.concatenate((data,image),axis=1)
print(data.shape)
plt.hist(data.reshape(-1,),bins=50)
plt.show()
(800, 110000)

在这里插入图片描述

#给定windowing自定义函数
def windowing(img, window_width, window_center):
    #img: 需要增强的图片
    #window_width:窗宽
    #window_center:中心
    minWindow = float(window_center)-0.5*float(window_width)
    new_img = (img-minWindow)/float(window_width)
    new_img[new_img<0] = 0
    new_img[new_img>1] = 1
    return (new_img*255).astype('uint8') #把数据整理成标准图像格式
data1=cv2.imread(datapath+'Image/0001.png',0)
img_ct = windowing(data1,200,100)
plt.imshow(img_ct,cmap='gray')
plt.show()
cv2.imwrite("/home/aistudio/test.png",img_ct)

在这里插入图片描述

True
#依次增大图像的位宽,图像对比度降低。
#如果窗宽过低,部分低的像素会直接区分不出背景。所以对比度不能过高,也不能过低。
j = 1
data1=cv2.imread(datapath+'Image/0001.png',0)
for i in range(1,10):
    plt.subplot(3,3,j)
    img_ct = windowing(data1,i*30,150)
    plt.imshow(img_ct,cmap='gray')
    plt.axis('off')
    j += 1
plt.show()

在这里插入图片描述

#依次增大图像的窗中心(窗位的另外一个叫法),亮度降低
j = 1
data1=cv2.imread(datapath+'Image/0001.png',0)
for i in range(1,10):
    plt.subplot(3,3,j)
    img_ct = windowing(data1,150,i*30)
    plt.imshow(img_ct,cmap='gray')
    plt.axis('off')
    j += 1
plt.show()

在这里插入图片描述

!mkdir /home/aistudio/PaddleSeg/data/mine/image
class windowImg(object):
    def __init__(self,mode='train'):
        self.dataPath = '/home/aistudio/PaddleSeg/data/mine/'
        self.mode=mode
    def run(self):
        if self.mode=='train':
            for i in range(1,101):
                name = str(i).zfill(4)
                image = cv2.imread(self.dataPath+'Image/'+name+'.png',0)
                image = windowing(image,200,100)
                cv2.imwrite(self.dataPath+'image/'+name+'.png', image)
        elif self.mode=='test':
            for i in range(101,201):
                name = str(i).zfill(4)
                image = cv2.imread(self.dataPath+'Test/'+name+'.png',0)
                image = windowing(image,200,100)
                cv2.imwrite(self.dataPath+'test/'+name+'.png', image)
win = windowImg('train')
win.run()
!mkdir /home/aistudio/PaddleSeg/data/mine/Image_Label
class Pre(object):
    def __init__(self):
        self.dataPath = '/home/aistudio/PaddleSeg/data/mine/'
    def run(self):
        # 生成train.txt和valid.txt
        trainFile = open(self.dataPath + 'train.txt', 'w')
        vaildFile = open(self.dataPath + 'valid.txt', 'w')
        for i in range(1,101):
            name = str(i).zfill(4)
            if i <= 90:
                trainFile.write('image/'+name+'.png Image_Label/'+name+'.png')
                trainFile.write('\n')
            else:
                vaildFile.write('image/'+name+'.png Image_Label/'+name+'.png')
                vaildFile.write('\n')
            # 将标签图片转为标准标准图片
            image = cv2.imread(self.dataPath+'Layer_Masks/'+name+'.png')
            # 像素值为0的是RNFL(类别 0)
            # 像素值为80的是GCIPL(类别 1)
            # 像素值为160的是脉络膜(类别 2)
            # 像素值为255的是其他(类别3)。
            image[image == 80] = 1
            image[image == 160] = 2
            image[image == 255] = 3
            image = image[:,:,1]
            cv2.imwrite(self.dataPath+'Image_Label/'+name+'.png', image)
pre = Pre()
pre.run()

三.训练

模型

模型使用了早期的网络U-net。刚刚发现了官方baseline用的也是U net。这另一方面说明了图像类任务主要是预处理和后处理,比赛而言模型大部分时候不会改。

  • 医学图像的语义较为简单,每一个部分都是人的很小一部分。不同的器官也会用不同的器械。器官本身结构固定,所以对于一个语义分割任务只有那么几类,不像是自动驾驶,室外场景会碰到各种不同的类别。进而高级语义信息和底层特征都很重要。这样就是U net的特点,高层特征和底层特征融合,这样网络兼顾两者。
  • 数据量少。本数据只有100张图片,模型参数不宜多。

U net的结构如下图,是一个U型的结果。左右是encoder,包含若干的卷积、降采样层。右方是decoder上采样,同时会把左面的特征进行拼接。右侧反卷集,最后恢复到图像原始形状。

配置文件我已放到家目录下面,后面直接复制到对应路径,覆盖掉原始的配置文件。

首先是改了cityscapes,用于读取数据。

batch_size: 4
iters: 2000

train_dataset:
  type: Dataset
  dataset_root: data/mine
  train_path: data/mine/train.txt
  transforms:
    - type: ResizeStepScaling
      min_scale_factor: 0.5
      max_scale_factor: 2.0
      scale_step_size: 0.25
    - type: RandomPaddingCrop
      crop_size: [1024, 512]
    - type: RandomHorizontalFlip
    - type: RandomDistort
      brightness_range: 0.4
      contrast_range: 0.4
      saturation_range: 0.4
    - type: Normalize
  num_classes: 4
  mode: train

val_dataset:
  type: Dataset
  dataset_root: data/mine
  val_path: data/mine/valid.txt
  transforms:
    - type: Normalize
  num_classes: 4
  mode: val


optimizer:
  type: sgd
  momentum: 0.9
  weight_decay: 4.0e-5

lr_scheduler:
  type: PolynomialDecay
  learning_rate: 0.01
  end_lr: 0
  power: 0.9

loss:
  types:
    - type: CrossEntropyLoss
  coef: [1]

然后是修改了适配的模型yml

_base_: '../_base_/cityscapes.yml'

batch_size: 4
iters: 2000

model:
  type: UNet
  num_classes: 4
  use_deconv: False
  pretrained: Null

!cp /home/aistudio/unet_cityscapes_1024x512_160k.yml /home/aistudio/PaddleSeg/configs/unet
!cp /home/aistudio/cityscapes.yml /home/aistudio/PaddleSeg/configs/_base_

%cd /home/aistudio/PaddleSeg/

!export CUDA_VISIBLE_DEVICES=0 # 设置1张可用的卡

!python train.py \
       --config configs/unet/unet_cityscapes_1024x512_160k.yml \
       --save_interval 500 \
       --do_eval \
       --use_vdl \
       --save_dir output
# !python train.py \
#        --config  configs/unet_3plus/unet_3plus_cityscapes_1024x512_160k.yml \
#        --save_interval 500 \
#        --do_eval \
#        --use_vdl \
#        --save_dir output
# !python train.py \
#        --config configs/unet_plusplus/unet_plusplus_cityscapes_1024x512_160k.yml \
#        --save_interval 500 \
#        --do_eval \
#        --use_vdl \
#        --save_dir output

四.评估

!python val.py \
       --config configs/unet/unet_cityscapes_1024x512_160k.yml \
       --model_path output/best_model/model.pdparams
2023-01-06 01:06:30 [INFO]	
---------------Config Information---------------
batch_size: 4
iters: 2000
loss:
  coef:
  - 1
  types:
  - type: CrossEntropyLoss
lr_scheduler:
  end_lr: 0
  learning_rate: 0.01
  power: 0.9
  type: PolynomialDecay
model:
  num_classes: 4
  pretrained: null
  type: UNet
  use_deconv: false
optimizer:
  momentum: 0.9
  type: sgd
  weight_decay: 4.0e-05
train_dataset:
  dataset_root: data/mine
  mode: train
  num_classes: 4
  train_path: data/mine/train.txt
  transforms:
  - max_scale_factor: 2.0
    min_scale_factor: 0.5
    scale_step_size: 0.25
    type: ResizeStepScaling
  - crop_size:
    - 1024
    - 512
    type: RandomPaddingCrop
  - type: RandomHorizontalFlip
  - brightness_range: 0.4
    contrast_range: 0.4
    saturation_range: 0.4
    type: RandomDistort
  - type: Normalize
  type: Dataset
val_dataset:
  dataset_root: data/mine
  mode: val
  num_classes: 4
  transforms:
  - type: Normalize
  type: Dataset
  val_path: data/mine/valid.txt
------------------------------------------------
W0106 01:06:30.194481  9162 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 8.0, Driver API Version: 11.2, Runtime API Version: 11.2
W0106 01:06:30.194511  9162 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
2023-01-06 01:06:31 [INFO]	Loading pretrained model from output/best_model/model.pdparams
2023-01-06 01:06:31 [INFO]	There are 112/112 variables loaded into UNet.
2023-01-06 01:06:31 [INFO]	Loaded trained params of model successfully
2023-01-06 01:06:31 [INFO]	Start evaluating (total_samples: 10, total_iters: 10)...
10/10 [==============================] - 3s 323ms/step - batch_cost: 0.3226 - reader cost: 0.1258
2023-01-06 01:06:34 [INFO]	[EVAL] #Images: 10 mIoU: 0.8762 Acc: 0.9874 Kappa: 0.9258 Dice: 0.9323
2023-01-06 01:06:34 [INFO]	[EVAL] Class IoU: 
[0.8823 0.7811 0.8526 0.9888]
2023-01-06 01:06:34 [INFO]	[EVAL] Class Precision: 
[0.9181 0.9032 0.9203 0.9945]
2023-01-06 01:06:34 [INFO]	[EVAL] Class Recall: 
[0.9576 0.8524 0.9206 0.9943]

五.A榜图片

需要注意的是,我们在前面数据增强使用了windowing方案,则推理时必须做同样的处理

!mkdir /home/aistudio/PaddleSeg/data/mine/test
testWin = windowImg('test')
testWin.run()
!python predict.py \
       --config      configs/unet/unet_cityscapes_1024x512_160k.yml \
       --model_path output/best_model/model.pdparams \
       --image_path data/mine/test \
       --save_dir output/result \
       --custom_color 0 0 0 80 80 80 160 160 160 255 255 255

2023-01-06 01:07:02 [INFO]	
---------------Config Information---------------
batch_size: 4
iters: 2000
loss:
  coef:
  - 1
  types:
  - type: CrossEntropyLoss
lr_scheduler:
  end_lr: 0
  learning_rate: 0.01
  power: 0.9
  type: PolynomialDecay
model:
  num_classes: 4
  pretrained: null
  type: UNet
  use_deconv: false
optimizer:
  momentum: 0.9
  type: sgd
  weight_decay: 4.0e-05
train_dataset:
  dataset_root: data/mine
  mode: train
  num_classes: 4
  train_path: data/mine/train.txt
  transforms:
  - max_scale_factor: 2.0
    min_scale_factor: 0.5
    scale_step_size: 0.25
    type: ResizeStepScaling
  - crop_size:
    - 1024
    - 512
    type: RandomPaddingCrop
  - type: RandomHorizontalFlip
  - brightness_range: 0.4
    contrast_range: 0.4
    saturation_range: 0.4
    type: RandomDistort
  - type: Normalize
  type: Dataset
val_dataset:
  dataset_root: data/mine
  mode: val
  num_classes: 4
  transforms:
  - type: Normalize
  type: Dataset
  val_path: data/mine/valid.txt
------------------------------------------------
W0106 01:07:02.329128  9342 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 8.0, Driver API Version: 11.2, Runtime API Version: 11.2
W0106 01:07:02.329154  9342 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
2023-01-06 01:07:03 [INFO]	Number of predict images = 100
2023-01-06 01:07:03 [INFO]	Loading pretrained model from output/best_model/model.pdparams
2023-01-06 01:07:03 [INFO]	There are 112/112 variables loaded into UNet.
2023-01-06 01:07:03 [INFO]	Start to predict...
100/100 [==============================] - 14s 140ms/step

d into UNet.
2023-01-06 01:07:03 [INFO] Start to predict…
100/100 [==============================] - 14s 140ms/step

六.提交比赛结果

!mv Layer_Segmentations output/result
!rm -rf /home/aistudio/PaddleSeg/output/result/added_prediction
!mv /home/aistudio/PaddleSeg/output/result/pseudo_color_prediction /home/aistudio/PaddleSeg/output/result/Layer_Segmentations
!zip -r result.zip  /home/aistudio/PaddleSeg/output/result/
#最后提交文件即可。

七.总结

分数其实并没有超过前几个月的选手,主要是多了一个数据增强的方案。OCT是一个很热门的技术,作者在做脑部OCT的一些工作,主要是数据处理以及器械。OCT的图像采集是一个很重要的工作,本文的应用场景是眼,其实不仅仅是眼。脑部血管也可以用。最后希望自己能早日获取PPDE。

参考链接:

飞桨学习赛:眼底OCT层分割(GOALS挑战赛任务一)第一名方案

分割网络

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值