1.FCN简介
终于来到图像分割的开山之作FCN,在FCN之前进行的图像分割,大多是根据图像的一些低阶视觉信息来进行分割,经典的方法有N-cut,Grab cut等,这些算法的优点是不需要使用算法进行训练,计算复杂度小,但是同时的,对于一些较困难的分割任务效果不太理想同时分割效率也比较低。伴随着机器学习等一系列技术的发展,图像分割也来到了一个新的时代,FCN便是其中的开山鼻祖,为往后的模型提供了基石。
FCN的一些优点可以简答概括为以下几点:
1.将原始的分类网络改编为全卷积神经网络,具体包括全连接层转化为卷积层,以及通过反卷积进行上采样
2.使用迁移学习的方法进行微调(因为使用了之前的VGG,GoogLeNet等网络做基础)
3.使用跳跃结构使得语义信息和表征信息相结合,使得分割结果更加准确。
全局信息与局部信息:
?
?离图片越近层次的网络越接近图片本身,几何信息保留较为完整,感受野小,局部信息越丰富,有助于分割小尺寸的目标
而离图片越远的网络基本看不出是个图片样了,此时网络注重整体性,感受野大,空间信息越丰富,有助于分割尺寸较大的目标。?
局部信息与全局信息可谓是不可兼得的鱼和熊掌,需要在两者之间做平衡来使得效果最好。
感受野相关概念:
?上采样相关概念:
FCN模型架构
?
?
按照论文里说的其实这张图其实包含三个模型。FCN-32s,FCN-16s,FCN-8s?
FCN-32s:其实就是第一层,con6-7之后直接进行32倍上采样至原图大小。
FCN-16s:把con6-7之后的网络先进行2倍上采样恢复至原图的1/16,再结合pool4中1/16的特征图,最后两个一起进行16倍上采样恢复至原图大小。
FCN-8s:则是在FCN-16s的基础上再加上pool3的特征图,最后一起进行8倍上采样。
其实大家大概可以想到,一个进行32倍上采样,一个8倍上采样,直观上有感觉32倍上采样放大的效果应该是不如8倍上采样的,最后实验结果也的确如此,因为直接进行32倍上采样有许多特征被丢失导致分割结果不太理想。
? ? ? ?
?
?
?小总结:
2.基于Pytorch的FCN实现
?数据预处理:dateset.py
目标就是把带颜色的label图转化为单通道的编码图
"""补充内容见 data process and load.ipynb"""
import pandas as pd
import os
import torch as t
import numpy as np
import torchvision.transforms.functional as ff
from torch.utils.data import Dataset
from PIL import Image
import torchvision.transforms as transforms
import cfg
class LabelProcessor:
"""对标签图像的编码"""
def __init__(self, file_path):
self.colormap = self.read_color_map(file_path)
self.cm2lbl = self.encode_label_pix(self.colormap)
# 静态方法装饰器, 可以理解为定义在类中的普通函数,可以用self.<name>方式调用
# 在静态方法内部不可以示例属性和实列对象,即不可以调用self.相关的内容
# 使用静态方法的原因之一是程序设计的需要(简洁代码,封装功能等)
@staticmethod
def read_color_map(file_path): # data process and load.ipynb: 处理标签文件中colormap的数据
pd_label_color = pd.read_csv(file_path, sep=',')
colormap = []
for i in range(len(pd_label_color.index)):
tmp = pd_label_color.iloc[i]
color = [tmp['r'], tmp['g'], tmp['b']]
colormap.append(color)
return colormap
@staticmethod
def encode_label_pix(colormap): # data process and load.ipynb: 标签编码,返回哈希表
cm2lbl = np.zeros(256 ** 3)
for i, cm in enumerate(colormap):
cm2lbl[(cm[0] * 256 + cm[1]) * 256 + cm[2]] = i
return cm2lbl
def encode_label_img(self, img):
data = np.array(img, dtype='int32')
idx = (data[:, :, 0] * 256 + data[:, :, 1]) * 256 + data[:, :, 2]
return np.array(self.cm2lbl[idx], dtype='int64')
class CamvidDataset(Dataset):
def __init__(self, file_path=[], crop_size=None):
"""para:
file_path(list): 数据和标签路径,列表元素第一个为图片路径,第二个为标签路径
"""
# 1 正确读入图片和标签路径
if len(file_path) != 2:
raise ValueError("同时需要图片和标签文件夹的路径,图片路径在前")
self.img_path = file_path[0]
self.label_path = file_path[1]
# 2 从路径中取出图片和标签数据的文件名保持到两个列表当中(程序中的数据来源)
self.imgs = self.read_file(self.img_path)
self.labels = self.read_file(self.label_path)
# 3 初始化数据处理函数设置
self.crop_size = crop_size
def __getitem__(self, index):
img = self.imgs[index]
label = self.labels[index]
# 从文件名中读取数据(图片和标签都是png格式的图像数据)
img = Image.open(img)
label = Image.open(label).convert('RGB')
img, label = self.center_crop(img, label, self.crop_size)
img, label = self.img_transform(img, label)
# print('处理后的图片和标签大小:',img.shape, label.shape)
sample = {'img': img, 'label': label}
return sample
def __len__(self):
return len(self.imgs)
def read_file(self, path):
"""从文件夹中读取数据"""
files_list = os.listdir(path)
file_path_list = [os.path.join(path, img) for img in files_list]
file_path_list.sort()
return file_path_list
def center_crop(self, data, label, crop_size):
"""裁剪输入的图片和标签大小"""
data = ff.center_crop(data, crop_size)
label = ff.center_crop(label, crop_size)
return data, label
def img_transform(self, img, label):
"""对图片和标签做一些数值处理"""
label = np.array(label) # 以免不是np格式的数据
label = Image.fromarray(label.astype('uint8'))
transform_img = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]
)
img = transform_img(img)
label = label_processor.encode_label_img(label)
label = t.from_numpy(label)
return img, label
label_processor = LabelProcessor(cfg.class_dict_path)
if __name__ == "__main__":
TRAIN_ROOT = './CamVid/train'
TRAIN_LABEL = './CamVid/train_labels'
VAL_ROOT = './CamVid/val'
VAL_LABEL = './CamVid/val_labels'
TEST_ROOT = './CamVid/test'
TEST_LABEL = './CamVid/test_labels'
crop_size = (352, 480)
Cam_train = CamvidDataset([TRAIN_ROOT, TRAIN_LABEL], crop_size)
Cam_val = CamvidDataset([VAL_ROOT, VAL_LABEL], crop_size)
Cam_test = CamvidDataset([TEST_ROOT, TEST_LABEL], crop_size)
?
?
?
?
?
?
?
?
?
?
?参考https://www.bilibili.com/video/BV16K411W782?from=search&seid=1183040530763136779
|