前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Keras上的分段模型和实施库进行道路检测

使用Keras上的分段模型和实施库进行道路检测

作者头像
代码医生工作室
发布2019-08-29 18:05:36
1.7K0
发布2019-08-29 18:05:36
举报
文章被收录于专栏:相约机器人相约机器人

作者 | Insaf Ashrapov

来源 | googleblog

编辑 | 代码医生团队

在本文中,将展示如何编写自己的数据生成器以及如何使用albumentations作为扩充库。与segmentation_models库一起,它为Unet和其他类似unet的架构提供了数十个预训练。有关完整代码,请访问Github。

https://github.com/Diyago/ML-DL-scripts/tree/master/DEEP%20LEARNING/segmentation/Segmentation%20pipeline

理论

语义图像分割的任务是用相应的所表示的类标记图像的每个像素。对于这样的任务,具有不同改进的Unet架构已经显示出最佳结果。它背后的核心思想只是几个卷积块,它们提取深度和不同类型的图像特征,接着是所谓的反卷积或上采样块,它们恢复了输入图像的初始形状。除了在每个卷积层之后,还有一些跳过连接,这有助于网络记住初始图像并帮助防止渐变渐变。有关更多详细信息,请阅读arxiv文章。

https://arxiv.org/abs/1505.04597

数据集 - 卫星图像

对于分段,不需要太多数据就能获得不错的结果,即使是100张带注释的照片也足够了。目前,将使用来自Massachusetts Roads Dataset ,大约有1100多个带注释的列车图像,它们甚至提供验证和测试数据集。不幸的是,没有下载按钮,所以必须使用脚本。此脚本将完成工作(可能需要一些时间才能完成)。

https://www.cs.toronto.edu/~vmnih/data/

https://gist.github.com/Diyago/83919fcaa9ca46e4fcaa632009ec2dbd

来看看图片示例:

马萨诸塞州道路数据集图像和地面真相掩模ex。

注释和图像质量似乎相当不错,网络应该能够检测道路。

库安装

首先,需要安装带有TensorFlow的Keras。对于Unet构造,将使用Pavel Yakubovskiy的库名为segmentation_models,用于数据扩充albumentation库。稍后会详细介绍它们。两个库都经常更新,所以更喜欢直接从git更新它们。

代码语言:javascript
复制
conda install -c conda-forge keras
pip install git + https://github.com/qubvel/efficientnet
pip install git + https://github.com/qubvel/classification_models.git
pip install git + https://github.com/qubvel/segmentation_models
pip install git + https://github.com/albu/albumentations
pip install tta-wrapper

定义数据生成器

作为数据生成器,将使用自定义生成器。它应该继承keras.utils.Sequence并且应该定义这样的方法:

  • __init__(类初始化)
  • __len __(返回数据集的长度)
  • on_epoch_end(时代末期的行为)
  • __getitem__(生成的批处理用于送入网络)

使用自定义生成器的一个主要优点是,可以使用拥有的每种格式数据,并且可以执行任何操作 - 只是不要忘记为keras生成所需的输出(批处理)。

这里定义__init__方法。它的主要部分是为图像设置路径(self.image_filenames)和掩码名称(self.mask_names)。不要忘记对它们进行排序,因为对于self.image_filenames [i]相应的掩码应该是self.mask_names [i]。

代码语言:javascript
复制
def __init__(self, root_dir=r'../data/val_test', image_folder='img/', mask_folder='masks/',
             batch_size=1, image_size=768, nb_y_features=1,
             augmentation=None,
             suffle=True):
    self.image_filenames = listdir_fullpath(os.path.join(root_dir, image_folder))
    self.mask_names = listdir_fullpath(os.path.join(root_dir, mask_folder))
    self.batch_size = batch_size
    self.augmentation = augmentation
    self.image_size = image_size
    self.nb_y_features = nb_y_features
    self.suffle = suffle
        
def listdir_fullpath(d):
return np.sort([os.path.join(d, f) for f in os.listdir(d)])

下一件重要的事__getitem__。通常,不能将所有图像存储在RAM中,因此每次生成新的一批数据时,都应该读取相应的图像。下面定义训练方法。为此创建一个空的numpy数组(np.empty),它将存储图像和掩码。然后通过read_image_mask方法读取图像,将增强应用到每对图像和蒙版中。最后返回批处理(X,y),它已准备好安装到网络中。

代码语言:javascript
复制
def __getitem__(self, index):
      data_index_min = int(index*self.batch_size)
      data_index_max = int(min((index+1)*self.batch_size, len(self.image_filenames)))
 
      indexes = self.image_filenames[data_index_min:data_index_max]
      this_batch_size = len(indexes) # The last batch can be smaller than the others
 
      X = np.empty((this_batch_size, self.image_size, self.image_size, 3), dtype=np.float32)
      y = np.empty((this_batch_size, self.image_size, self.image_size, self.nb_y_features), dtype=np.uint8)
 
      for i, sample_index in enumerate(indexes):
 
          X_sample, y_sample = self.read_image_mask(self.image_filenames[index * self.batch_size + i],
                                                  self.mask_names[index * self.batch_size + i])
 
          # if augmentation is defined, we assume its a train set
          if self.augmentation is not None:
 
              # Augmentation code
              augmented = self.augmentation(self.image_size)(image=X_sample, mask=y_sample)
              image_augm = augmented['image']
              mask_augm = augmented['mask'].reshape(self.image_size, self.image_size, self.nb_y_features)
              # divide by 255 to normalize images from 0 to 1
              X[i, ...] = image_augm/255
              y[i, ...] = mask_augm
          else:
              ...
  return X, y

接下来,定义生成器,它们将被送入网络:

代码语言:javascript
复制
test_generator = DataGeneratorFolder(root_dir = './data/road_segmentation_ideal/training',
                           image_folder = 'input/',
                           mask_folder = 'output/',
                           nb_y_features = 1)
 
train_generator = DataGeneratorFolder(root_dir = './data/road_segmentation_ideal/training',
                                      image_folder = 'input/',
                                      mask_folder = 'output/',
                                      batch_size=4,
                                      image_size=512,
                                      nb_y_features = 1, augmentation = aug_with_crop)

数据增强- albumentations

数据增强是一种策略,可以显着增加可用于训练模型的数据的多样性,而无需实际收集新数据。它有助于防止过度拟合并使模型更加健壮。

有很多用于此类任务的库:imaging,augmentor,solt,keras / pytorch的内置方法,或者可以使用OpenCV库编写自定义扩充。但我强烈推荐albumentations库。它使用起来非常快速和方便。有关用法示例,请访问官方存储库或查看示例笔记本。

https://github.com/albu/albumentations

https://github.com/albu/albumentations/tree/master/notebooks

在任务中,将使用基本的扩充,例如翻转和对比与非平凡的ElasticTransform。可以在上图中的示例。

代码语言:javascript
复制
def aug_with_crop(image_size = 256, crop_prob = 1):
    return Compose([
        RandomCrop(width = image_size, height = image_size, p=crop_prob),
        HorizontalFlip(p=0.5),
        VerticalFlip(p=0.5),
        RandomRotate90(p=0.5),
        Transpose(p=0.5),
        ShiftScaleRotate(shift_limit=0.01, scale_limit=0.04, rotate_limit=0, p=0.25),
        RandomBrightnessContrast(p=0.5),
        RandomGamma(p=0.25),
        IAAEmboss(p=0.25),
        Blur(p=0.01, blur_limit = 3),
        OneOf([
            ElasticTransform(p=0.5, alpha=120, sigma=120 * 0.05, alpha_affine=120 * 0.03),
            GridDistortion(p=0.5),
            OpticalDistortion(p=1, distort_limit=2, shift_limit=0.5)                  
        ], p=0.8)
], p = 1)

定义所需的扩充后,可以轻松获得输出:

代码语言:javascript
复制
augmented = aug_with_crop(image_size = 1024)(image=img, mask=mask)
image_aug = augmented['image']
mask_aug = augmented['mask']

回调

将使用常见的回调:

  • ModelCheckpoint - 允许在训练时保存模型的权重
  • ReduceLROnPlateau - 如果验证指标停止增加,则减少训练
  • EarlyStopping - 一旦验证指标停止增加几个时期,就停止训练
  • TensorBoard - 监控训练进度的好方法。链接到官方文档

https://www.tensorflow.org/tensorboard/r2/scalars_and_keras

代码语言:javascript
复制
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping, TensorBoard
 
# reduces learning rate on plateau
lr_reducer = ReduceLROnPlateau(factor=0.1,
                               cooldown= 10,
                               patience=10,verbose =1,
                               min_lr=0.1e-5)
# model autosave callbacks
mode_autosave = ModelCheckpoint("./weights/road_crop.efficientnetb0imgsize.h5",
                                monitor='val_iou_score',
                                mode='max', save_best_only=True, verbose=1, period=10)
 
# stop learining as metric on validatopn stop increasing
early_stopping = EarlyStopping(patience=10, verbose=1, mode = 'auto')
 
# tensorboard for monitoring logs
tensorboard = TensorBoard(log_dir='./logs/tenboard', histogram_freq=0,
                          write_graph=True, write_images=False)
 
callbacks = [mode_autosave, lr_reducer, tensorboard, early_stopping]

训练

作为模型,将使用Unet。最简单的使用方法是从segmentation_models库中获取。

https://github.com/qubvel/segmentation_models

  • backbone_name:用作编码器的分类模型的名称。EfficientNet目前在分类模型中是最先进的,所以尝试一下。虽然它应该提供更快的推理并且具有更少的训练参数,但它比着名的resnet模型消耗更多的GPU内存。还有很多其他选择可供尝试
  • encoder_weights - 使用imagenet权重加速训练
  • encoder_freeze:如果为True,则将编码器(骨干模型)的所有层设置为不可训练的。首先冻结和训练模型然后解冻可能是有用的
  • decoder_filters - 可以指定解码器块的数量。在某些情况下,具有简化解码器的较重编码器可能是有用的。

初始化Unet模型后,应该编译它。此外将IOU(交叉联合)设置为将监控的度量和bce_jaccard_loss(二进制交叉熵加jaccard损失)作为将优化的损失。

代码语言:javascript
复制
model = Unet(backbone_name = 'efficientnetb0', encoder_weights='imagenet', encoder_freeze = False)
model.compile(optimizer = Adam(), loss=bce_jaccard_loss, metrics=[iou_score])
 
history = model.fit_generator(train_generator, shuffle =True,
                  epochs=50, workers=4, use_multiprocessing=True,
                  validation_data = test_generator,
                  verbose = 1, callbacks=callbacks)

开始训练后,可以查看张量板日志。可以很好地看到模型训练,即使在50个时代之后也没有达到全球/地方的最佳状态。

Tensorboard日志

损失和IOU指标历史记录

推理

因此在验证时有0.558 IOU,但是每个像素预测都高于0,将其视为掩码。通过选择适当的阈值,可以进一步将结果提高0.039(7%)。

验证阈值调整

度量标准确实非常有趣,但更具洞察力的模型预测。从下面的图片中看到网络很好地完成了任务,这很棒。对于推理代码和计算指标,可以阅读完整代码。

https://github.com/Diyago/ML-DL-scripts/tree/master/DEEP%20LEARNING/segmentation/Segmentation%20pipeline

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 相约机器人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档