基于深度学习的语义分割初探fcn以及pytorch代码实现(代码片段)

卡子爹 卡子爹     2023-01-05     392

关键词:

基于深度学习的语义分割初探FCN以及pytorch代码实现

FCN论文

论文地址:https://arxiv.org/abs/1411.4038

FCN是基于深度学习方法的第一篇关于语义分割的开山之作,虽然这篇文章的分割结果现在看起来并不是目前最好的,但其意义还是非常重要的。其中跳跃链接、end-to-end、迁移学习、反卷积实现上采样也是FCN论文中的核心思想。

FCN论文整体结构

应用

无人车、地理信息系统、医疗影像、机器人。由于目前想在机器人上搭建视觉系统,想结合语义分割这种像素级预测的思想,是否可以与检测任务中的方式做一个结合。例如Mask-RCNN将实例分割与目标检测很好的融合为一体。

pytorch实现FCN_8x

由于让我们的代码更加易于理解以及更好的更正,代码中所有参数以及变量名称均使用我们的母语。
简单使用Camvid数据集做一个室外分割的例子。

Dataset构建

import torch
import os
from PIL import Image
import pandas as pd
import numpy as np
import torchvision.transforms.functional as F
from torch.utils.data import Dataset
import torchvision.transforms as transforms
import cfg


class 标签处理:

    def __init__(self, 标签所对应类别文件的路径):

        self.像素类别图 = self.读取类别所对应的像素值(标签所对应类别文件的路径)
        self.标签哈希表 = self.编码标签像素值(self.像素类别图)

    @staticmethod
    def 读取类别所对应的像素值(标签所对应类别文件的路径):
        标签像素值 = pd.read_csv(标签所对应类别文件的路径, sep=',')
        像素类别图 = []
        #标签像素值.index # 返回像素值所对应类别的索引 0-12
        for i in range(len(标签像素值.index)):
            按行读取每一个类别所对应的像素值 = 标签像素值.iloc[i]
            类别所对应的RGB像素值 = [按行读取每一个类别所对应的像素值['r'], 按行读取每一个类别所对应的像素值['g'], 按行读取每一个类别所对应的像素值['b']]
            像素类别图.append(类别所对应的RGB像素值)
            # 类别名称 = 标签像素值['name'].values
            # 类别数量 = len(类别名称)
        return 像素类别图

    @staticmethod
    def 编码标签像素值(像素类别图):
        # 哈希表(为了形成1对1或1对多的映射关系,加快查找的效率) 一个标签对应一个颜色  将像素类别图中的每一个像素映射到它所表示的类别
        # 希函数 像素类别图([0]*256+像素类别图[1])*256+像素类别图[2]
        # 哈希映射 像素类别图2lbl(希函数) = 所对应的类别
        # 哈希表 像素类别图2lbl
        # eg: 一个像素点P(128, 64, 128) 通过编码函数(P[0]*256+P[1])*256+P[2] 转成 整数(8405120)
        # 将该数作为像素点P在哈希表中的索引:像素类别图转成哈希表(8405120) 去查询像素点P所对应的类别P
        像素类别图转成哈希表 = np.zeros(256 ** 3)
        for 类别索引, 类别所对应RGB像素值 in enumerate(像素类别图):
            像素类别图转成哈希表[(类别所对应RGB像素值[0]*256 + 类别所对应RGB像素值[1]) * 256 + 类别所对应RGB像素值[2]] = 类别索引
        return 像素类别图转成哈希表

    def 编码标签图像(self, 图像):
        # rgb -> index -> identity
        数据 = np.array(图像, dtype='int32')
        哈希函数值 = (数据[:, :, 0] * 256 + 数据[:, :, 1]) * 256 + 数据[:, :, 2]
        return np.array(self.标签哈希表[哈希函数值], dtype='int64')


class 数据集(Dataset):
    def __init__(self, 图像和标签路径=[], 裁剪=None):
        if len(图像和标签路径) != 2:
            raise Exception('需同时输入图像和标签的路径')
        self.图像路径 = 图像和标签路径[0]
        self.标签路径 = 图像和标签路径[1]

        self.读取路径中的图片 = self.读取文件夹(self.图像路径)
        self.读取路径中的标签 = self.读取文件夹(self.标签路径)

        self.裁剪尺寸 = 裁剪

    def __getitem__(self, 索引):
        单张图像 = self.读取路径中的图片[索引]
        单个标签 = self.读取路径中的标签[索引]

        单张图像 = Image.open(单张图像)
        单个标签 = Image.open(单个标签).convert('RGB')

        单张图像, 单个标签 = self.中心裁剪(单张图像, 单个标签, self.裁剪尺寸)

        单张图像, 单个标签 = self.图像标签转换(单张图像, 单个标签)
        图像标签组合成字典 = '图像': 单张图像, '标签': 单个标签

        return 图像标签组合成字典

    def __len__(self):
        return len(self.读取路径中的图片)

    def 读取文件夹(self, 路径):
        文件夹列表 = os.listdir(路径)
        拼接图像完整路径 = [os.path.join(路径, 图片) for 图片 in 文件夹列表]
        拼接图像完整路径.sort()
        return 拼接图像完整路径

    def 中心裁剪(self, 图像, 标签, 裁剪尺寸):
        图像 = F.center_crop(图像, 裁剪尺寸)
        标签 = F.center_crop(标签, 裁剪尺寸)
        return 图像, 标签

    def 图像标签转换(self, 图像, 标签):
        标签 = np.array(标签)
        标签 = Image.fromarray(标签.astype('uint8'))

        图像转Tensor = transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
        图像 = 图像转Tensor(图像)
        # 原图不需要编码 标签需要编码
        标签 = 标签处理实例化.编码标签图像(标签)
        标签 = torch.from_numpy(标签)

        return 图像, 标签

标签处理实例化 = 标签处理(cfg.类别文件路径)

FCN模型搭建

import torch
import torch.nn as nn
from torchvision import models
import torch.nn.functional as F
import numpy as np
from Bilinear_init_deconv import 双线性插值初始化卷积核

VGG特征提取网络 = models.vgg16_bn(pretrained=True)

class 全卷积网络(nn.Module):
    def __init__(self, 类别个数):
        super(全卷积网络, self).__init__()

        self.特征提取网络中第一个下采样 = VGG特征提取网络.features[:7] # 64
        self.特征提取网络中第二个下采样 = VGG特征提取网络.features[7:14] # 128
        self.特征提取网络中第三个下采样 = VGG特征提取网络.features[14:24] # 256
        self.特征提取网络中第四个下采样 = VGG特征提取网络.features[24:34] # 512
        self.特征提取网络中第五个下采样 = VGG特征提取网络.features[34:] # 512

        # self.跨度_32的上采样预测图 = nn.Conv2d(512, 类别个数, 1) # 32
        # self.跨度_16的采样预测图 = nn.Conv2d(512, 类别个数, 1) # 16
        # self.跨度_8的上采样预测图 = nn.Conv2d(128, 类别个数, 1) # 8

        self.过渡卷积512 = nn.Conv2d(512, 256, 1)
        self.过渡卷积256 = nn.Conv2d(256, 类别个数, 1)

        self.上采样_8X = nn.ConvTranspose2d(类别个数, 类别个数, 16, 8, 4, bias=False)
        self.上采样_8X.weight.data = 双线性插值初始化卷积核(类别个数, 类别个数, 16)

        self.上采样_2X_512 = nn.ConvTranspose2d(512, 512, 4, 2, 1, bias=False)
        self.上采样_2X_512.weight.data = 双线性插值初始化卷积核(512, 512, 4)

        self.上采样_2X_256 = nn.ConvTranspose2d(256, 256, 4, 2, 1, bias=False)
        self.上采样_2X_256.weight.data = 双线性插值初始化卷积核(256, 256, 4)

    def forward(self, x):
        第一层特征提取 = self.特征提取网络中第一个下采样(x)
        第二层特征提取 = self.特征提取网络中第二个下采样(第一层特征提取)
        第三层特征提取 = self.特征提取网络中第三个下采样(第二层特征提取)
        第四层特征提取 = self.特征提取网络中第四个下采样(第三层特征提取)
        第五层特征提取 = self.特征提取网络中第五个下采样(第四层特征提取)

        第五层特征提取_2倍还原 = self.上采样_2X_512(第五层特征提取)
        第五层与第四层进行特征图融合 = 第四层特征提取 + 第五层特征提取_2倍还原

        融合后的图像转换通道数 = self.过渡卷积512(第五层与第四层进行特征图融合)

        第四层与第五层融合后的特征_2倍还原 = self.上采样_2X_256(融合后的图像转换通道数)

        与第三层特征图进行融合 = 第三层特征提取 + 第四层与第五层融合后的特征_2倍还原
        转换成类别个数的通道数 = self.过渡卷积256(与第三层特征图进行融合)

        还原原图大小_8X = self.上采样_8X(转换成类别个数的通道数)

        return 还原原图大小_8X

FCN论文中使用了双线性插值初始化反卷积核

import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
import cv2


def 双线性插值(原图, 目标尺寸):
    目标图像的高, 目标图像的宽 = 目标尺寸
    原图的高, 原图的宽 = 原图.shape[:2]

    if 原图的高 == 目标图像的高 and 原图的宽 == 目标图像的宽:
        return 原图.copy()

    原图与目标图像宽的缩放比例 = float(原图的宽) / 目标图像的宽
    原图与目标图像高的缩放比例 = float(原图的高) / 目标图像的高

    生成目标图像尺寸相同的空白图 = np.zeros((目标图像的高, 目标图像的宽, 3), dtype=np.uint8)

    for RGB in range(3):
        for 目标图像高方向 in range(目标图像的高):
            for 目标图像宽方向 in range(目标图像的宽):
                #  src_x + 0.5 = (dst_x + 0.5) * scale_x  0.5为一个像素默认1*1 其中心像素坐标+0.5的位置
                目标图像宽方向的像素在原图上的坐标 = (目标图像宽方向 + 0.5) * 原图与目标图像宽的缩放比例 - 0.5
                目标图像高方向的像素在原图上的坐标 = (目标图像高方向 + 0.5) * 原图与目标图像高的缩放比例 - 0.5

                原图上第一个近邻点 = int(np.floor(目标图像宽方向的像素在原图上的坐标))
                原图上第二个近邻点 = int(np.floor(目标图像高方向的像素在原图上的坐标))
                原图上第三个近邻点 = min(原图上第一个近邻点 + 1, 原图的宽 - 1)
                原图上第四个近邻点 = min(原图上第二个近邻点 + 1, 原图的高 - 1)

                比例1 = (原图上第三个近邻点 - 目标图像宽方向的像素在原图上的坐标) * 原图[原图上第二个近邻点, 原图上第一个近邻点, RGB] + (目标图像宽方向的像素在原图上的坐标 - 原图上第一个近邻点) * 原图[原图上第二个近邻点, 原图上第三个近邻点, RGB]
                比例2 = (原图上第三个近邻点 - 目标图像宽方向的像素在原图上的坐标) * 原图[原图上第四个近邻点, 原图上第一个近邻点, RGB] + (目标图像宽方向的像素在原图上的坐标 - 原图上第一个近邻点) * 原图[原图上第四个近邻点, 原图上第三个近邻点, RGB]
                生成目标图像尺寸相同的空白图[目标图像高方向, 目标图像宽方向, RGB] = int((原图上第四个近邻点 - 原图上第二个近邻点) * 比例1 + (目标图像高方向的像素在原图上的坐标 - 原图上第二个近邻点) * 比例2)


    return 生成目标图像尺寸相同的空白图


def 双线性插值初始化卷积核(输入通道, 输出通道, 卷积核大小):

    因子 = (卷积核大小 + 1) // 2
    if 卷积核大小 % 2 == 1:
        中心 = 因子 - 1
    else:
        中心 = 因子 - 0.5

    画网格 = np.ogrid[:卷积核大小, :卷积核大小]
    初始化 = (1 - abs(画网格[0] - 中心) / 因子) * (1 - abs(画网格[1] - 中心) / 因子)
    权重 = np.zeros((输入通道, 输出通道, 卷积核大小, 卷积核大小), dtype='float32')
    权重[range(输入通道), range(输出通道), :, :] = 初始化

    return torch.from_numpy(权重)


if __name__ == '__main__':
    img = cv2.imread('FCN_model.png')
    img_out = 双线性插值(img, (1000, 1000))

    cv2.imshow('src', img)
    cv2.imshow('dst', img_out)
    cv2.waitKey(0)
    print(img.shape)
    print(img_out.shape)

训练

import os
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
import evalution_segmentation
import cfg
from dataset import 数据集
from build_FCN_model import 全卷积网络
from datetime import datetime


计算单元 = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

训练数据集实例化 = 数据集([cfg.训练数据集, cfg.训练标签数据集], (352, 480))
验证数据集实例化 = 数据集([cfg.验证数据集, cfg.验证标签数据集], (352, 480))

训练数据 = DataLoader(训练数据集实例化, batch_size=16, shuffle=True, num_workers=0)
验证数据 = DataLoader(验证数据集实例化, batch_size=8, shuffle=True, num_workers=0)

模型实例化 = 全卷积网络(类别个数=12)
模型放到GPU = 模型实例化.to(计算单元)
损失函数 = nn.NLLLoss().to(计算单元) # 交叉熵没有本质区别 只是没有封装softmax
优化器 = optim.Adam(模型放到GPU.parameters(), lr=1e-4) # 2D Adam  rgb-D SGD


def 训练(模型):
    最优权重 = [0]
    网络状态 = 模型.train()

    for 训练轮次 in range(cfg.循环数据集的总次数):
        print('训练次数[ / ]'.format(训练轮次 + 1, cfg.循环数据集的总次数))

        if 训练轮次 % 50 == 0 and 训练轮次 != 0:
            for 学习率 in 优化器.param_groups:
                学习率['lr'] *= 0.5

        训练损失 = 0
        训练准确率 = 0
        训练miou = 0
        训练分类的准确率 = 0

        for 索引, 图像标签数据字典 in enumerate(训练数据):
            训练图像数据 = Variable(图像标签数据字典['图像'].to(计算单元))
            训练图像标签 = Variable(图像标签数据字典['标签'].to(计算单元))

            预测图获取 = 网络状态(训练图像数据)
            预测图获取 = F.log_softmax(预测图获取, dim=1)

            损失 = 损失函数(预测图获取, 训练图像标签) # 每一次迭代的loss
            优化器.zero_grad()
            损失.backward()
            优化器.step()

            训练损失 += 损失.item() # 对于一个epoch总的loss

            预测结果中取最大值 = 预测图获取.max(dim=1)[1].data.cpu().numpy() # max 返回两个值 1、最大值本身 2、最大值的索引
            预测结果中取最大值 = [序号 for 序号 in 预测结果中取最大值]

            真实标签数据 = 训练图像标签.data.cpu().numpy()
            真实标签数据 = [序号 for 序号 in 真实标签数据]

            混淆矩阵 = evalution_segmentation.验证语义分割指标(预测结果中取最大值, 真实标签数据)
            训练准确率 += 混淆矩阵['平均分类精度']
            训练miou += 混淆矩阵['miou']
            训练分类的准确率 += 混淆矩阵['分类精度']

            print('迭代到第[ / ]个数据, 损失为 :.8f'.format(索引 + 1, len(训练数据), 损失.item()))

        每一个大循环下的指标描述 = '训练准确率: :.5f 训练miou: :.5f 训练类别的准确率: :'.format(训练准确率 深度学习语义分割网络介绍对比-fcn,segnet,u-netdeconvnet

前言在这里,先介绍几个概念,也是图像处理当中的最常见任务.语义分割(semanticsegmentation)目标检测(objectdetection)目标识别(objectrecognition)实例分割(instancesegmentation)语义分割首先需要了解一下什么是语义分割(semanticsegmentation).语义分... 查看详情

《基于深度学习的图像语义分割方法综述》阅读理解

...,深度学习技术已经广泛应用到图像语义分割领域.主要对基于深度学习的图像语义分割的经典方法与研究现状进行分类、梳理和总结.根据分割特点和处理粒度的不同,将基于深度学习的图像语义分割方法分为基于区域分类的图像... 查看详情

基于深度学习的图像语义分割方法综述

...,深度学习技术已经广泛应用到图像语义分割领域.主要对基于深度学习的图像语义分割的经典方法与研究现状进行分类、梳理和总结.根据分割特点和处理粒度的不同,将基于深度学习的图像语义分割方法分为基于区域分类的图像... 查看详情

cv语义分割全卷积神经网络fcn(更新ing)(代码片段)

学习总结(1)paper《FullyConvolutionalNetworksforSemanticSegmentation》(2)论文翻译可以参考:https://www.cnblogs.com/xuanxufeng/p/6249834.html(3)当前最成功的图像分割深度学习技术都是基于一个共同 查看详情

深度学习方法下图像分割的记录和理解(代码片段)

...。条件随机场(ConditionalRandomField,CRF)进行后期处理FCNFCN是深度学习用于语义分割的鼻祖了。FullyConvolutionalNetworksforSemanticSegmentation是UCBerkeley提出来的,也是2015年CVPR的bestpaper。2014年11月发表在arXiv上。FCN的主要思想如下图所说:将... 查看详情

语义分割:基于opencv和深度学习

语义分割:基于openCV和深度学习(二)SemanticsegmentationinimageswithOpenCV开始吧-打开segment.py归档并插入以下代码:SemanticsegmentationwithOpenCVanddeeplearning#importthenecessarypackagesimportnumpyasnpimportargparseimportimutilsi 查看详情

自然和医学图像的深度语义分割:网络结构

...l/18223?from=jianshu0312一、写在前面:网络架构的设计主要是基于CNN结构延伸出来的。主要的改进方式有两点:新神经架构的设计(不同深度,宽度,连接性或者拓扑结构)或设计新的组件(或者层)。下面我们逐个去分析了解。本... 查看详情

基于深度学习的图像语义分割技术概述之背景与深度网络架构

...有方法,及其贡献。最后,给出提及方法的量化标准及其基于的数据集,接着是对于结果的讨论。最终,对于基于深度学习的语义分割,指出未来重点并得出结论。 细粒度分类:同一类中不同子类物体间的分类。 难点:... 查看详情

(新)基于深度学习的图像语义分割技术概述之5.1度量标准

http://blog.csdn.net/u014593748/article/details/71698246本文为论文阅读笔记,不当之处,敬请指正。 AReviewonDeepLearningTechniquesAppliedtoSemanticSegmentation:原文链接5.1度量标准为何需要语义分割系统的评价标准?为了衡量分割系统的作用及贡献... 查看详情

深度学习语义分割篇——fcn原理详解篇(代码片段)

...没想到今天是创作两周年,必须浅浅更新一篇⛳⛳⛳深度学习语义分割篇——FCN原理详解篇写在前面​  在过往的博客中,我已经介绍了几种经典神经网络(VGG、GoogleNet、Resnet等等)在图像分类上的应用,... 查看详情

基于深度学习的图像语义分割技术概述之5.1度量标准

本文为论文阅读笔记,不当之处,敬请指正。 AReviewonDeepLearningTechniquesAppliedtoSemanticSegmentation:原文链接5.1度量标准为何需要语义分割系统的评价标准?为了衡量分割系统的作用及贡献,其性能需要经过严格评估。并且,评估... 查看详情

fcn图像分割

...nbsp;  传统的图像分割方法主要包括以下几种:1)基于边缘检测2)基于阈值分割   比如直方图,颜色,灰度等3)水平集方法    这里我们要说的是语义分割,什么是语义分割呢?先来看张图:&nbs... 查看详情

从fcn到deeplab

...,对图片上的每一个像素点分类。图像语义分割,从FCN把深度学习引入这个任务,一个通用的框架事:前端使用FCN全卷积网络输出粗糙的labelmap,后端使用CRF条件随机场/MRF马尔科夫随机场等优化前端的输出,最后得到一个精细的... 查看详情

语义分割:基于opencv和深度学习

语义分割:基于openCV和深度学习(二)SemanticsegmentationinimageswithOpenCV开始吧-打开segment.py归档并插入以下代码:SemanticsegmentationwithOpenCVanddeeplearning#importthenecessarypackagesimportnumpyasnpimportargparseimportimutilsimporttimeimportcv2从输入必要的依... 查看详情

语义分割--全卷积网络fcn详解

...稠密的目标识别(需要预测每个像素点的类别)。传统的基于CNN的语义分割方法是:将像素周围一个小区域(如25*25)作为CNN输入,做训练和预测。这样做有3个问题: -像素区域的大小如何确定 -存储及计算量非常大 -... 查看详情

keras深度学习实战(18)——语义分割详解(代码片段)

...中,我们学习图像分割的基本概念,并且构建了基于U-Net网络模型的图像分割模型,在图像中 查看详情

计算机视觉面试之语义分割

...快上很多。之后,语义分割领域几乎所有的先进方法都是基于该模型进行扩展的。为了保留像素的空间位置信息,有两种方法可以解决这个问题:(1)编码器-解码器结构,编码器与解码器之间通常存在跨越连接(shortcutconnections... 查看详情

基于深度学习的图像语义编辑

...取得了很大的进展,被认为可以提取图像高层语义特征。基于此,衍生出了很多有意思的图像应用。为了提升本文的可读性,我们先来看几个效果图。图1.图像风格转换图2.图像修复,左上图为原始图,右下图为基于深度学习的... 查看详情