FCN 官方Github 地址:shelhamer/fcn.berkeleyvision.org
我修改的fcn代码Github地址:https://github.com/yeLer/fcn
论文地址:https://arxiv.org/abs/1411.4038

这里说明一下这个文件和文件夹都是做什么的,
data:是官方提供的四个数据集相关的文件,我们允许代码所下载的数据集都会放到这个里面
demo:是官方代码提供的演示效果
nyud,pascalcontext,sififlow,voc等四个数据集名称开头的文件夹是分别对应的caffe网络及训练参数文件,你拿对应的名称可以做对应的实验
nyud_layer.py,pascalcontext_layer.py,sififlow_layer.py,voc_layer.py分别是四个数据集对应的训练层,需要哪个用哪个。
infer.py:测试需要的文件
score.py:求取分割得分的文件
surgery.py:权重转换文件
vis.py:可视化文件
官方开源代码提供了PASCAL VOC models,SIFT Flow models,PASCAL-Context models的完整(32s,16s,8s)的代码,但对于NYUD只提供了32s的代码,这里我们就以NYUD作为例子说明一下FCN-8s训练的完整过程。
源代码下载和数据集预处理
进入到你的工程目录,下载官方源代码:
git clone https://github.com/shelhamer/fcn.berkeleyvision.org.git下载VGG16的预训练模型并放在FCN源码文件夹中的ilsvrc-nets文件夹下:
cd /fcn.berkeleyvision.org/ilsvrc-nets
wget http://www.robots.ox.ac.uk/~vgg/software/very_deep/caffe/VGG_ILSVRC_16_layers.caffemodel获取与其相对应的deploy文件,同样也放到ilsvrc-nets文件夹下:
wget https://gist.githubusercontent.com/ksimonyan/211839e770f7b538e2d8/raw/0067c9b32f60362c74f4c445a080beed06b07eb3/VGG_ILSVRC_16_layers_deploy.prototxt下载数据集:
cd data/nyud/
wget http://people.eecs.berkeley.edu/~sgupta/cvpr13/data.tgz
tar -xvf data.tgz解压以后的文件夹名称分别为benchmarkData, colorImage, pointCloud.
由于图片路径的变化,需要将colorImage重命名为images

其中benchmarkData/groundTruth中储存这所有我们需要的分割的真值,colorImage文件夹储存着原始的RGB文件.由于源代码设置的groundTruth路径和现有的路径不一样,所以我们要把groundTruth文件copy到指定路径:
mkdir segmentation
cp data/benchmarkData/groundTruth/*.mat segmentation/我们还要合并train.txt和val.txt: 在nyud文件夹中新建一个空白的.txt文件并命名为trainval.txt,然后将train.txt和val.txt中的内容Copy过去.这个时候nyud文件夹应为是这个样子:

FCN-32s网络训练:
- 把要用到的.py文件Copy到nyud-fcn32s-color文件夹:包括nyud_layer.py,infer.py,score.py,surgery.py和vis.py
- 修改solver.prototxt文件,下面是我个人的文件内容,关于相关参数的含义请参考:Caffe学习系列:solver及其配置
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: 300000
weight_decay: 0.0005
snapshot: 5000
snapshot_prefix: "snapshot/train"
test_initialization: false
其中打红色箭头的是该文件夹下默认有的py文件。
我们对solve.py文件作出的修改如下:
#coding: utf-8
import caffe
import surgery, score
import numpy as np
import os
import sys
try:
import setproctitle
setproctitle.setproctitle(os.path.basename(os.getcwd()))
#获得当前路径(返回最后的文件名)
#比如os.getcwd()获得的当前路径为/home/bxx-yll/fcn,则os.path.basename()为fcn;
#setproctitle是用来修改进程入口名称,如C++中入口为main()函数
except:
pass
# weights = '../ilsvrc-nets/vgg16-fcn.caffemodel'
vgg_weights = '../ilsvrc-nets/VGG_ILSVRC_16_layers.caffemodel' #用来fine-tune的FCN参数
vgg_proto = '../ilsvrc-nets/VGG_ILSVRC_16_layers_deploy.prototxt' #VGGNet模型
# init
# caffe.set_device(int(sys.argv[1]))
#获取命令行参数,其中sys.argv[0]为文件名,argv[1]为紧随其后的那个参数
caffe.set_device(1) #GPU型号id,这里指定第二块GPU
caffe.set_mode_gpu()
# solver = caffe.SGDSolver('solver.prototxt')
# solver.net.copy_from(weights) # 这个方法仅仅是从vgg-16模型中拷贝参数,但是并没有改造原先的网络,这才是不收敛的根源
solver = caffe.SGDSolver('solver.prototxt') #调用SGD(随即梯度下降)Solver方法,solver.prototxt为所需参数
vgg_net = caffe.Net(vgg_proto, vgg_weights, caffe.TRAIN) #vgg_net是原来的VGGNet模型(包括训练好的参数)
surgery.transplant(solver.net, vgg_net) #FCN模型(参数)与原来的VGGNet模型之间的转化
del vgg_net #删除VGGNet模型
# surgeries
interp_layers = [k for k in solver.net.params.keys() if 'up' in k] #interp_layers为upscore层
surgery.interp(solver.net, interp_layers) #将upscore层中每层的权重初始化为双线性内核插值。
# scoring
test = np.loadtxt('../data/nyud/test.txt', dtype=str) #载入测试图片信息
for _ in range(50):
solver.step(2000) #每2000次训练迭代执行后面的函数
score.seg_tests(solver, False, test, layer='score') #测试图片
- 同时由于路径原因,需要修改nyud_layers.py中load_label function 的内容:

在setup方法中加上红线指向的class_map定义 ,然后修改load_label方法
# self.class_map = scipy.io.loadmat('{}/data/benchmarkData/metadata/classMapping40.mat'.format(self.nyud_dir))['mapClass'].astype(np.uint8)
def load_label(self, idx):
"""
Load label image as 1 x height x width integer array of label indices.
Shift labels so that classes are 0-39 and void is 255 (to ignore it).
The leading singleton dimension is required by the loss.
"""
# label = scipy.io.loadmat('{}/segmentation/img_{}.mat'.format(self.nyud_dir, idx))['segmentation'].astype(np.uint8)
label = scipy.io.loadmat('{}/segmentation/img_{}.mat'.format(self.nyud_dir, idx))['groundTruth'][0,0][0,0]['SegmentationClass'].astype(np.uint16)
for (x,y), value in np.ndenumerate(label):
label[x,y] = self.class_map[0][value-1]
label = label.astype(np.uint8)
label -= 1 # rotate labels
label = label[np.newaxis, ...]
return label
- 以上配置全部结束,开始进行模型训练:
cd nyud-fcn32s-color
mkdir snapshot
python solve.py大概迭代150000次以后,就可以达到论文描述的精度,这里我只训练了100000次。


可以看到和原论文中的准确率已经很接近了。
接下来利用生成的模型进行测试,修改infer.py文件如下,这里会用到测试时的deploy.prototxt文件。
#!/usr/bin/env python
# encoding: utf-8
'''
@author: lele Ye
@contact: 1750112338@qq.com
@software: pycharm 2018.2
@file: infer.py
@time: 2019/1/2 15:59
@desc:
'''
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import scipy.io
CAFFE_ROOT = "/home/bxx-yll/caffe"
import sys
sys.path.insert(0, CAFFE_ROOT + '/python')
import caffe
# 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))
# 加载网络文件,与模型文件,并设置为测试模式
net = caffe.Net('./deploy.prototxt', './snapshot/train_iter_100000.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)
scipy.io.savemat('./out.mat', {'X': out})
# visualize segmentation in PASCAL VOC colors
plt.imshow(out)
plt.show()
plt.axis('off')
plt.savefig('../demo/testout_32s.png')
没有deploy文件,可以参考如下方法:
首先,根据你利用的模型,例如模型是nyud-fcn32s-color的,那么你就去nyud-fcn32s-color的文件夹,里面有trainval.prototxt文件,将文件打开,全选,复制,新建一个名为deploy.prototxt文件,粘贴进去,然后ctrl+F 寻找所有名为loss的layer 将这个layer统统删除,并去除第一层的python层。
实际上去除的是:
layer {
name: "data"
type: "Python"
top: "data"
top: "label"
python_param {
module: "nyud_layers"
layer: "NYUDSegDataLayer"
param_str: "{\'tops\': [\'color\', \'label\'], \'seed\': 1337, \'nyud_dir\': \'../data/nyud\', \'split\': \'trainval\'}"
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "score"
bottom: "label"
top: "loss"
loss_param {
ignore_label: 255
normalize: false
}
}然后在第一层添加的是:
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: 480 dim: 640 }
}
}运行效果,由于我的迭代次数不够,没有完全收敛,可以看到与论文的效果还是有差距:
