全卷積網絡Fully Convolutional Networks (FCN)實戰


全卷積網絡Fully Convolutional Networks (FCN)實戰

使用圖像中的每個像素進行類別預測的語義分割。全卷積網絡(FCN)使用卷積神經網絡將圖像像素轉換為像素類別。與之前介紹的卷積神經網絡不同,FCN通過轉置卷積層將中間層特征映射的高度和寬度轉換回輸入圖像的大小,使得預測結果在空間維度(高度和寬度)與輸入圖像一一對應。給定空間維度上的位置,信道維度的輸出將是對應於該位置的像素的類別預測。             

將首先導入實驗所需的包或模塊,然后解釋轉置卷積層。

%matplotlib inline

from d2l import mxnet as d2l

from mxnet import gluon, image, init, np, npx

from mxnet.gluon import nn

npx.set_np()

1. Constructing a Model

展示了一個完全卷積網絡模型的最基本設計。如圖1所示,全卷積網絡首先利用卷積神經網絡提取圖像特征,然后通過1卷積層將通道數轉換為類別數,最后利用轉置卷積層將特征映射的高度和寬度轉換為輸入圖像的大小。模型輸出與輸入圖像具有相同的高度和寬度,並且在空間位置上具有一對一的對應關系。最終輸出信道包含對應空間位置的像素的類別預測。

Fig. 1.  Fully convolutional network.

下面,我們使用在ImageNet數據集上預先訓練的ResNet-18模型來提取圖像特征,並將網絡實例記錄為pretrained_net。如您所見,模型成員變量特性的最后兩層是全局最大池化層GlobalAvgPool2D和示例展平層Flatten。輸出模塊包含用於輸出的完全連接層。完全卷積網絡不需要這些層。

pretrained_net = gluon.model_zoo.vision.resnet18_v2(pretrained=True)

pretrained_net.features[-4:], pretrained_net.output

(HybridSequential(

   (0): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=512)

   (1): Activation(relu)

   (2): GlobalAvgPool2D(size=(1, 1), stride=(1, 1), padding=(0, 0), ceil_mode=True, global_pool=True, pool_type=avg, layout=NCHW)

   (3): Flatten

 ),

 Dense(512 -> 1000, linear))

接下來,我們創建完全卷積的網絡實例網。它復制了除預訓練后的神經網絡實例成員變量特征和預訓練后得到的模型參數外的所有神經層。

net = nn.HybridSequential()

for layer in pretrained_net.features[:-2]:

net.add(layer)

當輸入的高度和寬度分別為320和480時,網絡的正向計算將使輸入的高度和寬度減小到原來的1/32,即10和15。

X = np.random.uniform(size=(1, 3, 320, 480))

net(X).shape

(1, 512, 10, 15)

通過1×1卷積層將輸出通道數轉換為Pascal VOC2012(21)的類別數。最后,我們需要將特征圖的高度和寬度放大32倍,以將它們變回輸入圖像的高度和寬度。依據卷積層輸出形狀的計算方法。因為(320−64+16×2+32)/32=10和(480−64+16×2+32)/32=15,我們構造了一個跨距為32的轉置卷積層,並將卷積核的高度和寬度設置為64,填充為16。如果s的寬度和高度都是整數,那么s/2的卷積因子很難被放大。

num_classes = 21

net.add(nn.Conv2D(num_classes, kernel_size=1),

        nn.Conv2DTranspose(

            num_classes, kernel_size=64, padding=16, strides=32))

1.2. Initializing the Transposed Convolution Layer

轉置卷積層可以放大特征地圖。在圖像處理中,有時需要放大圖像,即上采樣。上采樣的方法有很多種,最常用的方法是雙線性插值。簡單地說,為了在坐標(x,y)處獲得輸出圖像的像素,首先將坐標映射到輸入圖像的坐標。

這可以根據三個輸入的大小與輸出大小的比率來實現。映射值x′還有y′ 通常是實數。然后,我們找到離坐標最近的四個像素(x′,y′)在輸入圖像上。最后,輸出圖像在坐標(x,y)處的像素,根據輸入圖像上的這四個像素及其與(x′,y′)的相對距離計算。雙線性插值的上采樣可以通過使用以下雙線性核函數構造的卷積核的轉置卷積層來實現。由於篇幅的限制,我們只給出了雙線性_核函數的實現,而不討論算法的原理。

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 = (np.arange(kernel_size).reshape(-1, 1),

          np.arange(kernel_size).reshape(1, -1))

    filt = (1 - np.abs(og[0] - center) / factor) * \

           (1 - np.abs(og[1] - center) / factor)

    weight = np.zeros((in_channels, out_channels, kernel_size, kernel_size))

    weight[range(in_channels), range(out_channels), :, :] = filt

    return np.array(weight)

將實驗雙線性插值上采樣實現轉置卷積層。構造一個轉置卷積層,將輸入的高度和寬度放大2倍,並用雙線性_核函數初始化其卷積核。

conv_trans = nn.Conv2DTranspose(3, kernel_size=4, padding=1, strides=2)

conv_trans.initialize(init.Constant(bilinear_kernel(3, 3, 4)))

讀取圖像X並將上采樣結果記錄為Y。為了打印圖像,需要調整通道尺寸的位置。

img = image.imread('../img/catdog.jpg')

X = np.expand_dims(img.astype('float32').transpose(2, 0, 1), axis=0) / 255

Y = conv_trans(X)

out_img = Y[0].transpose(1, 2, 0)

如您所見,轉置卷積層將圖像的高度和寬度都放大了2倍。值得一提的是,除了坐標比例尺不同外,雙線性插值放大后的圖像與原始圖像看起來一樣。

d2l.set_figsize((3.5, 2.5))

print('input image shape:', img.shape)

d2l.plt.imshow(img.asnumpy());

print('output image shape:', out_img.shape)

d2l.plt.imshow(out_img.asnumpy());

input image shape: (561, 728, 3)

output image shape: (1122, 1456, 3)

在完全卷積網絡中,我們初始化轉置卷積層以進行上采樣雙線性插值。對於1卷積層,我們使用Xavier進行隨機初始化。

W = bilinear_kernel(num_classes, num_classes, 64)

net[-1].initialize(init.Constant(W))

net[-2].initialize(init=init.Xavier())

1.3. Reading the Dataset

將隨機裁剪的輸出圖像的形狀指定為320×480,所以高度和寬度都可以被32整除。

batch_size, crop_size = 32, (320, 480)

train_iter, test_iter = d2l.load_data_voc(batch_size, crop_size)

Downloading ../data/VOCtrainval_11-May-2012.tar from http://d2l-data.s3-accelerate.amazonaws.com/VOCtrainval_11-May-2012.tar...

read 1114 examples

read 1078 examples

1.4. Training

可以開始訓練模型了。這里的損失函數和精度計算與圖像分類中使用的損失函數和精度計算沒有實質性的不同。因為我們使用轉置卷積層的通道來預測像素類別,所以在SoftmaxCrossEntropyLoss中指定了axis=1(通道尺寸)選項。此外,該模型還根據每個像素的預測類別是否正確來計算精度。

num_epochs, lr, wd, ctx = 5, 0.1, 1e-3, d2l.try_all_gpus()

loss = gluon.loss.SoftmaxCrossEntropyLoss(axis=1)

net.collect_params().reset_ctx(ctx)

trainer = gluon.Trainer(net.collect_params(), 'sgd',

                        {'learning_rate': lr, 'wd': wd})

d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, ctx)

loss 0.331, train acc 0.890, test acc 0.841

290.4 examples/sec on [gpu(0), gpu(1)]

1.5. Prediction

在預測過程中,需要對每個通道的輸入圖像進行標准化,並將其轉換為卷積神經網絡所需的四維輸入格式。

def predict(img):

    X = test_iter._dataset.normalize_image(img)

    X = np.expand_dims(X.transpose(2, 0, 1), axis=0)

    pred = net(X.as_in_ctx(ctx[0])).argmax(axis=1)

return pred.reshape(pred.shape[1], pred.shape[2])

為了可視化每個像素的預測類別,我們將預測的類別映射回其在數據集中的標記顏色。

def label2image(pred):

    colormap = np.array(d2l.VOC_COLORMAP, ctx=ctx[0], dtype='uint8')

    X = pred.astype('int32')

    return colormap[X, :]

測試數據集中圖像的大小和形狀各不相同。由於該模型使用了一個跨距為32的轉置卷積層,當輸入圖像的高度或寬度不能被32整除時,轉置卷積層輸出的高度或寬度會偏離輸入圖像的大小。為了解決這個問題,我們可以在圖像中裁剪多個高寬為32的整數倍的矩形區域,然后對這些區域中的像素進行正演計算。合並時,這些區域必須完全覆蓋輸入圖像。當一個像素被多個區域覆蓋時,不同區域的前向計算中輸出的轉置卷積層的平均值可以用作softmax操作的輸入,以預測類別。             

為了簡單起見,我們只讀取一些大的測試圖像並裁剪出一個320×480形狀的區域             

從圖像的左上角開始。僅此區域用於預測。對於輸入圖像,首先打印裁剪區域,然后打印預測結果,最后打印標簽類別。

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

test_images, test_labels = d2l.read_voc_images(voc_dir, False)

n, imgs = 4, []

for i in range(n):

    crop_rect = (0, 0, 480, 320)

    X = image.fixed_crop(test_images[i], *crop_rect)

    pred = label2image(predict(X))

    imgs += [X, pred, image.fixed_crop(test_labels[i], *crop_rect)]

d2l.show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n, scale=2);

1.6. Summary

  • The fully convolutional network first uses the convolutional neural network to extract image features, then transforms the number of channels into the number of categories through the 1×1 convolution layer, and finally transforms the height and width of the feature map to the size of the input image by using the transposed convolution layer to output the category of each pixel.
  • In a fully convolutional network, we initialize the transposed convolution layer for upsampled bilinear interpolation.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM