基于U-Net的视网膜血管分割(Pytorch完整版)

news2024/11/30 20:52:00

基于 U-Net 的视网膜血管分割是一种应用深度学习的方法,特别是 U-Net 结构,用于从眼底图像中分割出视网膜血管。U-Net 是一种全卷积神经网络(FCN),通常用于图像分割任务。以下是基于 U-Net 的视网膜血管分割的内容:
框架结构:
在这里插入图片描述
代码结构:
在这里插入图片描述

U-Net分割代码:

unet_model.py

import torch.nn.functional as F
from .unet_parts import *
class UNet(nn.Module):
    def __init__(self, n_channels, n_classes, bilinear=True):
        super(UNet, self).__init__()
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.bilinear = bilinear

        self.inc = DoubleConv(n_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        self.down4 = Down(512, 512)
        self.up1 = Up(1024, 256, bilinear)
        self.up2 = Up(512, 128, bilinear)
        self.up3 = Up(256, 64, bilinear)
        self.up4 = Up(128, 64, bilinear)
        self.outc = OutConv(64, n_classes)



    def forward(self, x):
        x1 = self.inc(x)

        # 在编码器下采样过程加空间注意力
        # x2 = self.down1(self.sp1(x1))
        # x3 = self.down2(self.sp2(x2))
        # x4 = self.down3(self.sp3(x3))
        # x5 = self.down4(self.sp4(x4))

        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)

        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)

        logits = self.outc(x)
        return logits

if __name__ == '__main__':
    net = UNet(n_channels=3, n_classes=1)
    print(net)

unet_parts.py

import torch
import torch.nn as nn
import torch.nn.functional as F


class DoubleConv(nn.Module):
    """(convolution => [BN] => ReLU) * 2"""

    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.constant_(m.bias, 0)


    def forward(self, x):
        return self.double_conv(x)


class Down(nn.Module):
    """Downscaling with maxpool then double conv"""

    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.maxpool_conv = nn.Sequential(
            nn.MaxPool2d(2),
            DoubleConv(in_channels, out_channels)
        )

    def forward(self, x):
        return self.maxpool_conv(x)


class Up(nn.Module):
    """Upscaling then double conv"""

    def __init__(self, in_channels, out_channels, bilinear=True):
        super().__init__()

        # if bilinear, use the normal convolutions to reduce the number of channels
        if bilinear:
            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
        else:
            self.up = nn.ConvTranspose2d(in_channels // 2, in_channels // 2, kernel_size=2, stride=2)

        self.conv = DoubleConv(in_channels, out_channels)

    def forward(self, x1, x2):
        x1 = self.up(x1)
        # input is CHW
        diffY = torch.tensor([x2.size()[2] - x1.size()[2]])
        diffX = torch.tensor([x2.size()[3] - x1.size()[3]])

        x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
                        diffY // 2, diffY - diffY // 2])

        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)


class OutConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        return self.conv(x)

trainval.py

from model.unet_model import UNet
from utils.dataset import FundusSeg_Loader

from torch import optim
import torch.nn as nn
import torch
import sys
import matplotlib.pyplot as plt
from tqdm import tqdm
import time

train_data_path = "DRIVE/drive_train/"
valid_data_path = "DRIVE/drive_test/"
# hyperparameter-settings
N_epochs = 500
Init_lr = 0.00001

def train_net(net, device, epochs=N_epochs, batch_size=1, lr=Init_lr):
    # 加载训练集
    train_dataset = FundusSeg_Loader(train_data_path, 1)
    valid_dataset = FundusSeg_Loader(valid_data_path, 0)
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
    valid_loader = torch.utils.data.DataLoader(dataset=valid_dataset, batch_size=batch_size, shuffle=False)
    print('Traing images: %s' % len(train_loader))
    print('Valid  images: %s' % len(valid_loader))

    # 定义RMSprop算法
    optimizer = optim.RMSprop(net.parameters(), lr=lr, weight_decay=1e-8, momentum=0.9)
    # 定义Loss算法
    # BCEWithLogitsLoss会对predict进行sigmoid处理
    # criterion 常被用来定义损失函数,方便调换损失函数
    criterion = nn.BCEWithLogitsLoss()
    # 训练epochs次
    # 求最小值,所以初始化为正无穷
    best_loss = float('inf')
    train_loss_list = []
    val_loss_list = []
    for epoch in range(epochs):
        # 训练模式
        net.train()
        train_loss = 0
        print(f'Epoch {epoch + 1}/{epochs}')
        # SGD
        # train_loss_list = []
        # val_loss_list = []
        with tqdm(total=train_loader.__len__()) as pbar:
            for i, (image, label, filename) in enumerate(train_loader):
                optimizer.zero_grad()
                # 将数据拷贝到device中
                image = image.to(device=device, dtype=torch.float32)
                label = label.to(device=device, dtype=torch.float32)
                # 使用网络参数,输出预测结果
                pred = net(image)
                # print(pred)
                # 计算loss
                loss = criterion(pred, label)
                # print(loss)
                train_loss = train_loss + loss.item()

                loss.backward()
                optimizer.step()
                pbar.set_postfix(loss=float(loss.cpu()), epoch=epoch)
                pbar.update(1)

        train_loss_list.append(train_loss / i)
        print('Loss/train', train_loss / i)

        # Validation
        net.eval()
        val_loss = 0
        for i, (image, label, filename) in tqdm(enumerate(valid_loader), total=len(valid_loader)):
            image = image.to(device=device, dtype=torch.float32)
            label = label.to(device=device, dtype=torch.float32)
            pred = net(image)
            loss = criterion(pred, label)
            val_loss = val_loss + loss.item()
            # net.state_dict()就是用来保存模型参数的
            if val_loss < best_loss:
                best_loss = val_loss
                torch.save(net.state_dict(), 'best_model.pth')
                print('saving model............................................')

        val_loss_list.append(val_loss / i)
        print('Loss/valid', val_loss / i)
        sys.stdout.flush()
    return val_loss_list, train_loss_list


if __name__ == "__main__":
    # 选择设备cuda
    device = torch.device('cuda')
    # 加载网络,图片单通道1,分类为1。
    net = UNet(n_channels=3, n_classes=1)
    # 将网络拷贝到deivce中
    net.to(device=device)
    # 开始训练
    val_loss_list, train_loss_list = train_net(net, device)
    # 保存loss值到txt文件
    fileObject1 = open('train_loss.txt', 'w')
    for train_loss in train_loss_list:
        fileObject1.write(str(train_loss))
        fileObject1.write('\n')
    fileObject1.close()
    fileObject2 = open('val_loss.txt', 'w')
    for val_loss in val_loss_list:
        fileObject2.write(str(val_loss))
        fileObject2.write('\n')
    fileObject2.close()
    # 我这里迭代了5次,所以x的取值范围为(0,5),然后再将每次相对应的5损失率附在x上
    x = range(0, N_epochs)
    y1 = val_loss_list
    y2 = train_loss_list
    # 两行一列第一个
    plt.subplot(1, 1, 1)
    plt.plot(x, y1, 'r.-', label=u'val_loss')
    plt.plot(x, y2, 'g.-', label =u'train_loss')
    plt.title('loss')
    plt.xlabel('epochs')
    plt.ylabel('loss')
    plt.savefig("accuracy_loss.jpg")
    plt.show()

predict.py

import numpy as np
import torch
import cv2
from model.unet_model import UNet

from utils.dataset import FundusSeg_Loader
import copy
from sklearn.metrics import roc_auc_score

model_path='./best_model.pth'
test_data_path = "DRIVE/drive_test/"
save_path='./results/'

if __name__ == "__main__":
    test_dataset = FundusSeg_Loader(test_data_path,0)
    test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=1, shuffle=False)
    print('Testing images: %s' %len(test_loader))
    # 选择设备CUDA
    device = torch.device('cuda')
    # 加载网络,图片单通道,分类为1。
    net = UNet(n_channels=3, n_classes=1)
    # 将网络拷贝到deivce中
    net.to(device=device)
    # 加载模型参数
    print(f'Loading model {model_path}')
    net.load_state_dict(torch.load(model_path, map_location=device))

    # 测试模式
    net.eval()
    tp = 0
    tn = 0
    fp = 0
    fn = 0
    pred_list = []
    label_list = []
    for image, label, filename in test_loader:
        image = image.to(device=device, dtype=torch.float32)
        pred = net(image)
        # Normalize to [0, 1]
        pred = torch.sigmoid(pred)
        pred = np.array(pred.data.cpu()[0])[0]
        pred_list.append(pred)
        # ConfusionMAtrix
        pred_bin = copy.deepcopy(pred)
        label = np.array(label.data.cpu()[0])[0]
        label_list.append(label)
        pred_bin[pred_bin >= 0.5] = 1
        pred_bin[pred_bin < 0.5] = 0
        tp += ((pred_bin == 1) & (label == 1)).sum()
        tn += ((pred_bin == 0) & (label == 0)).sum()
        fn += ((pred_bin == 0) & (label == 1)).sum()
        fp += ((pred_bin == 1) & (label == 0)).sum()
        # 保存图片
        pred = pred * 255
        save_filename = save_path + filename[0] + '.png'
        cv2.imwrite(save_filename, pred)
        print(f'{save_filename} done!')
    # Evaluaiton Indicators
    precision = tp / (tp + fp)   # 预测为真并且正确/预测正确样本总和
    sen = tp / (tp + fn)    # 预测为真并且正确/正样本总和
    spe = tn / (tn + fp)
    acc = (tp + tn) / (tp + tn + fp + fn)
    f1score = 2 * precision * sen / (precision + sen)
    # auc computing
    pred_auc = np.stack(pred_list, axis=0)
    label_auc = np.stack(label_list, axis=0)
    auc = roc_auc_score(label_auc.reshape(-1), pred_auc.reshape(-1))
    print(f'Precision: {precision} Sen: {sen} Spe:{spe} F1-score: {f1score} Acc: {acc} AUC: {auc}')

dataset.py

import torch
import cv2
import os
import glob
from torch.utils.data import Dataset

# import random
# from PIL import Image
# import numpy as np


class FundusSeg_Loader(Dataset):
    def __init__(self, data_path, is_train):
        # 初始化函数,读取所有data_path下的图片
        self.data_path = data_path
        self.imgs_path = glob.glob(os.path.join(data_path, 'image/*.tif'))
        self.labels_path = glob.glob(os.path.join(data_path, 'label/*.tif'))
        self.is_train = is_train
        print(self.imgs_path)
        print(self.labels_path)
    def __getitem__(self, index):
        # 根据index读取图片
        image_path = self.imgs_path[index]
        if self.is_train == 1:
            label_path = image_path.replace('image', 'label')
            label_path = label_path.replace('training', 'manual1')
        else:
            label_path = image_path.replace('image', 'label')
            label_path = label_path.replace('test.tif', 'manual1.tif')
            
        
        # 读取训练图片和标签图片
        image = cv2.imread(image_path)
        label = cv2.imread(label_path)

        # image = np.array(image)
        # label = np.array(label)
        # label = cv2.imread(label_path)
        # image = cv2.resize(image, (600,400))
        # label = cv2.resize(label, (600,400))
        # 转为单通道的图片
        # image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        label = cv2.cvtColor(label, cv2.COLOR_BGR2GRAY)
        # label = Image.fromarray(label)
        # label = label.convert("1")
        # reshape()函数可以改变数组的形状,并且原始数据不发生变化。
        image = image.transpose(2, 0, 1)
        # image = image.reshape(1, label.shape[0], label.shape[1])
        label = label.reshape(1, label.shape[0], label.shape[1])
        # 处理标签,将像素值为255的改为1
        if label.max() > 1:
            label[label > 1] = 1

        return image, label, image_path[len(image_path)-12:len(image_path)-4]

    def __len__(self):
        # 返回训练集大小
        return len(self.imgs_path)

visual.py

import numpy as np
import matplotlib.pyplot as plt
import pylab as pl

from mpl_toolkits.axes_grid1.inset_locator import inset_axes
data1_loss =np.loadtxt("E:\\code\\UNet_lr00001\\train_loss.txt",dtype=str )
data2_loss = np.loadtxt("E:\\code\\UNet_lr00001\\val_loss.txt",dtype=str)
x = range(0,10)
y = data1_loss[:, 0]
x1 = range(0,10)
y1 = data2_loss[:, 0]
fig = plt.figure(figsize = (7,5))    #figsize是图片的大小`
ax1 = fig.add_subplot(1, 1, 1) # ax1是子图的名字`
pl.plot(x,y,'g-',label=u'Dense_Unet(block layer=5)')
# ‘'g‘'代表“green”,表示画出的曲线是绿色,“-”代表画的曲线是实线,可自行选择,label代表的是图例的名称,一般要在名称前面加一个u,如果名称是中文,会显示不出来,目前还不知道怎么解决。
p2 = pl.plot(x, y,'r-', label = u'train_loss')
pl.legend()
#显示图例
p3 = pl.plot(x1,y1, 'b-', label = u'val_loss')
pl.legend()
pl.xlabel(u'epoch')
pl.ylabel(u'loss')
plt.title('Compare loss for different models in training')

在这里插入图片描述
在这里插入图片描述

这种基于 U-Net 的方法已在医学图像分割领域取得了一些成功,特别是在视网膜图像处理中。通过深度学习的方法,这种技术能够更准确地提取视网膜血管,为眼科医生提供辅助诊断和治疗的信息。
如有疑问,请评论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1255751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

公司注册资金认缴的好处有哪些

公司注册资金认缴的好处 1、减少投资项目审批&#xff0c;最大限度地缩小审批、核准、备案范围&#xff0c;切实落实企业和个人投资自主权。对确需审批、核准、备案的项目&#xff0c;要简化程序、限时办结。同时&#xff0c;为避免重复投资和无序竞争&#xff0c;强调要加强土…

LCR 047. 二叉树剪枝 和 leetCode 1110. 删点成林 + 递归 + 图解

给定一个二叉树 根节点 root &#xff0c;树的每个节点的值要么是 0&#xff0c;要么是 1。请剪除该二叉树中所有节点的值为 0 的子树。节点 node 的子树为 node 本身&#xff0c;以及所有 node 的后代。 示例 1: 输入: [1,null,0,0,1] 输出: [1,null,0,null,1] 解释: 只有…

代码常见问题

1. 前端页面出现404了&#xff1a; 1&#xff09;那说明你该页面里面有某个接口地址&#xff08;url&#xff09;写错了&#xff0c;后台没有这个接口 2&#xff09;你后台写了这个接口&#xff0c;但是后台忘了重启服务了&#xff0c;这样的话前端也映射不上的 所以404的时…

安卓吸顶效果

当列表滑动时&#xff0c;图片逐渐消失&#xff0c;toolBar悬停在头部。 <?xml version"1.0" encoding"utf-8"?><androidx.coordinatorlayout.widget.CoordinatorLayoutxmlns:android"http://schemas.android.com/apk/res/android"x…

python--获取每张切片的不同PEF区间值的百分比

在全直径数字岩心中&#xff0c;如何获取每张切片的不同PEF区间值的百分比&#xff1f; import os import datetime from PIL import Image import numpy as np import csv import easygui as gclass Table(object):def __init__(self, table_data_path):self.table_data_path…

三菱GX WORRKS3 下载与安装

目录 下载 安装 准备好安装包 对电脑系统要求 安装 因为小编公司需要&#xff0c;所以开始了三菱plc软件的学习&#xff0c;并从今天开始记录学习&#xff0c;希望小编的内容能帮到你&#xff0c;对你的学习有帮助&#xff01; 下载 三菱电机官网 当然了&#xff0c;需要…

2023-11-26 事业-代号s-跨境物流-记录

摘要: 2023-11-26 事业-代号s-跨境物流-记录 跨境物流: 【结论】 中小卖家&#xff08;最低适合1个人经营的卖家&#xff09;首选以下两种物流&#xff0c;目前已知的是以下两种&#xff0c;后续有新的发现再更新。 1、云途物流&#xff08;YunExpress&#xff09;&#xff…

箱型图 Box Plot 数据分析的法宝

文章目录 一、箱形图的介绍二、六大因数三、Box plot的应用四、箱形图的优劣势五、图形拓展 一、箱形图的介绍 箱形图又称为盒须图、盒式图、盒状图或箱线图&#xff0c;是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。在各种领域也经常被使用&#xff0c;…

一、Lua基础

文章目录 一、Lua是什么二、Lua特性&#xff08;一&#xff09;轻量级&#xff08;二&#xff09;可扩展&#xff08;三&#xff09;其它特性 三、Lua安装四、Lua应用 看到评论说&#xff0c;C让我见识了语言的严谨与缜密&#xff0c;lua让我见识到了语言的精巧与创新&#xff…

基于51单片机交通灯夜间模式+紧急模式_易懂版_(仿真+代码_报告_讲解)

J029 51单片机交通灯_易懂版__夜间紧急(仿真代码_报告_讲解&#xff09; 51单片机交通灯_易懂版_ 1 **讲解视频&#xff1a;**2 **功能要求**3 **仿真图&#xff1a;**4 **程序设计&#xff1a;**5 **设计报告**6 **资料清单&&下载链接&#xff1a;****资料下载链接&am…

佳易王商超便利店进销存管理系统软件下载,扫描商品自动计算金额支持扫码支付

佳易王商超便利店进销存管理系统软件下载&#xff0c;扫描商品自动计算金额支持扫码支付 软件特色&#xff1a; 1、功能实用&#xff0c;操作简单&#xff0c;不会电脑也会操作&#xff0c;软件免安装&#xff0c;已内置数据库。软件在关闭的时候&#xff0c;可以设置会员数据…

Windows安装mysql8.0

官网地址&#xff1a;MySQL :: MySQL Community Downloads 选择相应版本信息下载 默认选择点击下一步 默认配置点击next 设置密码 默认配置

多模态大模型总结1(2021和2022年)

常用损失函数 ITC &#xff08;image-text contrasctive loss&#xff09; CLIP中采用的对比损失&#xff0c;最大化配对文本对的余弦相似度&#xff0c;最小化非配对文本对的余弦相似度&#xff0c;采用交叉熵损失实现 MLM &#xff08;masked language modeling&#xff0…

【Linux】23、内存超详细介绍

文章目录 零、资料一、内存映射1.1 TLB1.2 多级页表1.3 大页 二、虚拟内存空间分布2.1 用户空间的段2.2 内存分配和回收2.2.1 小对象2.2.2 释放 三、查看内存使用情况3.1 Buffer 和 Cache3.1.1 proc 文件系统3.1.2 案例3.1.2.1 场景 1&#xff1a;磁盘和文件写案例3.1.2.2 场景…

【STM32单片机】自动售货机控制系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器&#xff0c;使用OLED显示模块、矩阵按键模块、LED和蜂鸣器、继电器模块等。 主要功能&#xff1a; 系统运行后&#xff0c;OLED显示系统初始界面&#xff0c;可通过…

C#的函数

可以发现首字母一般为动词 可以看出void是返回空值 这个就不是放回空值了 例如 函数修饰符 1、如上面的实例一样——无修饰符&#xff1a;如果一个参数没有用参数修饰符标记&#xff0c;则认为它将按值进行传递&#xff0c;这将以为着被调用的方法收到原始数据的一份副本。&am…

2024北京交通大学计算机考研分析

24计算机考研|上岸指南 北京交通大学 北京交通大学是教育部是首批进入国家“211工程”建设高校&#xff0c;是全国具有研究生院的56所高校之一&#xff0c;计算机与信息技术学院为北京交通大学下属二级学院&#xff0c;学院成立于2000年3月&#xff0c;其前身是成立于1977年的…

面向对象编程:Rust的面向对象特性

欢迎关注我的公众号lincyang新自媒体&#xff0c;回复关键字【程序员经典书单】&#xff0c;领取程序员的100本经典书单 大家好&#xff01;我是lincyang。 今天我们将深入探讨Rust语言中的面向对象编程&#xff08;OOP&#xff09;特性&#xff0c;并将其与其他流行编程语言进…

C# PIE-SDK二次开发界面汉化方法

那些最好的程序员不是为了得到更高的薪水或者得到公众的仰慕而编程&#xff0c;他们只是觉得这是一件有趣的事情&#xff01; C# PIE-SDK二次开发界面汉化方法 &#x1f340;前言&#x1f338;配置方法&#x1f355;拷贝语言包文件夹&#x1f354;增加窗体代码&#x1f35f;运行…

链表?细啊!超详细的知识点总结!

链表 定义&#xff1a;链表是一种递归的数据结构&#xff0c;它或者为空&#xff08;null)&#xff0c;或者是指向一个结点&#xff08;node&#xff09;的引用&#xff0c;该结点含有一个泛型的元素和一个指向另一条链表的引用。 ​ 其实链表就是有序的列表&#xff0c;它在内…