Unet++语义分割网络(网络结构分析+代码分析)

1. 前言

许多初入视觉深度学习的小伙伴都会以图像分类网络作为入门案例来学习,个人觉得语义分割网络可以作为分类网络之后第二个学习的案例,因为其网络结构一般较为简单,只要对每个像素点进行分类即可。刚好课题组召开分享会,就和大家分享下Unet++语义分割网络。注:以下分享的许多地方是我的个人理解,可能有不恰当之处还请指出和包涵。视频和代码链接在下方。

视频分享链接:课题组技术分享会-Unet++网络_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1La411U7FS/?vd_source=73870594793a8be3d80e0be8a37582d3

github源码:GitHub - MrGiovanni/UNetPlusPlus: Official Keras Implementation for UNet++ in IEEE Transactions on Medical Imaging and DLMIA 2018https://github.com/MrGiovanni/UNetPlusPlus

2. 网络结构和思想

2.1 什么是语义分割

首先明白什么是语义分割,语义分割是对同一种类的物体进行提取,以掩码图的形式输出分割的结果。相比实例分割,它只能提取一个种类,而不能在同一个类中区分出不同的个体。可以简单理解为:实例分割=语义分割+目标检测。

2.2 传统语义分割

下图为一种非常经典的语义分割网络,backbone提取完特征,通过反卷积变回原图尺寸,然后对每个像素点分类,输出结果。

2.3 进阶语义分割

下图为进阶的语义分割,主要不同之处在于会将不同尺寸不同阶段的特征图进行融合,提升分割效果。

那么问题来了,为什么要多个特征图融合呢?原因在于卷积网络的浅层提供了纹理特征,深层提供了丰富的语义信息,两者都有用。拿下面ppt的问题类比一下,假设目前给你提供一个信息,一个目标有鼻子、 眼睛、耳朵、四肢,该目标最大可能性是什么?

有人可能会回答是人,但正如下面ppt所示,事实情况也可能是猩猩或者猴子或者其他小动物。我们不能判断的原因在于只用了语义信息而没用纹理信息。

 2.4 Unet++特性

上面提到了特征跨阶段融合的意义,接下去就不难理解UNet++网络结构为什么要这样做了。UNet++通过短连接和上下采样等操作,间接融合了多个不同层次的特征,而非简单的Encoder与Decoder同层级特征的简单拼接。从其网络特征来看,就像织了一张网一样,将特征融合做到了极致,用专业的术语来说就是稠密连接

 3. 代码讲解

代码讲解在视频里面,视频已经发布,在如下链接里。视频是课题组分享会录制的,里面不仅有Unet++网络结构的讲解,还有很多通用的pytorch代码讲解,创作不易,欢迎一键三连噢(●'◡'●)。注:视频里的代码是其他up主提供的注释版。

课题组技术分享会-Unet++网络_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1La411U7FS/?vd_source=73870594793a8be3d80e0be8a37582d3

4. 总结

Unet++是一个非常经典又易于学习的网络,非常值得前期研究一下。

以下是基于Pytorch实现的医学图像语义分割Unet++代码: ```python import torch import torch.nn as nn from torchvision import models class ConvBlock(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True, bn=True): super(ConvBlock, self).__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=bias) self.bn = nn.BatchNorm2d(out_channels) if bn else None self.relu = nn.ReLU(inplace=True) def forward(self, x): x = self.conv(x) if self.bn is not None: x = self.bn(x) x = self.relu(x) return x class UpConv(nn.Module): def __init__(self, in_channels, out_channels, upscale_factor, mode='transpose', align_corners=True): super(UpConv, self).__init__() self.upscale_factor = upscale_factor self.align_corners = align_corners if mode == 'transpose': self.conv = nn.ConvTranspose2d(in_channels, out_channels, kernel_size=2*self.upscale_factor, stride=self.upscale_factor, padding=self.upscale_factor//2, output_padding=self.upscale_factor%2, bias=True) else: self.conv = nn.Sequential( nn.Upsample(scale_factor=self.upscale_factor, mode=mode, align_corners=self.align_corners), nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True) ) def forward(self, x): return self.conv(x) class NestedUNet(nn.Module): def __init__(self, in_channels=1, out_channels=2, init_features=32): super(NestedUNet, self).__init__() self.down1 = nn.Sequential( ConvBlock(in_channels, init_features, bn=False), ConvBlock(init_features, init_features*2) ) self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True) self.down2 = nn.Sequential( ConvBlock(init_features*2, init_features*2*2), ConvBlock(init_features*2*2, init_features*2*2*2) ) self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True) self.down3 = nn.Sequential( ConvBlock(init_features*2*2*2, init_features*2*2*2*2), ConvBlock(init_features*2*2*2*2, init_features*2*2*2*2*2) ) self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True) self.down4 = nn.Sequential( ConvBlock(init_features*2*2*2*2, init_features*2*2*2*2*2), ConvBlock(init_features*2*2*2*2*2, init_features*2*2*2*2*2*2) ) self.pool4 = nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True) self.bottom = nn.Sequential( ConvBlock(init_features*2*2*2*2*2, init_features*2*2*2*2*2*2), ConvBlock(init_features*2*2*2*2*2*2, init_features*2*2*2*2*2*2), UpConv(init_features*2*2*2*2*2*2, init_features*2*2*2*2*2, upscale_factor=2) ) self.up4 = nn.Sequential( ConvBlock(init_features*2*2*2*2*2, init_features*2*2*2*2*2), ConvBlock(init_features*2*2*2*2*2, init_features*2*2*2*2), UpConv(init_features*2*2*2*2, init_features*2*2, upscale_factor=2) ) self.up3 = nn.Sequential( ConvBlock(init_features*2*2*2*2, init_features*2*2), ConvBlock(init_features*2*2, init_features*2), UpConv(init_features*2, init_features, upscale_factor=2) ) self.up2 = nn.Sequential( ConvBlock(init_features*2*2, init_features), ConvBlock(init_features, init_features), UpConv(init_features, init_features//2, upscale_factor=2) ) self.up1 = nn.Sequential( ConvBlock(init_features, init_features//2), ConvBlock(init_features//2, out_channels) ) def forward(self, x): x1 = self.down1(x) x2 = self.pool1(x1) x2 = self.down2(x2) x3 = self.pool2(x2) x3 = self.down3(x3) x4 = self.pool3(x3) x4 = self.down4(x4) btm = self.pool4(x4) btm = self.bottom(btm) x4 = torch.cat([btm, x4], dim=1) x4 = self.up4(x4) x3 = torch.cat([x4, x3], dim=1) x3 = self.up3(x3) x2 = torch.cat([x3, x2], dim=1) x2 = self.up2(x2) x1 = torch.cat([x2, x1], dim=1) x1 = self.up1(x1) return x1 ``` 这段代码实现了一个基于Pytorch的NestedUNet模型,包含四个下采样/池化层和四个上采样/卷积层。其中,第一个下采样/池化层的输出被送入第二个下采样/池化层,以此类推。最后的bottom层会将最后一层下采样/池化层的输出送入上采样/卷积层,以生成最终的分割结果。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值