【セマンティックセグメンテーションシリーズ】ICNET(リアルタイムセマンティックセグメンテーション)理論とコード実装

Gitアドレス:https//github.com/Tramac/awesome-semantic-segmentation-pytorch

含める:

FCN
ENet
PSPNet
ICNet
DeepLabv3
DeepLabv3+
DenseASPP
EncNet
BiSeNet
PSANet
DANet
OCNet
CGNet
ESPNetv2
CCNet
DUNet(DUpsampling)
FastFCN(JPU)
LEDNet
Fast-SCNN
LightSeg
DFANet

セマンティックセグメンテーションの主流のソースコードがほぼ含まれています。デバッグは悪くありません。

あなたが選ぶ必要があるものを見てください。

ICNET理論のいくつかの重要なポイント:

1.コアアイデア:

        画像を高、中、低の3つのレイヤーに分割します。パフォーマンスが大幅に向上する基本的な理由は、低解像度および低解像度の画像がセマンティックセグメンテーションネットワークを通過して大まかなセグメンテーション結果を生成できるようにすることです。次に、機能カスケードハイブリッドユニット(カスケードラベルガイダンス)とカスケードラベルガイダンス戦略(カスケードラベルガイダンス戦略)が解決されます。高速機能と高解像度機能の統合により、以前に生成された大まかなセグメンテーション結果が徐々に最適化されます。

2.ネットワーク図:

        下から上へ、高解像度から低解像度。低解像度の結果を中解像度の結果と組み合わせて青い正方形を生成し、中解像度の結果と青い正方形をCFFメソッドによって高解像度のフィーチャマップと融合します。最後に、結果がアップサンプリングされて最終出力が取得されます。

3.CFF:

      カスケード機能の融合、

まず、F1で双一次内挿を実行し、2倍アップサンプリングして、F2と同じサイズにします。

次に、サンプリングレート2のホールコンボリューションであるC3X3を使用して、F1をアップサンプリングします。生成される画像サイズはF2と同じです。穴のたたみ込みは、元の隣接するピクセルの特徴情報を組み合わせたものであり、受容野はF2と同じです。計算量はデコンボリューションよりも少なくなります。Aとしてマーク

機能F2のCX1X1畳み込みを使用して、F1チャネルと同じにします(昇順の寸法に相当)。表記B

BNレイヤーを使用して、処理された2つのフィーチャレイヤーABを標準化します

2つを足し合わせ、reluアクティベーションを使用して、F2と同じサイズの融合機能F2 'を取得します。

4.カスケードラベルガイダンス

        トレーニング中に使用される赤い円4の部分は、以下のコードのx_24_cls部分に対応します。そのようなポジションは3つあります。率直に言って、事前にカスケードする場所であり、異なる解像度でのアノテーションマップのアノテーション情報

x_cff_24, x_24_cls = self.cff_24(x_sub4, x_sub2)

 

5.パフォーマンス:

私は主にリアルタイムのセマンティックセグメンテーションを使用したいと思います。ICNETテストを選択するだけです。2048 * 1024画像予測GPU予測時間は約190msです。画像サイズを変更すると、ネットワークパラメータを少し速く圧縮できます

6.コード:

       注1:テスト用に選択したバックボーンネットワークはresnet50であり、このネットワークに関する次の注を確認する必要があります。一目でわかるバックボーンネットワークの役割は、特徴を抽出することです。他のバックボーンネットワークも同様で、主にICNET実装のフローを確認します。

       注2:コードのCascadeFeatureFusion部分に不明確な場所があります。紙を見てください 

図3に対応しているはずです。私の理解と一致して、それは低画像をアップサンプリングした直後であり、アップサンプリングを入力として直接取得し、カテゴリの数をサブコンボリューションの出力チャネルとして使用する必要があります。そこで、いくつか変更を加えましたが、まだトレーニングを終えていないので、トレーニングが効果を発揮するのを待ちましょう。

class CascadeFeatureFusion(nn.Module):
    """CFF Unit"""


        self.conv_low_cls = nn.Conv2d(low_channels, nclass, 1, bias=False)


        x_low = F.interpolate(x_low, size=x_high.size()[2:], mode='bilinear', align_corners=True)
        x_low1 = x_low
        
        x_low_cls = self.conv_low_cls(x_low1)

        return x, x_low_cls

   完全なコード:                                                

"""Image Cascade Network"""
import torch
import torch.nn as nn
import torch.nn.functional as F

from .segbase import SegBaseModel

__all__ = ['ICNet', 'get_icnet', 'get_icnet_resnet50_citys',
           'get_icnet_resnet101_citys', 'get_icnet_resnet152_citys']

class ICNet(SegBaseModel):
    """Image Cascade Network"""

    def __init__(self, nclass, backbone='resnet50', aux=False, jpu=False, pretrained_base=False, **kwargs):
        super(ICNet, self).__init__(nclass, aux, backbone, pretrained_base=pretrained_base, **kwargs)
        self.conv_sub1 = nn.Sequential(
            _ConvBNReLU(3, 32, 3, 2, **kwargs),#ksize 3  stride 2   
            _ConvBNReLU(32, 32, 3, 2, **kwargs),
            _ConvBNReLU(32, 64, 3, 2, **kwargs)
        )

        self.ppm = PyramidPoolingModule()

        self.head = _ICHead(nclass, **kwargs)

        self.__setattr__('exclusive', ['conv_sub1', 'head'])

    def forward(self, x):
        # sub 1  原图做普通卷积 BN RL                     对应fig.2的 红圈1    例如 x shape [20, 3, 480, 480]
        x_sub1 = self.conv_sub1(x)  #图像变为原图 1/8                               x_sub1 shape [20, 64, 60, 60]

        # sub 2  x_sub2是将原图缩放到  1/2                                          x_sub2 shape [20, 3, 240, 240]
        x_sub2 = F.interpolate(x, scale_factor=0.5, mode='bilinear', align_corners=True)
        #过主干网络 例如resnet之类。 对x_sub2做特征提取   对应fig.2的 红圈2  看取值是取的layer2   大小变为  1 / 16
        #                                                                           x_sub2 shape [20, 512, 30, 30]
        _, x_sub2, _, _ = self.base_forward(x_sub2)

        # sub 4 x_sub4 原图缩放到 1 / 4                                             x_sub4 shape [20, 3, 120, 120]
        x_sub4 = F.interpolate(x, scale_factor=0.25, mode='bilinear', align_corners=True)#双线性插值将为 原图 1/4
        #过主干网络  对x_sub4做特征提取                   对应fig.2 的红圈3   看取值是取的layer4          1/32
        #                                                                           x_sub4 shape [20, 2048, 15, 15]
        _, _, _, x_sub4 = self.base_forward(x_sub4)
        # add PyramidPoolingModule
        # x_sub4作为输出 就是加上全局池化后的信息了                                 x_sub4 shape [20, 2048, 15, 15]
        x_sub4 = self.ppm(x_sub4)
        #上采样部分  x_sub1 [20, 64, 60, 60]  x_sub2 [20, 512, 30, 30]  x_sub4 [20, 2048, 15, 15]
        outputs = self.head(x_sub1, x_sub2, x_sub4)

        return tuple(outputs)
#类似PPM结构
class PyramidPoolingModule(nn.Module):
    def __init__(self, pyramids=[1,2,3,6]):
        super(PyramidPoolingModule, self).__init__()
        self.pyramids = pyramids

    def forward(self, input):
        feat = input
        height, width = input.shape[2:]
        for bin_size in self.pyramids:
            #池化 x的尺寸分别为 1*1  2*2 3*3 6*6 通道数不变 与输入特征图一致
            #这么做点目的是保留全局信息,防止大块的目标中出现空洞,大目标检测不好 例如 马路 例如 天空
            x = F.adaptive_avg_pool2d(input, output_size=bin_size)
            # 将池化结果插值回原图大小
            x = F.interpolate(x, size=(height, width), mode='bilinear', align_corners=True)
            # 输入特征图与插值图像叠加
            feat  = feat + x
        return feat

class _ICHead(nn.Module):
    def __init__(self, nclass, norm_layer=nn.BatchNorm2d, **kwargs):
        super(_ICHead, self).__init__()
        self.cff_12 = CascadeFeatureFusion(512, 64, 128, nclass, norm_layer, **kwargs)
        #self.cff_12 = CascadeFeatureFusion(128, 64, 128, nclass, norm_layer, **kwargs)  
        self.cff_24 = CascadeFeatureFusion(2048, 512, 128, nclass, norm_layer, **kwargs)

        self.conv_cls = nn.Conv2d(128, nclass, 1, bias=False)

    def forward(self, x_sub1, x_sub2, x_sub4):
        outputs = list()
        # cff_24是论文中的CFF部分实现代码 这个是做最小和次小的特征融合呢
        # x_cff_24 shape [20, 128, 30, 30]   x_24_cls shape [20, 19, 30, 30]
        x_cff_24, x_24_cls = self.cff_24(x_sub4, x_sub2)
        outputs.append(x_24_cls)
        #x_cff_12, x_12_cls = self.cff_12(x_sub2, x_sub1)
        # 论文中的CFF部分实现代码 这个是做次小和最大的特征融合呢
        # # x_cff_12 shape [20, 128, 60, 60]   x_12_cls shape [20, 19, 60, 60]
        x_cff_12, x_12_cls = self.cff_12(x_cff_24, x_sub1)  
        outputs.append(x_12_cls)
        # x_cff_12 上采样一次           up_x2 shape [20, 128, 120, 120]
        up_x2 = F.interpolate(x_cff_12, scale_factor=2, mode='bilinear', align_corners=True)
        # 做一次loss函数需要的卷积       up_x2 shape [20, 19, 120, 120]     
        up_x2 = self.conv_cls(up_x2)
        outputs.append(up_x2)
        # up_x2 再做一次上采样  直接放大到原图大小
        # x_cff_12 上采样一次  up_x2 shape [20, 19, 480, 480]
        up_x8 = F.interpolate(up_x2, scale_factor=4, mode='bilinear', align_corners=True)
        outputs.append(up_x8)
        # 1 -> 1/4 -> 1/8 -> 1/16  将list反序
        outputs.reverse()
        # outputs 依次存放{[20, 19, 480, 480], [20, 19, 120, 120], [20, 19, 60, 60], [20, 19, 30, 30]}
        return outputs


class _ConvBNReLU(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1, dilation=1,
                 groups=1, norm_layer=nn.BatchNorm2d, bias=False, **kwargs):
        super(_ConvBNReLU, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias)
        self.bn = norm_layer(out_channels)
        self.relu = nn.ReLU(True)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x


class CascadeFeatureFusion(nn.Module):
    """CFF Unit"""

    def __init__(self, low_channels, high_channels, out_channels, nclass, norm_layer=nn.BatchNorm2d, **kwargs):
        super(CascadeFeatureFusion, self).__init__()
        self.conv_low = nn.Sequential(
            nn.Conv2d(low_channels, out_channels, 3, padding=2, dilation=2, bias=False),
            norm_layer(out_channels)
        )
        self.conv_high = nn.Sequential(
            nn.Conv2d(high_channels, out_channels, 1, bias=False),
            norm_layer(out_channels)
        )
        self.conv_low_cls = nn.Conv2d(out_channels, nclass, 1, bias=False)
    # 对应论文fig.3 
    def forward(self, x_low, x_high):
        # 先将 x_low特征图做双线性插值上采样                    对应fig.3 图中框1   
        # 例如对于cff_24   x_low 输入为[20, 2048, 15, 15] 输出为[20, 2048, 30, 30]
        x_low = F.interpolate(x_low, size=x_high.size()[2:], mode='bilinear', align_corners=True)
        # 再对x_low特征图做空洞卷积     结果通道为128           对应fig.3 图中框2
        # 例如对于cff_24   x_low 输入为[20, 2048, 30, 30] 输出为[20, 128, 30, 30]
        x_low = self.conv_low(x_low)
        # 对x_high特征图做 1*1的卷积    结果通道为128           对应fig.3 图中框3
        # 例如对于cff_24   x_high 输入为[20, 512, 30, 30] 输出为[20, 128, 30, 30]
        x_high = self.conv_high(x_high)
        # 求和做relu 作为输出数据之一                           对应fig.3 图中框4
        x = x_low + x_high
        x = F.relu(x, inplace=True)
        # 对空洞x_low特征图做直接做卷积并做BatchNorm2d 结果通道数为类别维度   用来算损失函数的  对应fig.3 图中 框5  
        # 代码与论文图对应不上了   按理应该是双线性插值的结果作为输入才对  
        # 例如对于cff_24   x_low 输入为[20, 128, 30, 30] 输出为[20, 19, 30, 30]
        # 按论文画的图 应该为  x_low 输入为[20, 2048, 30, 30] 输出为[20, 19, 30, 30]才对  改了训练一次看看效果  这块是
        # 这个代码我没看懂的地方之一
        x_low_cls = self.conv_low_cls(x_low)
        return x, x_low_cls


def get_icnet(dataset='citys', backbone='resnet50', pretrained=False, root='~/.torch/models',
              pretrained_base=False, **kwargs):
    acronyms = {
        'pascal_voc': 'pascal_voc',
        'pascal_aug': 'pascal_aug',
        'ade20k': 'ade',
        'coco': 'coco',
        'citys': 'citys',
    }
    from ..data.dataloader import datasets
    model = ICNet(datasets[dataset].NUM_CLASS, backbone=backbone, pretrained_base=pretrained_base, **kwargs)
    path = '/home/yuany/awesome-semantic-segmentation-pytorch-master/scripts/models/best.pth'
    pretrained = True
    if pretrained:
        from .model_store import get_model_file
        #device = torch.device(kwargs['local_rank'])
        device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        #model.load_state_dict(torch.load(get_model_file('icnet_%s_%s' % (backbone, acronyms[dataset]), root=root),
        #                      map_location=device))
        model.load_state_dict(torch.load(path,
                              map_location=device))
    return model


def get_icnet_resnet50_citys(**kwargs):
    return get_icnet('citys', 'resnet50', **kwargs)


def get_icnet_resnet101_citys(**kwargs):
    return get_icnet('citys', 'resnet101', **kwargs)


def get_icnet_resnet152_citys(**kwargs):
    return get_icnet('citys', 'resnet152', **kwargs)


if __name__ == '__main__':
    img = torch.randn(1, 3, 256, 256)
    model = get_icnet_resnet50_citys()
    outputs = model(img)

新しくて速いものを録音してください。最初に画像を保存し、コードに従って紙を確認します

完全な手順:主に道路でセマンティックセグメンテーションを実行したいのですが、選択したデータセットは都市の景観です。

1.データセットの都市景観を準備します。解凍する必要がある圧縮パッケージを解凍するだけです

2.コード、githubhttps ://github.com/Tramac/awesome-semantic-segmentation-pytorchのリンクを準備します

3.解凍後、scriptsフォルダーに入ります。中にはtrainファイルがあります。コアフォルダにはモデルが保存されます。あらゆる種類のセマンティックセグメンテーションモデルが含まれており、選択項目はトレインのパラメータリストに表示されます。

4.cityscapes.pyのパスを変更します。データセットcityscapesをデータセットpath ../ datasets / cityscapesの下に直接配置します。

5.また、データセット内の都市ファイルを変更してパスを配置しました。たとえば、私のパスは; / home / yuany / awesome-semantic-segmentation-pytorch-master / datasets / cityscapesです。どちらに注意を払っていませんでした。これらの2つのパスは機能します。

6.次に、trainコマンドを入力します。たとえば、モデルがicnetバックボーンネットワークを選択してresnet50を選択し、データセットが都市を選択し、初期学習率が0.01、反復回数が30000の場合、コマンドは次のようになります。

python train.py --model icnet --backbone resnet50 --dataset citys --lr 0.01 --epochs 30000

7.トレーニングされたモデルはmodelsフォルダーにあり、最高のものと最新のものがあります。

8.使用:デモを直接押して使用し、分割する画像のパスを設定します。分割するだけで、モデルをicntにロードするパスを設定します。これは、画像に便利です。

 

 

 

おすすめ

転載: blog.csdn.net/gbz3300255/article/details/111545273