基于全卷积神经网络(FCN)实现图像分割

您所在的位置:网站首页 图像分割的经典算法fcn 基于全卷积神经网络(FCN)实现图像分割

基于全卷积神经网络(FCN)实现图像分割

2024-06-29 11:58| 来源: 网络整理| 查看: 265

目录 1、作者介绍2、网络及数据集介绍2.1 FCN算法2.2 VOC_2012数据集2.3 制作自己的语义分割数据集2.3.1 标注方式一:多边形标注2.3.1.1 labelMe安装与数据标注2.3.1.2 数据格式转换2.3.1.3 数据集分类 2.3.2 标注方式二:像素级涂抹 3、基于RESNet50骨干的FCN语义分割模型实验3.1 网络结构3.2 代码实现3.2.1 RESNet主干3.2.2 改进膨胀卷积的残差模块3.2.2 FCN的思想 3.3 训练及测试 参考连接

1、作者介绍

李吉国,西安工程大学电子信息学院,2022级研究生 研究方向:机器视觉与人工智能 电子邮件:[email protected]

陈梦丹,女,西安工程大学电子信息学院,2022级研究生 研究方向:机器视觉与人工智能 电子邮件:[email protected]

2、网络及数据集介绍 2.1 FCN算法

CNN能够对图片进行分类,可是怎么样才能识别图片中特定部分的物体?在2015年之前还是一个世界难题。

Jonathan Long发表了论文:《Fully Convolutional Networks for Semantic Segmentation》 论文下载链接:https://arxiv.org/abs/1411.4038

通常CNN网络在卷积层之后会接上若干个全连接层, 将卷积层产生的特征图映射成一个固定长度的特征向量。以AlexNet为代表的经典CNN结构适合于图像级的分类和回归任务,因为它们最后都期望得到整个输入图像的一个数值描述(概率),比如AlexNet的ImageNet模型输出一个1000维的向量表示输入图像属于每一类的概率(softmax归一化)。 在这里插入图片描述 FCN对图像进行像素级的分类,从而解决了语义级别的图像分割问题。与经典的CNN在卷积层之后使用全连接层得到固定长度的特征向量进行分类(全联接层+softmax输出)不同,FCN可以接受任意尺寸的输入图像,采用反卷积层对最后一个卷积层的feature map进行上采样, 使它恢复到输入图像相同的尺寸,从而可以对每个像素都产生了一个预测, 同时保留了原始输入图像中的空间信息, 最后在上采样的特征图上进行逐像素分类。最后逐个像素计算softmax分类的损失, 相当于每一个像素对应一个训练样本。下图是Longjon用于语义分割所采用的全卷积网络(FCN)的结构示意图: 在这里插入图片描述 为了便于理解,这里给出一种基于VGG16主干的FCN分割模型结构,如下图,主干为原生的VGG16网络,但是,在最后阶段,并没有像做分类任务一样将特征图展平进行全连接,而是使用FCN的思想对逐像素进行分类,最后使用双线性插值还原原图尺寸,实现分割任务。 在这里插入图片描述

2.2 VOC_2012数据集

VOC_2012数据集来源于PASCAL VOC挑战赛(The PASCAL Visual Object Classes),该数据集主要包括20个类别,可用于目标检测、目标分割、行为识别等任务。可选择在官网下载或者使用百度网盘进行下载。

(1)官网下载地址: 链接1:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar 链接2:https://pjreddie.com/projects/pascal-voc-dataset-mirror/ (2)百度网盘下载地址: 链接: https://pan.baidu.com/s/1ln-1Pa2VzXx5P9179PPvvQ 提取码:miao

原始图片(左侧)及标签(右侧)如下图所示: 在这里插入图片描述

2.3 制作自己的语义分割数据集 2.3.1 标注方式一:多边形标注

使用工具:Labelme 在进行Segmentation训练之前需要准备训练集和验证集,本节将要来介绍如何使用LabelMe进行标记。LabelMe是个可以绘制多边形、矩形、圆形、直线、点的一套标记工具,可用于分类、目标检测、语义分割、实例分割任务上的数据标注。

2.3.1.1 labelMe安装与数据标注

首先安装LabelMe,我使用Anaconda 进行安装。打开Anaconda Prompt 执行以下指令:

pip install labelme

接着输入labelme 会出现以下UI 介面,点选Open Dir 选择要标记的图片资料夹Labelme 在这里插入图片描述 在图片上按右键选择Create Polygans 后,就可以开始画标记点了、画好后填入要标记的label 在这里插入图片描述 语义分割时若有多个相同类别的目标物也选择一样的label,不同类别分别标注标签: 在这里插入图片描述 再点击左边的Save,会生成与图片名相同json文件,将其存放在data_mask/jsons文件夹中,最后选择左边的Next Image。json格式文件如下:

{ "version": "4.5.9", "flags": {}, "shapes": [ { "label": "Gingerbread", "points": [ [ 888.2439024390243, 409.80487804878044 ], [ 823.6097560975609, 404.92682926829264 ], ... ], "group_id": null, "shape_type": "polygon", "flags": {} }, { "label": "Coconutmilk", "points": [ [ 882.1463414634146, 198.8292682926829 ], [ 887.0243902439024, 263.4634146341463 ], ... ], "group_id": null, "shape_type": "polygon", "flags": {} } ], "imagePath": "20210801150111.jpg", "imageData": "...", "imageHeight": 320, "imageWidth": 480 } 2.3.1.2 数据格式转换

全部数据集标注完成后,将json文件批量转化为训练所需的标注图像。标注图像所存放的文件夹为data_mask/masks,代码如下:

import os import cv2 import numpy as np import json ''' 制作一个只包含分类标注的标签图像,假如我们分类的标签为cat和dog,那么该标签图像中,Background为0,cat为1,dog为2。 我们首先要创建一个和原图大小一致的空白图像,该图像所有像素都是0,这表示在该图像中所有的内容都是Background。 然后根据标签对应的区域使用与之对应的类别索引来填充该图像,也就是说,将cat对应的区域用1填充,dog对应的区域用2填充。 特别注意的是,一定要有Background这一项且一定要放在index为0的位置。 ''' # 分类标签,一定要包含'Background'且必须放在最前面 category_types = ['Background', 'Gingerbread', 'Coconutmilk'] # 将图片标注json文件批量生成训练所需的标签图像png imgpath_list = os.listdir('data_mask/images') for img_path in imgpath_list: img_name = img_path.split('.')[0] img = cv2.imread(os.path.join('data_mask/images', img_path)) h, w = img.shape[:2] # 创建一个大小和原图相同的空白图像 mask = np.zeros([h, w, 1], np.uint8) with open('data_mask/jsons/'+img_name+'.json', encoding='utf-8') as f: label = json.load(f) shapes = label['shapes'] for shape in shapes: category = shape['label'] points = shape['points'] # 将图像标记填充至空白图像 points_array = np.array(points, dtype=np.int32) mask = cv2.fillPoly(mask, [points_array], category_types.index(category)) # 生成的标注图像必须为png格式 cv2.imwrite('data_mask/masks/'+img_name+'.png', mask)

此时,我们打开查看生成的标注图像,但是,会发现:怎么是全黑的图像? 在这里插入图片描述 此时,千万不要怀疑,因为这就是正确的标注图像。该标注图像包含背景和我们设定的两个标签,背景位置对应的像素值应该为0,标签图像对应的像素值应该为1或2,该图像只包含0、1和2,因为1和2这个像素值太小了,无法看清,如果打开图像查看的话,就是一个全黑的图像。

如果想查看某个类别的标注情况,可以在mask = cv2.fillPoly(mask, [points_array], category_types.index(category))中将该颜色改为一个清晰可见的颜色,例如将Gingerbread的像素值改成125,将Coconutmilk的像素值改为255,就可以清晰的看到我们标注的图像了。但是最终制作标签的时候要记得将其改回对应的索引值。

for shape in shapes: category = shape['label'] points = shape['points'] # 将图像标记填充至空白图像 points_array = np.array(points, dtype=np.int32) # mask = cv2.fillPoly(mask, [points_array], category_types.index(category)) if category == 'Gingerbread': # 调试时将某种标注的填充颜色改为255,便于查看用,实际时不需进行该操作 mask = cv2.fillPoly(mask, [points_array], 125) elif category == 'Coconutmilk': mask = cv2.fillPoly(mask, [points_array], 255) else: mask = cv2.fillPoly(mask, [points_array], category_types.index(category)) cv2.imshow('mask', mask) cv2.waitKey(0)

在这里插入图片描述

2.3.1.3 数据集分类

以上步骤完成后,我们需将数据集分为训练集、验证集和测试集进行训练,在这里划分数据集的代码我也写好了,按照7:2:1进行划分,代码如下:

import os import random import shutil ''' ├── data(按照7:2:1比例划分) │ ├── train 存放用于训练的图片 │ ├── trainannot 存放用于训练的图片标注 │ ├── val 存放用于验证的图片 │ ├── valannot 存放用于验证的图片标注 │ ├── test 存放用于测试的图片 │ ├── testannot 存放用于测试的图片标注 ''' # 创建数据集文件夹 dirpath_list = ['data/train', 'data/trainannot', 'data/val', 'data/valannot', 'data/test', 'data/testannot'] for dirpath in dirpath_list: if os.path.exists(dirpath): shutil.rmtree(dirpath) # 删除原有的文件夹 os.makedirs(dirpath) # 创建文件夹 elif not os.path.exists(dirpath): os.makedirs(dirpath) # 训练集、验证集、测试集所占比例 train_percent = 0.7 val_percent = 0.2 test_percent = 0.1 # 数据集原始图片所存放的文件夹,必须为png文件 imagefilepath = 'data_mask/images' total_img = os.listdir(imagefilepath) # 所有数据集的图片名列表 total_name_list = [row.split('.')[0] for row in total_img] num = len(total_name_list) num_list = range(num) # 训练集、验证集、测试集所包含的图片数目 train_tol = int(num * train_percent) val_tol = int(num * val_percent) test_tol = int(num * test_percent) # 训练集在total_name_list中的index train_numlist = random.sample(num_list, train_tol) # 验证集在total_name_list中的index val_test_numlist = list(set(num_list) - set(train_numlist)) val_numlist = random.sample(val_test_numlist, val_tol) # 测试集在total_name_list中的index test_numlist = list(set(val_test_numlist) - set(val_numlist)) # 将数据集和标签图片安装分类情况依次复制到对应的文件夹 for i in train_numlist: img_path = 'data_mask/images/'+total_name_list[i]+'.png' new_path = 'data/train/'+total_name_list[i]+'.png' shutil.copy(img_path, new_path) img_path = 'data_mask/masks/' + total_name_list[i] + '.png' new_path = 'data/trainannot/' + total_name_list[i] + '.png' shutil.copy(img_path, new_path) for i in val_numlist: img_path = 'data_mask/images/'+total_name_list[i]+'.png' new_path = 'data/val/'+total_name_list[i]+'.png' shutil.copy(img_path, new_path) img_path = 'data_mask/masks/' + total_name_list[i] + '.png' new_path = 'data/valannot/' + total_name_list[i] + '.png' shutil.copy(img_path, new_path) for i in test_numlist: img_path = 'data_mask/images/'+total_name_list[i]+'.png' new_path = 'data/test/'+total_name_list[i]+'.png' shutil.copy(img_path, new_path) img_path = 'data_mask/masks/' + total_name_list[i] + '.png' new_path = 'data/testannot/' + total_name_list[i] + '.png' shutil.copy(img_path, new_path) 2.3.2 标注方式二:像素级涂抹

使用工具:精灵标注助手 精灵标注助手是制作数据集中比较容易上手的一款软件。

精灵标注助手官网下载链接: http://www.jinglingbiaozhu.com/ 在这里插入图片描述

在精灵标注助手中实际操作的过程如下: 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 像素级标注时会给你一个画笔,涂抹需要标注的区域并标记标签,最后导出标注文件,这个软件不好的一点是,导出的路径不可以自选,操作如下: 在这里插入图片描述 导出的标签如下,导出的标签是调色板图像,是无法作为数据集的标签的,因为没有背景像素值,需要进行处理,给背景补0即可。 在这里插入图片描述

3、基于RESNet50骨干的FCN语义分割模型实验 3.1 网络结构

RESNet50的结构如下: 在这里插入图片描述 基于RESNet50的FCN结构: 在这里插入图片描述

3.2 代码实现 3.2.1 RESNet主干

在这里插入图片描述

3.2.2 改进膨胀卷积的残差模块

在这里插入图片描述

3.2.2 FCN的思想

在这里插入图片描述

3.3 训练及测试

完整项目代码地址(百度网盘): 链接: https://pan.baidu.com/s/1HQgce250IuG-I61UY2JNgw 提取码:uy5f

运行train.py即可开始训练(修改里面的相关配置参数以及RESNet的预训练权重) 在这里插入图片描述 测试结果: 在这里插入图片描述

参考连接

https://blog.csdn.net/oJiWuXuan/article/details/119569038 https://blog.csdn.net/qq_40280673/article/details/123682672



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3