基于DRIVE数据集的视网膜UNet分割

2024-01-21 09:36

本文主要是介绍基于DRIVE数据集的视网膜UNet分割,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 数据集介绍

这是一个非常小的数据集,非常适合用于视觉分割任务练手。数据集的文件夹如图所示:

图1-1文件夹结构

test中存放的是测试图片,training中存放的是20张用于训练的图片。imges文件夹中存放的是20张原始图片,mask中存放的是掩码,用于获取感兴趣的区域。manual中存放的是人工标注的groundtruth。

图1-2 原始图片

图1-3 groundtruth 

2 数据集的加载

获取数据集中image和对应的mask

class DriveDataset(Dataset):def __init__(self, root: str, train: bool, transforms=None):super(DriveDataset, self).__init__()self.flag = "training" if train else "test"data_root = os.path.join(root, "DRIVE", self.flag)# 使用断言,目录不存在,则发出警告assert os.path.exists(data_root), f"path '{data_root}' does not exists."# 在transform 中对图像预处理self.transforms = transforms"""(os.path.join(data_root, "images") 获得目录os.listdir(os.path.join(data_root, "images")) 获取目录下的文件名,返回列表[i for i in ...] 使用for循环形成新的列表"""img_names = [i for i in os.listdir(os.path.join(data_root, "images")) if i.endswith(".tif")]# 获取图片与GT的完整路径self.img_list = [os.path.join(data_root, "images", i) for i in img_names]self.manual = [os.path.join(data_root, "1st_manual", i.split("_")[0] + "_manual1.gif")for i in img_names]# check filesfor i in self.manual:if os.path.exists(i) is False:raise FileNotFoundError(f"file {i} does not exists.")self.roi_mask = [os.path.join(data_root, "mask", i.split("_")[0] + f"_{self.flag}_mask.gif")for i in img_names]# check filesfor i in self.roi_mask:if os.path.exists(i) is False:raise FileNotFoundError(f"file {i} does not exists.")def __getitem__(self, idx):img = Image.open(self.img_list[idx]).convert('RGB')manual = Image.open(self.manual[idx]).convert('L')# 0:背景,1:前景,而此时的mask中的前景像素值是255,所以÷255,令其为1manual = np.array(manual) / 255roi_mask = Image.open(self.roi_mask[idx]).convert('L')# 将不感兴趣区域的no_roi区域的像素值设置成255(不参与计算LOSS)roi_mask = 255 - np.array(roi_mask)# 使用np.clip()方法,为叠加了manual(GT)与roi_mask后的像素设置像素的上下限mask = np.clip(manual + roi_mask, a_min=0, a_max=255)# 这里转回PIL的原因是,transforms中是对PIL数据进行处理mask = Image.fromarray(mask)if self.transforms is not None:img, mask = self.transforms(img, mask)return img, maskdef __len__(self):return len(self.img_list)@staticmethoddef collate_fn(batch):images, targets = list(zip(*batch))batched_imgs = cat_list(images, fill_value=0)batched_targets = cat_list(targets, fill_value=255)return batched_imgs, batched_targets

3. 建立UNet模型

from typing import Dict
import torch
import torch.nn as nn
import torch.nn.functional as F
import netronclass DoubleConv(nn.Sequential):def __init__(self, in_channels, out_channels, mid_channels=None):if mid_channels is None:mid_channels = out_channelssuper(DoubleConv, self).__init__(nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),nn.BatchNorm2d(mid_channels),nn.ReLU(inplace=True),nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),nn.BatchNorm2d(out_channels),nn.ReLU(inplace=True))class Down(nn.Sequential):def __init__(self, in_channels, out_channels):super(Down, self).__init__(nn.MaxPool2d(2, stride=2),DoubleConv(in_channels, out_channels))class Up(nn.Module):def __init__(self, in_channels, out_channels, bilinear=True):super(Up, self).__init__()if bilinear:self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)else:self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)self.conv = DoubleConv(in_channels, out_channels)def forward(self, x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor:x1 = self.up(x1)# [N, C, H, W]diff_y = x2.size()[2] - x1.size()[2]diff_x = x2.size()[3] - x1.size()[3]# padding_left, padding_right, padding_top, padding_bottomx1 = F.pad(x1, [diff_x // 2, diff_x - diff_x // 2,diff_y // 2, diff_y - diff_y // 2])x = torch.cat([x2, x1], dim=1)x = self.conv(x)return xclass OutConv(nn.Sequential):def __init__(self, in_channels, num_classes):super(OutConv, self).__init__(nn.Conv2d(in_channels, num_classes, kernel_size=1))class UNet(nn.Module):def __init__(self,in_channels: int = 1,num_classes: int = 2,bilinear: bool = True,base_c: int = 64):super(UNet, self).__init__()self.in_channels = in_channelsself.num_classes = num_classesself.bilinear = bilinearself.in_conv = DoubleConv(in_channels, base_c)self.down1 = Down(base_c, base_c * 2)self.down2 = Down(base_c * 2, base_c * 4)self.down3 = Down(base_c * 4, base_c * 8)factor = 2 if bilinear else 1self.down4 = Down(base_c * 8, base_c * 16 // factor)self.up1 = Up(base_c * 16, base_c * 8 // factor, bilinear)self.up2 = Up(base_c * 8, base_c * 4 // factor, bilinear)self.up3 = Up(base_c * 4, base_c * 2 // factor, bilinear)self.up4 = Up(base_c * 2, base_c, bilinear)self.out_conv = OutConv(base_c, num_classes)def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:x1 = self.in_conv(x)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.out_conv(x)return {"out": logits}

4 编写损失函数和评价函数

import torch
import torch.nn as nndef build_target(target: torch.Tensor, num_classes: int = 2, ignore_index: int = -100):"""build target for dice coefficient"""dice_target = target.clone()if ignore_index >= 0:ignore_mask = torch.eq(target, ignore_index)dice_target[ignore_mask] = 0# [N, H, W] -> [N, H, W, C]dice_target = nn.functional.one_hot(dice_target, num_classes).float()dice_target[ignore_mask] = ignore_indexelse:dice_target = nn.functional.one_hot(dice_target, num_classes).float()return dice_target.permute(0, 3, 1, 2)def dice_coeff(x: torch.Tensor, target: torch.Tensor, ignore_index: int = -100, epsilon=1e-6):# Average of Dice coefficient for all batches, or for a single mask# 计算一个batch中所有图片某个类别的dice_coefficientd = 0.batch_size = x.shape[0]for i in range(batch_size):x_i = x[i].reshape(-1)t_i = target[i].reshape(-1)if ignore_index >= 0:# 找出mask中不为ignore_index的区域roi_mask = torch.ne(t_i, ignore_index)x_i = x_i[roi_mask]t_i = t_i[roi_mask]inter = torch.dot(x_i, t_i)sets_sum = torch.sum(x_i) + torch.sum(t_i)if sets_sum == 0:sets_sum = 2 * interd += (2 * inter + epsilon) / (sets_sum + epsilon)return d / batch_sizedef multiclass_dice_coeff(x: torch.Tensor, target: torch.Tensor, ignore_index: int = -100, epsilon=1e-6):"""Average of Dice coefficient for all classes"""dice = 0.for channel in range(x.shape[1]):dice += dice_coeff(x[:, channel, ...], target[:, channel, ...], ignore_index, epsilon)return dice / x.shape[1]def dice_loss(x: torch.Tensor, target: torch.Tensor, multiclass: bool = False, ignore_index: int = -100):# Dice loss (objective to minimize) between 0 and 1x = nn.functional.softmax(x, dim=1)fn = multiclass_dice_coeff if multiclass else dice_coeffreturn 1 - fn(x, target, ignore_index=ignore_index)

5.开始训练

def main(args):device = torch.device(args.device if torch.cuda.is_available() else "cpu")batch_size = args.batch_size# segmentation nun_classes + backgroundnum_classes = args.num_classes + 1# using compute_mean_std.pymean = (0.709, 0.381, 0.224)std = (0.127, 0.079, 0.043)# 用来保存训练以及验证过程中信息results_file = "results{}.txt".format(datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))train_dataset = DriveDataset(args.data_path,train=True,transforms=get_transform(train=True, mean=mean, std=std))val_dataset = DriveDataset(args.data_path,train=False,transforms=get_transform(train=False, mean=mean, std=std))num_workers = 0train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,num_workers=num_workers,shuffle=True,pin_memory=True,collate_fn=train_dataset.collate_fn)val_loader = torch.utils.data.DataLoader(val_dataset,batch_size=1,num_workers=num_workers,pin_memory=True,collate_fn=val_dataset.collate_fn)for img,lab in train_loader:print(img.shape)print(lab.shape)model = create_model(num_classes=num_classes)model.to(device)params_to_optimize = [p for p in model.parameters() if p.requires_grad]optimizer = torch.optim.SGD(params_to_optimize,lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay)scaler = torch.cuda.amp.GradScaler() if args.amp else None# 创建学习率更新策略,这里是每个step更新一次(不是每个epoch)lr_scheduler = create_lr_scheduler(optimizer, len(train_loader), args.epochs, warmup=True)if args.resume:checkpoint = torch.load(args.resume, map_location='cpu')  # load模型model.load_state_dict(checkpoint['model'])optimizer.load_state_dict(checkpoint['optimizer'])lr_scheduler.load_state_dict(checkpoint['lr_scheduler'])args.start_epoch = checkpoint['epoch'] + 1if args.amp:scaler.load_state_dict(checkpoint["scaler"])best_dice = 0.0start_time = time.time()for epoch in range(args.start_epoch, args.epochs):mean_loss, lr = train_one_epoch(model, optimizer, train_loader, device, epoch, num_classes,lr_scheduler=lr_scheduler, print_freq=args.print_freq, scaler=scaler)confmat, dice = evaluate(model, val_loader, device=device, num_classes=num_classes)val_info = str(confmat)print(val_info)print(f"dice coefficient: {dice:.3f}")# write into txtwith open(results_file, "a") as f:# 记录每个epoch对应的train_loss、lr以及验证集各指标train_info = f"[epoch: {epoch}]\n" \f"train_loss: {mean_loss:.4f}\n" \f"lr: {lr:.6f}\n" \f"dice coefficient: {dice:.3f}\n"f.write(train_info + val_info + "\n\n")if args.save_best is True:if best_dice < dice:best_dice = diceelse:continuesave_file = {"model": model.state_dict(),"optimizer": optimizer.state_dict(),"lr_scheduler": lr_scheduler.state_dict(),"epoch": epoch,"args": args}if args.amp:save_file["scaler"] = scaler.state_dict()if args.save_best is True:torch.save(save_file, "save_weights/best_model.pth")else:torch.save(save_file, "save_weights/model_{}.pth".format(epoch))total_time = time.time() - start_timetotal_time_str = str(datetime.timedelta(seconds=int(total_time)))print("training time {}".format(total_time_str))

这篇关于基于DRIVE数据集的视网膜UNet分割的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/629087

相关文章

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁

不删数据还能合并磁盘? 让电脑C盘D盘合并并保留数据的技巧

《不删数据还能合并磁盘?让电脑C盘D盘合并并保留数据的技巧》在Windows操作系统中,合并C盘和D盘是一个相对复杂的任务,尤其是当你不希望删除其中的数据时,幸运的是,有几种方法可以实现这一目标且在... 在电脑生产时,制造商常为C盘分配较小的磁盘空间,以确保软件在运行过程中不会出现磁盘空间不足的问题。但在

C#中字符串分割的多种方式

《C#中字符串分割的多种方式》在C#编程语言中,字符串处理是日常开发中不可或缺的一部分,字符串分割是处理文本数据时常用的操作,它允许我们将一个长字符串分解成多个子字符串,本文给大家介绍了C#中字符串分... 目录1. 使用 string.Split2. 使用正则表达式 (Regex.Split)3. 使用

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

Mybatis拦截器如何实现数据权限过滤

《Mybatis拦截器如何实现数据权限过滤》本文介绍了MyBatis拦截器的使用,通过实现Interceptor接口对SQL进行处理,实现数据权限过滤功能,通过在本地线程变量中存储数据权限相关信息,并... 目录背景基础知识MyBATis 拦截器介绍代码实战总结背景现在的项目负责人去年年底离职,导致前期规

Redis KEYS查询大批量数据替代方案

《RedisKEYS查询大批量数据替代方案》在使用Redis时,KEYS命令虽然简单直接,但其全表扫描的特性在处理大规模数据时会导致性能问题,甚至可能阻塞Redis服务,本文将介绍SCAN命令、有序... 目录前言KEYS命令问题背景替代方案1.使用 SCAN 命令2. 使用有序集合(Sorted Set)

SpringBoot整合Canal+RabbitMQ监听数据变更详解

《SpringBoot整合Canal+RabbitMQ监听数据变更详解》在现代分布式系统中,实时获取数据库的变更信息是一个常见的需求,本文将介绍SpringBoot如何通过整合Canal和Rabbit... 目录需求步骤环境搭建整合SpringBoot与Canal实现客户端Canal整合RabbitMQSp