作 者: 心有寶寶人自圓
聲 明: 歡迎轉(zhuǎn)載本文中的圖片或文字产捞,請(qǐng)說明出處
寫在前面
自從FCN提出以來(lái),越來(lái)越多的語(yǔ)義分割任務(wù)開始采用采用全卷積網(wǎng)絡(luò)結(jié)構(gòu)利耍,隨著FCN結(jié)構(gòu)使用的增加笔刹,研究人員發(fā)先了其結(jié)構(gòu)天生的缺陷極大的限制了分割的準(zhǔn)確度:CNNs在high-level (large scale) tasks中取得了十分優(yōu)異的成績(jī)囱持,這得益于局部空間不變性(主要是池化層增大了感受野,也丟棄了部分細(xì)節(jié)信息)使得網(wǎng)絡(luò)能夠?qū)W習(xí)到層次化的抽象信息闲擦,但這卻恰恰不利于low-level (small scale) tasks
所以Deeplab的作者引入了如下結(jié)構(gòu)來(lái)對(duì)抗這種細(xì)節(jié)丟失的問題:
- 帶洞的卷積慢味,atrous算法
- Fully connected CRF的后處理過程
1. Introduction
CNNs在high-level vision tasks(如圖像分類、目標(biāo)檢測(cè)等)取得優(yōu)異得表現(xiàn)墅冷,這些工作都有共同的主題:end-to-end訓(xùn)練的方法比人工的特征工程方法更優(yōu)纯路。這得益于CNNs內(nèi)在的局部空間不變性,然而對(duì)應(yīng)low-level vision tasks(語(yǔ)義分割)來(lái)說寞忿,我們需要準(zhǔn)確的位置信息而非空間信息抽象后的層次化信息驰唬。
CNNs應(yīng)用于low-level vision tasks主要技術(shù)障礙是:
信號(hào)的下采樣
-
空間的局部不變性
下采樣問題是池化層和striding的聯(lián)合影響產(chǎn)生,其目的是為了使較小的卷積核能夠去學(xué)習(xí)空間中有用的信息(因此需要增大感受野)腔彰,但這種下采樣必然造成信息的損失叫编。為了在不造成信息損失的情況下增大感受野,作者使用了帶洞的卷積(下均稱atrous方法)
該圖片來(lái)自:vdumoulin/conv_arithmetic
局部空間不變性是classifier獲得以對(duì)象為中心的決策的要求霹抛,主要還是由于池化層得作用只保留了局部空間中最重要的信息宵溅,作者使用Fully connected CRF(后稱DenseCRF)進(jìn)行全卷積網(wǎng)絡(luò)訓(xùn)練完成后的后處理,DenseCRF能夠在滿足長(zhǎng)程依賴性的同時(shí)捕獲細(xì)節(jié)邊緣信息
2. Related Work
遠(yuǎn)古時(shí)期上炎,分割主要基于信號(hào)系統(tǒng)恃逻,而后是概率模型占據(jù)主流
CNNs發(fā)展起來(lái)之后是two-stage的策略(提議區(qū)域+區(qū)域預(yù)測(cè))占據(jù)了主流,但提議區(qū)域會(huì)使得系統(tǒng)面臨前端分割系統(tǒng)的潛在錯(cuò)誤(提議區(qū)域的質(zhì)量直接能影響預(yù)測(cè)結(jié)果)
隨FCN提出后one-stage策略(直接基于像素預(yù)測(cè))占據(jù)主流
3.CNN NETWORKS FOR DENSE IMAGE LABELING
Deeplabv1就基本上是按照FCN的結(jié)構(gòu)來(lái)設(shè)計(jì)的藕施,只是部分結(jié)構(gòu)進(jìn)行了修改寇损。由于網(wǎng)絡(luò)使用了atrous算法,可以使作為encoder的CNN提取出比FCN更密集的final layer特征:FCN的encoder的final layer下采樣了32倍裳食,而Deeplabv1僅下采樣了8倍
本文和FCN一樣使用了預(yù)訓(xùn)練的VGG-16網(wǎng)絡(luò)
3.1 AGROUS算法
語(yǔ)義分割是一種dense prediction任務(wù)矛市,所以能夠使用CNN提取出更密集的feature是提升準(zhǔn)確率的關(guān)鍵,而基于密集feature評(píng)分成為Deeplabv1成功的關(guān)鍵诲祸。
為了獲得更密集的feature浊吏,作者跳過了最后兩個(gè)maxpooling層(maxpool4,5)的下采樣而昨,并在最后三個(gè)卷積層(conv5_1 - 3)和第一個(gè)全連接層(fc6)使用atrous算法
帶洞的原意是給卷積核中間插入0,而這樣的操作等同于給卷積層一個(gè)input_stride(普通的卷積默認(rèn)input_stride = 1)找田,這樣就可以使卷積計(jì)算后特征圖中同樣的像素具有更大的感受野(還需要調(diào)整padding = input_size *( kernel_size ) // 2才能保證特征圖保持輸入大小一致)歌憨。這樣不通過下采樣獲得的感受野增大不會(huì)缺失原有的信息,不像池化層那樣引入了近似
最后通過評(píng)分層輸出的class score maps 只需使用雙線性插值上采樣8倍即可
損失函數(shù)直接使用基于每個(gè)像素的Cross Entropy損失并相加墩衙,每個(gè)像素和每個(gè)類別的權(quán)重相同(大部分的類別為負(fù)類务嫡,即背景類,不會(huì)對(duì)最終分割效果產(chǎn)生影響)
3.2 控制感受野和加速計(jì)算
作者在這部分介紹的關(guān)于顯式控制感受野和加速密集計(jì)算的的方法主要涉及全連接層的轉(zhuǎn)換漆改。在我之前介紹FCN的文章中心铃,對(duì)于全連接層和卷積層之間的相互轉(zhuǎn)換有過講解。由于預(yù)訓(xùn)練模型都是針對(duì)大尺度目標(biāo)的分類任務(wù)的:例如VGG-16的fc6就是有4096個(gè)大小為7x7的核挫剑,而這么大的核通常又會(huì)帶來(lái)計(jì)算問題去扣。
作者使用了對(duì)卷積核進(jìn)行空間采樣的方法:simple decimation(在我之前的文章介紹SSD的2.2.2節(jié)有講解),這樣就可以顯式的降低感受野樊破,并且顯著的加快計(jì)算速度厅篓,節(jié)省存儲(chǔ)空間
4.恢復(fù)細(xì)節(jié)的邊界:全連接隨機(jī)條件場(chǎng)和多尺度預(yù)測(cè)
4.1 定位的挑戰(zhàn)
如圖2所示,CNN計(jì)算出的得分圖可以可靠地預(yù)測(cè)圖像中物體和大致位置捶码,但不能精確地指出它們的輪廓羽氮。CNN在分類準(zhǔn)確和定位準(zhǔn)確之間存在天然的trade-off:池化層提高了high-level tasks的效果,卻帶來(lái)了信息損失惫恼、大尺度的感受野和局部不變性档押,阻礙了low-level tasks的效果,因此為從得分圖推斷出原始空間的結(jié)果增加了難度祈纯。
在Deeplabv1提出前令宿,有兩種主流方法去解決定位的挑戰(zhàn):
利用來(lái)自CNN多個(gè)層的信息(比如FCN的跳躍結(jié)構(gòu))
采用超像素表示,本質(zhì)上是將定位任務(wù)委托給低級(jí)的分割方法
Deeplabv1提出了使用DenseCRF進(jìn)行后處理來(lái)解決定位的挑戰(zhàn)
4.2 使用DenseCRF進(jìn)行準(zhǔn)確定位
傳統(tǒng)上腕窥,CRFs模型是用來(lái)平滑分割圖上的噪聲粒没,尤其是這些模型包含連接鄰近節(jié)點(diǎn)的能量項(xiàng)(二元項(xiàng)),傾向于對(duì)空間近端像素進(jìn)行相同標(biāo)簽賦值簇爆。從定性的說癞松,這些短程CRFs(short-range CRFs)的主要功能是清除基于手工設(shè)計(jì)的局部特征構(gòu)建的弱分類器的錯(cuò)誤預(yù)測(cè)。
與弱分類器相比入蛆,Deeplab的CNN網(wǎng)絡(luò)是很強(qiáng)的分類器响蓉,圖2的結(jié)果也表明預(yù)測(cè)結(jié)果很平滑、有很強(qiáng)的同質(zhì)性哨毁。在這個(gè)背景下使用短程CRFs是有害的:因?yàn)槎坛藽RFs作用是平滑枫甲,而不是我們所期望的細(xì)化邊緣
為了克服短程CRFs的限制,作者使用了全連接CRF
DenseCRF模型采用能量函數(shù)
?是分配給像素的標(biāo)簽
其中一元項(xiàng)勢(shì)能?表示給像素i處為該標(biāo)簽的概率,該項(xiàng)基于CNN預(yù)測(cè)分?jǐn)?shù)圖
-
為二元項(xiàng)?想幻,該項(xiàng)基于圖像本身粱栖,即考慮像素間的聯(lián)系
其中?,由于是全連接脏毯,每?jī)蓚€(gè)像素之間都要連接
?是由?決定的高斯核函數(shù)闹究,為:
?是像素的位置,是像素的顏色抄沮,均是高斯核的超參數(shù)
最后談一談關(guān)于DenseCRF的優(yōu)化目標(biāo)跋核,即為調(diào)整輸入(每個(gè)像素的label)使能量函數(shù)最小化岖瑰。但由于是全連接叛买,直接計(jì)算的話計(jì)算量簡(jiǎn)直爆炸,所以采用平均場(chǎng)近似的方法進(jìn)行計(jì)算
對(duì)于DenseCRF具體內(nèi)容感興趣的可以看下有關(guān)論文蹋订,這里僅做感性的認(rèn)知率挣,并且僅使用有關(guān)pydensecrf庫(kù)。在后來(lái)的語(yǔ)義分割論文中很少看到DenseCRF的影子了露戒,因?yàn)閷?duì)于更強(qiáng)的網(wǎng)絡(luò)模型椒功,DenseCRF的提升效果不明顯,或甚至起到反作用智什。為什么不用DenseCRF了?
更多關(guān)于Dense的細(xì)節(jié)內(nèi)容和代碼中使用pydensecrf庫(kù)可參考:
Fully connected CRF(Dense CRF)的python庫(kù):我在Linux使用pip install git+https://github.com/lucasb-eyer/pydensecrf.git進(jìn)行安裝完全沒有問題动漾,但windows不管怎么弄都安不上??
CRF的通俗入門:從理論上易懂的介紹了概率圖模型CRF
Densecrf與圖像分割:內(nèi)附pydensecrf package的實(shí)例,該博客通過對(duì)比實(shí)驗(yàn)認(rèn)為當(dāng)模型夠強(qiáng)的情況下荠锭,添加crf優(yōu)化沒有太大必要旱眯,甚至可能起到反效果
【圖像后處理】python實(shí)現(xiàn)全連接CRFs后處理:原理+實(shí)例,這個(gè)代碼是目前來(lái)看最為清楚的
Github 項(xiàng)目 - Dense CRFs 之 pydensecrf 實(shí)現(xiàn):一些項(xiàng)目中使用Dense CRF的實(shí)例
4.2 MULTI-SCALE PREDICTION
作者也像FCN的跳躍結(jié)構(gòu)那樣結(jié)合了多尺度的得分圖证九,發(fā)現(xiàn)對(duì)于分割效果并沒很多提升删豺,更不如DenseCRF對(duì)于分割效果的提升明顯,并且?guī)?lái)了額外的計(jì)算消耗愧怜,因此只使用了最后一層得分圖
5. 實(shí)驗(yàn)和評(píng)價(jià)
Dataset:PASCAL VOC 2012 aug 數(shù)據(jù)集
-
Training:把CNN模型和CRF模型分開訓(xùn)練
先f(wàn)ine-tuneCNN模型呀页,mini-batch = 20,learning rate = 0.001 (最后的分類層為0.01)拥坛,每迭代2000次lr變?yōu)樵瓉?lái)的0.1蓬蝶,momentum = 0.9 , weight decay = 0.0005
CNN模型訓(xùn)練fine-tune完成后猜惋,開始為DenseCRF調(diào)參疾党,使用交叉驗(yàn)證的方法調(diào)參(val集里的100張圖片):預(yù)設(shè)?,尋找優(yōu)超參數(shù)?惨奕,平均場(chǎng)迭代次數(shù)固定為10
-
不同的網(wǎng)絡(luò)設(shè)計(jì)
DeepLab:只使用了CNN
-CRF:使用了DenseCRF進(jìn)行后處理
-MSc:結(jié)合了多尺度的得分圖
-7x7:fc6使用的卷積核大小為7x7雪位,默認(rèn)為4x4
-LargeFOV:arbitrarily control the Field-of- View (FOV) of the models
最后選擇的最優(yōu)組合:DeepLab-CRF-LargeFOV,同時(shí)把最后兩個(gè)classifier層的通道數(shù)從4096變?yōu)?024
我實(shí)現(xiàn)的代碼也是按照這個(gè)來(lái)進(jìn)行的
6. My code
這里主要列出網(wǎng)絡(luò)結(jié)構(gòu)和DenseCRF部分梨撞,其余部分(如Dataset雹洗,數(shù)據(jù)增廣處理香罐,訓(xùn)練、驗(yàn)證)都比較通用可用自己慣用的方法时肿,也可參考我寫的FCN主要代碼和SSD主要代碼
我使用了PASCAL VOC 2012數(shù)據(jù)集庇茫,而沒有使用aug版,所以效果比使用aug版的差螃成,mIOU才達(dá)到51.92%
- 網(wǎng)絡(luò)結(jié)構(gòu):主要由兩部分組成旦签,一部分是VGG的Base結(jié)構(gòu),第二部分是更改VGG的全連接層為L(zhǎng)argeFOV全卷積層
import torch
from torch import nn
from torchvision import models
class VggBase(nn.Module):
def __init__(self):
super(VggBase, self).__init__()
self.conv1_1 = nn.Conv2d(3, 64, kernel_size=3, padding=1)
self.conv1_2 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.pool1 = nn.MaxPool2d(2, 2)
self.conv2_1 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.conv2_2 = nn.Conv2d(128, 128, kernel_size=3, padding=1)
self.pool2 = nn.MaxPool2d(2, 2)
self.conv3_1 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
self.conv3_2 = nn.Conv2d(256, 256, kernel_size=3, padding=1)
self.conv3_3 = nn.Conv2d(256, 256, kernel_size=3, padding=1)
self.pool3 = nn.MaxPool2d(2, 2)
self.conv4_1 = nn.Conv2d(256, 512, kernel_size=3, padding=1)
self.conv4_2 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
self.conv4_3 = nn.Conv2d(512, 512, kernel_size=3, padding=1)
self.relu4 = nn.ReLU(inplace=True)
# 如文中所述:skip subsampling after the last two max-pooling layers in the network
# 2×inthe last three convolutional layers and 4×in the first fully connected layer
self.pool4 = nn.MaxPool2d(3, 1, 1)
self.conv5_1 = nn.Conv2d(512, 512, kernel_size=3, padding=2, dilation=2)
self.conv5_2 = nn.Conv2d(512, 512, kernel_size=3, padding=2, dilation=2)
self.conv5_3 = nn.Conv2d(512, 512, kernel_size=3, padding=2, dilation=2)
self.pool5 = nn.MaxPool2d(3, 1, 1)
self.load_pretrained_layers()
def load_pretrained_layers(self):
pretrained_net = models.vgg16(pretrained=True)
state_dict = self.state_dict()
for c, pc in zip(state_dict.keys(), pretrained_net.features.state_dict().values()):
assert state_dict[c].size() == pc.size()
state_dict[c] = pc
self.load_state_dict(state_dict)
def forward(self, x):
out = torch.relu(self.conv1_1(x))
out = torch.relu(self.conv1_2(out))
out = self.pool1(out)
out = torch.relu(self.conv2_1(out))
out = torch.relu(self.conv2_2(out))
out = self.pool2(out)
out = torch.relu(self.conv3_1(out))
out = torch.relu(self.conv3_2(out))
out = torch.relu(self.conv3_3(out))
out = self.pool3(out)
out = torch.relu(self.conv4_1(out))
out = torch.relu(self.conv4_2(out))
out = torch.relu(self.conv4_3(out))
out = self.pool4(out)
out = torch.relu(self.conv5_1(out))
out = torch.relu(self.conv5_2(out))
out = torch.relu(self.conv5_3(out))
out = self.pool5(out)
return out
class AtrousConv(nn.Module):
def __init__(self, n_classes, out_channels=1024, decimation=3, rate=12):
# 按文中所述:employ kernel size 3×3 and input stride = 12,
# and further change the filter sizes from 4096 to 1024 for the last two layers
super(AtrousConv, self).__init__()
self.decimation = [4096 // out_channels, None, decimation, decimation]
self.atrous = nn.Conv2d(512, out_channels, kernel_size=decimation, padding=rate * (decimation - 1) // 2,
dilation=rate)
self.relu1 = nn.ReLU(inplace=True)
self.fc1 = nn.Conv2d(out_channels, out_channels, kernel_size=1)
self.relu2 = nn.ReLU(inplace=True)
self.fc2 = nn.Conv2d(out_channels, n_classes, kernel_size=1)
self.init_param()
def decimate(self, param: torch.Tensor, decimation):
assert param.dim() == len(decimation)
for d in range(param.dim()):
if decimation[d] is not None:
param = param.index_select(dim=d,
index=torch.arange(start=0, end=param.size(d),
step=decimation[d]).long())
return param
def init_param(self):
pretrained_dict = models.vgg16(pretrained=True).state_dict()
self.atrous.weight.data = self.decimate(pretrained_dict['classifier.0.weight'].view(4096, 512, 7, 7),
self.decimation)
self.atrous.bias.data = self.decimate(pretrained_dict['classifier.0.bias'].view(4096), self.decimation[:1])
self.fc1.weight.data = self.decimate(pretrained_dict['classifier.3.weight'].view(4096, 4096, 1, 1),
[self.decimation[0], self.decimation[0], None, None])
self.fc1.bias.data = self.decimate(pretrained_dict['classifier.3.bias'].view(4096), self.decimation[:1])
nn.init.xavier_normal_(self.fc2.weight)
nn.init.constant_(self.fc2.bias, 0)
def forward(self, x):
for blk in self.children():
x = blk(x)
return x
class DeepLabv1(nn.Module):
def __init__(self, n_classes=21):
super(DeepLabv1, self).__init__()
self.base = VggBase()
self.atrous = AtrousConv(n_classes)
def forward(self, x):
x = self.base(x)
x = self.atrous(x)
# https://zhuanlan.zhihu.com/p/87572724?from_voters_page=true 介紹關(guān)于align_corners的內(nèi)容
# 為了和下采樣(transform寸宏,PIL)時(shí)圖像保持一致宁炫,使用align_corners=Fasle
# 上面的博文認(rèn)為align_corners=True對(duì)語(yǔ)義分割任務(wù)可能更好,有機(jī)會(huì)可以試一下
# also see:https://discuss.pytorch.org/t/what-we-should-use-align-corners-false/22663/9
x = nn.functional.interpolate(x, scale_factor=8, mode='bilinear', align_corners=False)
return x
- 損失函數(shù):等權(quán)重的Cross Entropy Loss
class NLLLoss2d(nn.Module):
def __init__(self):
super(NLLLoss2d, self).__init__()
self.loss = nn.NLLLoss()
def forward(self, pred, true):
pred = nn.functional.log_softmax(pred, dim=1)
return self.loss(pred, true)
- DenseCRF:我是在Linux完成安裝pydensecrf的氮凝,windows實(shí)在裝不上...
import numpy as np
import pydensecrf.densecrf as dcrf
import pydensecrf.utils as utils
class DenseCRF(object):
def __init__(self, max_epochs=5, delta_aphla=80, delta_beta=3, w1=10, delta_gamma=3, w2=3):
self.max_epochs = max_epochs
self.delta_gamma = delta_gamma
self.delta_alpha = delta_aphla
self.delta_beta = delta_beta
self.w1 = w1
self.w2 = w2
def __call__(self, image, probmap):
c, h, w = probmap.shape
U = utils.unary_from_softmax(probmap)
U = np.ascontiguousarray(U)
image = np.ascontiguousarray(image)
d = dcrf.DenseCRF2D(w, h, c)
d.setUnaryEnergy(U)
d.addPairwiseGaussian(sxy=self.delta_gamma, compat=self.w2)
d.addPairwiseBilateral(sxy=self.delta_alpha, srgb=self.delta_beta, rgbim=image, compat=self.w1)
Q = d.inference(self.max_epochs)
Q = np.array(Q).reshape((c, h, w))
return Q
- 看一下DenseCRF的使用:
import models # 寫Class DenseCRF的文件
import numpy as np
def crf(self, img, prob):
"""
:param img: a PIL image
:param prob: 網(wǎng)絡(luò)輸出score map, in shape of (21 , height, width)
:return: new prob map
"""
crf = models.DenseCRF()
prob = torch.softmax(prob, dim=1)[0].numpy()
res = crf(np.array(image, dtype=np.uint8), prob)
return res.argmax(axis=0)
注意:在進(jìn)行數(shù)據(jù)增廣時(shí)(resize)羔巢,插值的方法一定要選擇NEAREAST而不是默認(rèn)的Bilinear,否則會(huì)對(duì)true label image的pixel進(jìn)行誤標(biāo)罩阵,導(dǎo)致問題的出現(xiàn)
一些衡量的Metrics見:wkentaro/pytorch-fcn竿秆,它的算法方法非常巧妙
-
分割結(jié)果
看來(lái)自行車的準(zhǔn)確率不高啊??
Reference
[1] Chen, L. C. , Papandreou, G. , Kokkinos, I. , Murphy, K. , & Yuille, A. L. . (2014). Semantic image segmentation with deep convolutional nets and fully connected crfs. Computer Science.
[2] kazuto1011/ deeplab-pytorch
[4] 【圖像后處理】python實(shí)現(xiàn)全連接CRFs后處理
轉(zhuǎn)載請(qǐng)說明出處。