Semantic Segmentation의 기초와 이해

이상민·2023년 6월 5일
2

1 Abstract

딥러닝을 이용한 segmentation으로는 FCN이 있다. FCN이란 Fully convolutional Networks의 약자로 CVPR에 소개 되었고 주요한 특징은 아래와 같다.

  1. vgg 네트워크를 backbone로 사용
  2. vgg 네트워크의 FC layer를 Convolution으로 대체
  3. Transposed Convolution을 이용하여 Pixel Wise Prediction을 수행

1.1 VGG network backbone

vgg network는 3x3 convolution network를 깊게 쌓음으로써 적은 파라미터로 receptive field를 효과적으로 키워 높은 성능을 달성했다. FCN은 이러한 VGG network를 backbone로 사용한다. 그러나 아래의 그림과 다르게 FCN에서는 vgg의 (1x1xc)차원으로 mapping하는 FC layer 대신 convolution Layer를 사용한다.

receptive field: 출력 레이어의 뉴런하나에 영향을 미치는 입력 레이어 뉴런들의 공간 크기

1.2 Fully Connected Layer vs Convolution Layer

segmentation에서 backbone의 fc layer를 convolution layer로 변경하는 것이 어떤 의미가 있을까?

  • 우선 첫번째로는 각 pixel의 위치 정보를 헤치지 않는다는 특징이 있다.
    fc layer를 보면 flatten을 이용해 이미지를 일자로 펼치고 이후에 fc layer를 거치기 때문에 각 pixel의 위치정보가 이러한 과정을 거치면서 손실된다. classification task에서는 물체의 존재 여부만을 판단하기 때문에 위치정보가 필요하지 않지만 segmentation같은 경우 각 pixel 위치에 대해 classification을 적용하기 때문에 fc가 아니라 convolution을 사용해서 위치 정보를 보존해야한다.

1.3 Transposed Convolution


segmentatation task에서 입력이 224x224x3이라면 출력값은 각 픽셀에 대한 클래스의 확률으므로 224x224xclass_items로 출력되야한다. FCN은 feature extractor에서 축소된 차원을 다시 늘리는 upsampling 기법으로 Transposed Convolution을 사용한다.

1.4 FCN에서 성능 향상시키기 위한 방법(skip connection)

FCN의 경우 vgg16을 backbone으로 사용했기에 5개의 layer를 거쳐 feature를 추출하고 이후 7x7, 1x1, 1x1 convolution layer를 거쳐 transposed conv를 통해 최종 출력을 거치지만 fc 6에서 7x7을 적용했을때 문제가 발생하기 때문에 이번 파트에서는 fc6에 1x1 conv를 사용한 network를 설명한다.

FCN-32s pipeline

우선 FCN의 pipeline은 다음과 같다

  1. fc6: 5개의 conv를 거쳐 원본 이미지 사이즈에서 1/32만큼 줄어든 feature map을 512 channel에서 4096 channel로 증가
  2. fc7: 4096 -> 4096
  3. score: upsampling 진행

3단계를 보면 feature에 대해 단번에 32배 upsampling으로 사이즈를 복구하는 것이 성능에는 문제가 없을까?

실제 결과를 보면 FCN-32s와 Ground Truth를 비교해 봤을때 디테일한 부분의 정보가 사라진 것을 볼 수 있다.

FPN-16s pipeline

  1. FPN-32s와 다르게 32배가 아닌 2배로 deconv를 진행해 원본 이미지 대비 1/16 사이즈까지 feature map을 키운다.
  2. 1 단계에서 deconv를 거친 feature에 conv4를 거쳐서 나온 feature를 summation.
  3. 2단계 결과에 upsampling을 진행해 이미지 사이즈를 16배 키워 원본 사이즈의 이미지 만큼 키운다.

2 결론

2.1 Further Reading

앞서 설명한 FCN에서 Fc6는 7x7이 아닌 1x1 kernel을 사용하는지 알아보자.
그림과 같이 7x7 conv를 적용할때 padding이 없기 때문에 입력의 HxW와 출력이 달라진다 입출력의 크기가 다른 상태에서 upsampling을 적용하면 최종 결과의 사이즈가 원본과 달라질 수 있기 때문에 1x1 kernel을 사용한다.

원 논문에서는 7x7 kernel을 사용하기 위해 입력 이미지 사이즈를 늘리거나 conv1 layer에 padding을 100으로 설정하는 등 여러 기법이 적용된다.

2.2 FCN의 한계

FCN의 경우 Segmentation Task의 baseline으로 통하는 모델이지만 아래와 같은 한계를 갖고 있다.

  1. 객체의 크기가 크거나 작은 경우 예측을 잘 하지 못하는 문제
  • 위 그림을 보면 버스의 앞 부분 범퍼는 버스로 예측하지만, 유리창에 비친 자전거를 보고 자전거로 인식
  • 같은 Object여도 다르게 labeling
  1. Object의 디테일한 모습이 사라지는 문제발생.

  • Deconvolution 절차가 간단해 경계를 학습하기 어렵다.

3 FCN 개선 방향

3.1 Decoder를 개선한 models

앞으로 설명할 DeconvNet, SegNet과 같은 경우 앞서 설명한 FCN의 한계점을 Encoder & Decoder 구조를 이용해 개선하려고 한다.

DeconvNet

  • model architecture

    DeconvNet과 같은 경우 1x1 conv을 기점으로 대칭형태를 띄고 있는 것을 볼수 있다. Decoder 부분에 경우 Deconv(transposed convolution), upsampling 기법 중 하나인 Unpooling layer로 구성 되어 있다. 위 모델 구조에서 pooling layer사이 연결된 선은 maxpoolingmax_indices의 정보를 전달하는 것을 표현한다

  • Decoder
    DeconvNet의 Encoder구조는 FCN과 마찬가지로 vgg16을 Backbone를 사용하므로 생략하고 Decoder를 살펴보자.

    Decoder의 구조를 보면 Unpooling(디테일한 경계 포착)과 transposed Conv(전반적인 모습 포착)이 반복적으로 이루어진 형태이다.

  • Unpooling

디테일한 경계를 포착하는 unpooling이 무엇인지 알아보자.

우선 Maxpooling이란 각 영역 별로 가장 큰 값을 추출하는 과정이다. 하지만 이렇게 될경우 각 위치에 대한 spatial한 정보를 잃게 되는 문제점이 있는데. Unpooling같은 경우 Maxpooling의 결과가 입력값의 어느 위치에 존재하는지에 대한 정보 (max indices)를 저장했다가 해당 영역을 복원해 나가는 과정이라 생각할 수 있다.

일반적으로 classification의 경우 pooling은 대표값을 추출하여 노이즈를 제거하거나 가장 중요한 부분을 추출하여 일반화 성능을 높이고 해상도를 줄이기 때문에 메모리를 효율성을 높여주는 장점이 있다. 하지만 segmentation의 경우 pooling을 통해 손실된 경계에 대한 정보가 있고 이러한 정보를 복원해줄 필요가 있고 이를 가능하게 해주는 것이 Unpooling기법이다.

하지만 sparseactivation map을 가지기 때문에 이를 Transposed Convolution을 이용하여 채워줄 필요가 있음.

이러한 Unpooling과 Transposed Conv가 반복 되면서 unpooling에서 자세한 구조를 잡아내고, Transposed Conv를 거치며 unpooling결과의 빈 부분을 채워 넣는다

  • 코드
    코드를 보면 각 레이어의 max_indices정보를 Unpooling layer에 전달하는 것을 볼 수 있다.

SegNet

SegNet은 DeconvNet과 비슷하지만 빠른 성능 보다는 inference 속도에 중점을 둔 모델이다. 특히 SegNet은 자율주행과 같이 다양한 class를 빠르고, 정확하게 구분해야하는 Task에 적합하다.

SegNet 특징

  • DeconvNet구조와 달리 Encoder와 Decoder를 연결하는 conv layer를 제거하여 파라미터 수를 감소 시켰다.
  • Decoder sparse한 map을 dense하게 바꿔주기 위해 Deconv가 아닌 단순 Conv를 사용했다.

DeconvNet vs SegNet

3.2 Skip Connection을 적용한 models

FC DenseNet

Unet

3.3 Receptive Field를 확장시킨 models

앞서 FCN은 Object가 큰 물체는 잘 검출을 못하는 문제가 있다. 지금부터 설명한 두 모델들은 Receptive Field를 키우는 것으로 FCN의 한계를 극복했다.

Receptive Field를 키우는 법

  • Max Pooling
    conv 과 max pooling을 사용하면 효율적으로 receptive field를 키울수 있다. 정리하면 max pooling을 이용하면 노이즈를 제거하고 이미지의 크기를 줄여 메모리를 효과적으로 사용하는 것 이외에도 Receptive Field를 키울 수 있다.

    그러나 max pooling을 이용하여 receptive field를 키우는 방법에는 한가지 한계점이 있다. segmentation에서는 각 pixel마다 분류를 해야하기 때문에 각 pixel들의 정보가 중요하다. 그러나 이미지의 크기를 줄인 상황에서 convolution 진행후 upsampling을 진행하면 그림과 같이 feature map의 해상도가 많이 줄어드는 문제가 발생한다.

  • Dilated Convolution
    Dilated convolution을 사용하면 image의 크기를 적게 줄이면서도 효율적으로 receptive field를 넓힐 수 있다.

DeepLab v1


Dilated Kernel을 적용한 DeepLab v1모델은 FCN과 비슷해 보이지만 conv1 ~ conv4 layer까지 2x2 max pooling을 3x3으로 변경했고, conv5와 FC6에 convolution이 아닌 Dilated convolution을 적용했다는 점이 다르다.

  • conv1,conv2,conv3

# conv1 layer의 구성
conv1 = nn.Sequential(conv_relu(3,64,3,1), #conv block을 두개 쌓는다
                      conv_relu(64,64,3,1),
                      nn.MaxPool2d(3, stride=2,padding=1))#maxpooling을 kernel size 3 stride 2 padding 1로 설정하여 사이즈를 1/2로 줄임
                      
  • conv4
conv4 = nn.Sequential(conv_relu(256,512,3,1), 
                      conv_relu(512,512,3,1),
                      conv_relu(512,512,3,1),
                      #stride를 1로 설정해서 feature map의 크기를 고정
                      nn.MaxPool2d(3, stride=1,padding=1))
  • conv5
conv4 = nn.Sequential(#rate=2로 설정해서 dilated conv적용
					  conv_relu(256,512,3,rate=2), 
                      conv_relu(512,512,3,rate=2),
                      conv_relu(512,512,3,rate=2),
                      #stride를 1로 설정해서 feature map의 크기를 고정
                      nn.MaxPool2d(3, stride=1,padding=1))
                      nn.AvgPool2d(3, stride=1,padding=1)),
  • FC6~Score
class classifier(nn.Module):
    def __init__(self,num_classes):
        super(classifier,self).__init__()
        self.classifier = nn.Sequential(conv_relu(512,1024,3,rate=12),
                                        nn.Dropout2d(0.5),
                                        conv_relu(1024,1024,1,1),
                                        nn.Dropout2d(0.5),
                                        nn.Conv2d(1024,num_classes,1))
    def foward(self,x):
        out=self.classifier(x)
        return out

DilatedNet

profile
잘하자

1개의 댓글

comment-user-thumbnail
2023년 6월 6일

굿~👍

답글 달기