SSD: Single Shot MultiBox Detector 学习笔记

Posted by 111qqz on Sunday, December 8, 2019

TOC

概述

SSD是一种单阶段目标检测算法.所谓单阶段,是指只使用了一个deep neural network,而不是像faster-rcnn这种两阶段网络. 为什么有了faster-rcnn还要使用SSD? 最主要是慢… 两阶段网络虽然准确率高,但是在嵌入式等算力不足的设备上做inference速度非常感人,很难达到real time的要求. (实际业务上也是这样,公有云上的检测模型几乎都是faster-rcnn,而到了一些盒子之类的硬件设备,检测模型就全是SSD等single stage 模型了)

之前一直没有写SSD是因为相比faster rcnn的细节,SSD的问题似乎并不是很多.直到最近转模型的时候被FASF模型的一个细节卡了蛮久,因此决定还是记录一下.

基本概念

这部分描述SSD中涉及到的一些想法.

prior box

prior box的概念其实与faster-rcnn中anchor的概念是一样的,没有本质区别. 与faster-rcnn中的anchor不同的是,SSD会在多个feature map中的每个cell 都生成若干个prior_box.

对于一个特定的feature map,尺寸为m*n,假设有k个prior box,c种类别. 那么feature map的每个location会生成 k*(c+4) 个结果,其中c代表每一类的confidence. 4代表相对prior_box中心点的offset. 整个feature_map会生成  kmn(c+4) 个结果.

prior_box的参数选择,以及一些训练有关的细节可以参考原论文,这里不再赘述. 这里主要想强调一下和priox box有关的inference 细节. 主要是decode box的部分.

由于模型输出的bbox其实是相对每个prior_box的offset,不是真正的bbox,因此需要由网络输出的box_pred和prior box得到真正的bbox 坐标.这部分通常称为decode box,其实已经算是后处理部分了.

pytorch中decode box的代码如下:

 variance1, variance2 = variance
        cx = box_prior[:, :,
                       0] + box_pred[:, :, 0] * variance1 * box_prior[:, :, 2]
        cy = box_prior[:, :,
                       1] + box_pred[:, :, 1] * variance1 * box_prior[:, :, 3]
        w = box_prior[:, :, 2] + torch.exp(box_pred[:, :, 2] * variance2)
        h = box_prior[:, :, 3] + torch.exp(box_pred[:, :, 3] * variance2)
        x1 = cx - w / 2
        y1 = cy - h / 2
        x2 = w + x1
        y2 = h + y1

不考虑variance的话,box_prior存储的四个数据按顺序分别为cx,cy,w,h 也就是prior_box的中心点坐标(cx,cy)以及宽和高.

而variance是一个原始paper中没有提到的实现细节. 按照 What is the purpose of the variances? 的说法,

Probably, the naming comes from the idea, that the ground truth bounding boxes are not always precise, in other words, they vary from image to image probably for the same object in the same position just because human labellers cannot ideally repeat themselves. Thus, the encoded values are some random values, and we want them to have unit variance that is why we divide by some value.

可以理解成一个用来消除由标注引入的随机因素的手段.

更多bbox encoding/decoding的内容可以参考 Bounding Box Encoding and Decoding in Object Detection

对应的cuda代码

template <typename Dtype>
__global__ void OneStageDecodeBBoxesSSDKernel_v2(const int nthreads, const Dtype* loc_data,
        const Dtype* prior_data, const Dtype variance1, const Dtype variance2,
        const int num_priors,const bool clip_bbox, Dtype* bbox_loc){

    CUDA_KERNEL_LOOP(index, nthreads) {
        // loc: Batch, num_priors, 4, 1
        // proir: 1, num_priors, 4, 1
        // nthreads = Batch * num_priors * 2
        const int x_or_y = index % 2;
        const int pri_idx = 4 * int( (index %(2*num_priors)) / 2) + x_or_y;
        const int loc_idx = 4 * int(index /2) + x_or_y;
        Dtype box0 =  prior_data[pri_idx];
        box0 = box0 + loc_data[loc_idx] * variance1 * box0; 
        Dtype box2 = prior_data[pri_idx + 2] * exp(variance2* loc_data[loc_idx+2]);
        // box0就是prior box的中心点坐标x或者y,
        // box2 就是prior box的w或者h. 
        // 下面的box0和box2复用了变量,但是实际上分别表示水平或者垂直方向的两个坐标.
        box0 -= box2/2; 
        box2 += box0;
        bbox_loc[loc_idx]  = clip_bbox ? min(max(box0, 0.0),1.0): box0;
        bbox_loc[loc_idx+2] = clip_bbox ? min(max(box2, 0.0),1.0): box2;
    }
}

如果涉及到部署,一个要注意的细节是,pytorch代码和caffe代码中prior_box 顺序的一致性.

loss

由于不(wo)涉(bu)及(hui)训练,loss不是关注的重点,简单说一句. loss function是localization loss 和confidence loss 的加权和. 前半部分用来衡量bbox的loss,后半部分是分数的loss. 其中localization loss 如下图: ssd_loc_loss.png

而confidence loss就是 softmax loss.

different scales of feature maps

神经网络越深的layer有着越大的感受野(receptive fileds),每个feature map cell包含着更抽象的信息. 我们可以用浅层的feature map来检测小物体,用深层的feture map来检测大物体. Pyramidal feature hierarchy.png

这个想法后面会延伸到FPN,因此这里不详细讲了.

参考资料

「真诚赞赏,手留余香」

111qqz的小窝

真诚赞赏,手留余香

使用微信扫描二维码完成支付