基於多尺度深度網絡的單幅圖像深度估計(轉)

基於多尺度深度網絡的單幅圖像深度估計

原文地址http://blog.csdn.net/hjimce/article/details/50569474

作者: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的主頁。本層網絡的相關參數如下:

[python] view plain copy
  1. [imnet_conv1]  
  2. type = conv  
  3. load_key = imagenet  
  4. filter_shape = (9631111)  
  5. stride = 4  
  6. conv_mode = valid  
  7. init_w = lambda shp: 0.01*np.random.randn(*shp)  
  8. learning_rate_scale_w = 0.001  
  9. learning_rate_scale_b = 0.001  
  10. weight_decay_w = 0.0005  
  11.    
  12. [imnet_pool1]  
  13. type = maxpool  
  14. load_key = imagenet  
  15. poolsize = (3,3)  
  16. poolstride = (2,2)  

3、網絡第二層:卷積核大小爲5*5,卷積跨步爲1,接着進行最大重疊池化,得到圖片大小爲[18,13](需要加入pad)。網絡結構設計方面可以參考Alexnet網絡。源碼如下:

[python] view plain copy
  1. [imnet_conv2]  
  2. type = conv  
  3. load_key = imagenet  
  4. filter_shape = (2569655)  
  5. conv_mode = same  
  6. stride = 1  
  7. init_w = lambda shp: 0.01*np.random.randn(*shp)  
  8. learning_rate_scale_w = 0.001  
  9. learning_rate_scale_b = 0.001  
  10. weight_decay_w = 0.0005  
  11.    
  12. [imnet_pool2]  
  13. type = maxpool  
  14. load_key = imagenet  
  15. poolsize = (3,3)  
  16. 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)。這一層主要用於提取邊緣特徵。因爲我們通過粗網絡的輸出可以看出,基本上沒有了邊緣信息,因此我們需要利用精網絡,重構這些邊緣信息。本層網絡的相關參數:

[python] view plain copy
  1. [conv_s2_1]  
  2. type = conv  
  3. load_key = fine_stack  
  4. filter_shape = (64,3,9,9)  
  5. stride = 2  
  6. init_w = lambda shp: 0.001*np.random.randn(*shp)  
  7. init_b = 0.0  
  8. conv_mode = valid  
  9. weight_decay_w = 0.0001  
  10. learning_rate_scale_w = 0.001  
  11. learning_rate_scale_b = 0.001  
  12. [pool_s2_1]  
  13. type = maxpool  
  14. poolsize = (3,3)  
  15. poolstride = (2,2)  

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

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

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

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

[python] view plain copy
  1. [conv_s2_2]  
  2. type = conv  
  3. load_key = fine_stack  
  4. filter_shape = (64,64,5,5)  
  5. init_w = lambda shp: 0.01*np.random.randn(*shp)  
  6. init_b = 0.0  
  7. conv_mode = same  
  8. weight_decay_w = 0.0001  
  9. learning_rate_scale_w = 0.01  
  10. learning_rate_scale_b = 0.01  

4、第三層:也就是連接到輸出層去

[python] view plain copy
  1. [conv_s2_3]  
  2. type = conv  
  3. load_key = fine_stack  
  4. filter_shape = (64,1,5,5)  
  5. transpose = True  
  6. init_w = lambda shp: 0.01*np.random.randn(*shp)  
  7. init_b = 0.0  
  8. conv_mode = same  
  9. weight_decay_w = 0.0001  
  10. learning_rate_scale_w = 0.001  
  11. learning_rate_scale_b = 0.001  

五、尺度不變損失函數

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

 

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

 

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

 

其中參數λ取值爲0.5。具體的源碼如下:

[python] view plain copy
  1. #定義損失函數 縮放不變損失函數,pred預測值,y0標準值、m0爲mask(爲0表示無效點,爲1表示有效點)  
  2. def define_cost(self, pred, y0, m0):  
  3.     bsize = self.bsize  
  4.     npix = int(np.prod(test_shape(y0)[1:]))  
  5.     y0_target = y0.reshape((self.bsize, npix))  
  6.     y0_mask = m0.reshape((self.bsize, npix))  
  7.     pred = pred.reshape((self.bsize, npix))  
  8.   
  9.   
  10. #因爲在mask中,所有的無效的像素點的值都爲0,所以p、t中對應的像素點的值也爲0,這樣我們的損失函數,這些像素點的值也爲0,對參數不起更新作用  
  11.     p = pred * y0_mask  
  12.     t = y0_target * y0_mask  
  13.   
  14.     d = (p - t)  
  15.   
  16.     nvalid_pix = T.sum(y0_mask, axis=1)#這個表示深度值有效的像素點  
  17.     #文獻中公式4 ,參數λ取值爲0.5.公式採用的是簡化爲(n×sum(d^2)-λ*(sum(d))^2)/(n^2)  
  18.     depth_cost = (T.sum(nvalid_pix * T.sum(d**2, axis=1))  
  19.                      - 0.5*T.sum(T.sum(d, axis=1)**2)) \  
  20.                  / T.maximum(T.sum(nvalid_pix**2), 1)  
  21.   
  22.     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),這樣才能與網絡的輸出大小相同,構造損失函數。相關源碼如下:

[python] view plain copy
  1. y0 = depths#深度值  
  2. m0 = masks#mask  
  3.   
  4. #圖片採用的是直接下采樣,而不是用雙線性插值進行插值得到訓練的depths,下采樣的比例是4  
  5. m0 = m0[:,1::4,1::4]  
  6. 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》

3、http://www.cs.nyu.edu/~deigen/depth/

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

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

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