# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from typing import Union, List, Tuple import math import paddle import paddle.nn as nn import stdc1_seg_cityscapes.layers as L __all__ = ["STDC1", "STDC2"] class STDCNet(nn.Layer): """ The STDCNet implementation based on PaddlePaddle. The original article refers to Meituan Fan, Mingyuan, et al. "Rethinking BiSeNet For Real-time Semantic Segmentation." (https://arxiv.org/abs/2104.13188) Args: base(int, optional): base channels. Default: 64. layers(list, optional): layers numbers list. It determines STDC block numbers of STDCNet's stage3\4\5. Defualt: [4, 5, 3]. block_num(int,optional): block_num of features block. Default: 4. type(str,optional): feature fusion method "cat"/"add". Default: "cat". num_classes(int, optional): class number for image classification. Default: 1000. dropout(float,optional): dropout ratio. if >0,use dropout ratio. Default: 0.20. use_conv_last(bool,optional): whether to use the last ConvBNReLU layer . Default: False. pretrained(str, optional): the path of pretrained model. """ def __init__(self, base: int = 64, layers: List[int] = [4, 5, 3], block_num: int = 4, type: str = "cat", num_classes: int = 1000, dropout: float = 0.20, use_conv_last: bool = False): super(STDCNet, self).__init__() if type == "cat": block = CatBottleneck elif type == "add": block = AddBottleneck self.use_conv_last = use_conv_last self.features = self._make_layers(base, layers, block_num, block) self.conv_last = ConvBNRelu(base * 16, max(1024, base * 16), 1, 1) if (layers == [4, 5, 3]): #stdc1446 self.x2 = nn.Sequential(self.features[:1]) self.x4 = nn.Sequential(self.features[1:2]) self.x8 = nn.Sequential(self.features[2:6]) self.x16 = nn.Sequential(self.features[6:11]) self.x32 = nn.Sequential(self.features[11:]) elif (layers == [2, 2, 2]): #stdc813 self.x2 = nn.Sequential(self.features[:1]) self.x4 = nn.Sequential(self.features[1:2]) self.x8 = nn.Sequential(self.features[2:4]) self.x16 = nn.Sequential(self.features[4:6]) self.x32 = nn.Sequential(self.features[6:]) else: raise NotImplementedError( "model with layers:{} is not implemented!".format(layers)) def forward(self, x: paddle.Tensor) -> paddle.Tensor: """ forward function for feature extract. """ feat2 = self.x2(x) feat4 = self.x4(feat2) feat8 = self.x8(feat4) feat16 = self.x16(feat8) feat32 = self.x32(feat16) if self.use_conv_last: feat32 = self.conv_last(feat32) return feat2, feat4, feat8, feat16, feat32 def _make_layers(self, base, layers, block_num, block): features = [] features += [ConvBNRelu(3, base // 2, 3, 2)] features += [ConvBNRelu(base // 2, base, 3, 2)] for i, layer in enumerate(layers): for j in range(layer): if i == 0 and j == 0: features.append(block(base, base * 4, block_num, 2)) elif j == 0: features.append( block(base * int(math.pow(2, i + 1)), base * int(math.pow(2, i + 2)), block_num, 2)) else: features.append( block(base * int(math.pow(2, i + 2)), base * int(math.pow(2, i + 2)), block_num, 1)) return nn.Sequential(*features) class ConvBNRelu(nn.Layer): def __init__(self, in_planes: int, out_planes: int, kernel: int = 3, stride: int = 1): super(ConvBNRelu, self).__init__() self.conv = nn.Conv2D( in_planes, out_planes, kernel_size=kernel, stride=stride, padding=kernel // 2, bias_attr=False) self.bn = L.SyncBatchNorm(out_planes, data_format='NCHW') self.relu = nn.ReLU() def forward(self, x): out = self.relu(self.bn(self.conv(x))) return out class AddBottleneck(nn.Layer): def __init__(self, in_planes: int, out_planes: int, block_num: int = 3, stride: int = 1): super(AddBottleneck, self).__init__() assert block_num > 1, "block number should be larger than 1." self.conv_list = nn.LayerList() self.stride = stride if stride == 2: self.avd_layer = nn.Sequential( nn.Conv2D( out_planes // 2, out_planes // 2, kernel_size=3, stride=2, padding=1, groups=out_planes // 2, bias_attr=False), nn.BatchNorm2D(out_planes // 2), ) self.skip = nn.Sequential( nn.Conv2D( in_planes, in_planes, kernel_size=3, stride=2, padding=1, groups=in_planes, bias_attr=False), nn.BatchNorm2D(in_planes), nn.Conv2D( in_planes, out_planes, kernel_size=1, bias_attr=False), nn.BatchNorm2D(out_planes), ) stride = 1 for idx in range(block_num): if idx == 0: self.conv_list.append( ConvBNRelu(in_planes, out_planes // 2, kernel=1)) elif idx == 1 and block_num == 2: self.conv_list.append( ConvBNRelu(out_planes // 2, out_planes // 2, stride=stride)) elif idx == 1 and block_num > 2: self.conv_list.append( ConvBNRelu(out_planes // 2, out_planes // 4, stride=stride)) elif idx < block_num - 1: self.conv_list.append( ConvBNRelu(out_planes // int(math.pow(2, idx)), out_planes // int(math.pow(2, idx + 1)))) else: self.conv_list.append( ConvBNRelu(out_planes // int(math.pow(2, idx)), out_planes // int(math.pow(2, idx)))) def forward(self, x: paddle.Tensor) -> paddle.Tensor: out_list = [] out = x for idx, conv in enumerate(self.conv_list): if idx == 0 and self.stride == 2: out = self.avd_layer(conv(out)) else: out = conv(out) out_list.append(out) if self.stride == 2: x = self.skip(x) return paddle.concat(out_list, axis=1) + x class CatBottleneck(nn.Layer): def __init__(self, in_planes: int, out_planes: int, block_num: int = 3, stride: int = 1): super(CatBottleneck, self).__init__() assert block_num > 1, "block number should be larger than 1." self.conv_list = nn.LayerList() self.stride = stride if stride == 2: self.avd_layer = nn.Sequential( nn.Conv2D( out_planes // 2, out_planes // 2, kernel_size=3, stride=2, padding=1, groups=out_planes // 2, bias_attr=False), nn.BatchNorm2D(out_planes // 2), ) self.skip = nn.AvgPool2D(kernel_size=3, stride=2, padding=1) stride = 1 for idx in range(block_num): if idx == 0: self.conv_list.append( ConvBNRelu(in_planes, out_planes // 2, kernel=1)) elif idx == 1 and block_num == 2: self.conv_list.append( ConvBNRelu(out_planes // 2, out_planes // 2, stride=stride)) elif idx == 1 and block_num > 2: self.conv_list.append( ConvBNRelu(out_planes // 2, out_planes // 4, stride=stride)) elif idx < block_num - 1: self.conv_list.append( ConvBNRelu(out_planes // int(math.pow(2, idx)), out_planes // int(math.pow(2, idx + 1)))) else: self.conv_list.append( ConvBNRelu(out_planes // int(math.pow(2, idx)), out_planes // int(math.pow(2, idx)))) def forward(self, x: paddle.Tensor) -> paddle.Tensor: out_list = [] out1 = self.conv_list[0](x) for idx, conv in enumerate(self.conv_list[1:]): if idx == 0: if self.stride == 2: out = conv(self.avd_layer(out1)) else: out = conv(out1) else: out = conv(out) out_list.append(out) if self.stride == 2: out1 = self.skip(out1) out_list.insert(0, out1) out = paddle.concat(out_list, axis=1) return out def STDC2(**kwargs): model = STDCNet(base=64, layers=[4, 5, 3], **kwargs) return model def STDC1(**kwargs): model = STDCNet(base=64, layers=[2, 2, 2], **kwargs) return model