Skip to content

15. Object Detection

Prelude: Tasks in CV

image-20240506064632136

Challenges

  1. 多物体:你不知道有几个物体,因此什么时候结束也无法确定
  2. 多类别输出:你不仅要输出 what,还要输出 where
  3. 大图片:对于分类而言,图片尺寸只需要在 224x224 即可;但是对于 object detection,需要在 800x600 左右

Single Object Detection

img

如上图,我们使用一个预训练的网络(往往是在 ImageNet 上训练的),通过微调(预训练模型以及两个 fc layers)进行迁移学习。我们要确保 class scores 和 box coords 都尽量判断正确。

缺点就是:无法探测一个以上的物体

Multiple Object Detection

Sliding Windows

最原始的想法,就是对于不同大小、不同中心的 sub-image,都抽出来,通过一个 CNN 求 softmaxed scores,然后根据 score 的无穷范数来选取 top-k 个区域(或者使用 threshold),作为输出。

但是,这里的问题就是:计算量过于巨大。因此实际上不可取。

R-CNN

image-20240506205019844

training sketch

首先,通过 selective search 算法(非神经网络的传统方法),得到 region proposals(大约有 2000 个)

然后,对于每一个 region,我们将其

  1. resize 成 224x224
  2. 丢到 CNN 模型里去
  3. 最后得到两个输出:
    1. (softmaxed) scores
    2. region transform
  4. 然后,我们通过 region transform 来变换该 region,得到最终的 region
  5. 最后,通过和 ground truth 的 region 以及 category 两者,分别计算 loss,然后加权加起来得到最终 loss,再进行反向传播

training details

image-20240507061318805

如图,我们通过 ground truth (i.e. boxes of training set) 来将 box 划分成不同类别:positive, neutral and negative。

其中,

  1. positive 的,我们需要完成分类+调整box位置的操作;
  2. negative 的,由于调整box位置的操作没有任何作用,因此只需要完成分类即可。
    • 目标分类是 background,是我们自己加上去的分类
  3. neutral 的,我们就不管了

predicting/testing

首先,通过 selective search 算法(非神经网络的传统方法),得到 region proposals(大约有 2000 个)

然后,对于每一个 region,我们将其

  1. resize 成 224x224
  2. 丢到 CNN 模型里去
  3. 最后得到两个输出:
    1. predicted category and its score
    2. region transform

然后,通过一些规则选取部分 region(比如分数 top-k 的/超过 threshold的),输出对应 region 和对应的 category。

Note

我们的 CNN 不会接受任何和原始 subimage 大小相关的参数。

Comparing Boxes

image-20240506212729118image-20240506212808359

Overlapping Boxes

有时候,若干个 overlapped boxes 可能指代的是同一个物体,此时,我们需要将一些 boxes 清除出去。

image-20240506213345183

如图,这是一个贪心算法

我们首先抽取分数最高的 box(也就是蓝色 box),然后计算其到其它 boxes 的 IoU 距离。然后剔除所有 IoU 距离小于 threshold 的 dogs(也就是橙色 box)。

然后,我们抽取剩下的分数次高的 box(也就是紫色 box),然后剔除所有 IoU 距离小于 threshold 的 dogs(也就是黄色 box)。

由于没有比紫色 box 分数更低的 box,算法结束。

// 伪代码
fn NMS(Category "c", Float "threshold") -> Array:
    "Arr" = all boxes of category "c"
    Sort "Arr" by their score in descending order
    "IsEliminated" = boolean array of "Arr".shape
    Set all elements in "IsEliminated" to False
    for each object "obj", "check" in zip("Arr", "IsEliminated"):
        if "check":
            continue
        for each object "o", "c" after ("obj", "check") in zip("Arr", "IsEliminated"):
            if (IoU("obj", "o") >= "threshold"):
                "c" = True
    return "Arr"[!"IsEliminated"]

Performance Metric

image-20240506215622731

如上图,对于 dog category 的每一个 box,我们将其根据 score 从大到小进行排序,然后就可以得到一个 dog boxes sorted by score。

然后,我们根据排序顺逐一进行匹配:

  • If it matches some GT boxes with IoU > 0.5, mark it positive
  • Otherwise mark it negative

然后,将当前的 precision-recall ratio 作图到 P-R 图上。

什么是 P-R 图
  1. 设想一下,我们有若干个正负样本,每一个样本有一个分数值。
  2. 我们设计一个简单的二分类器:如果分数值大于等于 threshold,就是正样本;否则就是负样本。
  3. 然后,将 threshold 从 0% 增加到 100%。对于每一个 threshold,都有可以计算出来一个 recall 和 precision。我们将所有这些点连起来,就得到了上图中的曲线。
  4. 实际上,由于 recall 和 precision 只会在 threshold 达到了其中一点分数的时候,才会变化,因此,我们只需要求出 threshold 等于每一个点分数时的 (recall, precision),然后将这些离散的点在上图连起来,就能得到这个 graph

之后,我们

  • 对所有的 category 都这样计算,从而求出 mean average performance (mAP)
  • 将 IoU 的 lower bound 从 0.5 逐渐往上调(比如 [0.5, 0.6, 0.7, 0.8, 0.9, 0.95] 这样),求出每一个 IoU threshold 对应的 mAP,然后再求平均

不难设想,最好的情况就是:分数最高的几张图片,已经将所有的 GT boxes recall 了。然后剩下的就不管了。

Faster R-CNN

image-20240507001318714

左图中,我们不去对每一个 proposal 都走完整的一遍流程,而是

  1. 将整个图片透过网络前面的若干个纯 CNN 层(这里称为 backbone),得到若干个 image features
  2. 由于 backbone 为纯 CNN,因此每一个 input image 的 box 都可以对应一个 image features 的一个 box
  3. 然后,我们就将 image feature 上对应的 box 进行
    1. pooling(细节见下)
      • 从而不论 box 的大小,pooling 之后都是一样大的
    2. 再输入到一个规模比较小的 CNN 里
    3. 最后得到 class 以及 box transformation

这样做的好处就是通过共用 backbone,大大降低 training 时的计算量(i.e. 正向计算只用算一次,反向求导也只用求一次,而不是算/求 # of proposals 次)

Details of Pooling

image-20240507164156902image-20240507002502297

左侧是比较粗糙的 pooling,问题在于

  1. 由于 snapping,input image 上的绿色框对应到 feature map 上,无法完全对齐,从而必须要 snap。snap 之后,偏移就更大了。
  2. 将 box coordinates 作为参数,loss 的改变不是连续的,而且在非间断处的导数为 0
    • 因为,如左图,微扰 box 的坐标,snap 后的蓝色框区域并不会发生变化

右侧是优化的 pooling,均匀划分 4 个区域,在每一个区域内均匀采样,采样使用双线性插值,最后使用 max pooling。好处在于

  1. feature map 上的均匀采样点,在 input image 上也是均匀的
  2. 将 box coordinates 作为参数,loss 的改变是连续的,而且在非边界处是可微的

Faster R-CNN

由于 selective search 算法用不了 GPU,因此我们如果需要加速,那么就必须使用神经网络:

RPN (Region Proposal Network) 的主要工作是根据输入的图片,生成一些可能包含物体的区域,每一个区域都会有一个得分,表示这个区域是否真的包含物体的概率。这些区域和得分,就可以作为下一步物体检测的输入。RPN使用了一种叫做锚框(anchor box)的机制,这种机制会在图片的每一个位置上,生成一些预设的大小和形状的窗口,然后对这些窗口进行打分和调整。

RPN的工作流程大概是这样的(如下图所示):

  1. 首先,输入的图片会通过一些卷积层,得到一个特征图(feature map)。
  2. 然后,在特征图的每一个位置上,生成一些预设的锚框。每一个锚框都有一个中心点和一个大小,中心点就是锚框所在的位置,大小包括宽度和高度。
  3. 对每一个锚框,通过一些卷积层,得到一个得分和一个位置调整量。得分表示这个锚框内是否包含物体的概率,位置调整量用来微调锚框的位置,使其更准确地覆盖物体。
  4. 最后,根据得分和位置调整量,得到一些可能包含物体的候选区域。

image-20240507034909857

Two Stages VS One Stage

基本情况就如图下图所示,可以发现,有两个 stages:

image-20240507044849382

  • 第一个 stage 就是判断 anchors 是否含有 object,以及 transformation
  • 第二个 stage 就是判断第一轮中通过的 anchors 是否含有 object,以及 transformation

    • 由于第二个 stage 依赖第一个 stage 的判断,因此,含有 object 的 anchors 是会动态发生变化的,实现起来有一定难度
  • 当然,实际上,也可以将两个 stage 合二为一:第一个 stage 处理 RPN 的时候,与其判断 "anchor is an object?",不如判断 "what category does this object belong to?"。

    • 同时,我们需要加上一个 background category,从而模仿 one stage
    • 效果一般来说比 two stages 差一些,但是速度更快
  • 同时,我们的 box transforms 可以为 \(C \times 4K \times 20 \times 15\),从而每一个类别都有其不同的 box transformation 参数,在实际中更加准确。
RPN 训练时的一些细节

问题 1:在 first stage 训练的时候,我们必须选出部分 anchor 参与 loss 的计算,怎么选出这些 anchors?

RPN每次从待选anchor中抽取256个anchors,每次让正负样本比为1:1。如果正样本少于128,假设为x,那么我们就让负样本为256-x。

问题 1.1:这里是怎么定义正负样本的呢?

  • 正样本: 我们的anchor与ground-truth box最大的IoU>0.7,那么就可以认为是正样本,如果最大的都不大于0.7,那么我们就把IoU最大的Anchor作为正样本即可。
  • 负样本: 我们的anchor与所有的ground-truth box的IoU<0.3,那么就可以认为是负样本。
  • 正负样本之外的我们就会丢弃掉。

问题 1.2:最终公式是啥?

Takeaways

  • Two stage method (Faster-CNN) get the best accuracy, but are slower
  • Single-stage methods (SSD) are much faster, but don't perform as well
  • Bigger backbones (e.g. Resnet instead of MobileNet) improve performance, but are slower

YOLO: You Only Look Once

Faster R-CNN 是 two stage 的,速度还是比较慢。我们希望更快,所以寄希望于 one stage。YOLO 就是 one stage 的模型架构。

Architecture

如上图:YOLO 将图片划分为 SxS 个格子。对于每一个格子,我们生成 B 个 anchor box。

  • 对于每个 anchor box,我们生成一个 confidence 和一个 position
  • 对于每个格子,我们生成一个种类

上图就是每一个格子的参数数量,以及每个格子的 output tensor 的格式。

Prediction

如上图:

  • 首先生成 bounding boxes 以及对应的 confidence
  • 然后 filter 掉 confidence 小的,再进行极大值抑制
  • 最后,剩下的 bounding box 就是 prediction
    • 每个 bounding box 的 category 就是所属格子的 category

Losses

\(\mathbb 1^{obj}_{ij}\)\(\mathbb 1^{obj}_{i}\) 的意思

图片的标注信息只有每个 object 的 bounding box。

我们找出每一个 object 的 bounding box 中心点,则这个中心点所在的格子的 \(i\),决定了 \(\mathbb 1^{obj}_{i}\)

对于格子 \(i\) 而言,里面有 \(B\) 个 anchor,其中 IOU 最高的那个 anchor \(j\),决定了 \(\mathbb 1^{obj}_{ij}\)

CornerNet: multi-object detection without anchors

image-20240507171643885

由于 RPN 需要大量的 anchor,而且 anchor 本身就带有大量的超参数,因此使用起来不方便。因此,我们不妨直接不使用任何的 anchor。

上图中的 CornerNet 的工作原理是:

  1. 通过 backbone CNN,对 upper left corner 和 lower right corner 分别生成一个 heatmap 和 embeddings。其中,
    • heatmap 就是该区域是某个 bounding box 的 upper left/lower right corner 的可能性
    • embeddings 就是该区域的 embedding 向量
  2. 我们通过将 heatmap 上高分的 upper left 和 lower left,根据 embedding 的相似程度来匹配,从而无需 anchor 即可得到 bounding box

Conclusion

image-20240507055139922