基於PaddleSeg的眼底血管分割——使用飛槳助力醫學影像分析

基於PaddleSeg的眼底血管分割——使用飛槳助力醫學影像分析python

1、項目背景

研究代表,各種眼科疾病以及心腦血管疾病會對視網膜血管形成形變、出血等不一樣程度的影響。隨着生活水平的提升,這類疾病的發病率呈現逐年增加的趨勢。臨牀上,醫療人員可以從檢眼鏡採集的彩色眼底圖像中提取視網膜血管,而後經過對血管形態情況的分析達到診斷這類疾病的目的算法

可是,因爲受眼底圖像採集技術的限制,圖像中每每存在大量噪聲,再加之視網膜血管自身結構複雜多變,使得視網膜血管的分割變得困難重重。shell

傳統方法中依靠人工手動分割視網膜血管,不只工做量巨大極爲耗時,並且受主觀因素影響嚴重網絡

所以,利用計算機技術,找到一種可以快速、準確分割視網膜血管的算法,實現對眼底圖像血管特徵的實時提取,對輔助醫療人員診斷眼科疾病、心腦血管疾病等具備重要做用。app

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

2、數據集介紹

本項目使用的數據集照片來自荷蘭的糖尿病視網膜病變篩查項目。篩查人羣包括400名年齡在25-90歲之間的糖尿病患者。但只有40張照片被選取,其中33張沒有顯示任何糖尿病視網膜病變的跡象,7張顯示輕度早期糖尿病視網膜病變的跡象。測試

AI Studio上已經有DRIVE糖尿病人眼底血管分割數據集了,可是該數據集的數據量很是少,只有20張訓練集。ui

所以,我在處理數據時作了一些處理來增長個人訓練集數據量。url

數據集圖片格式轉換

原數據集裏的眼底圖像:spa

原數據集手工分好的的血管圖像:

.net

仔細看一下圖片格式:

  • 眼底圖像格式是.tif
  • 手工標註的血管圖像格式是.gif

這裏我作了圖片格式的轉換:

  • 把眼底圖像格式轉換爲.jpg
  • 把手工標註的血管圖像格式轉換爲.png

這裏作格式轉換的目的是我想把數據集放到PaddleX裏試着運行一下,但它的圖片格式只支持png, jpg, jpeg, bmp格式,所以,我這裏作了一個格式轉換。

血管標籤圖像二值化

若是直接將格式轉換後的格式送入模型,會發現最多有256個標籤,這是由於PaddleSeg採用單通道的標註圖片,每一種像素值表明一種類別,像素標註類別須要從0開始遞增,例如0,1,2,3表示有4種類別,因此標註類別最多爲256類。

但其實咱們只須要找到血管的位置,所以血管就做爲一個類,其背景做爲另外一個類別,這樣總共有2個類別。下面來看一下如何使用opencv作圖像二值化處理。

先來看看如何使用opencv讀取圖片,下面是未處理的圖片0.png,位於work目錄下:

# 使用opencv讀取圖像
import cv2
import matplotlib.pyplot as plt

img = cv2.imread("work/0.png") # 讀取的圖片路徑

plt.imshow(img)
plt.show()

在這裏插入圖片描述

# 使用opencv讀取圖像
import cv2
import matplotlib.pyplot as plt

img = cv2.imread("work/0.png") # 讀取的圖片路徑
# 轉換爲灰度圖
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

plt.imshow(img_gray)
plt.show()

在這裏插入圖片描述

# 使用opencv讀取圖像
import cv2
import matplotlib.pyplot as plt

img = cv2.imread("work/0.png") # 讀取的圖片路徑
# 轉換爲灰度圖
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 將灰度極差的一半做爲閾值
difference = (img_gray.max() - img_gray.min()) // 2
# 將圖像二值化
_, img_binary = cv2.threshold(img_gray, difference, 1, cv2.THRESH_BINARY)
print("閾值:", _)
plt.imshow(img_binary)
plt.show()
閾值: 127.0

在這裏插入圖片描述

將上面的代碼整理一下,能夠整理出以下代碼:

import cv2
import matplotlib.pyplot as plt

#循環灰度圖片並保存
def grayImg():
    for x in range(200):
        #讀取圖片
        img = cv2.imread("FundusVessels/Annotations/{}.png".format(str(x)))
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        difference = (img_gray.max() - img_gray.min()) // 2
        _, img_binary = cv2.threshold(img_gray, difference, 1, cv2.THRESH_BINARY)
        # print("閾值:", _)
        #保存灰度後的新圖片
        cv2.imwrite("FundusVessels/{}.png".format(str(x)), img_binary)
    plt.imshow(img_binary)
    plt.show()
grayImg()

以上代碼能夠將像素值在0-255的圖像轉換成0-1二值圖像。最後我已經將整理好的數據集上傳至AI Studio,同時也加載到該項目上了:
https://aistudio.baidu.com/aistudio/datasetdetail/56726

咱們也能夠把數據集導入PaddleX可視化地看一下:



生成圖像列表

PaddleSeg採用通用的文件列表方式組織訓練集、驗證集和測試集。在訓練、評估、可視化過程前必須準備好相應的文件列表。

文件列表組織形式以下

原始圖片路徑 [SEP] 標註圖片路徑

其中[SEP]是文件路徑分割符,能夠在DATASET.SEPARATOR配置項中修改, 默認爲空格。文件列表的路徑以數據集根目錄做爲相對路徑起始點,DATASET.DATA_DIR即爲數據集根目錄。

以下圖所示,左邊爲原圖的圖片路徑,右邊爲圖片對應的標註路徑:

# 解壓數據集
!unzip data/data56918/FundusVessels.zip
# 生成圖像列表
import os

path_origin = 'FundusVessels/JPEGImages/'
path_seg = 'FundusVessels/Annotations/'
pic_dir = os.listdir(path_origin)

f_train = open('train_list.txt', 'w')
f_val = open('val_list.txt', 'w')

for i in range(len(pic_dir)):
    if i % 30 != 0:
        f_train.write(path_origin + pic_dir[i] + ' ' + path_seg + pic_dir[i].split('.')[0] + '.png' + '\n')
    else:
        f_val.write(path_origin + pic_dir[i] + ' ' + path_seg + pic_dir[i].split('.')[0] + '.png' + '\n')

f_train.close()
f_val.close()

2、PaddleSeg的安裝

PaddleSeg是基於PaddlePaddle生態下的語義分割庫,可結合豐富的預訓練模型更便捷高效地進行語義分割。

本項目已經掛載了PaddleSeg壓縮包,解壓便可使用。

注: 在AI studio中運行shell命令須要在最開始添加!

# 解壓從PaddleSeg Github倉庫下載好的壓縮包
!unzip -o work/PaddleSeg.zip
# 運行腳本需在PaddleSeg目錄下
%cd PaddleSeg
# 安裝所需依賴項
!pip install -r requirements.txt

3、配置文件進行訓練

這裏咱們須要使用兩個文件:

  • 訓練文件:PaddleSeg/pdseg/train.py
  • 訓練的配置文件:PaddleSeg/configs/unet_optic.yaml

這裏使用U-Net,你們能夠嘗試使用其餘網絡進行配置。

.yaml文件的配置

下面是我配置的.yaml文件,

# 數據集配置
DATASET:
    DATA_DIR: ""
    NUM_CLASSES: 2
    TEST_FILE_LIST: "train_list.txt"
    TRAIN_FILE_LIST: "train_list.txt"
    VAL_FILE_LIST: "val_list.txt"
    VIS_FILE_LIST: "train_list.txt"

# 預訓練模型配置
MODEL:
    MODEL_NAME: "unet"
    DEFAULT_NORM_TYPE: "bn"

# 其餘配置
TRAIN_CROP_SIZE: (565, 584)
EVAL_CROP_SIZE: (565, 584)
AUG:
    AUG_METHOD: "unpadding"
    FIX_RESIZE_SIZE: (565, 584)
BATCH_SIZE: 4
TRAIN:
    # PRETRAINED_MODEL_DIR: "./pretrained_model/unet_bn_coco/"
    MODEL_SAVE_DIR: "./saved_model/unet_optic/"
    SNAPSHOT_EPOCH: 2
TEST:
    TEST_MODEL: "./saved_model/unet_optic/final"
SOLVER:
    NUM_EPOCHS: 10
    LR: 0.001
    LR_POLICY: "poly"
    OPTIMIZER: "adam"

開始訓練

訓練命令的格式參考:

python PaddleSeg/pdseg/train.py --cfg configs/unet_optic.yaml \
                      --use_gpu \
                      --do_eval \
                      --use_vdl \
                      --vdl_log_dir train_log \
                      BATCH_SIZE 4 \
                      SOLVER.LR 0.001

 

!python PaddleSeg/pdseg/train.py  --cfg PaddleSeg/configs/unet_optic.yaml

 

{'AUG': {'AUG_METHOD': 'unpadding',
         'FIX_RESIZE_SIZE': (565, 584),
         'FLIP': False,
         'FLIP_RATIO': 0.5,
         'INF_RESIZE_VALUE': 500,
         'MAX_RESIZE_VALUE': 600,
         'MAX_SCALE_FACTOR': 2.0,
         'MIN_RESIZE_VALUE': 400,
         'MIN_SCALE_FACTOR': 0.5,
         'MIRROR': True,
         'RICH_CROP': {'ASPECT_RATIO': 0.33,
                       'BLUR': False,
                       'BLUR_RATIO': 0.1,
                       'BRIGHTNESS_JITTER_RATIO': 0.5,
                       'CONTRAST_JITTER_RATIO': 0.5,
                       'ENABLE': False,
                       'MAX_ROTATION': 15,
                       'MIN_AREA_RATIO': 0.5,
                       'SATURATION_JITTER_RATIO': 0.5},
         'SCALE_STEP_SIZE': 0.25,
         'TO_RGB': False},
 'BATCH_SIZE': 4,
 'DATALOADER': {'BUF_SIZE': 256, 'NUM_WORKERS': 8},
 'DATASET': {'DATA_DIM': 3,
             'DATA_DIR': '',
             'IGNORE_INDEX': 255,
             'IMAGE_TYPE': 'rgb',
             'NUM_CLASSES': 2,
             'PADDING_VALUE': [127.5, 127.5, 127.5],
             'SEPARATOR': ' ',
             'TEST_FILE_LIST': 'train_list.txt',
             'TEST_TOTAL_IMAGES': 193,
             'TRAIN_FILE_LIST': 'train_list.txt',
             'TRAIN_TOTAL_IMAGES': 193,
             'VAL_FILE_LIST': 'val_list.txt',
             'VAL_TOTAL_IMAGES': 7,
             'VIS_FILE_LIST': 'train_list.txt'},
 'EVAL_CROP_SIZE': (565, 584),
 'FREEZE': {'MODEL_FILENAME': '__model__',
            'PARAMS_FILENAME': '__params__',
            'SAVE_DIR': 'freeze_model'},
 'MEAN': [0.5, 0.5, 0.5],
 'MODEL': {'BN_MOMENTUM': 0.99,
           'DEEPLAB': {'ASPP_WITH_SEP_CONV': True,
                       'BACKBONE': 'xception_65',
                       'BACKBONE_LR_MULT_LIST': None,
                       'DECODER': {'CONV_FILTERS': 256,
                                   'OUTPUT_IS_LOGITS': False,
                                   'USE_SUM_MERGE': False},
                       'DECODER_USE_SEP_CONV': True,
                       'DEPTH_MULTIPLIER': 1.0,
                       'ENABLE_DECODER': True,
                       'ENCODER': {'ADD_IMAGE_LEVEL_FEATURE': True,
                                   'ASPP_CONVS_FILTERS': 256,
                                   'ASPP_RATIOS': None,
                                   'ASPP_WITH_CONCAT_PROJECTION': True,
                                   'ASPP_WITH_SE': False,
                                   'POOLING_CROP_SIZE': None,
                                   'POOLING_STRIDE': [1, 1],
                                   'SE_USE_QSIGMOID': False},
                       'ENCODER_WITH_ASPP': True,
                       'OUTPUT_STRIDE': 16},
           'DEFAULT_EPSILON': 1e-05,
           'DEFAULT_GROUP_NUMBER': 32,
           'DEFAULT_NORM_TYPE': 'bn',
           'FP16': False,
           'HRNET': {'STAGE2': {'NUM_CHANNELS': [40, 80], 'NUM_MODULES': 1},
                     'STAGE3': {'NUM_CHANNELS': [40, 80, 160],
                                'NUM_MODULES': 4},
                     'STAGE4': {'NUM_CHANNELS': [40, 80, 160, 320],
                                'NUM_MODULES': 3}},
           'ICNET': {'DEPTH_MULTIPLIER': 0.5, 'LAYERS': 50},
           'MODEL_NAME': 'unet',
           'MULTI_LOSS_WEIGHT': [1.0],
           'OCR': {'OCR_KEY_CHANNELS': 256, 'OCR_MID_CHANNELS': 512},
           'PSPNET': {'DEPTH_MULTIPLIER': 1, 'LAYERS': 50},
           'SCALE_LOSS': 'DYNAMIC',
           'UNET': {'UPSAMPLE_MODE': 'bilinear'}},
 'NUM_TRAINERS': 1,
 'SLIM': {'KNOWLEDGE_DISTILL': False,
          'KNOWLEDGE_DISTILL_IS_TEACHER': False,
          'KNOWLEDGE_DISTILL_TEACHER_MODEL_DIR': '',
          'NAS_ADDRESS': '',
          'NAS_IS_SERVER': True,
          'NAS_PORT': 23333,
          'NAS_SEARCH_STEPS': 100,
          'NAS_SPACE_NAME': '',
          'NAS_START_EVAL_EPOCH': 0,
          'PREPROCESS': False,
          'PRUNE_PARAMS': '',
          'PRUNE_RATIOS': []},
 'SOLVER': {'BEGIN_EPOCH': 1,
            'CROSS_ENTROPY_WEIGHT': None,
            'DECAY_EPOCH': [10, 20],
            'GAMMA': 0.1,
            'LOSS': ['softmax_loss'],
            'LOSS_WEIGHT': {'BCE_LOSS': 1,
                            'DICE_LOSS': 1,
                            'LOVASZ_HINGE_LOSS': 1,
                            'LOVASZ_SOFTMAX_LOSS': 1,
                            'SOFTMAX_LOSS': 1},
            'LR': 0.001,
            'LR_POLICY': 'poly',
            'LR_WARMUP': False,
            'LR_WARMUP_STEPS': 2000,
            'MOMENTUM': 0.9,
            'MOMENTUM2': 0.999,
            'NUM_EPOCHS': 10,
            'OPTIMIZER': 'adam',
            'POWER': 0.9,
            'WEIGHT_DECAY': 4e-05},
 'STD': [0.5, 0.5, 0.5],
 'TEST': {'TEST_MODEL': './saved_model/unet_optic/final'},
 'TRAIN': {'MODEL_SAVE_DIR': './saved_model/unet_optic/',
           'PRETRAINED_MODEL_DIR': '',
           'RESUME_MODEL_DIR': '',
           'SNAPSHOT_EPOCH': 2,
           'SYNC_BATCH_NORM': False},
 'TRAINER_ID': 0,
 'TRAIN_CROP_SIZE': (565, 584)}
#Device count: 1
batch_size_per_dev: 4
2020-10-24 12:56:05,329-INFO: If regularizer of a Parameter has been set by 'fluid.ParamAttr' or 'fluid.WeightNormParamAttr' already. The Regularization[L2Decay, regularization_coeff=0.000040] in Optimizer will not take effect, and it will only be applied to other Parameters!
W1024 12:56:05.553278 21897 device_context.cc:252] Please NOTE: device: 0, CUDA Capability: 70, Driver API Version: 9.2, Runtime API Version: 9.0
W1024 12:56:05.557957 21897 device_context.cc:260] device: 0, cuDNN Version: 7.6.
Pretrained model dir  not exists, training from scratch...
Use multi-thread reader
epoch=1 step=20 lr=0.00097 loss=0.3196 step/sec=2.958 | ETA 00:02:35
epoch=1 step=40 lr=0.00093 loss=0.2341 step/sec=3.114 | ETA 00:02:21
epoch=2 step=60 lr=0.00089 loss=0.1845 step/sec=3.064 | ETA 00:02:17
epoch=2 step=80 lr=0.00085 loss=0.1879 step/sec=3.106 | ETA 00:02:08
Save model checkpoint to ./saved_model/unet_optic/2
epoch=3 step=100 lr=0.00082 loss=0.1657 step/sec=2.347 | ETA 00:02:41
epoch=3 step=120 lr=0.00078 loss=0.1631 step/sec=3.100 | ETA 00:01:56
epoch=3 step=140 lr=0.00074 loss=0.1708 step/sec=3.095 | ETA 00:01:49
epoch=4 step=160 lr=0.00070 loss=0.1522 step/sec=3.047 | ETA 00:01:45
epoch=4 step=180 lr=0.00066 loss=0.1638 step/sec=3.088 | ETA 00:01:37
Save model checkpoint to ./saved_model/unet_optic/4
epoch=5 step=200 lr=0.00063 loss=0.1485 step/sec=2.354 | ETA 00:01:58
epoch=5 step=220 lr=0.00059 loss=0.1493 step/sec=3.084 | ETA 00:01:24
epoch=5 step=240 lr=0.00055 loss=0.1462 step/sec=3.082 | ETA 00:01:17
epoch=6 step=260 lr=0.00051 loss=0.1460 step/sec=3.034 | ETA 00:01:12
epoch=6 step=280 lr=0.00047 loss=0.1460 step/sec=3.050 | ETA 00:01:05
Save model checkpoint to ./saved_model/unet_optic/6
epoch=7 step=300 lr=0.00043 loss=0.1480 step/sec=2.347 | ETA 00:01:16
epoch=7 step=320 lr=0.00039 loss=0.1397 step/sec=3.082 | ETA 00:00:51
epoch=8 step=340 lr=0.00035 loss=0.1359 step/sec=3.030 | ETA 00:00:46
epoch=8 step=360 lr=0.00031 loss=0.1368 step/sec=3.078 | ETA 00:00:38
epoch=8 step=380 lr=0.00026 loss=0.1386 step/sec=3.078 | ETA 00:00:32
Save model checkpoint to ./saved_model/unet_optic/8
epoch=9 step=400 lr=0.00022 loss=0.1311 step/sec=2.340 | ETA 00:00:34
epoch=9 step=420 lr=0.00018 loss=0.1272 step/sec=3.076 | ETA 00:00:19
epoch=10 step=440 lr=0.00013 loss=0.1381 step/sec=3.008 | ETA 00:00:13
epoch=10 step=460 lr=0.00009 loss=0.1280 step/sec=3.072 | ETA 00:00:06
epoch=10 step=480 lr=0.00004 loss=0.1315 step/sec=3.075 | ETA 00:00:00
Save model checkpoint to ./saved_model/unet_optic/10
Save model checkpoint to ./saved_model/unet_optic/final

4、模型評估

該命令與訓練的命令格式一致。

!python PaddleSeg/pdseg/eval.py  --cfg PaddleSeg/configs/unet_optic.yaml

由以上的輸出中能夠看出咱們的模型效果:

[EVAL]step=1 loss=0.15743 acc=0.9396 IoU=0.7404 step/sec=2.02 | ETA 00:00:23
[EVAL]step=2 loss=0.14508 acc=0.9417 IoU=0.7482 step/sec=7.37 | ETA 00:00:06
[EVAL]#image=7 acc=0.9417 IoU=0.7482
[EVAL]Category IoU: [0.9371 0.5592]
[EVAL]Category Acc: [0.9445 0.9105]
[EVAL]Kappa:0.6865

5、模型導出

經過訓練獲得一個知足要求的模型後,若是想要將該模型接入到C++預測庫或者Serving服務,咱們須要經過pdseg/export_model.py來導出該模型。

該腳本的使用方法和train.py/eval.py/vis.py徹底同樣。

# 模型導出
!python PaddleSeg/pdseg/export_model.py --cfg PaddleSeg/configs/unet_optic.yaml TEST.TEST_MODEL ./saved_model/unet_optic/final

預測模型會導出到freeze_model目錄,用於C++或者Python預測的模型配置會導出到freeze_model/deploy.yaml下

6、PaddleSeg Python 預測部署

在預測前,咱們須要使用pip安裝Python依賴包:


!pip install -r PaddleSeg/deploy/python/requirements.txt

使用如下命令進行預測:

python infer.py --conf=/path/to/deploy.yaml --input_dir=/path/to/images_directory

運行後程序會掃描input_dir 目錄下全部指定格式圖片,並生成預測mask和可視化的結果。

對於圖片a.jpeg, 預測mask 存在a_jpeg.png 中,而可視化結果則在a_jpeg_result.png 中。

# 模型預測
!python PaddleSeg/deploy/python/infer.py --conf=freeze_model/deploy.yaml --input_dir=work/test

輸入圖片:

import numpy as np
import cv2
import matplotlib.pyplot as plt

img = cv2.imread("work/test/1.jpg")

plt.imshow(img)
plt.show()

在這裏插入圖片描述

預測結果:

import numpy as np
import cv2
import matplotlib.pyplot as plt

img = cv2.imread("work/test/1_jpg_result.png")

plt.imshow(img)
plt.show()

在這裏插入圖片描述

7、總結與昇華

前不久,安定醫院在作腦科學的研究,招募志願者,我報名且有幸被錄取去安定醫院作志願者,在此期間,我瞭解到目前的醫學生在作醫學影像分析,用的也是神經網絡,但畢竟是學科交叉,對醫學生來講仍是有必定的困難的。

所以,我在想,能不能作些什麼幫助他們。回來之後,我找了不少醫學影像的數據集,最後選擇了這個糖尿病人的眼底血管數據集,我想把這個項目看成使用PaddleSeg研究醫學影像圖像分割的Hello World。

下載安裝命令

## CPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安裝命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

8、我的介紹

  • 北京聯合大學 機器人學院 自動化專業 2018級 本科生 鄭博培
  • 百度飛槳開發者技術專家 PPDE
  • 深圳柴火創客空間 認證會員
  • 百度大腦 智能對話訓練師

來AI Studio互粉吧,等你哦~ https://aistudio.baidu.com/aistudio/personalcenter/thirdview/147378

歡迎你們fork喜歡評論三連,感興趣的朋友也可互相關注一下啊~

 

本文同步分享在 博客「Mr.鄭先生_」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索