Sparse Convolutional Network 基於稀疏採樣不變性的深度稠密重建

爲了從稀疏的深度採樣(Lidar)中重建出稠密的深度信息, 研究人員提出了一種高效的稀疏卷積層,用於在訓練過程中精確定位數據缺失的位置,實現了具有稀疏不變性的特徵抽取和重建手段。Sparsity Invariant CNNs

1.基於稀疏採樣的稠密深度重建

激光雷達是十分重要的傳感器,能給出非常準確的距離信息。但其缺陷在於採集的數據較爲稀疏、同時數據也不規則,現有的神經網絡在處理非規則稀疏數據時會產生一系列問題(無法明確定義卷積操作)。爲了處理這個問題,研究人員將三維點雲投影到虛擬的二維平面實現2.5D的表示,並利用圖像修復/深度補全的方法進行處理,得到稠密的2.5D表示。最重要的是,這種特徵還不隨稀疏性變化,具有不變性。
在這裏插入圖片描述
這篇文章的主要目的是>>>通過稀疏的輸入,獲得稠密的輸出結果,並得到稀疏不變性的表示<<<<


2.Sparse Conv稀疏卷積

可以將稠密重建視爲從稀疏空間向稠密空間中的映射過程,這個過程由多層神經網絡來實現。
在這裏插入圖片描述


與先前方法不同的是,研究人員根據採樣點的位置同時定義了一個觀測二進制掩膜O,來爲網絡提供輸入點的位置信息:
在這裏插入圖片描述
針對稀疏的輸入數據,只有精確地在觀測像素位置上點才被考慮(也就是o爲1的位置),並利用觀測點的數量進行歸一化處理。這一操作背後的想法是由於輸入數據的稀疏性不同,這種方法直接針對輸入的有效點進行處理,使得輸出具有不變性。


隨後利用下面的最大池化方法來爲後續的層提供新的觀測mask,如果濾波器中的領域都沒有值那麼這個點就屬於沒有觀測到的點,在mask中置零,如果有值則證明這是有值的觀測點置爲一:
在這裏插入圖片描述

3.模型架構

研究人員提出了了下面的網絡架構,其中輸入包含了稀疏的深度圖(黃色)和二進制的觀測掩膜(紅色),輸出預測後的稠密深度圖(這個圖可以對應第二個代碼實現中最後一部分網絡結構來理解)。
在這裏插入圖片描述
這裏面最重要的是將mask加入到了模型的訓練過程中,可以看到在每一層都有稀疏卷積在作用,對真正觀測到的值進行處理。特徵和mask同時輸入稀疏卷積操作。首先上部分支中,a.特徵和mask先進行點乘,保留下觀測到的值,b.而後與權重進行第一次卷積;c.mask與全1矩陣進行歸一化卷積,d.而後與權重作用後的特徵j進行點乘,e.最後加上偏置得到最終的特徵結果;f.而mask分支則利用第二節裏面提到的改進最大池化方法來得到新的mask(這裏的a~f可以對應代碼註釋中對應步驟來理解):
在這裏插入圖片描述
通過mask與特徵的共同作用,使模型專注於對於觀測數據的特徵提取和學習,使得最後恢復的稠密深度圖更爲精確,得到的表示特徵具有稀疏不變性。下面的表格中可以看到不同採樣率下的稀疏卷積結果都比較穩定
在這裏插入圖片描述

4.代碼實現

找到了一個關於這篇論文的代碼實現,主要對文章中的稀疏卷積部分進行了定義:

import tensorflow as tf
# copy from https://github.com/PeterTor/sparse_convolution/blob/master/sparse.py
"""輸入爲一個特徵張量和一個觀測值的mask
   輸出爲稀疏卷積後的張量和對應新的mask
   函數內部和一般卷積類似,包含了濾波器個數、核的大小、步長以及L2的幅值
   對應上面稀疏卷積來看會更清晰,對應a~f步驟
"""

def sparse_conv(tensor,binary_mask = None,filters=32,kernel_size=3,strides=2,l2_scale=0.0):

    if binary_mask == None: #first layer has no binary mask
        b,h,w,c = tensor.get_shape()
        channels=tf.split(tensor,c,axis=3)
        #assume that if one channel has no information, all channels have no information
        binary_mask = tf.where(tf.equal(channels[0], 0), tf.zeros_like(channels[0]), tf.ones_like(channels[0])) #mask should only have the size of (B,H,W,1)   #生成掩膜
    
    #稀疏卷積的上半部分支
    features = tf.multiply(tensor,binary_mask)  #對應第一步逐點乘法>>a
    features = tf.layers.conv2d(features, filters=filters, kernel_size=kernel_size, strides=(strides, strides), trainable=True, use_bias=False, padding="same",kernel_regularizer=tf.contrib.layers.l2_regularizer(scale=l2_scale))  #對應與權重的卷積>>b
 
    #稀疏卷積的下半部分支,處理mask
    norm = tf.layers.conv2d(binary_mask, filters=filters,kernel_size=kernel_size,strides=(strides, strides),kernel_initializer=tf.ones_initializer(),trainable=False,use_bias=False,padding="same")
    norm = tf.where(tf.equal(norm,0),tf.zeros_like(norm),tf.reciprocal(norm))  #>>c,歸一化mask
    _,_,_,bias_size = norm.get_shape()

    b = tf.Variable(tf.constant(0.0, shape=[bias_size]),trainable=True)   #這個是卷積裏面的b
    feature = tf.multiply(features,norm)+b   #點乘卷積結果>>d, 加上偏置結果>>e
    mask = tf.layers.max_pooling2d(binary_mask,strides = strides,pool_size=kernel_size,padding="same")    #>>f,最大池化輸出新的mask

    # 返回稀疏卷積後新的特徵值和mask
    return feature,mask


#下面是一個使用的示例
image = tf.placeholder(tf.float32, shape=[None,64,64,2], name="input_image")
b_mask = tf.placeholder(tf.float32, shape=[None,64,64,1], name="binary_mask")
features,b_mask = sparse_conv(image)
features,b_mask = sparse_conv(features,binary_mask=b_mask)

sess = tf.Session()
sess.run(tf.global_variables_initializer())

在另一個代碼中有整個個sparseConvNet的實現,包含了對於稀疏卷積的定義和模塊的定義,模型基於pytorch實現:

from __future__ import absolute_import
# copy from:https://github.com/yxgeee/DepthComplete/blob/release/models/SparseConvNet.py
import torch
from torch import nn
from torch.nn import functional as F
import torchvision

# 定義稀疏卷積類以及前向函數
class SparseConv(nn.Module):
	# Convolution layer for sparse data
	def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, bias=True):
		super(SparseConv, self).__init__()
		self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=False)   #定義卷積
		self.if_bias = bias  #定義偏置
		if self.if_bias:
			self.bias = nn.Parameter(torch.zeros(out_channels).float(), requires_grad=True)
		self.pool = nn.MaxPool2d(kernel_size, stride=stride, padding=padding, dilation=dilation)    #定義池化

		nn.init.kaiming_normal_(self.conv.weight, mode='fan_out', nonlinearity='relu')
		self.pool.require_grad = False

	def forward(self, input):
		x, m = input         # 輸入特徵和mask
		mc = m.expand_as(x)  # 得到掩膜
		x = x * mc           # 掩膜與輸入特徵點乘>>a 
		x = self.conv(x)     # 結果卷積>>b

		weights = torch.ones_like(self.conv.weight)
		mc = F.conv2d(mc, weights, bias=None, stride=self.conv.stride, padding=self.conv.padding, dilation=self.conv.dilation)   # 掩膜卷積歸一化>>c
		mc = torch.clamp(mc, min=1e-5)
		mc = 1. / mc
		x = x * mc   # 歸一化後點乘>>d
		if self.if_bias:
			x = x + self.bias.view(1, self.bias.size(0), 1, 1).expand_as(x)    # 加偏置>>e
		m = self.pool(m)     # 最後池化輸入新的mask>>f

		return x, m

隨後基於稀疏卷積構建整個稀疏卷積模型:

class SparseConvBlock(nn.Module):

	def __init__(self, in_channel, out_channel, kernel_size, stride=1, padding=0, dilation=1, bias=True):
		super(SparseConvBlock, self).__init__()
		self.sparse_conv = SparseConv(in_channel, out_channel, kernel_size, stride=stride, padding=padding, dilation=dilation, bias=True)
		self.relu = nn.ReLU(inplace=True)
		# 構建稀疏卷積和對應的激活函數

	def forward(self, input):
		x, m = input
		x, m = self.sparse_conv((x, m))
		assert (m.size(1)==1)
		x = self.relu(x)
		return x, m  
		# 返回稀疏卷積後的mask和特徵x

最後構建整個網絡,可對應模型架構部分第一個圖理解:

class SparseConvNet(nn.Module):
# 稀疏卷積網絡模型
	def __init__(self, in_channel=1, out_channel=1, kernels=[11,7,5,3,3], mid_channel=16):
		super(SparseConvNet, self).__init__()
		channel = in_channel   # 輸入通道數
		convs = []
		for i in range(len(kernels)):   # 構建多層不同核大小的卷積層
			assert (kernels[i]%2==1)
			convs += [SparseConvBlock(channel, mid_channel, kernels[i], padding=(kernels[i]-1)//2)]
			channel = mid_channel
		self.sparse_convs = nn.Sequential(*convs)       # 多個卷積層序列操作
		self.mask_conv = nn.Conv2d(mid_channel+1, out_channel, 1)

	def forward(self, x):
		m = (x>0).detach().float()
		x, m = self.sparse_convs((x,m))    # 稀疏卷積
		x = torch.cat((x,m), dim=1)
		x = self.mask_conv(x)              # mask卷積
		# x = F.relu(x, inplace=True)
		return x

稀疏卷積性能不錯,可以衍生出深度圖像補全和深圖修復等任務,以及激光雷達點雲加密等工作,可以繼續參考文章
Sparse-to-Dense Self-Supervised Depth Completion from LiDAR and Monocular Camera
Sparse-to-Dense: Depth Prediction from Sparse Depth Samples and a Single Image
DFineNet
mafangchan

picture from https://images.pexels.com/photos/1876291/pexels-photo-1876291.png


ref:
paper:https://lmb.informatik.uni-freiburg.de/Publications/2017/UB17a/sparsity_invariant_cnns.pdf
supplements:https://lmb.informatik.uni-freiburg.de/Publications/2017/UB17a/supplemental.pdf http://www.cvlibs.net/publications/Uhrig2017THREEDV_supplementary.pdf
code:https://github.com/PeterTor/sparse_convolution
code:https://github.com/yxgeee/DepthComplete
Author:https://lmb.informatik.uni-freiburg.de/people/uhrigj/publications.html
弗萊堡大學視覺組:https://lmb.informatik.uni-freiburg.de/index.php
#-------------------------------------------------------------------#
Facebook SparseConvNet:https://github.com/facebookresearch/SparseConvNet
SparseConvNetwiki:https://github.com/btgraham/SparseConvNet/wiki
fastai version:https://github.com/goodok/fastai_sparse
Sparsely Aggreagated Convolutional Networ: https://github.com/Lyken17/SparseNet
Efficient Sparse-Winograd Convolutional Neural Networks:https://github.com/xingyul/sparse-winograd-cnn
intelab skimcaffe:https://github.com/IntelLabs/SkimCaffe
sfm深度預測:https://github.com/tinghuiz/SfMLearner

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章