【ODHead】CenterNet代码分析

CenterNet 全称为 Objects as Points,论文的分析见链接

特点:

  • 无需 NMS 后处理(非多尺度的情况下才是无NMS)
  • 强任务扩展的框架,可以将大部分任务都归纳为预测中心+基于中心点的偏移属性,例如目标检测是中心点+基于中心点的宽高属性偏移;关键点检测是中心点+基于中心点的人体关键点偏移预测等等

1 算法核心实现

CenterNet 对于目标检测而言,其输出主要包括两条分支,一个是中心点 heatmap 回归分支;一个是基于中心点的宽高属性预测分支,为了提高中心点的预测精度,还引入了额外的 offset 回归分支,回归用于量化误差导致的中心点偏移。

1.1 Backbone

考虑采用 ResNet-18 作为复现的 base 模型,其配置为:

backbone=dict( 
    type='ResNet', depth=18, norm_eval=False, norm_cfg=dict(type='BN')) 

需要特别注意:由于 ResNet-18 模型比较小,而且 CenterNet 训练 epoch 非常长为140,所以最好是所有 BN 层都参与训练,故修改更改默认设置为 norm_eval=False。

1.2 Neck

为了代码解耦,我们将 CenterNet 模型也切分出了 Neck 模块,对应模型是 CTResNetNeck,主要完成上采样操作。

neck=dict( 
    type='CTResNetNeck', 
    in_channel=512, 
    num_deconv_filters=(256, 128, 64), 
    num_deconv_kernels=(4, 4, 4), 
    use_dcn=True), 

由于输入和输出特征图是相差 4 倍,ResNet-18 输出特征图最大是下采样 32 倍,故需要上采样 3 次。 为了提高性能,作者在上采样模块中引入了可变形卷积,实现结果表明提升了较多性能(从 26.0 提升到29.5),而且上采样模块是可学习的转置卷积,而非常用的双线性上采样模块。

layers = [] 
for i in range(len(num_deconv_filters)): 
    feat_channel = num_deconv_filters[i] 
    conv_module = ConvModule( 
        self.in_channel, 
        feat_channel, 
        3, 
        padding=1, 
        conv_cfg=dict(type='DCNv2') if self.use_dcn else None, 
        norm_cfg=dict(type='BN')) 
    layers.append(conv_module) 
    upsample_module = ConvModule( 
        feat_channel, 
        feat_channel, 
        num_deconv_kernels[i], 
        stride=2, 
        padding=1, 
        conv_cfg=dict(type='deconv'), # 转置卷积 
        norm_cfg=dict(type='BN')) 
    layers.append(upsample_module) 
    self.in_channel = feat_channel 
return nn.Sequential(*layers)  

需要注意一个细节:

if isinstance(m, nn.ConvTranspose2d): 
    # 采用 ConvTranspose2d 默认初始化方法 
    m.reset_parameters() 
    # 模拟双线性上采样 kernel 初始化 
    w = m.weight.data 
    f = math.ceil(w.size(2) / 2) 
    c = (2 * f - 1 - f % 2) / (2. * f) 
    for i in range(w.size(2)): 
        for j in range(w.size(3)): 
            w[0, 0, i, j] = \ 
                (1 - math.fabs(i / f - c)) * ( 
                        1 - math.fabs(j / f - c)) 
    for c in range(1, w.size(0)): 
        w[c, 0, :, :] = w[0, 0, :, :]  

上述初始化过程非常复杂,实际上作者是希望 ConvTranspose2d 初始化时候能够提供类似双线性上采样层功能,故其初始化参数设置为了双线性上采样核,有助于收敛。而 m.reset_parameters() 存在的原因是 MMDetection 中的 ConvModule 会修改掉原始 ConvTranspose2d 层的初始化方式。

1.3 Head

拆解出 Neck 后,Head 模块就非常简单了,输出三个特征图,分别是:

  • 高斯热图 (h/4, w/4, cls_nums),每个通道代表一个类别;
  • 宽高输出图 (h/4, w/4, 2),代表中心点距离左上边距值;
  • 中心点量化偏移图(h/4, w/4, 2):
def _build_head(self, in_channel, feat_channel, out_channel): 
 """Build head for each branch.""" 
 layer = nn.Sequential( 
        nn.Conv2d(in_channel, feat_channel, kernel_size=3, padding=1), 
        nn.ReLU(inplace=True), 
        nn.Conv2d(feat_channel, out_channel, kernel_size=1)) 
    return layer  

1.4 训练关键点预测网络(heatmap)

处理真值
对于 c 类的每个 ground truth 关键点 p ∈ R 2 p ∈ R^{2} pR2,我们计算一个低分辨率等价 p* = [p/R]。 然后,我们将所有的ground truth 关键点投射到热图上 Y ∈[0, 1]{ W/R × H/R ×C} ,使用的高斯核计算,其中 σp 是对象尺寸自适应标准差 [30]。
Y x y c = e x p ( − ( ( x − p ∗ x ) 2 + ( y − p ∗ y ) 2 ) / σ p 2 ) Y{xyc} = exp{(−((x−p*_x)^{ 2}+(y−p*_y )^{2})/σ^{2}_{p})} Yxyc=exp(((xpx)2+(ypy)2)/σp2)

如果同一类的两个高斯重叠,我们采用元素方式的最大值[4]。

训练目标:
a penalty-reduced pixelwise logistic regression,像素级别回归,
使用focal loss[33],其中 α 和 β 是焦点损失的超参数 [33],N 是图像 I 中关键点的数量。选择 N 的归一化以将所有正焦点损失实例归一化为 1。我们使用 α = 2 和 β = 4 在我们所有的实验中。

在这里插入图片描述

1.4 训练 offsetmap

为了恢复由输出步幅引起的离散化误差,我们额外预测了每个中心点的局部偏移 ^O ∈ R{W/R × H/R ×2}。 所有类 c 共享相同的偏移量预测。 使用 L1 损失训练偏移量。监督仅作用于关键点位置 ̃p,所有其他位置都被忽略

在这里插入图片描述

1.5 训练sizemap

在这里插入图片描述

1.6 整体的损失函数:

在这里插入图片描述

2 复现细节

2.1 收敛速度

CenterNet 虽然很好,但是收敛速度比较慢,和 RetinaNet 等模型相比收敛速度慢的太多了,常规算法都仅仅需要 12 epoch 就能得到比较好的结果,而 CenterNet 需要 140。这主要是因为其正样本太少了,宽高预测和 offset 预测分支仅仅在 gt bbox 中心才算 loss。在 COCO 数据集上,为了能够达到还不错的性能,作者采用了非常多的数据增强手段。

2.2 超参设置

因为 CenterNet 算法发布比较早且很实用,故基于源码也有很多更好的第三方复现,在阅读源码过程中以及参考第三方复现 https://github.com/FateScript/CenterNet-better,我们相应的对 CenterNet 超参进行了调整,细节如下:

  • 修复了源码中预训练模型均值和方差错误问题。torchvision 发布的模型均值和方差实际上和源码发布的不一样
  • 因为超长的训练时间以及参考现代目标检测优化器设置,我们直接采用了 SGD+Momentum+Warmup 优化器,而没有采用原始的 Adam,结果表明在 ResNet18,且含 DCNv2 模型上,SGD 跑出的性能是 29.7,而 Adam 是 29.1
  • 分布式训练中采用了 DDP 模式,而非源码中的 DP 当训练过程中某个 batch 内没有 gt bbox,那么 heatmap 分支会输出比较大的 Loss,导致梯度激增,后续难以得到比较好的性能,为了稳定训练过程,我们额外引入了梯度裁剪

经过上述改进,最终训练出来的 CenterNet 性能会比源码高大概 1.7 个点。如果想知道更多性能对比结果,可以参考 https://github.com/open-mmlab/mmdetection/pull/4602 。

在小模型 ResNet18-DCNv2 上多次训练,我们发现性能其实还是不太稳定,最低出现过 29.4 mAP,最高出现过 29.9 mAP,波动如此大的原因可能有多方面:

  • 小模型在出现 loss 波动大的情况下,后续很难稳定,特别是当 loss 波动出现在后期 CenterNet 数据增强比较多,可能会出现极端场景而影响最终性能

2.3 其他细节
We use three levels of test augmentations:

  • no augmentation,
  • flip augmentation,
  • flip and multi-scale (0.5, 0.75, 1, 1.25, 1.5) and For multi-scale, we use NMS to merge results.

以 ResNet18-DCNv2 为例,在作者所提的 flip 多尺度测试中,和常规的做法不同,其是在特征图上面进行平均,而不是单图得到 bbox 后,统一进行 nms 操作。为此,我们在 mmdet/models/detectors/centernet.py 中特意重写了这部分逻辑。

而且比较奇怪的是,三个输出分支中,正常来说应该是都要进行特征图平均,但是实际上 offset 分支是没有进行平均操作的。

center_hm = (center_hm[0:1] + self.flip_tensor(center_hm[1:2])) / 2
wh_hm = (wh_hm[0:1] + self.flip_tensor(wh_hm[1:2])) / 2
offset_hm = offset_hm[0:1]

实际测试发现,如果 offset 也进行特征图平均操作,mAP 会降低 0.2。

不管是单尺度测试还是 flip 测试,都没有采用 nms,只在多尺度测试时候才用了 nms。实际上发现如果对 flip 也进行 nms 操作,最终性能可以由 31.0 提升到31.6。

Cite

知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值