컴퓨터 비전: 시맨틱 분할 이론 및 실습

시맨틱 분할

Semantic Segmentation은 이미지를 여러 영역으로 나누고 각 영역에 의미 레이블을 할당하는 작업을 말합니다. 컴퓨터 비전에서 중요한 기술로 자율주행, 의료영상분석, 지리정보시스템 등의 분야에서 널리 사용되고 있다.

기존의 이미지 분할 작업과 달리 시맨틱 분할은 이미지를 여러 영역으로 분할해야 할 뿐만 아니라 각 영역에 의미 레이블을 지정해야 합니다. 예를 들어, 자율주행에서 시맨틱 세그멘테이션은 도로, 차량, 보행자 등의 영역을 세분화하고 각 영역에 해당 시맨틱 레이블을 할당하여 차량이 자율적으로 운전할 수 있도록 합니다. 의료 영상 분석에서 시맨틱 분할은 다양한 조직 구조(예: 장기, 근육, 뼈 등)를 분할하고 이를 분석 및 진단할 수 있습니다.

시맨틱 분할의 구현 방법은 크게 영역 기반 방법과 픽셀 기반 방법으로 나눌 수 있다. 영역 기반 방법은 이미지를 일련의 영역으로 분할한 다음 각 영역을 분류하고 해당 시맨틱 레이블을 할당합니다. 픽셀 기반 방법은 각 픽셀을 직접 분류하고 해당 의미 레이블을 할당합니다. 현재 시맨틱 분할을 위한 딥러닝 기반 방법이 주류를 이루고 있으며, 그 중 CNN(Convolutional Neural Network) 기반 방법이 가장 일반적이다. 일반적으로 사용되는 시맨틱 분할 모델에는 FCN, SegNet, U-Net, DeepLab 등이 있습니다.

컴퓨터 비전 분야에는 시맨틱 분할과 유사한 두 가지 다른 중요한 문제, 즉 이미지 분할과 인스턴스 분할이 있습니다. 여기서는 시맨틱 분할과 간단히 구분합니다.
이미지 분할은 이미지를 여러 구성 요소 영역으로 나누고 이러한 유형의 문제에 대한 방법은 일반적으로 이미지의 픽셀 간의 상관 관계를 이용합니다. 훈련하는 동안 이미지 픽셀에 대한 레이블 정보가 필요하지 않으며 분할된 영역이 예측할 때 원하는 의미 체계를 갖는다는 것을 보장할 수 없습니다.
인스턴스 분할은 이미지에서 각 대상 인스턴스의 픽셀 수준 영역을 식별하는 방법을 연구하는 동시 감지 및 분할이라고도 합니다. 시맨틱 분할과 달리 인스턴스 분할은 시맨틱뿐만 아니라 다른 대상 인스턴스도 구분해야 합니다. 예를 들어, 이미지에 두 마리의 개가 있는 경우 인스턴스 분할은 픽셀이 속한 두 마리의 개를 구분해야 합니다.



데이터 세트

시맨틱 분할을 위한 가장 중요한 데이터 세트 중 하나는 Pascal VOC2012입니다.

데이터 세트 다운로드

%matplotlib inline
import os
import torch
import torchvision
from d2l import torch as d2l
#@save
d2l.DATA_HUB['voc2012'] = (d2l.DATA_URL + 'VOCtrainval_11-May-2012.tar',
                           '4e443f8a2eca6b1dac8a6c57641b67dd40621a49')

voc_dir = d2l.download_extract('voc2012', 'VOCdevkit/VOC2012')

데이터 세트 읽기

#@save
def read_voc_images(voc_dir, is_train=True):
    """读取所有VOC图像并标注"""
    txt_fname = os.path.join(voc_dir, 'ImageSets', 'Segmentation',
                             'train.txt' if is_train else 'val.txt')
    mode = torchvision.io.image.ImageReadMode.RGB
    with open(txt_fname, 'r') as f:
        images = f.read().split()
    features, labels = [], []
    for i, fname in enumerate(images):
        features.append(torchvision.io.read_image(os.path.join(
            voc_dir, 'JPEGImages', f'{
      
      fname}.jpg')))
        labels.append(torchvision.io.read_image(os.path.join(
            voc_dir, 'SegmentationClass' ,f'{
      
      fname}.png'), mode))
    return features, labels

train_features, train_labels = read_voc_images(voc_dir, True)

아래에서 처음 5개의 입력 이미지와 해당 레이블을 플로팅합니다. 레이블 이미지에서 흰색과 검은색은 각각 테두리와 배경을 나타내고 다른 색상은 다른 범주에 해당합니다.

n = 5
imgs = train_features[0:n] + train_labels[0:n]
imgs = [img.permute(1,2,0) for img in imgs]
d2l.show_images(imgs, 2, n);

여기에 이미지 설명 삽입
다음으로 RGB 색상 값과 클래스 이름을 열거합니다.

VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
                [0, 64, 128]]

#@save
VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
               'diningtable', 'dog', 'horse', 'motorbike', 'person',
               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']

위에서 정의한 두 개의 상수를 사용하여 레이블에서 각 픽셀의 클래스 인덱스를 편리하게 조회할 수 있습니다. 우리는 voc_colormap2label 함수를 정의하여 위의 RGB 색상 값에서 카테고리 인덱스로의 매핑을 구성하고 voc_label_indices 함수를 정의하여 RGB 값을 Pascal VOC2012 데이터 세트의 카테고리 인덱스로 매핑합니다.

#@save
def voc_colormap2label():
    """构建从RGB到VOC类别索引的映射"""
    colormap2label = torch.zeros(256 ** 3, dtype=torch.long)
    for i, colormap in enumerate(VOC_COLORMAP):
        colormap2label[
            (colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    return colormap2label

구체적으로 코드는 RGB 색상 값을 고유한 정수로 변환하는 방법, 즉 R, G, B 세 채널의 값에 25 6 2 256^ 2를 곱하는 방법을 사용합니다 .25 62 ,25 6 1 256^125 61 ,25 6 0 256^025 60 , 함께 추가하십시오. RGB 색상 값의 삼중항을 고유한 정수로 변환합니다.

#@save
def voc_label_indices(colormap, colormap2label):
    """将VOC标签中的RGB值映射到它们的类别索引"""
    colormap = colormap.permute(1, 2, 0).numpy().astype('int32')
    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256
           + colormap[:, :, 2])
    return colormap2label[idx]

특히 이 함수는 먼저 "colormap" 텐서의 RGB 값을 크기(H, W)의 정수 텐서 "idx"로 변환합니다. 이것은 3개의 채널 R, G, B의 값에 25 6 2 256^2 를 곱한 것입니다.25 62 ,25 6 1 256^125 61 ,25 6 0 256^025 60 , 그리고 그것들을 함께 더합니다. 이 정수는 "colormap2label" 텐서에서 해당 클래스 인덱스를 추출하기 위한 인덱스로 사용됩니다. 이러한 범주 인덱스는 "colormap" 텐서와 동일한 모양의 새로운 텐서 "idx"를 형성하며, 여기서 각 요소는 "colormap" 텐서의 픽셀에 해당합니다.

여기에 이미지 설명 삽입

데이터 전처리

시맨틱 분할에서 이렇게 하려면 예측된 픽셀 클래스를 입력 이미지의 원래 크기로 다시 매핑해야 합니다. 이러한 매핑은 특히 다른 의미론적으로 분할된 영역에서 충분히 정확하지 않을 수 있습니다. 이 문제를 방지하기 위해 크기를 다시 조정하는 대신 이미지를 고정된 크기로 자릅니다. 구체적으로, 이미지 확대에서 임의 자르기를 사용하여 입력 이미지와 레이블의 동일한 영역을 자릅니다.

#@save
def voc_rand_crop(feature, label, height, width):
    """随机裁剪特征和标签图像"""
    rect = torchvision.transforms.RandomCrop.get_params(
        feature, (height, width))
    feature = torchvision.transforms.functional.crop(feature, *rect)
    label = torchvision.transforms.functional.crop(label, *rect)
    return feature, label

imgs = []
for _ in range(n):
    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)

imgs = [img.permute(1, 2, 0) for img in imgs]
d2l.show_images(imgs[::2] + imgs[1::2], 2, n);

이 함수는 torchvision.transforms.RandomCrop.get_params()를 사용하여 임의 자르기 사각형을 가져온 다음 torchvision.transforms.functional.crop() 함수를 사용하여 입력 기능 이미지와 레이블 이미지를 자릅니다. 마지막으로 이 함수는 자른 기능 이미지와 레이블 이미지를 반환합니다. 특히 이 함수는 다음 단계를 통해 임의 자르기를 달성합니다.

  1. torchvision.transforms.RandomCrop.get_params()를 사용하여 높이와 너비가 각각 있는 임의 자르기 사각형을 가져옵니다.
  2. 입력 기능 이미지와 레이블 이미지를 자르려면 torchvision.transforms.functional.crop()을 사용하고 잘린 사각형은 이전 단계에서 얻은 임의의 사각형입니다.
  3. 잘린 기능 이미지와 레이블 이미지를 반환합니다.

사용자 정의 시맨틱 분할 데이터 세트 클래스

#@save
class VOCSegDataset(torch.utils.data.Dataset):
    """一个用于加载VOC数据集的自定义数据集"""

    def __init__(self, is_train, crop_size, voc_dir):
        self.transform = torchvision.transforms.Normalize(
            mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
        self.crop_size = crop_size
        features, labels = read_voc_images(voc_dir, is_train=is_train)
        self.features = [self.normalize_image(feature)
                         for feature in self.filter(features)]
        self.labels = self.filter(labels)
        self.colormap2label = voc_colormap2label()
        print('read ' + str(len(self.features)) + ' examples')

    def normalize_image(self, img):
        return self.transform(img.float() / 255)

    def filter(self, imgs):
        return [img for img in imgs if (
            img.shape[1] >= self.crop_size[0] and
            img.shape[2] >= self.crop_size[1])]

    def __getitem__(self, idx):
        feature, label = voc_rand_crop(self.features[idx], self.labels[idx],
                                       *self.crop_size)
        return (feature, voc_label_indices(label, self.colormap2label))

    def __len__(self):
        return len(self.features)

이것은 Pascal VOC 데이터 세트를 로드하기 위한 사용자 정의 데이터 세트 클래스 VOCSegDataset입니다. dataset 클래스는 다음 메서드를 제공합니다.

init (self, is_train, crop_size, voc_dir): 생성자, 데이터 세트를 초기화하는 데 사용됩니다. 이 함수는 세 가지 매개변수를 허용합니다.

  1. is_train: 이 데이터 세트가 훈련용인지 테스트용인지 나타내는 부울입니다.
  2. crop_size: 임의로 자른 이미지의 크기를 나타내는 튜플.
  3. voc_dir: 데이터 세트 저장 경로.

normalize_image(self, img): 이미지를 정규화하기 위해 사용합니다.

filter(self, imgs): crop_size보다 크기가 작은 이미지를 걸러내기 위해 사용합니다.

getitem (self, idx): 데이터 세트에서 항목을 가져오는 데 사용됩니다. 이 함수는 인덱스 매개변수 idx를 수락하고 튜플(기능, 레이블)을 반환합니다. 여기서 기능은 기능 이미지를 나타내고 레이블은 해당 레이블 이미지를 나타냅니다.

len (self): 데이터 세트의 샘플 수를 가져오는 데 사용됩니다.

생성자에서 dataset 클래스는 먼저 read_voc_images() 함수를 사용하여 데이터세트의 이미지와 레이블을 읽고 filter() 메서드를 사용하여 크기가 crop_size보다 작은 이미지를 필터링합니다. 그런 다음 데이터 세트 클래스는 normalize_image() 메서드를 사용하여 각 기능 이미지를 정규화하고 voc_rand_crop() 및 voc_label_indices() 메서드를 사용하여 기능 이미지 및 레이블 이미지의 무작위 자르기 및 레이블 매핑을 달성합니다. 마지막으로 dataset 클래스는 getitem () 메서드 에서 자른 기능 및 레이블 이미지를 반환합니다 .

이 데이터셋 클래스의 주요 기능은 Pascal VOC 데이터셋을 PyTorch에서 데이터셋 유형으로 변환하여 모델 학습 시 데이터를 일괄 처리하는 데 PyTorch에서 제공하는 DataLoader 클래스를 사용할 수 있도록 하는 것입니다.

crop_size = (320, 480)
voc_train = VOCSegDataset(True, crop_size, voc_dir)
voc_test = VOCSegDataset(False, crop_size, voc_dir)
batch_size = 64
train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
                                    drop_last=True,
                                    num_workers=d2l.get_dataloader_workers())
for X, Y in train_iter:
    print(X.shape)
    print(Y.shape)
    break

모든 구성 요소 통합

#@save
def load_data_voc(batch_size, crop_size):
    """加载VOC语义分割数据集"""
    voc_dir = d2l.download_extract('voc2012', os.path.join(
        'VOCdevkit', 'VOC2012'))
    num_workers = d2l.get_dataloader_workers()
    train_iter = torch.utils.data.DataLoader(
        VOCSegDataset(True, crop_size, voc_dir), batch_size,
        shuffle=True, drop_last=True, num_workers=num_workers)
    test_iter = torch.utils.data.DataLoader(
        VOCSegDataset(False, crop_size, voc_dir), batch_size,
        drop_last=True, num_workers=num_workers)
    return train_iter, test_iter

완전 컨벌루션 네트워크

완전 합성곱 모델은 입력 영상을 픽셀 단위로 분류 또는 분할하여 입력 영상과 동일한 크기의 분할 결과를 출력할 수 있는 특수 합성곱 신경망입니다. 완전 컨볼루션 모델은 일반적으로 컨볼루션 레이어, 디컨볼루션 레이어, 풀링 레이어로 구성되며, 컨볼루션 레이어는 이미지 특징을 추출하는 데 사용되고, 디컨볼루션 레이어는 피처 이미지를 원래 이미지 크기로 복원하는 데 사용되며, 풀링 레이어는 피처 이미지의 크기를 줄이는 데 사용됩니다.

완전 컨벌루션 모델은 무엇보다도 시맨틱 분할, 인스턴스 분할 및 에지 감지와 같은 이미지 분할 작업에 가장 일반적으로 사용됩니다. 그 중 시맨틱 분할 작업은 이미지의 각 픽셀을 해당 범주에 할당하는 것이고 인스턴스 분할 작업은 이미지의 각 개체 인스턴스를 다른 범주에 할당하는 것입니다.

완전 컨벌루션 모델의 고전적인 구조는 인코더와 디코더의 두 부분으로 구성된 U-Net입니다. 인코더는 일반적으로 컨벌루션 및 풀링 레이어의 조합을 사용하여 이미지 특징을 추출하고 특징 이미지의 크기를 줄입니다. 디코더는 디콘볼루션 레이어와 스킵 연결을 사용하여 특징 이미지를 원래 이미지 크기로 복원하고 인코더의 특징 이미지를 디코더의 특징 이미지와 융합합니다.

완전 컨벌루션 모델을 훈련할 때 교차 엔트로피 손실 함수는 일반적으로 모델 출력과 실제 레이블 간의 차이를 측정하는 데 사용되며 역전파 알고리즘은 모델 매개변수를 업데이트하는 데 사용됩니다. 완전 컨벌루션 모델의 출력은 분할된 이미지이므로 일반적으로 손실 함수를 계산할 때 벡터로 평면화해야 합니다.

건설 모델

완전 컨벌루션 네트워크 모델의 가장 기본적인 설계를 살펴보겠습니다. 아래 그림과 같이 전체 컨볼루션 네트워크는 먼저 컨볼루션 신경망을 사용하여 이미지 특징을 추출한 다음 1 × 1 1\times 1을 전달합니다.1×1 컨볼루션 레이어는 채널 수를 카테고리 수로 변환하고 마지막으로 트랜스포즈된 컨볼루션 레이어를 통해 피처 맵의 높이와 너비를 입력 이미지의 크기로 변환합니다. 따라서 모델 출력은 입력 이미지와 동일한 높이와 너비를 가지며 최종 출력 채널에는 해당 공간 위치의 픽셀에 대한 클래스 예측이 포함됩니다.
여기에 이미지 설명 삽입
아래에서는 ImageNet 데이터 세트에서 사전 훈련된 ResNet-18 모델을 사용하여 이미지 기능을 추출하고 이 네트워크를 pretrained_net으로 표시합니다. ResNet-18 모델의 마지막 몇 계층에는 전역 평균 풀링 계층과 완전 연결 계층이 포함되지만 완전 컨벌루션 네트워크에는 필요하지 않습니다.
여기에 이미지 설명 삽입
다음으로 완전히 컨볼루션 네트워크 네트워크를 만듭니다. 마지막에 있는 전역 평균 풀링 계층과 출력에 가장 가까운 완전 연결 계층을 제외하고 ResNet-18의 대부분의 사전 훈련 계층을 복제합니다.

net = nn.Sequential(*list(pretrained_net.children())[:-2])

높이가 320이고 너비가 480인 입력이 주어지면 net의 정방향 전파는 입력의 높이와 너비를 원래 1 32 \frac로 줄입니다.{1}{32}321, 즉 10 합 15.

X = torch.rand(size=(1, 3, 320, 480))
net(X).shape

여기에 이미지 설명 삽입
다음 사용 1 × 1 1\times 11×1개의 컨볼루션 레이어는 출력 채널 수를 Pascal VOC2012 데이터셋의 클래스 수(21개 클래스)로 변환합니다. 마지막으로 기능 맵의 높이와 너비를 32배로 늘려 입력 이미지의 높이와 너비로 다시 변경해야 합니다. ( 320 − 64 + 16 ∗ 2 + 32 ) / 32 = 10 (320-64+16*2+32)/32=10이므로( 320-64+162+32 ) /32=10( 480 - 64 + 16 * 2 + 32 ) / 32 = 15 (480-64+16*2+32)/32=15( 480-64+162+32 ) /32=15 , 우리는 보폭을32 3232 전치 컨볼루션 레이어, 컨볼루션 커널의 높이와 너비를64 6464 , 16 16으로 채워짐16 . 보폭이 ss 이면s , s / 2 s/2로 채워짐s /2 (sss 는 정수) 컨볼루션 커널의 높이와 너비는2s 2s2 s , 전치 컨볼루션 커널은 입력 높이와 너비를ss시간 .

전치된 컨볼루션 계층 초기화

이미지 처리에서 이미지를 확대, 즉 업샘플링해야 하는 경우가 있습니다. 쌍선형 보간은 일반적으로 사용되는 업샘플링 방법 중 하나이며 전치된 컨볼루션 레이어를 초기화하는 데 자주 사용됩니다.

업샘플링은 저해상도 이미지나 신호를 고해상도로 높이는 방법입니다. 바이리니어 보간법(Bilinear interpolation)은 일반적으로 사용되는 업샘플링 방법 중 하나로 기존의 저해상도 영상을 보간하여 고해상도 영상을 생성할 수 있다.

쌍선형 보간에서는 저해상도 이미지가 두 번 샘플링된다고 가정합니다. 즉, 각 픽셀은 2 × 2 2 \times 2 가 됩니다.2×2 픽셀 블록. 각각의 새 픽셀 블록의 픽셀에 대해 주변 4개의 알려진 픽셀 값 값에서 쌍선형 보간이 계산됩니다. 특히 대상 이미지(x, y)의 새 픽셀(x , y( 엑스 ,y ) , 회색 값은 다음 단계로 계산할 수 있습니다.

대상 픽셀 찾기 ( x , y ) (x, y)( 엑스 ,y )는 원본 저해상도 이미지의( x 1 , y 1 ) (x_1, y_1)( 엑스1,와이1)( x 2 , y 1 ) (x_2, y_1)( 엑스2,와이1)( x 1 , y 2 ) (x_1, y_2)( 엑스1,와이2)( x 2 , y 2 ) (x_2, y_2)( 엑스2,와이2) , 其中( x 1 , y 1 ) (x_1, y_1)( 엑스1,와이1)( x 2 , y 2 ) (x_2, y_2)( 엑스2,와이2) 는 ( x , y ) (x, y)에 가장 가깝습니다.( 엑스 ,y ) ,( x 1 , y 2 ) (x_1, y_2)( 엑스1,와이2)( x 2 , y 1 ) (x_2, y_1)( 엑스2,와이1) 는 다른 두 픽셀입니다.

대상 픽셀 계산 ( x , y ) (x, y)( 엑스 ,y ) 및 4개의 알려진 픽셀 사이의 거리, 즉d 1 = ( x − x 1 ) 2 + ( y − y 1 ) 2 d_{1} = \sqrt{(x-x_1)^2 + (y -y_1) ^2}1=( 엑스-엑스1)2+( y-와이1)2 .d 2 = ( x − x 2 ) 2 + ( y − y 1 ) 2 d_{2} = \sqrt{(x-x_2)^2 + (y-y_1)^2}2=( 엑스-엑스2)2+( y-와이1)2 .d 3 = ( x − x 1 ) 2 + ( y − y 2 ) 2 d_{3} = \sqrt{(x-x_1)^2 + (y-y_2)^2}3=( 엑스-엑스1)2+( y-와이2)2 .d 4 = ( x − x 2 ) 2 + ( y − y 2 ) 2 d_{4} = \sqrt{(x-x_2)^2 + (y-y_2)^2}4=( 엑스-엑스2)2+( y-와이2)2 .

대상 픽셀 계산 ( x , y ) (x, y)( 엑스 ,y ) , 주변 4개 픽셀의 그레이 값을 가중 평균으로 사용하고 가중치는 대상 픽셀과 알려진 4개 픽셀 사이의 거리에 반비례한다. 지금 바로:

에프 ( x , y ) = 1d1d 3 에프 ( x 1 , y 1 ) + 1d 2d 3 에프 ( x 2 , y 1 ) + 1d 1d 4 에프 ( x 1 , y 2 ) + 1 d 2 d 4 f ( x 2 , y 2 ) f(x, y) = \frac{1}{d_{1}d_{3}}f(x_1, y_1) + \frac{1}{d_{2 }d_{3}}f(x_2, y_1) + \frac{1}{d_{1}d_{4}}f(x_1, y_2) + \frac{1}{d_{2}d_{4}} 에프(x_2, y_2)에프 ( 엑스 ,)=131에프 ( 엑스1,와이1)+231에프 ( 엑스2,와이1)+141에프 ( 엑스1,와이2)+241에프 ( 엑스2,와이2)

其中f ( x 1 , y 1 ) f(x_1, y_1)에프 ( 엑스1,와이1)에프( x 2 , y 1 ) 에프(x_2, y_1)에프 ( 엑스2,와이1)에프( x 1 , y 2 ) 에프(x_1, y_2)에프 ( 엑스1,와이2)에프( x 2 , y 2 ) 에프(x_2, y_2)에프 ( 엑스2,와이2)는 각각 4개의 알려진 픽셀의 회색 값을 나타냅니다.

쌍선형 보간을 사용하면 저해상도 이미지를 더 높은 해상도로 업샘플링하여 더 선명한 이미지를 얻을 수 있습니다.

def bilinear_kernel(in_channels, out_channels, kernel_size):
    factor = (kernel_size + 1) // 2
    if kernel_size % 2 == 1:
        center = factor - 1
    else:
        center = factor - 0.5
    og = (torch.arange(kernel_size).reshape(-1, 1),
          torch.arange(kernel_size).reshape(1, -1))
    filt = (1 - torch.abs(og[0] - center) / factor) * \
           (1 - torch.abs(og[1] - center) / factor)
    weight = torch.zeros((in_channels, out_channels,
                          kernel_size, kernel_size))
    weight[range(in_channels), range(out_channels), :, :] = filt
    return weight

쌍선형 보간 컨벌루션 커널을 생성하는 함수입니다. 입력에는 입력 채널 수, 출력 채널 수 및 컨볼루션 커널 크기가 포함되며 출력은 이 함수에 의해 생성된 쌍선형 보간 컨볼루션 커널을 나타내는 모양(in_channels, out_channels, kernel_size, kernel_size)의 텐서입니다. .

구체적으로, 이 함수는 먼저 컨볼루션 커널의 중심 위치를 계산한 다음 모양(kernel_size, kernel_size)의 텐서 filt를 생성합니다. 여기서 filt의 각 요소는 쌍선형 보간 컨볼루션 커널에서 해당 위치의 가중치를 나타냅니다. 마지막으로 이 함수는 입력 채널과 출력 채널의 수에 따라 모양(in_channels, out_channels, kernel_size, kernel_size)의 텐서 가중치를 생성합니다. 가중치의 각 요소는 쌍선형 보간 컨볼루션 커널에서 해당 위치의 가중치를 나타냅니다. 구체적으로 weight[i, j, :, :]는 i번째 입력 채널에서 j번째 출력 채널까지의 쌍선형 보간 커널을 나타냅니다.

이 함수는 입력 텐서를 더 높은 해상도로 업샘플링하는 컨볼루션 신경망에서 쌍선형 보간 컨볼루션 레이어를 정의하는 데 사용할 수 있습니다.

완전 컨볼루션 네트워크는 쌍선형 보간을 통한 업샘플링으로 전치된 컨벌루션 레이어를 초기화합니다. 1 × 1 1\times 1 의 경우1×1 컨볼루션 레이어에서는 Xavier 초기화 매개변수를 사용합니다.

W = bilinear_kernel(num_classes, num_classes, 64)
net.transpose_conv.weight.data.copy_(W);

기차

def loss(inputs, targets):
    return F.cross_entropy(inputs, targets, reduction='none').mean(1).mean(1)

num_epochs, lr, wd, devices = 5, 0.001, 1e-3, d2l.try_all_gpus()
trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=wd)
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)

예측하다

예측할 때 각 채널의 입력 이미지를 정규화하고 컨볼루션 신경망에서 요구하는 4차원 입력 형식으로 변환해야 합니다.

def predict(img):
    X = test_iter.dataset.normalize_image(img).unsqueeze(0)
    pred = net(X.to(devices[0])).argmax(dim=1)
    return pred.reshape(pred.shape[1], pred.shape[2])

각 픽셀에 대해 예측된 클래스를 시각화하기 위해 예측된 클래스를 데이터 세트의 레이블이 지정된 색상에 다시 매핑합니다.

def label2image(pred):
    colormap = torch.tensor(VOC_COLORMAP, device=devices[0])
    X = pred.long()
    return colormap[X, :]

테스트 데이터 세트의 이미지는 크기와 모양이 다양합니다. 모델은 stride가 32인 transposed convolution layer를 사용하기 때문에 입력 이미지의 높이나 너비를 32로 나눌 수 없을 때 transposed convolution layer의 출력 높이나 너비는 입력 이미지의 크기에서 벗어날 것입니다. . 이 문제를 해결하기 위해 높이와 너비가 32의 정수배인 이미지의 여러 직사각형 영역을 가로채고 이 영역의 픽셀에 각각 순방향 전파를 수행할 수 있습니다. 이러한 영역의 합집합은 입력 이미지를 완전히 덮어야 합니다. 픽셀이 여러 영역에 걸쳐 있는 경우 서로 다른 영역의 순방향 전파에서 전치된 컨볼루션 레이어 출력의 평균 값을 소프트맥스 연산의 입력으로 사용하여 카테고리를 예측할 수 있습니다.

간단하게 하기 위해 몇 개의 더 큰 테스트 이미지만 읽고 이미지의 왼쪽 상단 모서리부터 시작하여 320 × 480 320\times 480 모양을 가로챕니다.320×480 의 영역이 예측에 사용됩니다. 이러한 테스트 이미지의 경우 가로챈 영역을 하나씩 인쇄한 다음 예측 결과를 인쇄하고 마지막으로 레이블이 지정된 범주를 인쇄합니다.

여기에 이미지 설명 삽입

추천

출처blog.csdn.net/qq_51957239/article/details/131058002