用keras实现fcn全卷积网络的图像分割

一开始对图像分割,全卷积网络很懵逼,根本不知道说的是什么,怎样实现,直到发现了这篇博文https://blog.csdn.net/u012931582/article/details/70215756/
在此感谢博客上大佬精彩的文章

首先谈一下FCN,FCN的全称是Fully Convolutional Networks,首次出现在Jonathan Long大神14年发表的经典论文“Fully Convolutional Networks for Semantic Segmentation”中 https://arxiv.org/abs/1411.4038 。文中首次提出了FCN全卷积结构,把传统分类网络的全连接替换为了卷积层,运用upsampling和 concatenate操作,对图像进行了像素级的分类,完成了语义分割的任务。
Unet http://www.arxiv.org/pdf/1505.04597.pdf 是15年提出的,结构上主要借鉴了‘Fully Convolutional Networks for Semantic Segmentation’的思想,在先前的fcn网络里进行了改动,满足了医学图像领域中样本尺度大,对网络性能需求高的要求。
个人觉得fcn这种结构已经和cnn一样是一类结构而不是一个特定的网络了,因此在这篇记录里面没有详细说明区别,造成误解向大家道歉。关于伪代码中损失函数问题,代码可能是我在测试前景背景分割时复制下来的,所以损失函数用了二分类,如果要改为多分类,可以改为softmax激活函数和对应的损失函数。

图像分割源于医疗领域的需求,现阶段图像分割还主要是应用在医疗影像处理方面。什么是图像分割呢?查阅论文之后,用我的理解简单的概括一下:在处理图像分类问题时,我们的输出层总是由全连接层(FC)组成,这就直接导致了输出的标签只可能作为一个标签来使用,不能对应到图像上,早些年的研究为了使预测结果能直观的反映在原图上,就用每一个像素点或者几个邻近的像素块作为一个标签来计算,这样做的直接结果导致了训练过程中产生了大量的计算,并且这些像素点由于相互临近,导致很大一部分计算都是重复的(也导致了类别和定位不能同时精确的问题)。为了解决这个问题,医学领域提出了FCN理论,他用最后几层上卷积的map来提升分类的精度,用最初几步下卷积的map提升定位的精度

网络结构—-U-net
并在训练的时候以预处理的灰度图作为标签(mask)进行训练
这是我在GitHub找到的一个代码加以修改

from __future__ import print_function
from keras.preprocessing.image import ImageDataGenerator
import numpy as np 
import os
import glob
import skimage.io as io
import skimage.transform as trans
import cv2
##这一步获取了原图和mask
def trainGenerator(batch_size,train_path,train_path2,image_folder,mask_folder,aug_dict,
                    flag_multi_class, num_class ,image_color_mode = "grayscale",
                    mask_color_mode = "grayscale",image_save_prefix  = "image",mask_save_prefix  = "mask",save_to_dir = None,target_size = (256,256),seed = 1):

    image_datagen = ImageDataGenerator(**aug_dict)
    mask_datagen = ImageDataGenerator(**aug_dict)
    image_generator = image_datagen.flow_from_directory(
        train_path,
        classes = [image_folder],
        class_mode = None,
        color_mode = image_color_mode,
        target_size = target_size,
        batch_size = batch_size,
        save_to_dir = save_to_dir,
        save_prefix  = image_save_prefix,
        seed = seed)
    mask_generator = mask_datagen.flow_from_directory(
        train_path2,
        classes = [mask_folder],
        class_mode = None,
        color_mode = mask_color_mode,
        target_size = target_size,
        batch_size = batch_size,
        save_to_dir = save_to_dir,
        save_prefix  = mask_save_prefix,
        seed = seed)
    train_generator = zip(image_generator, mask_generator)
    for (img,mask) in train_generator:
        img,mask = adjustData(img,mask,
                           flag_multi_class,   ##参数(flag_multi_class)用来开启多分类
                           num_class)##这里对图像进行了处理,用以减小计算量(函数定义在下面)
        yield (img,mask)##用生成器进行迭代数据,可以传入model.fit_generator()这个函数进行训练

def adjustData(img,mask,flag_multi_class,num_class):
    if (flag_multi_class):#如果多分类,在mask添加多层,每层对应一个类别
        img = img / 255
        mask = mask[:, :, :, 0] if (len(mask.shape) == 4) else mask[:, :, 0]
        new_mask = np.ones(mask.shape + (len(num_class),))
        for i in range(len(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 == num_class[i], i] = 0
        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,mask进行操作,不难理解
        img = img / 255
        mask = mask / 255
        mask[mask > 0.5] = 1
        mask[mask <= 0.5] = 0
    return (img,mask)

获取数据完成之后要定义模型

mport numpy as np 
import os
import skimage.io as io
import skimage.transform as trans
import numpy as np
from keras.models import *
from keras.layers import *
from keras.optimizers import *
from keras.callbacks import ModelCheckpoint, LearningRateScheduler
from keras import backend as keras


def unet(pretrained_weights = None,input_size = (512, 512, 1)):
    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],)
    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(8, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
    conv10 = Conv2D(4, 1, activation = 'sigmoid')(conv9) ##注意output层,我训练的是4分类模型,
                                                         ##所以output有4通道
    model = Model(input = inputs, output = conv10)

    model.compile(optimizer = Adam(lr = 1e-4), loss = 'binary_crossentropy', metrics = ['accuracy'])
    
    #model.summary()

    if(pretrained_weights):
    	model.load_weights(pretrained_weights)

    return model

接着就开启训练吧

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(1,img_path,         #图像路径
                              mask_path,    #mask路径
                            img_files,      #mask文件夹名称
                            mask_files,     #图像文件夹名称(不懂的话看此函数定义和keras文档)
                            data_gen_args,True,#
                            num_class,target_size=(256,256))

    model = unet()
    model_checkpoint = ModelCheckpoint('saver\module.hdf5', monitor='loss',verbose=1, save_best_only=True)
    model.fit_generator(myGene,steps_per_epoch=300,epochs=1,callbacks=[model_checkpoint])

训练好之后要对test进行预测,这几个函数自己应该可以定义的

testGene = testGenerator("data/membrane/test")
results = model.predict_generator(testGene,30,verbose=1)

然后吧result写入t图片中

def save(save_path, result,num_class):
    for i, item in enumerate(result):
        pred = np.ones((256,256,1))*128
        for j in range(len(num_class)):
            item_prrd = item[:, :, j]
            pred[item_prrd < 0.5,:] = num_class[j]
        cv2.imwrite(os.path.join(save_path, "%d_predict.bmp" % i),pred)
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值