Anytime Stereo Image Depth Estimation on Mobile Devices 文章及代碼調(diào)試

AnyNet是Yang Wang等人2016年提出的一種雙目深度計算網(wǎng)絡(luò)。最近做項目有用到該網(wǎng)絡(luò),其中碰到一些小坑為大家展示一下。第一部分是論文中重要地方的翻譯和講解;第二部分是源碼修改及調(diào)試。
原文地址:https://arxiv.org/abs/1810.11408
源碼地址:https://github.com/mileyan/AnyNet

翻譯

摘要

機器人立體深度估計的許多應(yīng)用都需要實時生成精確的視差圖,但計算條件非??量?。當(dāng)前最先進(jìn)的算法迫使用戶在緩慢生成精確映射和快速生成不精確映射之間做出選擇,此外,這些方法通常需要太多參數(shù),無法在電源或內(nèi)存受限的設(shè)備上使用。針對這些缺點,我們提出了一種新的視差預(yù)測方法。與之前的工作相比,我們的端到端學(xué)習(xí)方法可以在推理時權(quán)衡計算量和準(zhǔn)確性。深度估計是分階段進(jìn)行的,在此期間,可以在任何時間查詢模型輸出結(jié)果,得到其當(dāng)前的最佳估計。我們的最終模型可以在10-35幀的范圍內(nèi)處理1242×375分辨率的圖像,誤差只會有微小的增加——可訓(xùn)練參數(shù)比其他渣渣少了兩個數(shù)級。

介紹

前兩段掠過
在本文中,我們提出了一種隨時計算的視差估計方法,并提出了一種在速度和精度之間動態(tài)權(quán)衡的模型(見圖1)。例如,一架高速飛行的自主無人機可以在高頻率上詢問我們的3D深度估計方法。如果一個物體出現(xiàn)在它的飛行路徑上,它將能夠迅速地感知它,并通過降低速度或執(zhí)行規(guī)避操作做出相應(yīng)的反應(yīng)。當(dāng)以低速飛行時,延遲并不那么重要,同樣的無人機可以計算出更高的分辨率和更精確的3D深度地圖,實現(xiàn)在擁擠場景中進(jìn)行高精度導(dǎo)航或詳細(xì)繪制環(huán)境地圖等任務(wù)。

image

AnyNet預(yù)測時間線的例子。隨著時間的推移,深度估計變得越來越準(zhǔn)確。該算法可在任何時候輪詢以返回當(dāng)前深度圖的最佳估計值。最初的估計可能足以觸發(fā)避障機動,而后來的圖像包含了足夠的細(xì)節(jié),可以進(jìn)行更高級的路徑規(guī)劃程序

卷積網(wǎng)絡(luò)深度估計的計算復(fù)雜度通常與圖像分辨率成三次方,與被認(rèn)為是的最大視差成線性關(guān)系。記住這些特征,我們不斷細(xì)化深度圖,同時始終確保分辨率或最大視差范圍足夠低,以確保最小的計算時間。我們從低分辨率(1/16)估算全視差范圍的深度圖開始。立方復(fù)雜度允許我們在幾毫秒內(nèi)計算出這個初始深度圖(大部分時間花在初始特征提取和下采樣上)。從這個低分辨率估計值開始,我們通過上行采樣逐步增加視差圖的分辨率,然后修正現(xiàn)在在高分辨率下很明顯的誤差。盡管使用了更高的分辨率,這些更新仍然很快,因為可以假設(shè)剩余的視差被限制在幾個像素內(nèi),允許我們限制最大的視差,和相關(guān)的計算,僅10% - 20%的全范圍。這些連續(xù)的更新除了初始的低分辨率設(shè)置外,完全避免了全范圍的視差計算,并確保所有計算都是重復(fù)使用的,這使得我們的方法有別于大多數(shù)現(xiàn)有的多尺度網(wǎng)絡(luò)結(jié)構(gòu)。此外,我們的算法可以在任何時間輪詢,以檢索當(dāng)前最佳估計深度圖??梢詫崿F(xiàn)廣泛的可能幀率范圍(在TX2模塊上是10-35FPS),同時在高延遲設(shè)置中仍然保留精確的視差估計。我們的整個網(wǎng)絡(luò)可以在所有尺度上使用一個聯(lián)合損失進(jìn)行端到端訓(xùn)練,我們稱之為AnyNet。
我們在多個基準(zhǔn)數(shù)據(jù)集上評估了AnyNet,得到了各種令人鼓舞的結(jié)果:首先,AnyNet使用最先進(jìn)的方法獲得了具有競爭力的準(zhǔn)確性,同時可訓(xùn)練參數(shù)比其他渣渣少了一個數(shù)量級。這對于資源受限的嵌入式設(shè)備尤其有影響。其次,我們發(fā)現(xiàn)深度卷積網(wǎng)絡(luò)能夠從粗糙的視差圖預(yù)測殘差。最后,包含最終空間傳播模型(SPNet)的大大提高了視差圖的質(zhì)量,在計算成本(和參數(shù)存儲需求)比現(xiàn)有方法低的情況下產(chǎn)生了最先進(jìn)的結(jié)果。
圖2顯示了AnyNet體系結(jié)構(gòu)的示意圖布局。一個輸入圖像對首先通過U-Net特征提取器,它計算幾個輸出分辨率的特征地圖(比例1/16,1/8,1/4)。在第一階段,只計算最低尺度的特征(1/16),并通過視差網(wǎng)絡(luò)(圖4)生成一個低分辨率的視差圖(stage 1)。視差圖估計右輸入圖像中每個像素的水平偏移量,它可以計算一個深度圖。由于輸入分辨率低,整個stage1的計算只需要幾毫秒。如果允許更多的計算時間,我們進(jìn)入stage2,在U-Net中繼續(xù)計算,以獲得更大尺度(1/8)的特征。而不是計算一個完整的視差圖在這個更高的分辨率,在stage2,我們簡單地修正已經(jīng)計算的視差圖從階段1。首先,我們放大視差圖來匹配階段2的分辨率。然后我們計算一個殘差貼圖,其中包含小的修正,指定每個像素應(yīng)該增加或減少多少視差貼圖。如果時間允許,stage3與第stage2的過程相似,將分辨率從1/8到1/4再翻倍。stage4使用SPNet細(xì)化了階段3的視差圖。


在這里插入圖片描述

在本節(jié)的其余部分中,我們將更詳細(xì)地描述模型的各個組件。

  1. U-Net特征提取網(wǎng)絡(luò): 圖3詳細(xì)說明了U-Net特征提取器,該特征提取器同時應(yīng)用于左右圖像。U-Net體系結(jié)構(gòu)以不同的分辨率(1/16、1/8、1/4)計算特征圖,在第1-3階段stage作為輸入,只有在需要時才計算。對原始輸入圖像進(jìn)行最大pooling或strided convolution的下采樣,然后用卷積濾波器進(jìn)行處理。低分辨率的feature map捕獲全局上下文,而高分辨率feature map捕獲局部細(xì)節(jié)。在尺度為1/8和1/4時,最后的卷積層包含了之前計算的低尺度特征。


    在這里插入圖片描述
  2. 視差計算網(wǎng)絡(luò):它的輸入為U-Net輸出的特征圖。我們使用這個組件來計算初始視差圖(stage1)以及為后續(xù)校正計算剩余的視差圖(stage2和3)。視差網(wǎng)絡(luò)首先計算cost volume。這里的cost volume是指左側(cè)圖像中的一個像素與右側(cè)圖像中對應(yīng)的一個像素之間的相似性。如果輸入特征圖尺寸為H\times W,那么cost volume尺寸為H\times W\times M。其中(i, j, k)項描述左側(cè)圖像的像素(i, j)與右側(cè)圖像的像素(i, j?k)的匹配程度。M為考慮的最大視差。我們可以將左側(cè)圖像中的每個像素(i, j)表示為矢量P_{ij}^{L},我們同樣定義右視圖矢量。M為本文考慮的最大視差。因此cost volume可認(rèn)為是兩個向量 之間的1范數(shù)距離。即: C_{ijk}=||P_{ij}^{L}-P_{i\left( j-k \right)}^{R}||。Cost volume可能由于圖像中物體模糊、遮擋或模糊匹配而產(chǎn)生的錯誤。因此本文添加3D卷積層細(xì)化cost volume。提高其精確度。如果左圖\left( i,j \right)與右圖\left( i,j-k \right)相似??傻贸鏊麄兊囊暡頺,如果不相似則按照kendall等人的建議計算加權(quán)平均值。 \hat{D}_{ij}=\sum_{k=0}^M{k\times \frac{e^{-C_{ijk}}}{\sum_{k=0}^M{e^{-C_{ijk?}}}}}

    在這里插入圖片描述

  3. SpNet:為了進(jìn)一步改善我們的結(jié)果,我們增加了最后的第四個階段,其中我們使用一個空間傳播網(wǎng)絡(luò)(SPNet)來改進(jìn)我們的視差預(yù)測。SPNet通過應(yīng)用一個局部濾波器來銳化視差圖,局部濾波器的權(quán)值是通過對左側(cè)輸入圖像應(yīng)用一個小CNN來預(yù)測的。我們表明,這種改進(jìn)以相對較少的額外成本顯著改善了我們的結(jié)果。(你可以把它看作是一個銳化過程)

視差計算網(wǎng)絡(luò)公式太多點(我又懶得打了),以上內(nèi)容足夠了解網(wǎng)絡(luò)內(nèi)部參數(shù)細(xì)節(jié)了,如果需要了解具體運作方式還是得去看原文剩下的兩段

源碼Debug

文章源碼是在ubuntu中運行的,我這里用的windows+WSL。首先從博文頂端下載代碼后解壓。

作者在github中的教程步驟是:

  1. 處理Scene Flow數(shù)據(jù)集,準(zhǔn)備好數(shù)據(jù)后訓(xùn)練sh ./create_dataset.sh。需要將Scene Flow存入D:\sampler, 大致看了代碼,需要下載Scene Flow中的:FlyingThings3D、Driving、Monkaa。三個數(shù)據(jù)集大概50G吧(注意要下載帶視差圖的),而且下載及慢,迅雷+會員可以解決上述問題。。。
  2. spn網(wǎng)絡(luò)make:cd model/spn_1 sh make.sh,需要bash,我用的window子系統(tǒng)進(jìn)行配置,可以借鑒我的另一篇配置WSL,配置到gcc能用就行?;蛘呤褂胠inux,或者干脆舍棄第四層輸出
  3. 訓(xùn)練:python main.py --maxdisp 192 --with_spn,作者推薦最大視差192,打開spn。
  4. 微調(diào):python finetune.py --maxdisp 192 --with_spn --datapath 具體地址/training,作者使用kitti數(shù)據(jù)集,打開spn。
  5. 載入預(yù)訓(xùn)練模型:python finetune.py --maxdisp 192 --with_spn --datapath path-to-kitti2012/training/ --save_path results/kitti2012 --datatype 2012 --pretrained checkpoint /kitti2012_ck/checkpoint.tar \ --split_file checkpoint/kitti2012_ck/split.txt --evaluate
    可以仔細(xì)看一下上面的參數(shù),有一個--pretrained checkpoint /kitti2012_ck/checkpoint.tar,在這里我們可以載入作者的訓(xùn)練結(jié)果。下載地址作者已經(jīng)給出。

我建議直接跑作者的預(yù)訓(xùn)練模型

  1. 首先,下載以上數(shù)據(jù)集,建議kitti,因為比較小。
  2. 其次,下載作者的訓(xùn)練結(jié)果,使用經(jīng)過微調(diào)后的checkpoint.tar
  3. 直接通過finetune.py看結(jié)果:python finetune.py --maxdisp 192 --datapath F:/data_scene_flow/training/ --save_path results/kitti2015 --datatype 2015 --pretrained checkpoint/kitti2015_ck/checkpoint.tar --evaluate 我用的scene flow的示例數(shù)據(jù)集,關(guān)閉spn(如果沒編譯spn就無法使用)。最終代碼跑起來,得到每一個epoch的損失即準(zhǔn)確度。
  4. 作者給出的代碼是沒有可視化環(huán)節(jié)的!,因此我寫了一個輸出可視化py文件,它可以輸出你的樣本的視差圖:
    首先是可視化函數(shù)show.py,它存入utils文件夾中:
#作者:Rayne
#作用:打印Output信息
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
def save_data(tensor_batch):
    for i,tensor in enumerate(tensor_batch):
        slice=tensor.cpu().detach().numpy()
        img_save = np.clip(slice, 0,2**16)
        img_save = (img_save * 256.0).astype(np.uint16).squeeze()
        image=Image.fromarray(img_save)
        image.save('data/stage'+str(i)+'.png')

def imshow(tensor_batch):

    for i,tensor in enumerate(tensor_batch):
        img_cpu = tensor.cpu().detach().numpy()
        img_save = np.clip(img_cpu, 0, 2**16)
        img_save = (img_save ).astype(np.uint16)
        img_save=img_save.squeeze()
        plt.subplot(3, 3, i + 1)
        plt.imshow(img_save)
        plt.title('stage '+str(i), fontsize=12)
    plt.show()
    

這里有個坑,outputs輸出必須過濾小于零的數(shù),并且乘256.否則輸出的就是雪花圖。
其次是可視化代碼,把它放在主目錄下:

import argparse
import torch
import torch.nn.parallel
import torch.utils.data
import models.anynet
from utils.show import save_data,imshow

from PIL import Image
from dataloader import preprocess
import random


parser = argparse.ArgumentParser(description='AnyNet with Flyingthings3d')

parser.add_argument('--maxdisplist', type=int, nargs='+', default=[12, 3, 3])
parser.add_argument('--datapath', default='F:/data_scene_flow/training/',
                    help='datapath')
parser.add_argument('--epochs', type=int, default=10,
                    help='number of epochs to train')
parser.add_argument('--train_bsize', type=int, default=6,
                    help='batch size for training (default: 12)')
parser.add_argument('--test_bsize', type=int, default=4,
                    help='batch size for testing (default: 8)')
parser.add_argument('--save_path', type=str, default='results/pretrained_anynet',
                    help='the path of saving checkpoints and log')
parser.add_argument('--resume', type=str, default=None,
                    help='resume path')
parser.add_argument('--lr', type=float, default=5e-4,
                    help='learning rate')
parser.add_argument('--with_spn', action='store_true', help='with spn network or not')
parser.add_argument('--print_freq', type=int, default=5, help='print frequence')
parser.add_argument('--init_channels', type=int, default=1, help='initial channels for 2d feature extractor')
parser.add_argument('--nblocks', type=int, default=2, help='number of layers in each stage')
parser.add_argument('--channels_3d', type=int, default=4, help='number of initial channels of the 3d network')
parser.add_argument('--layers_3d', type=int, default=4, help='number of initial layers of the 3d network')
parser.add_argument('--growth_rate', type=int, nargs='+', default=[4,1,1], help='growth rate in the 3d network')
parser.add_argument('--spn_init_channels', type=int, default=8, help='initial channels for spnet')


args = parser.parse_args()
def main():
    global args
    model = models.anynet.AnyNet(args)

    # print(model)
    model = torch.nn.DataParallel(model).cuda()
    #載入預(yù)訓(xùn)練模型
    checkpoint = torch.load('results/finetune_anynet/checkpoint.tar')
    model.load_state_dict(checkpoint['state_dict'])
    imgL, imgR = data_load('data/left_1.png', 'data/right_1.png')
    imgL=torch.unsqueeze(imgL,0)
    imgR=torch.unsqueeze(imgR,0)
    imgL = imgL.float().cuda()
    imgR = imgR.float().cuda()
    outputs=model(imgL,imgR)
    save_data(outputs)
    imshow(outputs)


def data_load(left_img_dir,right_img_dir):
    left_img=Image.open(left_img_dir).convert('RGB')
    right_img=Image.open(right_img_dir).convert('RGB')

    w, h = left_img.size
    th, tw = 256, 512
    # 變?yōu)?56,512
    x1 = random.randint(0, w - tw)
    y1 = random.randint(0, h - th)
    left_img = left_img.crop((x1, y1, x1 + tw, y1 + th))
    right_img = right_img.crop((x1, y1, x1 + tw, y1 + th))

    # dataL = dataL.crop((w - 1232, h - 368, w, h))
    # dataL = np.ascontiguousarray(dataL, dtype=np.float32) / 256
    processed = preprocess.get_transform(augment=False)
    left_img = processed(left_img)
    right_img = processed(right_img)
    return left_img, right_img


main()
# imgL,imgR=data_load('data/left.png','data/right.png')

我的輸入時Scene flow中的一組數(shù)據(jù):


在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

將你的文件存入data中,修改好名字后運行即可得出以下結(jié)果:

在這里插入圖片描述

呃。。。。還行吧,并沒有作者給出的圖片那么準(zhǔn)確。。。。

補充

損失函數(shù)

論文沒提到損失函數(shù),我從源碼中得到內(nèi)容如下:
由于具有四層輸出,因此損失函數(shù)定義了一個損失權(quán)重:
\lambda =\left[ \frac{1}{4},\frac{1}{2},1,1 \right]
每一層的損失函數(shù)采用smooth L1作為該層輸出損失量:
Smooth_{L_1}\left( y,\hat{y} \right) =\begin{cases} 0.5\left( y-\widehat{y} \right) ^2\,\, |y-\widehat{y}|<1\\ |y-\widehat{y}|-0.5 |y-\widehat{y}|\geqslant 1\\\end{cases}

暫時想到的就是這些了,有時間會繼續(xù)補充后續(xù)內(nèi)容。如果有什么問題歡迎留言,歡迎大家提出自己寶貴的意見。希望可以幫到你~

應(yīng)用

作者給出的網(wǎng)絡(luò)速度確實還算可以,跑起來很快,得到視差圖可以根據(jù)攝像頭內(nèi)參輸出深度圖。下一步打算輸出模型至ONNX,用c++封裝后在板子上跑跑試試實時輸出效果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容