1. 程式人生 > >深度學習(二十八)基於多尺度深度網路的單幅影象深度估計

深度學習(二十八)基於多尺度深度網路的單幅影象深度估計

基於多尺度深度網路的單幅影象深度估計

作者:hjimce

一、相關理論

本篇博文主要講解來自2014年NIPS上的一篇paper:《Depth Map Prediction from a Single Image using a Multi-Scale Deep Network》,屬於CNN應用類別的文章,主要是利用卷積神經網路進行單幅影象的深度估計。我們拍照的時候,把三維的圖形,投影到二維的平面上,形成了二維影象。而深度估計的目的就是要通過二維的圖片,估計出三維的資訊,是一個逆過程,這個在三維重建領域相當重要。

這個如果是利用多張不同視角的圖片進行三維重建,會比較簡單,研究的也比較多,比如:立體視覺。然而僅僅從一張圖片進行三維深度估計,確實是一個很艱難的事。

因為從三維到二維,肯定會丟失掉物體的深度值;因此從二維到三維本來就是一個資訊缺失的、不可逆過程。然而大牛們依舊想方設法去嘗試估計深度值,其中比較牛逼的演算法當屬Make3D,不過再牛逼的演算法也是那樣,因為本來就是一個資訊缺失的問題,所以深度估計的精度,依舊很爛。

然而本篇paper,通過深度學習的方法,從大量的訓練資料中,進行學習,一口氣提高了35%的相對精度,超出了傳統方法十幾條街。這個就像我們人一樣,我們看一張照片中的物體的時候,雖然深度資訊缺失,但是我們依舊可以估計它的形狀,這是因為我們的腦海中,儲存了無數的物體,有豐富的先驗知識,可以結合這些先驗,對一張圖片中的物體做出形狀估計。利用深度學習進行一張圖片的深度估計,也是差不多一樣的道理,通過在大量的訓練資料上,學習先驗知識,最後就可以把精度提高上去。

有點囉嗦了,迴歸正題吧,估計都等得不耐煩了,我們下面開始講解文獻:《Depth Map Prediction from a Single Image using a Multi-Scale Deep Network》的演算法原理,及其實現。

二、網路總體架構

先貼一下,網路架構圖:


網路分為全域性粗估計和區域性精估計,這個跟人臉特徵點的DCNN網路有點類似,都屬於deep network。全域性粗估計CNN:這個網路包含了五個特徵提取層(每層包好了卷積、最大池化操作),在這五個卷積層後面,有連結了兩個全連線層,我們最後的輸出圖片的寬高變為原來的1/4。

不管是粗還是精網路,兩個網路的輸入圖片是一樣的,輸出圖片的大小也是一樣的。對於粗網路和精網路的訓練方法,paper採用的方法是,先訓練粗網路,訓練完畢後,固定粗網路的引數,然後在訓練精網路,這個與另外一篇paper:《Predicting Depth, Surface Normals and Semantic Labels》的訓練方法不同,這篇paper前面兩個scale的訓練是一起訓練的,引數一起更新。

三、coarse的網路結構

網路結構方面,基本上是模仿Alexnet的,具體可以看一下,上面的表格。我們以NYU資料集上,為例,進行下面網路結構講解。

1、輸入圖片:圖片大小為304*228

2、網路第一層:卷積核大小為11*11,卷積跨步大小為4,卷積後圖片大小為[(304-11)/4+1,(228-11)/4+1]=[74,55],特徵圖個數為96,即filter_shape = (96, 3, 11, 11)。池化採用最大重疊池化size=(3,3),跨步為2。因此網路輸出圖片的大小為:[74/2,55/2]=[37,27]。為了簡單起見,我們結合文獻作者給的原始碼進行講解,paper主頁:http://www.cs.nyu.edu/~deigen/depth/作者提供了訓練好的模型,demo供我們測試,訓練部分的原始碼沒有提供,後面博文中貼出的原始碼均來自於paper的主頁。本層網路的相關引數如下:

[imnet_conv1]
type = conv
load_key = imagenet
filter_shape = (96, 3, 11, 11)
stride = 4
conv_mode = valid
init_w = lambda shp: 0.01*np.random.randn(*shp)
learning_rate_scale_w = 0.001
learning_rate_scale_b = 0.001
weight_decay_w = 0.0005
 
[imnet_pool1]
type = maxpool
load_key = imagenet
poolsize = (3,3)
poolstride = (2,2)

3、網路第二層:卷積核大小為5*5,卷積跨步為1,接著進行最大重疊池化,得到圖片大小為[18,13](需要加入pad)。網路結構設計方面可以參考Alexnet網路。原始碼如下:
[imnet_conv2]
type = conv
load_key = imagenet
filter_shape = (256, 96, 5, 5)
conv_mode = same
stride = 1
init_w = lambda shp: 0.01*np.random.randn(*shp)
learning_rate_scale_w = 0.001
learning_rate_scale_b = 0.001
weight_decay_w = 0.0005
 
[imnet_pool2]
type = maxpool
load_key = imagenet
poolsize = (3,3)
poolstride = (2,2)

4、細節方面:除了網路的最後一層輸出層之外,其它的啟用函式都是採用Relu函式。因為最後一層是線性迴歸問題,因此最後一層的啟用函式應該是線性函式。在全連線層layer6,採用Dropout。

5、引數初始化:引數初始化,採用遷移學習的思想,直接把Alexnet的網路訓練好的引數的前面幾層拿過來,進行fine-tuning。文章提到,採用fine-tuning的方法,效果會比較好。通過閱讀原始碼可以判斷,paper除了全連線層之外,粗網路卷積層的引數都是利用Alexnet進行fine-tuning。

四、精細化網路結構-Fine scale Network

精網路的結構,採用的是全連線卷積神經網路,也就是不存在全連線層,這個如果搞過FCN語義分割的,應該會比較明白。這個網路包含了三個卷積層。

通過上面粗網路的深度值預測,我們得到的深度圖是比較模糊的,基本上沒有什麼邊緣資訊,接著接著我們需要精細化,使得我們的深度預測圖與影象的邊緣等資訊相吻合,因為一個物體的邊緣,也就是相當於深度值發生突變的地方,因此我們預測出來的深度值影象也應該是有邊緣的。從粗到精的思想就像文獻《Deep Convolutional Network Cascade for Facial Point Detection》,從粗估計到精預測的過程一樣,如果你之前已經搞過coarse to refine 相關的網路的話,那麼學習這篇文獻會比較容易。為了簡單起見,我這邊把本層網路稱之為:精網路。精網路的結構如下:

1、輸入層:304*228 大小的彩色圖片

2、第一層輸入原始圖片,第一層包含卷積、RELU、池化。卷積核大小為9*9,卷積跨步選擇2,特徵圖個數選擇64個(這個文獻是不是中的圖片是不是錯了,好像標的是63),即:filter_shape = (64,3,9,9)。最大池化採用重疊池化取樣size=(3,3),跨步選擇2,即poolsize = (3,3),poolstride = (2,2)。這一層主要用於提取邊緣特徵。因為我們通過粗網路的輸出可以看出,基本上沒有了邊緣資訊,因此我們需要利用精網路,重構這些邊緣資訊。本層網路的相關引數:

[conv_s2_1]
type = conv
load_key = fine_stack
filter_shape = (64,3,9,9)
stride = 2
init_w = lambda shp: 0.001*np.random.randn(*shp)
init_b = 0.0
conv_mode = valid
weight_decay_w = 0.0001
learning_rate_scale_w = 0.001
learning_rate_scale_b = 0.001
[pool_s2_1]
type = maxpool
poolsize = (3,3)
poolstride = (2,2)

經過卷積層,我們可以得到大小為(110*148)的圖片,然後在進行pooling,就可以得到55*74的圖片了。這樣經過這一層,我們就得到了與粗網路的輸出大小相同的圖片了。

3、第二層這一層的輸入,除了第一層得到的特徵圖外,同時還額外添加了粗網路的輸出圖(作為特徵圖,加入網路輸入)。

網路細節基本和粗網路相同,這裡需要注意的是,我們訓練網路的時候,是先把粗網路訓練好了,然後在進行訓練精網路,精網路訓練過程中,粗網路的引數是不用迭代更新的。DCNN的思想都是這樣的,如果你有看了我的另外一篇關於特徵點定位的博文DCNN,就知道怎麼訓練了。

還有我們這一層的輸入圖片的大小,已經和輸出層所要求的大小一樣了,因此後面卷積的時候,卷積要保證圖片大小還是一樣的。

[conv_s2_2]
type = conv
load_key = fine_stack
filter_shape = (64,64,5,5)
init_w = lambda shp: 0.01*np.random.randn(*shp)
init_b = 0.0
conv_mode = same
weight_decay_w = 0.0001
learning_rate_scale_w = 0.01
learning_rate_scale_b = 0.01

4、第三層:也就是連線到輸出層去
[conv_s2_3]
type = conv
load_key = fine_stack
filter_shape = (64,1,5,5)
transpose = True
init_w = lambda shp: 0.01*np.random.randn(*shp)
init_b = 0.0
conv_mode = same
weight_decay_w = 0.0001
learning_rate_scale_w = 0.001
learning_rate_scale_b = 0.001

五、尺度不變損失函式

這個是文獻的主要創新點之一,主要是提出了尺度不變的均方誤差函式:

 

其中y和y*就是我們的圖片標註資料和預測資料了,本文指的是每個畫素點的實際的深度值和預測的深度值。α的計算公式如下:

 

根據上面定義的損失函式,paper訓練過程中採用如下的損失函式:

 

其中引數λ取值為0.5。具體的原始碼如下:

#定義損失函式 縮放不變損失函式,pred預測值,y0標準值、m0為mask(為0表示無效點,為1表示有效點)
def define_cost(self, pred, y0, m0):
    bsize = self.bsize
    npix = int(np.prod(test_shape(y0)[1:]))
    y0_target = y0.reshape((self.bsize, npix))
    y0_mask = m0.reshape((self.bsize, npix))
    pred = pred.reshape((self.bsize, npix))


#因為在mask中,所有的無效的畫素點的值都為0,所以p、t中對應的畫素點的值也為0,這樣我們的損失函式,這些畫素點的值也為0,對引數不起更新作用
    p = pred * y0_mask
    t = y0_target * y0_mask

    d = (p - t)

    nvalid_pix = T.sum(y0_mask, axis=1)#這個表示深度值有效的畫素點
    #文獻中公式4 ,引數λ取值為0.5.公式採用的是簡化為(n×sum(d^2)-λ*(sum(d))^2)/(n^2)
    depth_cost = (T.sum(nvalid_pix * T.sum(d**2, axis=1))
                     - 0.5*T.sum(T.sum(d, axis=1)**2)) \
                 / T.maximum(T.sum(nvalid_pix**2), 1)

    return depth_cost

六、資料擴充

1、縮放:縮放比例s取(1,1.5),因為縮放深度值並不是不變的,所以文獻採用把深度值對應的也除以比例s。(這一點我有點不明白,難道一張圖片拍好了,我們把它放大s倍,那麼會相當於攝像頭往物體靠近了s倍進行拍照嗎?這樣解釋的,讓我有點想不通)。

2、旋轉資料擴充,這個比較容易

3、資料加噪擴充:主要是把圖片的每個畫素點的值,乘以一個(0.8,1.2)之間的隨機數。

4、映象資料擴充,用0.5的概率,對資料進行翻轉。

5、隨機裁剪擴充,跟Alexnet一樣。

在測試階段,採用中心裁剪的方式,和Alexnet的各個角落裁剪後平均有所不同。

七、訓練相關細節

這邊我只講解NYU資料集上的訓練。NYU訓練資料可以自己網上下載,NYU原始的資料中,每張圖片的每個畫素點label、depth值,其中label是物體標籤,主要用於影象分割,後面paper的作者也發表了一篇關於語意分割、法矢估計的文獻:《Predicting Depth, Surface Normals and Semantic Labels》。

NYU原始的訓練資料有個特點,就是圖片並不是每個畫素點都有depth值,這個可能是因為裝置採集深度值的時候,會有缺失的畫素點。首先NYU資料是640*480的圖片,depth也是640*480,因為每個畫素點,對應一個深度值嘛,可是這些深度值,有的畫素點是無效的,有效和無效的畫素點我們可以用一個mask表示。那麼我們如何進行訓練呢?

我們知道網路採用的是對320*240的圖片,進行random crop的,因此首先我們需要把image、depth、mask都由640*480縮小到320*240。這邊需要注意的是這裡的縮小,是採用直接下采樣的方法,而不是採用線性插值等方法。因為我們需要保證圖片每個畫素點和depth、mask都是對應的,而不是採用插值的方法,如果採用插值,那麼我們的mask就不再是mask了,這個小細節一開始困擾了我好久。還有需要再提醒一下,320*240不是網路的輸入大小,我們還要採用random crop,把它裁剪成304*228,這個才是網路的輸入。

另一方面就是depth的問題,我們知道我們輸出的depth的大小是74*55。而網路輸入資料image、depth、mask的大小是304*228,因此我們在構造損失函式,需要把標註資料depth、mask又採用直接下采樣的方法縮小到74*55(下采樣比例為4),這樣才能與網路的輸出大小相同,構造損失函式。相關原始碼如下:

y0 = depths#深度值
m0 = masks#mask

#圖片採用的是直接下采樣,而不是用雙線性插值進行插值得到訓練的depths,下采樣的比例是4
m0 = m0[:,1::4,1::4]
y0 = y0[:,1::4,1::4]

個人總結:這篇文獻的深度估計,文獻上面可以說是深度學習領域崛起的一個牛逼應用,相比於傳統的方法精度提高了很多。不過即便如此,精度離我們要商用的地步還是有一段的距離要走。從這篇文獻我們主要學習到了兩個知識點:1、多尺度CNN模型 。2、標註資料部分缺失的情況下,網路的訓練,這個文獻給了最大的啟發就是:在kaggle競賽上面有個人臉特徵點定位,但是有的圖片的標註資料,是部分缺失的,這個時候我們就可以借用這篇文獻的訓練思路,採用mask的方法。3、文獻提出了Scale-Invariant損失函式,也是文獻的一大創新點。

參考文獻:

1、Depth Map Prediction from a Single Image using a Multi-Scale Deep Network

2、《Predicting Depth, Surface Normals and Semantic Labels with a Common Multi-Scale Convolutional Architecture》

4、《Make3d: Learning 3-d scene structure from a single still image》

**********************作者:hjimce   時間:2016.1.23  聯絡QQ:1393852684   地址:http://blog.csdn.net/hjimce   原創文章,轉載請保留本行資訊************