AnyNet是Yang Wang等人2016年提出的一種雙目深度計(jì)算網(wǎng)絡(luò)谍憔。最近做項(xiàng)目有用到該網(wǎng)絡(luò)匪蝙,其中碰到一些小坑為大家展示一下。第一部分是論文中重要地方的翻譯和講解习贫;第二部分是源碼修改及調(diào)試逛球。
原文地址:https://arxiv.org/abs/1810.11408
源碼地址:https://github.com/mileyan/AnyNet
翻譯
摘要
機(jī)器人立體深度估計(jì)的許多應(yīng)用都需要實(shí)時(shí)生成精確的視差圖,但計(jì)算條件非成徊苛刻颤绕。當(dāng)前最先進(jìn)的算法迫使用戶在緩慢生成精確映射和快速生成不精確映射之間做出選擇,此外祟身,這些方法通常需要太多參數(shù)奥务,無法在電源或內(nèi)存受限的設(shè)備上使用。針對這些缺點(diǎn)袜硫,我們提出了一種新的視差預(yù)測方法氯葬。與之前的工作相比,我們的端到端學(xué)習(xí)方法可以在推理時(shí)權(quán)衡計(jì)算量和準(zhǔn)確性婉陷。深度估計(jì)是分階段進(jìn)行的帚称,在此期間,可以在任何時(shí)間查詢模型輸出結(jié)果秽澳,得到其當(dāng)前的最佳估計(jì)闯睹。我們的最終模型可以在10-35幀的范圍內(nèi)處理1242×375分辨率的圖像,誤差只會(huì)有微小的增加——可訓(xùn)練參數(shù)比其他渣渣少了兩個(gè)數(shù)級(jí)担神。
介紹
前兩段掠過
在本文中瞻坝,我們提出了一種隨時(shí)計(jì)算的視差估計(jì)方法,并提出了一種在速度和精度之間動(dòng)態(tài)權(quán)衡的模型(見圖1)杏瞻。例如所刀,一架高速飛行的自主無人機(jī)可以在高頻率上詢問我們的3D深度估計(jì)方法。如果一個(gè)物體出現(xiàn)在它的飛行路徑上捞挥,它將能夠迅速地感知它浮创,并通過降低速度或執(zhí)行規(guī)避操作做出相應(yīng)的反應(yīng)。當(dāng)以低速飛行時(shí)砌函,延遲并不那么重要斩披,同樣的無人機(jī)可以計(jì)算出更高的分辨率和更精確的3D深度地圖,實(shí)現(xiàn)在擁擠場景中進(jìn)行高精度導(dǎo)航或詳細(xì)繪制環(huán)境地圖等任務(wù)讹俊。
AnyNet預(yù)測時(shí)間線的例子垦沉。隨著時(shí)間的推移,深度估計(jì)變得越來越準(zhǔn)確仍劈。該算法可在任何時(shí)候輪詢以返回當(dāng)前深度圖的最佳估計(jì)值厕倍。最初的估計(jì)可能足以觸發(fā)避障機(jī)動(dòng),而后來的圖像包含了足夠的細(xì)節(jié)贩疙,可以進(jìn)行更高級(jí)的路徑規(guī)劃程序
卷積網(wǎng)絡(luò)深度估計(jì)的計(jì)算復(fù)雜度通常與圖像分辨率成三次方讹弯,與被認(rèn)為是的最大視差成線性關(guān)系况既。記住這些特征,我們不斷細(xì)化深度圖组民,同時(shí)始終確保分辨率或最大視差范圍足夠低棒仍,以確保最小的計(jì)算時(shí)間。我們從低分辨率(1/16)估算全視差范圍的深度圖開始臭胜。立方復(fù)雜度允許我們在幾毫秒內(nèi)計(jì)算出這個(gè)初始深度圖(大部分時(shí)間花在初始特征提取和下采樣上)莫其。從這個(gè)低分辨率估計(jì)值開始,我們通過上行采樣逐步增加視差圖的分辨率耸三,然后修正現(xiàn)在在高分辨率下很明顯的誤差榜配。盡管使用了更高的分辨率,這些更新仍然很快吕晌,因?yàn)榭梢约僭O(shè)剩余的視差被限制在幾個(gè)像素內(nèi)蛋褥,允許我們限制最大的視差,和相關(guān)的計(jì)算睛驳,僅10% - 20%的全范圍烙心。這些連續(xù)的更新除了初始的低分辨率設(shè)置外,完全避免了全范圍的視差計(jì)算乏沸,并確保所有計(jì)算都是重復(fù)使用的淫茵,這使得我們的方法有別于大多數(shù)現(xiàn)有的多尺度網(wǎng)絡(luò)結(jié)構(gòu)。此外蹬跃,我們的算法可以在任何時(shí)間輪詢匙瘪,以檢索當(dāng)前最佳估計(jì)深度圖〉海可以實(shí)現(xiàn)廣泛的可能幀率范圍(在TX2模塊上是10-35FPS)丹喻,同時(shí)在高延遲設(shè)置中仍然保留精確的視差估計(jì)。我們的整個(gè)網(wǎng)絡(luò)可以在所有尺度上使用一個(gè)聯(lián)合損失進(jìn)行端到端訓(xùn)練翁都,我們稱之為AnyNet碍论。
我們在多個(gè)基準(zhǔn)數(shù)據(jù)集上評估了AnyNet,得到了各種令人鼓舞的結(jié)果:首先柄慰,AnyNet使用最先進(jìn)的方法獲得了具有競爭力的準(zhǔn)確性鳍悠,同時(shí)可訓(xùn)練參數(shù)比其他渣渣少了一個(gè)數(shù)量級(jí)。這對于資源受限的嵌入式設(shè)備尤其有影響坐搔。其次藏研,我們發(fā)現(xiàn)深度卷積網(wǎng)絡(luò)能夠從粗糙的視差圖預(yù)測殘差。最后概行,包含最終空間傳播模型(SPNet)的大大提高了視差圖的質(zhì)量蠢挡,在計(jì)算成本(和參數(shù)存儲(chǔ)需求)比現(xiàn)有方法低的情況下產(chǎn)生了最先進(jìn)的結(jié)果。
圖2顯示了AnyNet體系結(jié)構(gòu)的示意圖布局。一個(gè)輸入圖像對首先通過U-Net特征提取器袒哥,它計(jì)算幾個(gè)輸出分辨率的特征地圖(比例1/16,1/8,1/4)。在第一階段消略,只計(jì)算最低尺度的特征(1/16)堡称,并通過視差網(wǎng)絡(luò)(圖4)生成一個(gè)低分辨率的視差圖(stage 1)。視差圖估計(jì)右輸入圖像中每個(gè)像素的水平偏移量艺演,它可以計(jì)算一個(gè)深度圖却紧。由于輸入分辨率低,整個(gè)stage1的計(jì)算只需要幾毫秒胎撤。如果允許更多的計(jì)算時(shí)間晓殊,我們進(jìn)入stage2,在U-Net中繼續(xù)計(jì)算伤提,以獲得更大尺度(1/8)的特征巫俺。而不是計(jì)算一個(gè)完整的視差圖在這個(gè)更高的分辨率,在stage2肿男,我們簡單地修正已經(jīng)計(jì)算的視差圖從階段1介汹。首先,我們放大視差圖來匹配階段2的分辨率舶沛。然后我們計(jì)算一個(gè)殘差貼圖嘹承,其中包含小的修正,指定每個(gè)像素應(yīng)該增加或減少多少視差貼圖如庭。如果時(shí)間允許叹卷,stage3與第stage2的過程相似,將分辨率從1/8到1/4再翻倍坪它。stage4使用SPNet細(xì)化了階段3的視差圖骤竹。
在本節(jié)的其余部分中,我們將更詳細(xì)地描述模型的各個(gè)組件往毡。
-
U-Net特征提取網(wǎng)絡(luò): 圖3詳細(xì)說明了U-Net特征提取器瘤载,該特征提取器同時(shí)應(yīng)用于左右圖像。U-Net體系結(jié)構(gòu)以不同的分辨率(1/16卖擅、1/8鸣奔、1/4)計(jì)算特征圖,在第1-3階段stage作為輸入惩阶,只有在需要時(shí)才計(jì)算挎狸。對原始輸入圖像進(jìn)行最大pooling或strided convolution的下采樣,然后用卷積濾波器進(jìn)行處理断楷。低分辨率的feature map捕獲全局上下文锨匆,而高分辨率feature map捕獲局部細(xì)節(jié)。在尺度為1/8和1/4時(shí),最后的卷積層包含了之前計(jì)算的低尺度特征恐锣。
在這里插入圖片描述 -
視差計(jì)算網(wǎng)絡(luò):它的輸入為U-Net輸出的特征圖茅主。我們使用這個(gè)組件來計(jì)算初始視差圖(stage1)以及為后續(xù)校正計(jì)算剩余的視差圖(stage2和3)。視差網(wǎng)絡(luò)首先計(jì)算cost volume土榴。這里的cost volume是指左側(cè)圖像中的一個(gè)像素與右側(cè)圖像中對應(yīng)的一個(gè)像素之間的相似性诀姚。如果輸入特征圖尺寸為
,那么cost volume尺寸為
玷禽。其中(i, j, k)項(xiàng)描述左側(cè)圖像的像素(i, j)與右側(cè)圖像的像素(i, j?k)的匹配程度赫段。M為考慮的最大視差。我們可以將左側(cè)圖像中的每個(gè)像素(i, j)表示為矢量
矢赁,我們同樣定義右視圖矢量糯笙。M為本文考慮的最大視差。因此cost volume可認(rèn)為是兩個(gè)向量 之間的1范數(shù)距離撩银。即:
给涕。Cost volume可能由于圖像中物體模糊、遮擋或模糊匹配而產(chǎn)生的錯(cuò)誤额获。因此本文添加3D卷積層細(xì)化cost volume稠炬。提高其精確度。如果左圖
與右圖
相似咪啡∈灼簦可得出他們的視差k,如果不相似則按照kendall等人的建議計(jì)算加權(quán)平均值撤摸。
在這里插入圖片描述 SpNet:為了進(jìn)一步改善我們的結(jié)果毅桃,我們增加了最后的第四個(gè)階段,其中我們使用一個(gè)空間傳播網(wǎng)絡(luò)(SPNet)來改進(jìn)我們的視差預(yù)測准夷。SPNet通過應(yīng)用一個(gè)局部濾波器來銳化視差圖钥飞,局部濾波器的權(quán)值是通過對左側(cè)輸入圖像應(yīng)用一個(gè)小CNN來預(yù)測的。我們表明衫嵌,這種改進(jìn)以相對較少的額外成本顯著改善了我們的結(jié)果读宙。(你可以把它看作是一個(gè)銳化過程)
視差計(jì)算網(wǎng)絡(luò)公式太多點(diǎn)(我又懶得打了),以上內(nèi)容足夠了解網(wǎng)絡(luò)內(nèi)部參數(shù)細(xì)節(jié)了楔绞,如果需要了解具體運(yùn)作方式還是得去看原文剩下的兩段
源碼Debug
文章源碼是在ubuntu中運(yùn)行的结闸,我這里用的windows+WSL。首先從博文頂端下載代碼后解壓酒朵。
作者在github中的教程步驟是:
- 處理Scene Flow數(shù)據(jù)集桦锄,準(zhǔn)備好數(shù)據(jù)后訓(xùn)練
sh ./create_dataset.sh
。需要將Scene Flow存入D:\sampler, 大致看了代碼蔫耽,需要下載Scene Flow中的:FlyingThings3D结耀、Driving、Monkaa。三個(gè)數(shù)據(jù)集大概50G吧(注意要下載帶視差圖的)图甜,而且下載及慢碍粥,迅雷+會(huì)員可以解決上述問題。黑毅。嚼摩。 - spn網(wǎng)絡(luò)make:
cd model/spn_1 sh make.sh
,需要bash博肋,我用的window子系統(tǒng)進(jìn)行配置低斋,可以借鑒我的另一篇配置WSL蜂厅,配置到gcc能用就行匪凡。或者使用linux掘猿,或者干脆舍棄第四層輸出 - 訓(xùn)練:
python main.py --maxdisp 192 --with_spn
病游,作者推薦最大視差192,打開spn稠通。 - 微調(diào):
python finetune.py --maxdisp 192 --with_spn --datapath 具體地址/training
衬衬,作者使用kitti數(shù)據(jù)集,打開spn改橘。 - 載入預(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ù)滋尉,有一個(gè)--pretrained checkpoint /kitti2012_ck/checkpoint.tar
,在這里我們可以載入作者的訓(xùn)練結(jié)果飞主。下載地址作者已經(jīng)給出狮惜。
我建議直接跑作者的預(yù)訓(xùn)練模型:
- 首先,下載以上數(shù)據(jù)集碌识,建議kitti碾篡,因?yàn)楸容^小。
- 其次筏餐,下載作者的訓(xùn)練結(jié)果开泽,使用經(jīng)過微調(diào)后的checkpoint.tar
- 直接通過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就無法使用)魁瞪。最終代碼跑起來穆律,得到每一個(gè)epoch的損失即準(zhǔn)確度。 -
作者給出的代碼是沒有可視化環(huán)節(jié)的导俘!众旗,因此我寫了一個(gè)輸出可視化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()
這里有個(gè)坑贡歧,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')
我的輸入時(shí)Scene flow中的一組數(shù)據(jù):
將你的文件存入data中,修改好名字后運(yùn)行即可得出以下結(jié)果:
呃绍弟。技即。。樟遣。還行吧而叼,并沒有作者給出的圖片那么準(zhǔn)確。豹悬。葵陵。。
補(bǔ)充
損失函數(shù)
論文沒提到損失函數(shù)瞻佛,我從源碼中得到內(nèi)容如下:
由于具有四層輸出脱篙,因此損失函數(shù)定義了一個(gè)損失權(quán)重:
每一層的損失函數(shù)采用smooth L1作為該層輸出損失量:
暫時(shí)想到的就是這些了,有時(shí)間會(huì)繼續(xù)補(bǔ)充后續(xù)內(nèi)容伤柄。如果有什么問題歡迎留言绊困,歡迎大家提出自己寶貴的意見。希望可以幫到你~
應(yīng)用
作者給出的網(wǎng)絡(luò)速度確實(shí)還算可以适刀,跑起來很快秤朗,得到視差圖可以根據(jù)攝像頭內(nèi)參輸出深度圖。下一步打算輸出模型至ONNX笔喉,用c++封裝后在板子上跑跑試試實(shí)時(shí)輸出效果取视。