15. Object Detection
Prelude: Tasks in CV¶
Challenges¶
- 多物体:你不知道有几个物体,因此什么时候结束也无法确定
- 多类别输出:你不仅要输出 what,还要输出 where
- 大图片:对于分类而言,图片尺寸只需要在 224x224 即可;但是对于 object detection,需要在 800x600 左右
Single Object Detection¶
如上图,我们使用一个预训练的网络(往往是在 ImageNet 上训练的),通过微调(预训练模型以及两个 fc layers)进行迁移学习。我们要确保 class scores 和 box coords 都尽量判断正确。
缺点就是:无法探测一个以上的物体
Multiple Object Detection¶
Sliding Windows¶
最原始的想法,就是对于不同大小、不同中心的 sub-image,都抽出来,通过一个 CNN 求 softmaxed scores,然后根据 score 的无穷范数来选取 top-k 个区域(或者使用 threshold),作为输出。
但是,这里的问题就是:计算量过于巨大。因此实际上不可取。
R-CNN¶
training sketch¶
首先,通过 selective search 算法(非神经网络的传统方法),得到 region proposals(大约有 2000 个)
然后,对于每一个 region,我们将其
- resize 成 224x224
- 丢到 CNN 模型里去
- 最后得到两个输出:
- (softmaxed) scores
- region transform
- 然后,我们通过 region transform 来变换该 region,得到最终的 region
- 最后,通过和 ground truth 的 region 以及 category 两者,分别计算 loss,然后加权加起来得到最终 loss,再进行反向传播
training details¶
如图,我们通过 ground truth (i.e. boxes of training set) 来将 box 划分成不同类别:positive, neutral and negative。
其中,
- positive 的,我们需要完成分类+调整box位置的操作;
- negative 的,由于调整box位置的操作没有任何作用,因此只需要完成分类即可。
- 目标分类是 background,是我们自己加上去的分类
- neutral 的,我们就不管了
predicting/testing¶
首先,通过 selective search 算法(非神经网络的传统方法),得到 region proposals(大约有 2000 个)
然后,对于每一个 region,我们将其
- resize 成 224x224
- 丢到 CNN 模型里去
- 最后得到两个输出:
- predicted category and its score
- region transform
然后,通过一些规则选取部分 region(比如分数 top-k 的/超过 threshold的),输出对应 region 和对应的 category。
Note¶
我们的 CNN 不会接受任何和原始 subimage 大小相关的参数。
Comparing Boxes¶
Overlapping Boxes¶
有时候,若干个 overlapped boxes 可能指代的是同一个物体,此时,我们需要将一些 boxes 清除出去。
如图,这是一个贪心算法。
我们首先抽取分数最高的 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¶
如上图,对于 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 图
- 设想一下,我们有若干个正负样本,每一个样本有一个分数值。
- 我们设计一个简单的二分类器:如果分数值大于等于 threshold,就是正样本;否则就是负样本。
- 然后,将 threshold 从 0% 增加到 100%。对于每一个 threshold,都有可以计算出来一个 recall 和 precision。我们将所有这些点连起来,就得到了上图中的曲线。
- 实际上,由于 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¶
左图中,我们不去对每一个 proposal 都走完整的一遍流程,而是
- 将整个图片透过网络前面的若干个纯 CNN 层(这里称为 backbone),得到若干个 image features
- 由于 backbone 为纯 CNN,因此每一个 input image 的 box 都可以对应一个 image features 的一个 box
- 然后,我们就将 image feature 上对应的 box 进行
- pooling(细节见下)
- 从而不论 box 的大小,pooling 之后都是一样大的
- 再输入到一个规模比较小的 CNN 里
- 最后得到 class 以及 box transformation
- pooling(细节见下)
这样做的好处就是通过共用 backbone,大大降低 training 时的计算量(i.e. 正向计算只用算一次,反向求导也只用求一次,而不是算/求 # of proposals 次)
Details of Pooling¶
左侧是比较粗糙的 pooling,问题在于
- 由于 snapping,input image 上的绿色框对应到 feature map 上,无法完全对齐,从而必须要 snap。snap 之后,偏移就更大了。
- 将 box coordinates 作为参数,loss 的改变不是连续的,而且在非间断处的导数为 0
- 因为,如左图,微扰 box 的坐标,snap 后的蓝色框区域并不会发生变化
右侧是优化的 pooling,均匀划分 4 个区域,在每一个区域内均匀采样,采样使用双线性插值,最后使用 max pooling。好处在于
- feature map 上的均匀采样点,在 input image 上也是均匀的
- 将 box coordinates 作为参数,loss 的改变是连续的,而且在非边界处是可微的
Faster R-CNN¶
由于 selective search 算法用不了 GPU,因此我们如果需要加速,那么就必须使用神经网络:
RPN (Region Proposal Network) 的主要工作是根据输入的图片,生成一些可能包含物体的区域,每一个区域都会有一个得分,表示这个区域是否真的包含物体的概率。这些区域和得分,就可以作为下一步物体检测的输入。RPN使用了一种叫做锚框(anchor box)的机制,这种机制会在图片的每一个位置上,生成一些预设的大小和形状的窗口,然后对这些窗口进行打分和调整。
RPN的工作流程大概是这样的(如下图所示):
- 首先,输入的图片会通过一些卷积层,得到一个特征图(feature map)。
- 然后,在特征图的每一个位置上,生成一些预设的锚框。每一个锚框都有一个中心点和一个大小,中心点就是锚框所在的位置,大小包括宽度和高度。
- 对每一个锚框,通过一些卷积层,得到一个得分和一个位置调整量。得分表示这个锚框内是否包含物体的概率,位置调整量用来微调锚框的位置,使其更准确地覆盖物体。
- 最后,根据得分和位置调整量,得到一些可能包含物体的候选区域。
Two Stages VS One Stage¶
基本情况就如图下图所示,可以发现,有两个 stages:
- 第一个 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¶
由于 RPN 需要大量的 anchor,而且 anchor 本身就带有大量的超参数,因此使用起来不方便。因此,我们不妨直接不使用任何的 anchor。
上图中的 CornerNet 的工作原理是:
- 通过 backbone CNN,对 upper left corner 和 lower right corner 分别生成一个 heatmap 和 embeddings。其中,
- heatmap 就是该区域是某个 bounding box 的 upper left/lower right corner 的可能性
- embeddings 就是该区域的 embedding 向量
- 我们通过将 heatmap 上高分的 upper left 和 lower left,根据 embedding 的相似程度来匹配,从而无需 anchor 即可得到 bounding box