Unet实战-定位图片中猫的位置

最近在研究语义分割,于是想用它做点有趣的事情,很喜欢狗狗的我决定用它来识别图片中猫的位置。我会介绍从原始数据到结果的整个过程,整个过程包括 数据标注-模型训练-验证结果

数据标注

在工业应用中,我们拿到的原始数据一般都是不带标注的,不像参加竞赛给你的都是打好标注的干净的数据。所以你可能没有很多时间去优化算法,大部分时间都是在处理数据上,包括数据的清洗,预处理,数据标注等等,如果数据量比较大的话可能需要雇佣第三方平台来做数据标注工作。

这次我使用的数据集是 kaggle 猫狗分类 比赛的数据集,比赛的内容是区分图片中是猫还是狗,所以数据集只标注了每张图片的类别,而语义分割需要对每个像素做标注。由于我的时间有限(主要是手酸),我挑选了其中近 200 张猫的图像进行标注,就这 200 张也标了我好几个小时,T. T。

介绍一个专门做标注的工具 - labelme,github 地址: https://github.com/wkentaro/labelme

直接使用 pip 安装:

pip install labelme

安装完成之后 cmd 输入 labelme 就启动了(我是 windows 操作系统)。用法如下:

  • 第一步,选择打开文件或者文件夹,打开文件夹的话会自动遍历文件夹下所有图片。
image
  • 第二步,选择标注的形状,图中框出来的是多边形(Polygon),你也可以点击菜单栏上的 edit 选择其他图形例如矩形(Rectangle),圆形(Circle)等。多边形是我最常用的。

[站外图片上传中...(image-dd5577-1568188101476)]

  • 第三步,标注完成之后点击保存,会将标注信息保存为 json 格式。

  • 第四步,将保存的 json 文件转换为 image 以及 label 图像,labelme 自带了转换工具,运行下面的 python 代码可以将指定文件夹下所有 json 文件统一转换。

import os
from glob import glob

json_files = glob(os.path.join(r"your_dir", "*.json"))
for json_file in json_files:
    os.system(f"labelme_json_to_dataset {json_file} -o {''.join(json_file.split('.')[:-1])}")

转换之后每个 json 文件会生成一个对应的文件夹,文件夹下包括了 image.png-原图,label.png-标注图像,label_viz.png-标注可视化图像:

image

至此,数据的准备工作就完成了,下面我们用代码来读取这些数据并进行训练。

模型训练

环境:

  • Win10
  • python 3.7.3
  • pytorch 1.1.0
  • cuda 10.1

首先构建数据类 CatDataset,负责读取图像数据以及数据增强,这里我只用了简单的水平和垂直翻转:

def get_transforms(phase, mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)):
    list_transforms = []
    if phase == "train":
        list_transforms.extend(
            [
                HorizontalFlip(),
                VerticalFlip()
            ]
        )
    list_transforms.extend(
        [
            Resize(128, 128, interpolation=Image.BILINEAR),
            Normalize(mean=mean, std=std, p=1),
            ToTensor(),
        ]
    )
    list_trfms = Compose(list_transforms)
    return list_trfms

class CatDataset(Dataset):
    def __init__(self, idx, image_list, phase="train"):
        assert phase in ("train", "val", "test")
        self.idx = idx
        self.image_list = image_list
        self.phase = phase

        self.transform = get_transforms(phase)

    def __getitem__(self, index):
        real_idx = self.idx[index]
        image_path = os.path.join(self.image_list[real_idx], "img.png")
        mask_path = os.path.join(self.image_list[real_idx], "label.png")

        image = cv2.imread(image_path)
        mask = cv2.imread(mask_path)


        augmented = self.transform(image=image, mask=mask/128)

        return augmented["image"], augmented["mask"][:, :, :, 2]

    def __len__(self):
        return len(self.idx)
        

模型选择之前刚研究过的 Unet,由于数据量较少,在选用 U-Net 的 backbone 时应该避免参数量太大的模型,所以我先选择了 Resnet-18 做为 Encoder 部分。

import segmentation_models_pytorch as smp
model = smp.Unet('resnet18', classes=1, activation=None)

给大家介绍一个 pytorch 写的第三方库(没错我就是调库工程师): segmentation_models_pytorch,该库把基于 Encoder-Decoder 的几个语义分割模型做了一个整合,包括 FPN,UNet,PSPNet 等,你可以根据自己的需要配置这些模型的 Encoder 部分,支持包括 Resnet,DenseNet,VGG 等 backbone。注意安装时要从 github 上下载源码安装,用 pip 安装会有小 bug,github 地址: https://github.com/qubvel/segmentation_models.pytorch

数据以及模型准备完毕,下面可以开始训练了,我的训练参数:

  • batch size=32
  • lr=1e-3
  • optimizer=Adam
  • loss=BCEWithLogitsLoss
  • epoch=30

因为数据量比较小,训练只花了一首歌的时间,在经过了 30 轮训练之后,模型收敛,可以看看模型的表现了。

训练结果

Resnet18 做为 backbone 的模型训练结果如下,最好的表现在第 15 个 epoch,训练集上的 IoU 为 0.8777,验证集为 0.8583。

Starting epoch: 15 | phase: train | ⏰: 08:48:16

Loss: 0.127440 | IoU: 0.8777 | dice: 0.9312 | dice_neg: 0.0000 | dice_pos: 0.9312

Starting epoch: 15 | phase: val | ⏰: 08:48:20

Loss: 0.165582 | IoU: 0.8583 | dice: 0.9203 | dice_neg: 0.0000 | dice_pos: 0.9203

******** New optimal found, saving state ********

发现训练集拟合的不够好,于是尝试用一个稍复杂点的 backbone - Resnet50,训练大概花了一首 《情歌王》的时间。结果如下:

Starting epoch: 40 | phase: train | ⏰: 09:25:38

Loss: 0.073017 | IoU: 0.9285 | dice: 0.9624 | dice_neg: 0.0000 | dice_pos: 0.9624

Starting epoch: 40 | phase: val | ⏰: 09:25:43

Loss: 0.150036 | IoU: 0.8785 | dice: 0.9330 | dice_neg: 0.0000 | dice_pos: 0.9330

******** New optimal found, saving state ********

表现有明显的提升,且计算时间也在我们可以接受的范围内,这个模型是更加适合的。我们在选择模型的时候,需要综合考虑其计算量与精度,在两者之间达到平衡是最好的。

这个模型收敛用了 40 个 epoch,这很符合常理,因为更复杂的模型意味这更多的参数,在训练时需要训练更多 epoch 才能收敛。

我在验证集上用 visdom 做了可视化,见证奇迹的时刻:

image

图中黑白的就是模型预测的猫的位置,在训练集只有 200 张图片不到的情况下,模型的表现已经很不错了。当然还有很大的提升空间,可以尝试的包括增加数据集,更复杂的模型,多模型融合等方法。

整个项目的代码已经放在我的 github,欢迎 star,地址: https://github.com/FroyoZzz/CV-Papers-Codes

最后,求赞求关注,欢迎关注我的微信公众号[MachineLearning学习之路] ,深度学习 & CV 方向的童鞋不要错过!!

你可能感兴趣的:(Unet实战-定位图片中猫的位置)