Оптический поток | поток | CVPR2015 | бумага + код pytorch

искусственный интеллект
  • Статья перенесена из паблика WeChat "Алхимия машинного обучения"
  • Автор: Alchemy Brother (авторизованный)
  • Контактная информация автора: WeChat cyx645016617 (Добро пожаловать, чтобы общаться и добиваться прогресса вместе)
  • Название статьи: «FlowNet: изучение оптического потока с помощью сверточных сетей»
  • Ссылка на бумагу:Хи-хи-хи. IT восхождение. AC. Talent/ABS/1504.06…

image.png

0 Обзор

Основных вкладов статьи, на мой взгляд, два:

  • Предлагается структура Flownet, то есть Flownet-v1 (сейчас обновлена ​​до версии Flownet-v2), Flownet-v1 содержит две версии, Flownet-v1S (простая) и Flownet-v1C (корреляционная).
  • Предлагаем знаменитый набор данных Flying Chairs, летающие кресла, ха-ха, каждый, кто занимается оптическим потоком, должен знать этот интересный набор данных.

1 flownetsimple

1.1 Модуль извлечения функций

Известно, что сверточные нейронные сети очень хорошо изучают отношения ввода-вывода при наличии достаточного количества размеченных данных. Поэтому для прогнозирования оптического потока используется сквозной метод обучения:

Учитывая набор данных, состоящий из пар изображений и оптического потока, мы обучаем сеть прогнозировать поле оптического потока xy непосредственно из изображения. Но что такое хорошая архитектура для этой цели?

Простой вариант - объединить два входных изображениясложены вместе, и передавать его через довольно общую сеть, позволяя сети самой решать, как обрабатывать пары изображений для извлечения информации о движении. Эта архитектура только со сверточными слоями называется «FlowNetSimple»:

image.png

Алхимический брат кратко рассказывает о структуре сети:

  • Входное изображение представляет собой трехканальное изображение, объедините два изображения в 6-канальное изображение;
  • После этого условно: convolution convolution convolution, а даунсемплинг stride=2 будет смешиваться в середине;
  • Затем карты признаков, выдаваемые несколькими сверточными слоями, будут напрямую связаны с уточняющей частью (зеленый блок песочных часов справа на рисунке), и эти признаки будут объединены для восстановления изображения до большего размера;
  • Входное изображение исходной модели имеет размер 384x512, но окончательный размер выходного поля оптического потока составляет 136x320, что требует внимания.

1.2 refinement

Давайте посмотрим на часть уточнения, по сути, эта часть похожа на Unet, но имеет характеристики уникальной модели оптического потока.

image.png

  • Видно, что в основном карта признаков каждого блока состоит из трех частей:
    • Функции, полученные в результате деконверсии предыдущей карты объектов небольшого размера;
    • Признаки, полученные путем преобразования предыдущей карты признаков небольшого размера в поле оптического потока малого размера, а затем деконвертирования;
    • В процессе извлечения признаков элементы, соответствующие его размеру;
  • После того, как три вышеупомянутых объекта будут объединены, он станет входным блоком объектов следующего размера, и цикл будет продолжать увеличивать размер объекта;

1.3 pytorch

lass FlowNetS(nn.Module):
    expansion = 1

    def __init__(self,batchNorm=True):
        super(FlowNetS,self).__init__()

        self.batchNorm = batchNorm
        self.conv1   = conv(self.batchNorm,   6,   64, kernel_size=7, stride=2)
        self.conv2   = conv(self.batchNorm,  64,  128, kernel_size=5, stride=2)
        self.conv3   = conv(self.batchNorm, 128,  256, kernel_size=5, stride=2)
        self.conv3_1 = conv(self.batchNorm, 256,  256)
        self.conv4   = conv(self.batchNorm, 256,  512, stride=2)
        self.conv4_1 = conv(self.batchNorm, 512,  512)
        self.conv5   = conv(self.batchNorm, 512,  512, stride=2)
        self.conv5_1 = conv(self.batchNorm, 512,  512)
        self.conv6   = conv(self.batchNorm, 512, 1024, stride=2)
        self.conv6_1 = conv(self.batchNorm,1024, 1024)

        self.deconv5 = deconv(1024,512)
        self.deconv4 = deconv(1026,256)
        self.deconv3 = deconv(770,128)
        self.deconv2 = deconv(386,64)

        self.predict_flow6 = predict_flow(1024)
        self.predict_flow5 = predict_flow(1026)
        self.predict_flow4 = predict_flow(770)
        self.predict_flow3 = predict_flow(386)
        self.predict_flow2 = predict_flow(194)

        self.upsampled_flow6_to_5 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)
        self.upsampled_flow5_to_4 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)
        self.upsampled_flow4_to_3 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)
        self.upsampled_flow3_to_2 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)

        for m in self.modules():
            if isinstance(m, nn.Conv2d) or isinstance(m, nn.ConvTranspose2d):
                kaiming_normal_(m.weight, 0.1)
                if m.bias is not None:
                    constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                constant_(m.weight, 1)
                constant_(m.bias, 0)

    def forward(self, x):
        out_conv2 = self.conv2(self.conv1(x))
        out_conv3 = self.conv3_1(self.conv3(out_conv2))
        out_conv4 = self.conv4_1(self.conv4(out_conv3))
        out_conv5 = self.conv5_1(self.conv5(out_conv4))
        out_conv6 = self.conv6_1(self.conv6(out_conv5))

        flow6       = self.predict_flow6(out_conv6)
        flow6_up    = crop_like(self.upsampled_flow6_to_5(flow6), out_conv5)
        out_deconv5 = crop_like(self.deconv5(out_conv6), out_conv5)

        concat5 = torch.cat((out_conv5,out_deconv5,flow6_up),1)
        flow5       = self.predict_flow5(concat5)
        flow5_up    = crop_like(self.upsampled_flow5_to_4(flow5), out_conv4)
        out_deconv4 = crop_like(self.deconv4(concat5), out_conv4)

        concat4 = torch.cat((out_conv4,out_deconv4,flow5_up),1)
        flow4       = self.predict_flow4(concat4)
        flow4_up    = crop_like(self.upsampled_flow4_to_3(flow4), out_conv3)
        out_deconv3 = crop_like(self.deconv3(concat4), out_conv3)

        concat3 = torch.cat((out_conv3,out_deconv3,flow4_up),1)
        flow3       = self.predict_flow3(concat3)
        flow3_up    = crop_like(self.upsampled_flow3_to_2(flow3), out_conv2)
        out_deconv2 = crop_like(self.deconv2(concat3), out_conv2)

        concat2 = torch.cat((out_conv2,out_deconv2,flow3_up),1)
        flow2 = self.predict_flow2(concat2)

        if self.training:
            return flow2,flow3,flow4,flow5,flow6
        else:
            return flow2

    def weight_parameters(self):
        return [param for name, param in self.named_parameters() if 'weight' in name]

    def bias_parameters(self):
        return [param for name, param in self.named_parameters() if 'bias' in name]
  • В коде deconv — это слой деконволюции, upsampled_flow — тоже слой деконволюции, а остальные — это комбинация слоя свертки + bn + LeakyReLU;
  • Процесс в коде полностью соответствует описанному ранее процессу, который я проверил в процессе воспроизведения.
  • Наконец, было обнаружено, что в процессе обучения вводились пять полей оптического потока разного размера, таких как поток2 и поток3.Это естественно для подсчета потерь, Хотя функция потерь не упоминается в документе, из кода видно, что используются многомасштабные потери (аналогично концепции вспомогательных потерь). Функция потерь будет объяснена позже.

2 Функция потерь

import torch
import torch.nn.functional as F


def EPE(input_flow, target_flow, sparse=False, mean=True):
    EPE_map = torch.norm(target_flow-input_flow,2,1)
    batch_size = EPE_map.size(0)
    if sparse:
        # invalid flow is defined with both flow coordinates to be exactly 0
        mask = (target_flow[:,0] == 0) & (target_flow[:,1] == 0)

        EPE_map = EPE_map[~mask]
    if mean:
        return EPE_map.mean()
    else:
        return EPE_map.sum()/batch_size
  • Взглянув сначала на эту часть, ключом к вычислению потерь является норма l2 разницы между входным оптическим потоком и выходным оптическим потоком;
  • разреженный должен быть ли поле входного оптического потока разреженным оптическим потоком или плотным оптическим потоком.В моих экспериментах это все плотный оптический поток, поэтому я игнорирую операции в разреженном;
def multiscaleEPE(network_output, target_flow, weights=None, sparse=False):
    def one_scale(output, target, sparse):

        b, _, h, w = output.size()

        if sparse:
            target_scaled = sparse_max_pool(target, (h, w))
        else:
            target_scaled = F.interpolate(target, (h, w), mode='area')
        return EPE(output, target_scaled, sparse, mean=False)

    if type(network_output) not in [tuple, list]:
        network_output = [network_output]
    if weights is None:
        weights = [0.005, 0.01, 0.02, 0.08, 0.32]  # as in original article
    assert(len(weights) == len(network_output))

    loss = 0
    for output, weight in zip(network_output, weights):
        loss += weight * one_scale(output, target_flow, sparse)
    return loss
  • Эта часть является полной потерей, поскольку вывод кода предыдущей части модели представляет собой форму кортежа, например (поток2, поток3, поток4, поток5, поток6);
  • В этой функции метод one_scale заключается в понижении дискретизации данных оптического потока наземной истины до истинности наземной информации с тем же размером, что и размер потока с интерполяцией, а затем для вычисления вводится в EPE;
  • Потери, рассчитанные по картам признаков разных размеров, умножаются на вес, а вес в коде представляет собой исходное значение веса в бумаге Flownet.

4 correlation

image.pngРазница между этой и предыдущей простой версией заключается в следующем:Сначала выполните ту же обработку признаков на изображении, как в сети близнецов, а затем выполните корреляционную обработку, предложенную в документе для двух извлеченных карт признаков, объедините их в карту признаков, а затем выполните последующую обработку, аналогичную простая версия.

Посмотрите прямо на код модели здесь:

class FlowNetC(nn.Module):
    expansion = 1

    def __init__(self,batchNorm=True):
        super(FlowNetC,self).__init__()

        self.batchNorm = batchNorm
        self.conv1      = conv(self.batchNorm,   3,   64, kernel_size=7, stride=2)
        self.conv2      = conv(self.batchNorm,  64,  128, kernel_size=5, stride=2)
        self.conv3      = conv(self.batchNorm, 128,  256, kernel_size=5, stride=2)
        self.conv_redir = conv(self.batchNorm, 256,   32, kernel_size=1, stride=1)

        self.conv3_1 = conv(self.batchNorm, 473,  256)
        self.conv4   = conv(self.batchNorm, 256,  512, stride=2)
        self.conv4_1 = conv(self.batchNorm, 512,  512)
        self.conv5   = conv(self.batchNorm, 512,  512, stride=2)
        self.conv5_1 = conv(self.batchNorm, 512,  512)
        self.conv6   = conv(self.batchNorm, 512, 1024, stride=2)
        self.conv6_1 = conv(self.batchNorm,1024, 1024)

        self.deconv5 = deconv(1024,512)
        self.deconv4 = deconv(1026,256)
        self.deconv3 = deconv(770,128)
        self.deconv2 = deconv(386,64)

        self.predict_flow6 = predict_flow(1024)
        self.predict_flow5 = predict_flow(1026)
        self.predict_flow4 = predict_flow(770)
        self.predict_flow3 = predict_flow(386)
        self.predict_flow2 = predict_flow(194)

        self.upsampled_flow6_to_5 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)
        self.upsampled_flow5_to_4 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)
        self.upsampled_flow4_to_3 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)
        self.upsampled_flow3_to_2 = nn.ConvTranspose2d(2, 2, 4, 2, 1, bias=False)

        for m in self.modules():
            if isinstance(m, nn.Conv2d) or isinstance(m, nn.ConvTranspose2d):
                kaiming_normal_(m.weight, 0.1)
                if m.bias is not None:
                    constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                constant_(m.weight, 1)
                constant_(m.bias, 0)

    def forward(self, x):
        x1 = x[:,:3]
        x2 = x[:,3:]

        out_conv1a = self.conv1(x1)
        out_conv2a = self.conv2(out_conv1a)
        out_conv3a = self.conv3(out_conv2a)

        out_conv1b = self.conv1(x2)
        out_conv2b = self.conv2(out_conv1b)
        out_conv3b = self.conv3(out_conv2b)

        out_conv_redir = self.conv_redir(out_conv3a)
        out_correlation = correlate(out_conv3a,out_conv3b)

        in_conv3_1 = torch.cat([out_conv_redir, out_correlation], dim=1)

        out_conv3 = self.conv3_1(in_conv3_1)
        out_conv4 = self.conv4_1(self.conv4(out_conv3))
        out_conv5 = self.conv5_1(self.conv5(out_conv4))
        out_conv6 = self.conv6_1(self.conv6(out_conv5))

        flow6       = self.predict_flow6(out_conv6)
        flow6_up    = crop_like(self.upsampled_flow6_to_5(flow6), out_conv5)
        out_deconv5 = crop_like(self.deconv5(out_conv6), out_conv5)

        concat5 = torch.cat((out_conv5,out_deconv5,flow6_up),1)
        flow5       = self.predict_flow5(concat5)
        flow5_up    = crop_like(self.upsampled_flow5_to_4(flow5), out_conv4)
        out_deconv4 = crop_like(self.deconv4(concat5), out_conv4)

        concat4 = torch.cat((out_conv4,out_deconv4,flow5_up),1)
        flow4       = self.predict_flow4(concat4)
        flow4_up    = crop_like(self.upsampled_flow4_to_3(flow4), out_conv3)
        out_deconv3 = crop_like(self.deconv3(concat4), out_conv3)

        concat3 = torch.cat((out_conv3,out_deconv3,flow4_up),1)
        flow3       = self.predict_flow3(concat3)
        flow3_up    = crop_like(self.upsampled_flow3_to_2(flow3), out_conv2a)
        out_deconv2 = crop_like(self.deconv2(concat3), out_conv2a)

        concat2 = torch.cat((out_conv2a,out_deconv2,flow3_up),1)
        flow2 = self.predict_flow2(concat2)

        if self.training:
            return flow2,flow3,flow4,flow5,flow6
        else:
            return flow2

    def weight_parameters(self):
        return [param for name, param in self.named_parameters() if 'weight' in name]

    def bias_parameters(self):
        return [param for name, param in self.named_parameters() if 'bias' in name]

Ключ внутри находится в этом разделе:

out_conv_redir = self.conv_redir(out_conv3a)
out_correlation = correlate(out_conv3a,out_conv3b)
in_conv3_1 = torch.cat([out_conv_redir, out_correlation], dim=1)
  • Здесь self.conv_redir представляет собой набор сверточных слоев +bn+leakyrelu;
  • Но в разделе о корреляции автор github процитировалfrom spatial_correlation_sampler import spatial_correlation_sample, но эта библиотека не предусмотрена в коде, поэтому я откажусь от этой версии flownet. Я предполагаю, что этот модуль является ссылкой автора на код других людей, который должен быть объяснен на домашней странице github, но я слишком застрял здесь на github, я добавлю эти знания, когда у меня будет время. (Но вообще статью никто не читает ха-ха, если меня никто не спросит, то эту яму 2333 я проигнорирую)

3 Резюме

  • Flownet действительно полезен в некоторых случаях, и обучение сходится довольно быстро.