1. 程式人生 > >FCN語義分割訓練資料(以siftflow和voc2012資料集為例)

FCN語義分割訓練資料(以siftflow和voc2012資料集為例)

截至目前,現已經跑通了siftflow-fcn32s,voc-fcn32s,並製作好了自己的資料集,現在就等大批資料的到來,進而針對資料進行引數fine-tuning,現對我訓練的訓練流程和訓練過程中遇到的問題,做出總結和記錄,從而對以後的學習作鋪墊。

通過這篇分析語義分割的文章可以知道,FCN作為2014年的網路,現在看來確實有些老舊了,從下表可以看出FCN的表現得分也不是最高的,但是不管黑貓白貓,能捉住老鼠的就是好貓,能應用到專案中的,解決問題就是ok的。

chapter 1 基礎環境安裝和搭建

首先,必要的環境是ubuntu作業系統,FCN使用的框架是caffe,所以還要完成caffe的搭建。在搭建cffe環境的時候,建議直接使用乾淨的系統進行配置,因為常用的深度學習框架除了caffe還有就是tensorflow,而兩者之間是不相容的,當然可以藉助於Anaconda環境進行共存,但是由於FCN是對caffe的直接呼叫且應用的是python2,所以還是建議前期不要藉助於anaconda進行安裝,從裝顯示卡驅動,裝CUDA8.0,裝cudnn6.0,再到opencv3.1,最後編譯caffe,安裝caffe,一氣呵成!

後期親測,雖然都說安裝了caffe之後再安裝tensorflow會出現軟連結錯誤的問題,如果不使用anaconda的話,只能捨去其中一個來成全另一個,但是通過anaconda,藉助於conda進行安裝,在虛擬的環境下,應用conda install重新安裝新的cuda和cudnn(儘量不要優先選擇pip,因為pip安裝不會重灌cuda和cudnn,還是會出現軟連結錯誤),這樣就不會出現兩個框架公用一個cuda和cudnn的錯誤,則不會出現軟連結錯誤。

chapter 2 應用訓練好的module實現語義分割

相關文獻的連結如下:

FCN在多個數據集上都進行了訓練,比如:Pascal VOC模型,nyudv2模型,siftflow模型,Pascal-Context模型。

2.下載原始碼之後解壓,首先就是要先應用用作者已經訓練好的模型,對滿足要求的資料進行預測,看一下效果。通過閱讀原始碼和文章可以知道,訓練FCN的過程是:要先根據Vgg16的model,訓練FCN32s,再應用得到的FCN32s的model,訓練FCN16s,再應用得到的FCN16s的model,訓練FCN8s,得到最後的model,應用該model對網路正向傳播,可以得到較好的表現結果。

下載voc-fcn32s,voc-fcn16s和voc-fcn8s的caffemodel,並應用於網路正向傳播進行預測,作者已經訓練好的model可以在每個資料集中進行下載,下載方式為進入對應目錄下,找到一個名為caffemodel-url的檔案,該檔案儲存的就是對應model的下載連結,通過連結即可以下載到model,並將model放在對應的目錄下,由於fcn8s是最終表現效果最好的model,所以應用的也是該model。

接下來就是修改fcn.berkeleyvision.org目錄下的infer.py檔案了,以下是我的檔案,僅供參考。

import sys
sys.path.append('/home/pzn/caffe/python')
import numpy as np
from PIL import Image

import caffe
import vis

# the demo image is "2007_000129" from PASCAL VOC

# load image, switch to BGR, subtract mean, and make dims C x H x W for Caffe
im = Image.open('demo/image.jpg')
in_ = np.array(im, dtype=np.float32)
in_ = in_[:,:,::-1]
in_ -= np.array((104.00698793,116.66876762,122.67891434))
in_ = in_.transpose((2,0,1))

# load net
net = caffe.Net('voc-fcn8s/deploy.prototxt', 'voc-fcn8s/fcn8s-heavy-pascal.caffemodel', caffe.TEST)
# shape for input (data blob is N x C x H x W), set data
net.blobs['data'].reshape(1, *in_.shape)
net.blobs['data'].data[...] = in_
# run net and take argmax for prediction
net.forward()
out = net.blobs['score'].data[0].argmax(axis=0)

# visualize segmentation in PASCAL VOC colors
voc_palette = vis.make_palette(21)
out_im = Image.fromarray(vis.color_seg(out, voc_palette))
out_im.save('demo/output.png')
masked_im = Image.fromarray(vis.vis_seg(im, out, voc_palette))
masked_im.save('demo/visualization.jpg')

其中,加入

import sys
sys.path.append('/home/pzn/caffe/python')

的目的是為了可以應用sudo的方式呼叫已經配置好的caffe框架。

最終,設定好自己的圖片路徑、deploy.prototxt和model路徑後,在terminal中鍵入:

sudo python infer.py

即可以進行網路正向傳播,得到分割後的結果。

原圖:

分割後圖像:

視覺化後的影象:

完成!

chapter 3 應用FCN在siftflow和voc2012資料集上訓練權重

由於資料集不需要自己準備,可以方便的用別人的資料集先把網路給跑通,並熟悉網路的結構、引數和用法。

Part 1 在siftflow資料集上進行訓練同時解決loss不收斂問題

由於訓練最開始時需要VGG-16的預訓練模型,可以去該地址的如下圖所示的地方

下載所需要的訓練模型,並將其重新命名為vgg16-fcn.caffemodel,並將其放在fcn.berkeleyvision.org/ilsvrc-nets/的目錄下,以供訓練時使用。

1.siftflow資料集,下載地址,共包含33類,類別入下所示:

Semantic and geometric segmentation classes for scenes.

Semantic: 0 is void and 1–33 are classes.

01 awning   雨篷
02 balcony   陽臺
03 bird
04 boat
05 bridge
06 building
07 bus
08 car
09 cow    母牛;奶牛
10 crosswalk    人行橫道
11 desert    沙漠
12 door
13 fence
14 field    牧場
15 grass    草坪
16 moon
17 mountain
18 person
19 plant
20 pole   杆
21 river
22 road
23 rock
24 sand
25 sea
26 sidewalk   人行道
27 sign    指示牌
28 sky
29 staircase   樓梯
30 streetlight   
31 sun
32 tree
33 window

Geometric: -1 is void and 1–3 are classes.

01 sky
02 horizontal
03 vertical

N.B. Three classes (cow, desert, and moon) are absent from the test set, so
they are excluded from evaluation. The highway_bost181 and street_urb506 images
are missing annotations so these are likewise excluded from evaluation.

下載好資料集之後,可將其解壓至fcn.berkeleyvision.org/data/目錄下,並將資料夾重新命名為sift-flow。特別注意的是,由於原目錄下已經包含一個命名為sift-flow的資料夾,但是原資料夾中的內容仍需要被copy進新的資料夾下來使用,所以千萬不要提前將其刪除掉,提前重新命名以下即可。

2. 訓練檔案準備

關於常用檔案一些解釋:

caffemodel-url中是訓練之前預載入的權值模型的下載地址,開啟這個檔案,並下載這個模型;

deploy.protptxt是訓練好模型之後,進行圖片預測的網路模型;

net.py是生成網路模型的檔案,暫時用不到;

solve.py和solve.prototxt是網路訓練之前一些資料路徑和引數的設定;

train.prototxt和val.prototxt不用說了,一個是訓練模型,一個是訓練過程中測試的模型。

step1:修改solver.prototxt。其中,snapshot: 10000,表示每訓練10000次儲存一次model檔案;snapshot_prefix:"/home/pzn/pang1/fcn.berkeleyvision.org/siftflow-fcn32s/train/" 表示把model儲存到相應的資料夾下,如果沒有,自行新建資料夾即可。我的solver.prototxt檔案如下:

train_net: "trainval.prototxt"
test_net: "test.prototxt"
test_iter: 200
# make test net, but don't invoke it from the solver itself
test_interval: 999999999
display: 20
average_loss: 20
lr_policy: "fixed"
# lr for unnormalized softmax
base_lr: 1e-10
# high momentum
momentum: 0.99
# no gradient accumulation
iter_size: 1
max_iter: 30000
weight_decay: 0.0005
snapshot:10000
snapshot_prefix:"/home/pzn/pang1/fcn.berkeleyvision.org/siftflow-fcn32s/train/"
test_initialization: false

step2:修改slove.py。特別地,在訓練fcn32s時,為了避免loss不下降,要通過transplant的方式來獲取vgg16的網路權重;後期訓練fcn16s和fcn8s的時候,直接可以拿上一級的model來即可,不需要transplant。我的slove.py檔案,僅供參考:

import sys
sys.path.append('/home/pzn/caffe/python')
import caffe  
import surgery, score  
  
import numpy as np  
import os  
import sys  
  
try:  
    import setproctitle  
    setproctitle.setproctitle(os.path.basename(os.getcwd()))  
except:  
    pass  
  
vgg_weights = '../ilsvrc-nets/vgg16-fcn.caffemodel'  
vgg_proto = '../ilsvrc-nets/VGG_ILSVRC_16_layers_deploy.prototxt'  
weights = '../ilsvrc-nets/vgg16-fcn.caffemodel'  
  
# init  
caffe.set_mode_gpu()  
#caffe.set_device(int(sys.argv[0]))  
caffe.set_device(0)  
  
#solver = caffe.SGDSolver('solver.prototxt')  
#solver.net.copy_from(weights)  
solver = caffe.SGDSolver('solver.prototxt')  
vgg_net=caffe.Net(vgg_proto,vgg_weights,caffe.TRAIN)  
surgery.transplant(solver.net,vgg_net)  
del vgg_net  
  
# surgeries  
interp_layers = [k for k in solver.net.params.keys() if 'up' in k]  
surgery.interp(solver.net, interp_layers)  
  
# scoring  
test = np.loadtxt('../data/sift-flow/test.txt', dtype=str)  
  
for _ in range(5):  
    solver.step(2000)  
    # N.B. metrics on the semantic labels are off b.c. of missing classes;  
    # score manually from the histogram instead for proper evaluation  
    score.seg_tests(solver, False, test, layer='score_sem', gt='sem')  
    score.seg_tests(solver, False, test, layer='score_geo', gt='geo')  

下載

VGG_ILSVRC_16_layers_deploy.prototxt

連結: https://pan.baidu.com/s/1QA691IgCiBC-LTU449jb8g 密碼: irxa

step3:開始訓練

cd siftflow-fcn32s/
sudo python slove.py

如果報錯No module named surgery,score,siftflow_layers等,則需要把fcn根目錄下的相應的.py檔案copy到siftflow-fcn32s目錄下即可。

我的訓練結果如下:

step4:利用訓練好的model進行網路正向傳播測試

關於infer.py,強烈建議每個資料集對應一個infer.py,儘量不要共用fcn根目錄下的infer.py的檔案,就像儘量不要共用slove.py一樣,否則在訓練多個數據集之後,網路正向傳播時會亂掉。

我的infer.py檔案,僅供參考:

import sys
sys.path.append('/home/pzn/caffe/python')
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

import caffe
import vis

# the demo image is "2007_000129" from PASCAL VOC

# load image, switch to BGR, subtract mean, and make dims C x H x W for Caffe
im = Image.open('../demo/siftflowyuantu/coast_nat1091.jpg')
in_ = np.array(im, dtype=np.float32)
in_ = in_[:,:,::-1]
in_ -= np.array((104.00698793,116.66876762,122.67891434))
in_ = in_.transpose((2,0,1))

# load net
# net = caffe.Net('voc-fcn8s/deploy.prototxt', 'voc-fcn8s/fcn8s-heavy-pascal.caffemodel', caffe.TEST)
net = caffe.Net('../siftflow-fcn32s/deploy.prototxt', '../siftflow-fcn32s/train/solver_iter_10000.caffemodel', caffe.TEST)
# shape for input (data blob is N x C x H x W), set data
net.blobs['data'].reshape(1, *in_.shape)
net.blobs['data'].data[...] = in_
# run net and take argmax for prediction
net.forward()
out = net.blobs['score_sem'].data[0].argmax(axis=0)  #注意修改

#plt.imshow(out,cmap='gray');
plt.imshow(out)
#plt.show()
plt.axis('off')
plt.savefig('../demo/outputsiftflow/coast_nat1091_op.png')


'''
# visualize segmentation in PASCAL VOC colors
voc_palette = vis.make_palette(21)
out_im = Image.fromarray(vis.color_seg(out, voc_palette))
out_im.save('demo/output.png')
masked_im = Image.fromarray(vis.vis_seg(im, out, voc_palette))
masked_im.save('demo/visualization.jpg')
'''

'''
coast_bea1
coast_n286045
coast_nat1091
coast_natu975
forest_natc52
mountain_n213081
mountain_nat426
opencountry_land660
street_par151
street_urb885
tallbuilding_art1004
'''

網路正向傳播時需要的deploy.prototxt檔案,可以直接下載:

連結: https://pan.baidu.com/s/1MJLitNDl9EU3O3Acj2DOWA 密碼: 6gdn

製作方式如下:

首先,根據你利用的模型,例如模型是siftflow32s的,那麼你就去siftflow32s的資料夾,裡面有train.prototxt檔案,將檔案開啟,全選,複製,新建一個名為deploy.prototxt檔案,貼上進去,然後ctrl+F 尋找所有名為loss的layer 只要有loss 無論是loss還是geo_loss 將這個layer統統刪除,然後刪除輸入層,在fcn中就是第一個python層,即刪除:

layer {
  name: "data"
  type: "Python"
  top: "data"
  top: "sem"
  top: "geo"
  python_param {
    module: "siftflow_layers"
    layer: "SIFTFlowSegDataLayer"
    param_str: "{\'siftflow_dir\': \'../data/sift-flow\', \'seed\': 1337, \'split\': \'trainval\'}"
  }
}

然後在檔案頂部加上:

layer {
  name: "input"
  type: "Input"
  top: "data"
  input_param {
    # These dimensions are purely for sake of example;
    # see infer.py for how to reshape the net to the given input size.
    shape { dim: 1 dim: 3 dim: 256 dim: 256 }
  }
}

其中shape{dim:1 dim:3 dim:256 dim:256}中的dim可以隨意設定,此處雖然為1 3 256 256,但是caffe會根據所讀取的圖片進行重新設定dim。

最終,對demo.jpg最終測試可以發現測試後的分割結果並不盡人意,那是因為原來的demo屬於voc資料集中的資料,而此資料集為siftflow,並不符合demo的特徵,所以分割效果差是理所應當的。採用siftflow資料集中的資料進行測試效果才會更好。

結果圖:

因為不是自己的資料集,跑通了siftflow-fcn32s就好,siftflow-fcn16s和siftflow-fcn8s以此類推,最終8s的model是最好的。

Part 2 在VOC2012資料集上進行訓練

VOC資料集,下載地址,共包含21類(包括背景),voc資料集中的顏色類別

類別名稱 R G B 
background 0 0 0 背景 
aeroplane 128 0 0 飛機 
bicycle 0 128 0 
bird 128 128 0 
boat 0 0 128 
bottle 128 0 128 瓶子 
bus 0 128 128  
car 128 128 128 
cat 64 0 0  
chair 192 0 0 
cow 64 128 0 
diningtable 192 128 0 餐桌 
dog 64 0 128 
horse 192 0 128 
motorbike 64 128 128 
person 192 128 128 
pottedplant 0 64 0 盆栽 
sheep 128 64 0 
sofa 0 192 0 
train 128 192 0 
tvmonitor 0 64 128 顯示器

資料集結構如下:

JPEGImages中存放的是所有的原圖資訊,包括訓練圖片和測試圖片;

Annotation中存放的是xml格式的標籤資料,每張圖片對應一個xml檔案,作為目標檢測中的labels,用於目標檢測;

ImageSets下的Main資料夾下存放的是train.txt,val.txt,trainval.txt等檔案,用於訓練過程中遍歷和讀取圖片;

ImageSets下的Segmentation下存放的是用於語義分割的train.txt,seg11valid.txt(驗證集,直接把val.txt重新命名即可),trainval.txt檔案,用於用於訓練過程中遍歷和讀取圖片。

SegmentationClass中存放的是.png格式的索引圖,作為語義分割中的labels,用於語義分割。

參考了許多大神的blog,大家都在說在訓練自己的資料時,要把benchmark資料集和VOC資料集結合起來,把benchmark作為測試集,voc資料集作為驗證集,但是在初次跑別人的benchmark時,我就各種跑不通,因為在bechmark資料集中語義級標註的資料中,train.txt和validition資料集寫的不是很一致,導致出現有原圖但是沒有標籤,有標籤,但是原圖可能找不到等類似的情況,其中,報錯最厲害的一個錯誤是:

File "D:\python\lib\site-packages\scipy\io\matlab\miobase.py", line 224, in ge
t_matfile_version
raise MatReadError("Mat file appears to be empty")
scipy.io.matlab.miobase.MatReadError: Mat file appears to be empty

報錯截圖:一迭代到8700-9000次左右就報這個錯誤也是很無奈。

多方搶救無效後,參考別的小組做faster-RCNN的資料製作方式,我想到了只用VOC2012資料集的方式來解決問題。

在兩個(benchmark和voc)資料集合並使用時會遇到的錯誤和建議的解決方案:

訓練FCN的過程中,當報錯某個(些).mat檔案找不到時,最好的方式不是找到這個mat檔案,因為會因為非法引入的mat檔案破壞掉結構,從而導致產生其他的錯誤。

相反,最好的方式而是修改訓練時的train.txt,舍掉這些找不到資料的原影象的讀入,有舍才有得,方為上上策。

所以,只應用VOC資料集,直接跑語義分割的資料,應用的檔案參考如下:

首先是solve.py檔案:

# -*- coding: utf-8 -*-
import sys
sys.path.append('/home/pzn/caffe/python')
import caffe
import surgery, score
import matplotlib.pyplot as plt
import numpy as np
import os
import math

try:
    import setproctitle
    setproctitle.setproctitle(os.path.basename(os.getcwd()))
except:
    pass

#weights = '../ilsvrc-nets/vgg16-fcn.caffemodel'
vgg_weights = '../ilsvrc-nets/vgg16-fcn.caffemodel'  
vgg_proto = '../ilsvrc-nets/VGG_ILSVRC_16_layers_deploy.prototxt'  
weights = '../ilsvrc-nets/vgg16-fcn.caffemodel'


# init
#caffe.set_device(int(sys.argv[1]))
caffe.set_mode_gpu()
caffe.set_device(0)

#solver = caffe.SGDSolver('solver.prototxt')
#solver.net.copy_from(weights)
solver = caffe.SGDSolver('solver.prototxt')
vgg_net=caffe.Net(vgg_proto,vgg_weights,caffe.TRAIN) 
surgery.transplant(solver.net,vgg_net)  
del vgg_net


# surgeries
interp_layers = [k for k in solver.net.params.keys() if 'up' in k]
surgery.interp(solver.net, interp_layers)

# scoring
val = np.loadtxt('/home/pzn/pang1/fcn.berkeleyvision.org/data/pascal/VOCdevkit/VOC2012/ImageSets/Segmentation/seg11valid.txt', dtype=str)

for _ in range(500):
    solver.step(100)
    score.seg_tests(solver, False, val, layer='score')

然後是slover.prototxt檔案:

train_net: "/home/pzn/pang1/fcn.berkeleyvision.org/voc-fcn32s/train.prototxt"
test_net: "/home/pzn/pang1/fcn.berkeleyvision.org/voc-fcn32s/val.prototxt"
test_iter: 736
# make test net, but don't invoke it from the solver itself
test_interval: 999999999
display: 20
average_loss: 20
lr_policy: "fixed"
# lr for unnormalized softmax
base_lr: 1e-10
# high momentum
momentum: 0.99
# no gradient accumulation
iter_size: 1
max_iter: 100000
weight_decay: 0.0005
snapshot: 100
snapshot_prefix: "/home/pzn/pang1/fcn.berkeleyvision.org/voc-fcn32s/train/"
solver_mode: GPU
device_id: 0
test_initialization: false

再就是train.prototxt和val.prototxt的開頭需要修改:

layer {
  name: "data"
  type: "Python"
  top: "data"
  top: "label"
  python_param {
    module: "voc_layers"
    layer: "VOCSegDataLayer"
    param_str: "{\'voc_dir\': \'../data/pascal/VOCdevkit/VOC2012\', \'seed\': 1337, \'split\': \'train\', \'mean\': (104.00699, 116.66877, 122.67892)}"
  }
}
layer {
  name: "data"
  type: "Python"
  top: "data"
  top: "label"
  python_param {
    module: "voc_layers"
    layer: "VOCSegDataLayer"
    param_str: "{\'voc_dir\': \'../data/pascal/VOCdevkit/VOC2012\', \'seed\': 1337, \'split\': \'seg11valid\', \'mean\': (104.00699, 116.66877, 122.67892)}"
  }
}

修改之後,則可以執行:

sudo python slove.py

若網路一旦跑起來無異常,則說明配置成功!

 接下來就是訓練自己的資料啦!