当前位置:   article > 正文

CFPNet:用于实时语义分割的通道特征金字塔

cfpnet

论文地址:CFPNet: Channel-wise Feature Pyramid for Real-Time Semantic Segmentation

代码地址: https://github.com/chukai123/CFPNet

目录

1、摘要

2、本文的主要贡献

3、background

3.1、Inception module

4、the proposed method

4.1、CFP模块

 5、实验部分

5.1、数据集

5.2、实验结果比较

 6、模型代码


1、摘要

本文为了实现更好的性能,模型尺寸和推断速度,提出了channel-wise feature pyramid(CFP)模块。并且基于CFP模块,构建了CFPNet用于实时语义分割,其中采用了一系列dliate卷积通道来提取有效特征。

Cityscapes数据集中,CFPNet取得了70.1%的class-wise mIoU,并且只有0.55亿参数和2.5 MB内存。推断速度可以在单个rtx 2080ti gpu上达到30 fps,图像为1024×2048像素。

class-wise mIoU:在cityscapes中,class表示19个小类别,而mIoU表示先计算每个类别的IoU,然后对所有类别的IoU取平均即可;

category-wise (mIoU):一般是指大类别;

mIoU:针对语义分割的一个评估指标,平均交并比Mean Intersection over Union:

从上图可以看出,IoU就是分子中重叠的蓝色部分/分母中的蓝色部分减去重叠的蓝色块的比值,然后对每个类别的IoU取平均即可得到mIoU。公式如下:

mIoU=\frac{1}{k}\sum_{i=1}^{K}\frac{P\bigcap G }{P\bigcup G}

其中K表示类别个数,P表示预测集,G表示真实集;

mIoU代码实现:

(1)可以看作是分类任务,借助混淆矩阵计算mIoU

  1. from sklearn.metrics import confusion_matrix
  2. import numpy as np
  3. def miou(y_true, y_pred):
  4. # y_true表示真实值,y_pred表示预测
  5. com = confusion_matrix(y_true, y_pred)
  6. TP = np.diag(cm) # 混淆矩阵中的对角线部分,表示预测对的数量
  7. FP = com.sum(axis=0) - TP
  8. FN = com.sum(axis=1) - TP
  9. return np.mean(TP / (FN + FP + TP + np.finfo(float).eps))

(2)numpy计算版本:可以参考https://github.com/dilligencer-zrj/code_zoo/blob/master/compute_mIOU

  1. #设标签宽W,长H
  2. def fast_hist(a, b, n):#a是转化成一维数组的标签,形状(H×W,);b是转化成一维数组的预测特征图,形状(H×W,);n是类别数目
  3. k = (a > 0) & (a <= n) #k是一个一维bool数组,形状(H×W,);目的是找出标签中需要计算的类别(去掉了背景),假设0是背景
  4. return np.bincount(n * a[k].astype(int) + b[k], minlength=n ** 2).reshape(n, n)
  5. def per_class_iu(hist):#分别为每个类别(在这里是19类)计算mIoU,hist的形状(n, n)
  6. '''
  7. 核心代码
  8. '''
  9. return np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist))#矩阵的对角线上的值组成的一维数组/矩阵的所有元素之和,返回值形状(n,)
  10. def compute_mIoU(pred,label,n_classes = args.num_class):
  11. hist = np.zeros((num_classes, n_classes))#hist初始化为全零,在这里的hist的形状是[n_classes, n_classes]
  12. hist += fast_hist(label.flatten(), pred.flatten(), n_classes) #对一张图片计算 n_classes×n_classes 的hist矩阵,并累加
  13. mIoUs = per_class_iu(hist)#计算逐类别mIoU值
  14. for ind_class in range(n_classes):#逐类别输出一下mIoU值
  15. print(str(round(mIoUs[ind_class] * 100, 2)))
  16. print('===> mIoU: ' + str(round(np.nanmean(mIoUs) * 100, 2)))#在所有验证集图像上求所有类别平均的mIoU值,计算时忽略NaN值
  17. return mIoUs

FPS:frame per second,检测器每秒能处理的图像张数。就是跟踪算法每秒钟给出多少张图片的跟踪结果。实时性一般fps>=30就表示具有实时性了。fps越高表示效率越高。省机器,省钱。

  1. # Copyright (c) OpenMMLab. All rights reserved.
  2. import argparse
  3. import copy
  4. import os
  5. import time
  6. import torch
  7. from mmcv import Config, DictAction
  8. from mmcv.cnn import fuse_conv_bn
  9. from mmcv.parallel import MMDistributedDataParallel
  10. from mmcv.runner import init_dist, load_checkpoint, wrap_fp16_model
  11. from mmdet.datasets import (build_dataloader, build_dataset,
  12. replace_ImageToTensor)
  13. from mmdet.models import build_detector
  14. from mmdet.utils import update_data_root
  15. def parse_args():
  16. parser = argparse.ArgumentParser(description='MMDet benchmark a model')
  17. parser.add_argument('config', help='test config file path')
  18. parser.add_argument('checkpoint', help='checkpoint file')
  19. parser.add_argument(
  20. '--repeat-num',
  21. type=int,
  22. default=1,
  23. help='number of repeat times of measurement for averaging the results')
  24. parser.add_argument(
  25. '--max-iter', type=int, default=2000, help='num of max iter')
  26. parser.add_argument(
  27. '--log-interval', type=int, default=50, help='interval of logging')
  28. parser.add_argument(
  29. '--fuse-conv-bn',
  30. action='store_true',
  31. help='Whether to fuse conv and bn, this will slightly increase'
  32. 'the inference speed')
  33. parser.add_argument(
  34. '--cfg-options',
  35. nargs='+',
  36. action=DictAction,
  37. help='override some settings in the used config, the key-value pair '
  38. 'in xxx=yyy format will be merged into config file. If the value to '
  39. 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
  40. 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
  41. 'Note that the quotation marks are necessary and that no white space '
  42. 'is allowed.')
  43. parser.add_argument(
  44. '--launcher',
  45. choices=['none', 'pytorch', 'slurm', 'mpi'],
  46. default='none',
  47. help='job launcher')
  48. parser.add_argument('--local_rank', type=int, default=0)
  49. args = parser.parse_args()
  50. if 'LOCAL_RANK' not in os.environ:
  51. os.environ['LOCAL_RANK'] = str(args.local_rank)
  52. return args
  53. def measure_inference_speed(cfg, checkpoint, max_iter, log_interval,
  54. is_fuse_conv_bn):
  55. # set cudnn_benchmark
  56. if cfg.get('cudnn_benchmark', False):
  57. torch.backends.cudnn.benchmark = True
  58. cfg.model.pretrained = None
  59. cfg.data.test.test_mode = True
  60. # build the dataloader
  61. samples_per_gpu = cfg.data.test.pop('samples_per_gpu', 1)
  62. if samples_per_gpu > 1:
  63. # Replace 'ImageToTensor' to 'DefaultFormatBundle'
  64. cfg.data.test.pipeline = replace_ImageToTensor(cfg.data.test.pipeline)
  65. dataset = build_dataset(cfg.data.test)
  66. data_loader = build_dataloader(
  67. dataset,
  68. samples_per_gpu=1,
  69. # Because multiple processes will occupy additional CPU resources,
  70. # FPS statistics will be more unstable when workers_per_gpu is not 0.
  71. # It is reasonable to set workers_per_gpu to 0.
  72. workers_per_gpu=0,
  73. dist=True,
  74. shuffle=False)
  75. # build the model and load checkpoint
  76. cfg.model.train_cfg = None
  77. model = build_detector(cfg.model, test_cfg=cfg.get('test_cfg'))
  78. fp16_cfg = cfg.get('fp16', None)
  79. if fp16_cfg is not None:
  80. wrap_fp16_model(model)
  81. load_checkpoint(model, checkpoint, map_location='cpu')
  82. if is_fuse_conv_bn:
  83. model = fuse_conv_bn(model)
  84. model = MMDistributedDataParallel(
  85. model.cuda(),
  86. device_ids=[torch.cuda.current_device()],
  87. broadcast_buffers=False)
  88. model.eval()
  89. # the first several iterations may be very slow so skip them
  90. num_warmup = 5
  91. pure_inf_time = 0
  92. fps = 0
  93. # benchmark with 2000 image and take the average
  94. for i, data in enumerate(data_loader):
  95. torch.cuda.synchronize()
  96. start_time = time.perf_counter()
  97. with torch.no_grad():
  98. model(return_loss=False, rescale=True, **data)
  99. torch.cuda.synchronize()
  100. elapsed = time.perf_counter() - start_time
  101. if i >= num_warmup:
  102. pure_inf_time += elapsed
  103. if (i + 1) % log_interval == 0:
  104. fps = (i + 1 - num_warmup) / pure_inf_time
  105. print(
  106. f'Done image [{i + 1:<3}/ {max_iter}], '
  107. f'fps: {fps:.1f} img / s, '
  108. f'times per image: {1000 / fps:.1f} ms / img',
  109. flush=True)
  110. if (i + 1) == max_iter:
  111. fps = (i + 1 - num_warmup) / pure_inf_time
  112. print(
  113. f'Overall fps: {fps:.1f} img / s, '
  114. f'times per image: {1000 / fps:.1f} ms / img',
  115. flush=True)
  116. break
  117. return fps
  118. def repeat_measure_inference_speed(cfg,
  119. checkpoint,
  120. max_iter,
  121. log_interval,
  122. is_fuse_conv_bn,
  123. repeat_num=1):
  124. assert repeat_num >= 1
  125. fps_list = []
  126. for _ in range(repeat_num):
  127. #
  128. cp_cfg = copy.deepcopy(cfg)
  129. fps_list.append(
  130. measure_inference_speed(cp_cfg, checkpoint, max_iter, log_interval,
  131. is_fuse_conv_bn))
  132. if repeat_num > 1:
  133. fps_list_ = [round(fps, 1) for fps in fps_list]
  134. times_pre_image_list_ = [round(1000 / fps, 1) for fps in fps_list]
  135. mean_fps_ = sum(fps_list_) / len(fps_list_)
  136. mean_times_pre_image_ = sum(times_pre_image_list_) / len(
  137. times_pre_image_list_)
  138. print(
  139. f'Overall fps: {fps_list_}[{mean_fps_:.1f}] img / s, '
  140. f'times per image: '
  141. f'{times_pre_image_list_}[{mean_times_pre_image_:.1f}] ms / img',
  142. flush=True)
  143. return fps_list
  144. return fps_list[0]
  145. def main():
  146. args = parse_args()
  147. cfg = Config.fromfile(args.config)
  148. # update data root according to MMDET_DATASETS
  149. update_data_root(cfg)
  150. if args.cfg_options is not None:
  151. cfg.merge_from_dict(args.cfg_options)
  152. if args.launcher == 'none':
  153. raise NotImplementedError('Only supports distributed mode')
  154. else:
  155. init_dist(args.launcher, **cfg.dist_params)
  156. repeat_measure_inference_speed(cfg, args.checkpoint, args.max_iter,
  157. args.log_interval, args.fuse_conv_bn,
  158. args.repeat_num)
  159. if __name__ == '__main__':
  160. main()

2、本文的主要贡献

  1. 提出了一个结合了 Inception 模块和空洞卷积(dliate)的模块,被称为 Channel-wise Feature Pyramid (CFP) 模块。 该模块联合提取各种尺寸的特征图和上下文信息,显著减少参数数量和模型尺寸。
  2. 基于 CFP 模块设计了 Channel-wise Feature Pyramid Network (CFPNet)。 它比现有的最先进的实时语义分割网络具有更少的参数和更好的性能。
  3. 在没有任何上下文模块、预训练模型或后处理的情况下,在 Cityscapes 和 CamVid 基准测试中都取得了极具竞争力的结果。 使用更少的参数,所提出的 CFPNet 大大优于现有的分割网络。 它可以在单个 RTX 2080Ti GPU 上以 30 FPS 处理高分辨率图像 (1024×2048),在 Cityscapes 测试数据集上产生 70.1% 的class-wise和 87.4% 的category-wise平均交并比 (mIoU) 55 万个参数。

多尺度卷积:

多尺度卷积层就是用不同大小的卷积核对某一时刻所得到的特征图进行卷积操作,得到新的大小不同的特征图,之后针对不同大小的特征图上采样到输入特征图的大小。也就是说,多尺度卷积层不会改变原有特征图的大小,只是通过不同卷积核的卷积操作,丰富了图像的特征,从全局的视角对图像中的感兴趣的特征信息进行编码解码,进而提高图像的分割性能。
参考:https://zhuanlan.zhihu.com/p/451122397
 

dliate convolution:空洞卷积

具有单一扩张率的空洞卷积可以提取全局信息,但可能会丢失局部特征。许多模型都采用空洞卷积来构建空间特征金字塔来提取多尺度特征。本文在CFP模块的每个通道中都采用了空洞卷积。

具有dilation rate为r的n×n的空洞卷积核的有效大小为:[

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/346049
推荐阅读
相关标签