对计算机而言,能够“看到”的是图像被编码之后的数字,但它很难解高层语义概念,比如图像或者视频帧中出现目标的是人还是物体,更无法定位目标出现在图像中哪个区域。目标检测的主要目的是让计算机可以自动识别图片或者视频帧中所有目标的类别,并在该目标周围绘制边界框,标示出每个目标的位置,如下图所示。
📃 目标检测 - 图1

  • 图(a)是图像分类任务,只需识别出这是一张斑马的图片。
  • 图(b)是目标检测任务,不仅要识别出这是一张斑马的图片,还要标出图中斑马的位置。

目标检测的方法

为了能够然计算机“看到”并“理解”图像中所包含的的语义,结合图片分类任务取得的成功经验,我们可以将目标检测任务进行拆分。假设我们现在有某种方式可以在输入图片上生成一系列可能包含物体的区域,这些区域称为候选区域,在一张图上可以生成很多个候选区域。然后对每个候选区域,可以把它单独当成一幅图像来看待,使用图像分类模型对它进行分类,看它属于哪个类别或者背景(即不包含任何物体的类别)。

候选区域可以参考下图的表示方式:
📃 目标检测 - 图2
A为图像上的某个像素点,B为A右下方另外一个像素点,A、B两点可以确定一个矩形框,记作AB。

  • 如左图所示:A在图片左上角位置,B遍历除A之外的所有位置,生成矩形框A1B1, …, A1Bn, …
  • 如右图所示:A在图片中间某个位置,B遍历A右下方所有位置,生成矩形框AkB1, …, AkBn, …

当A遍历图像上所有像素点,B则遍历它右下方所有的像素点,最终生成的矩形框集合{AiBj}将会包含图像上所有可以选择的区域。

这种方式就是穷举法:
📃 目标检测 - 图3

只要我们对每个候选区域的分类足够的准确,则一定能找到跟实际物体足够接近的区域来。穷举法也许能得到正确的预测结果,但其计算量也是非常巨大的,其所生成的总的候选区域数目约为📃 目标检测 - 图4,假设📃 目标检测 - 图5,总数将会达到📃 目标检测 - 图6个,如此多的候选区域使得这种方法几乎没有什么实用性。但是通过这种方式,我们可以看出,假设分类任务完成的足够完美,从理论上来讲检测任务也是可以解决的,亟待解决的问题是如何设计出合适的方法来产生候选区域。

科学家们开始思考,是否可以应用传统图像算法先产生候选区域,然后再用卷积神经网络对这些区域进行分类?

  • 2013年,Ross Girshick 等人于首次将CNN的方法应用在目标检测任务上,他们使用传统图像算法selective search产生候选区域,取得了极大的成功,这就是对目标检测领域影响深远的区域卷积神经网络(R-CNN)模型。
  • 2015年,Ross Girshick 对此方法进行了改进,提出了Fast RCNN模型。通过将不同区域的物体共用卷积层的计算,大大缩减了计算量,提高了处理速度,而且还引入了调整目标物体位置的回归方法,进一步提高了位置预测的准确性。
  • 2015年,Shaoqing Ren 等人提出了Faster RCNN模型,提出了RPN的方法来产生物体的候选区域,这一方法里面不再需要使用传统的图像处理算法来产生候选区域,进一步提升了处理速度。
  • 2017年,Kaiming He 等人提出了Mask RCNN模型,只需要在Faster RCNN模型上添加比较少的计算量,就可以同时实现目标检测和物体实例分割两个任务。

以上都是基于R-CNN系列的著名模型,对目标检测方向的发展有着较大的影响力。此外,还有一些其他模型,比如SSD、YOLO(1, 2, 3)、R-FCN等也都是目标检测领域流行的模型结构。

R-CNN的系列算法分成两个阶段,先在图像上产生候选区域,再对候选区域进行分类并预测目标物体位置,它们通常被叫做两阶段检测算法SSDYOLO算法则只使用一个网络同时产生候选区域并预测出物体的类别和位置,所以它们通常被叫做单阶段检测算法

目标检测基础概念

在介绍目标检测算法之前,先介绍一些跟检测相关的基本概念,包括边界框、锚框和交并比等。

边界框(bounding box)

检测任务需要同时预测物体的类别和位置,因此需要引入一些跟位置相关的概念。通常使用边界框(bounding box,bbox)来表示物体的位置,边界框是正好能包含物体的矩形框,如下图所示,图中3个人分别对应3个边界框。
📃 目标检测 - 图7
通常有两种格式来表示边界框的位置:

  1. 📃 目标检测 - 图8,即📃 目标检测 - 图9,其中📃 目标检测 - 图10是矩形框左上角的坐标,📃 目标检测 - 图11是矩形框右下角的坐标。图4中3个红色矩形框用xyxy格式表示如下:
    • 左:📃 目标检测 - 图12
    • 中:📃 目标检测 - 图13
    • 右:📃 目标检测 - 图14
  2. 📃 目标检测 - 图15,即📃 目标检测 - 图16,其中📃 目标检测 - 图17是矩形框中心点的坐标,📃 目标检测 - 图18是矩形框的宽度,📃 目标检测 - 图19是矩形框的高度。

在检测任务中,训练数据集的标签里会给出目标物体真实边界框所对应的📃 目标检测 - 图20,这样的边界框也被称为真实框(ground truth box),如上图所示,图中画出了3个人像所对应的真实框。模型会对目标物体可能出现的位置进行预测,由模型预测出的边界框则称为预测框(prediction box)

注意:

  1. 在阅读代码时,需要注意使用的是哪一种格式(xyxy,xywh)的表示方式。
  2. 图片坐标的原点在左上角,x轴向右为正方向,y轴向下为正方向。

要完成一项检测任务,我们通常希望模型能够根据输入的图片,输出一些预测的边界框,以及边界框中所包含的物体的类别或者说属于某个类别的概率,例如这种格式: 📃 目标检测 - 图21,其中📃 目标检测 - 图22是类别标签,📃 目标检测 - 图23是物体属于该类别的概率。一张输入图片可能会产生多个预测框。

锚框(Anchor box)

锚框与物体边界框不同,是由人们假想出来的一种框。先设定好锚框的大小和形状,再以图像上某一个点为中心画出矩形框。在下图中,以像素点[300, 500]为中心生成了3个框,如图中蓝色框所示,其中锚框A1跟人像区域非常接近。
📃 目标检测 - 图24
在目标检测模型中,通常会以某种规则在图片上生成一系列锚框,将这些锚框当成可能的候选区域。模型对这些候选区域是否包含物体进行预测,如果包含目标物体,则还需要进一步预测出物体所属的类别。还有更为重要的一点是,由于锚框位置是固定的,它不大可能刚好跟物体边界框重合,所以需要在锚框的基础上进行微调以形成能准确描述物体位置的预测框,模型需要预测出微调的幅度。在训练过程中,模型通过学习不断的调整参数,最终能学会如何判别出锚框所代表的候选区域是否包含物体,如果包含物体的话,物体属于哪个类别,以及物体边界框相对于锚框位置需要调整的幅度。

不同的模型往往有着不同的生成锚框的方式,在后面的内容中,会详细介绍YOLO-V3算法里面产生锚框的规则,理解了它的设计方案,也很容易类推到其它模型上。

交并比

上面我们画出了以点📃 目标检测 - 图25为中心,生成的三个锚框,我们可以看到锚框A1 与真实框 G1的重合度比较好。那么如何衡量这三个锚框跟真实框之间的关系呢,在检测任务中是使用交并比(Intersection of Union,IoU)作为衡量指标。这一概念来源于数学中的集合,用来描述两个集合📃 目标检测 - 图26📃 目标检测 - 图27之间的关系,它等于两个集合的交集里面所包含的元素个数,除以它们的并集里面所包含的元素个数,具体计算公式如下:

📃 目标检测 - 图28

我们将用这个概念来描述两个框之间的重合度。两个框可以看成是两个像素的集合,它们的交并比等于两个框重合部分的面积除以它们合并起来的面积。下图“交集”中青色区域是两个框的重合面积,图“并集”中蓝色区域是两个框的相并面积。用这两个面积相除即可得到它们之间的交并比,如下图所示。
📃 目标检测 - 图29
假设两个矩形框A和B的位置分别为:
📃 目标检测 - 图30
📃 目标检测 - 图31
假如位置关系如下图所示:
📃 目标检测 - 图32
如果二者有相交部分,则相交部分左上角坐标为:
📃 目标检测 - 图33
相交部分右下角坐标为:
📃 目标检测 - 图34
计算先交部分面积:
📃 目标检测 - 图35

矩形框A和B的面积分别是:
📃 目标检测 - 图36
📃 目标检测 - 图37
计算相并部分面积:
📃 目标检测 - 图38
计算交并比:
📃 目标检测 - 图39

交并比计算程序如下:
xyxy格式:

  1. # 计算IoU,矩形框的坐标形式为xyxy
  2. def box_iou_xyxy(box1, box2):
  3. # 获取box1左上角和右下角的坐标
  4. x1min, y1min, x1max, y1max = box1[0], box1[1], box1[2], box1[3]
  5. # 计算box1的面积
  6. s1 = (y1max - y1min + 1.) * (x1max - x1min + 1.)
  7. # 获取box2左上角和右下角的坐标
  8. x2min, y2min, x2max, y2max = box2[0], box2[1], box2[2], box2[3]
  9. # 计算box2的面积
  10. s2 = (y2max - y2min + 1.) * (x2max - x2min + 1.)
  11. # 计算相交矩形框的坐标
  12. xmin = np.maximum(x1min, x2min)
  13. ymin = np.maximum(y1min, y2min)
  14. xmax = np.minimum(x1max, x2max)
  15. ymax = np.minimum(y1max, y2max)
  16. # 计算相交矩形行的高度、宽度、面积
  17. inter_h = np.maximum(ymax - ymin + 1., 0.)
  18. inter_w = np.maximum(xmax - xmin + 1., 0.)
  19. intersection = inter_h * inter_w
  20. # 计算相并面积
  21. union = s1 + s2 - intersection
  22. # 计算交并比
  23. iou = intersection / union
  24. return iou
  25. bbox1 = [100., 100., 200., 200.]
  26. bbox2 = [120., 120., 220., 220.]
  27. iou = box_iou_xyxy(bbox1, bbox2)
  28. print('IoU is {}'.format(iou)) # IoU is 0.47402644317607107

xywh格式:

  1. # 计算IoU,矩形框的坐标形式为xywh
  2. def box_iou_xywh(box1, box2):
  3. x1min, y1min = box1[0] - box1[2]/2.0, box1[1] - box1[3]/2.0
  4. x1max, y1max = box1[0] + box1[2]/2.0, box1[1] + box1[3]/2.0
  5. s1 = box1[2] * box1[3]
  6. x2min, y2min = box2[0] - box2[2]/2.0, box2[1] - box2[3]/2.0
  7. x2max, y2max = box2[0] + box2[2]/2.0, box2[1] + box2[3]/2.0
  8. s2 = box2[2] * box2[3]
  9. xmin = np.maximum(x1min, x2min)
  10. ymin = np.maximum(y1min, y2min)
  11. xmax = np.minimum(x1max, x2max)
  12. ymax = np.minimum(y1max, y2max)
  13. inter_h = np.maximum(ymax - ymin, 0.)
  14. inter_w = np.maximum(xmax - xmin, 0.)
  15. intersection = inter_h * inter_w
  16. union = s1 + s2 - intersection
  17. iou = intersection / union
  18. return iou

为了直观的展示交并比的大小跟重合程度之间的关系,下图示意了不同交并比下两个框之间的相对位置关系,从 IoU = 0.95 到 IoU = 0.
📃 目标检测 - 图40

YOLO-V3 模型

设计思想

YOLO-V3算法的基本思想可以分成两部分:

  • 按一定规则在图片上产生一系列的候选区域,然后根据这些候选区域与图片上物体真实框之间的位置关系对候选区域进行标注。跟真实框足够接近的那些候选区域会被标注为正样本,同时将真实框的位置作为正样本的位置目标。偏离真实框较大的那些候选区域则会被标注为负样本,负样本不需要预测位置或者类别。
  • 使用卷积神经网络提取图片特征并对候选区域的位置和类别进行预测。这样每个预测框就可以看成是一个样本,根据真实框相对它的位置和类别进行了标注而获得标签值,通过网络模型预测其位置和类别,将网络预测值和标签值进行比较,就可以建立起损失函数。

YOLO-V3算法训练过程的流程图如下图所示:
📃 目标检测 - 图41

  • 左边是输入图片,上半部分所示的过程是使用卷积神经网络对图片提取特征,随着网络不断向前传播,特征图的尺寸越来越小,每个像素点会代表更加抽象的特征模式,直到输出特征图,其尺寸减小为原图的📃 目标检测 - 图42
  • 下半部分描述了生成候选区域的过程,首先将原图划分成多个小方块,每个小方块的大小是📃 目标检测 - 图43,然后以每个小方块为中心分别生成一系列锚框,整张图片都会被锚框覆盖到,在每个锚框的基础上产生一个与之对应的预测框,根据锚框和预测框与图片上物体真实框之间的位置关系,对这些预测框进行标注。
  • 将上方支路中输出的特征图与下方支路中产生的预测框标签建立关联,创建损失函数,开启端到端的训练过程。

具体步骤如下。

产生候选区域

如何产生候选区域,是检测模型的核心设计方案。目前大多数基于卷积神经网络的模型所采用的方式大体如下:

  • 按一定的规则在图片上生成一系列位置固定的锚框,将这些锚框看作是可能的候选区域,
  • 对锚框是否包含目标物体进行预测,如果包含目标物体,还需要预测所包含物体的类别,以及预测框相对于锚框位置需要调整的幅度。

生成锚框

将原始图片划分成📃 目标检测 - 图44个区域,如下图所示,原始图片高度H=640, 宽度W=480,如果我们选择小块区域的尺寸为📃 目标检测 - 图45,则m和n分别为:
📃 目标检测 - 图46

📃 目标检测 - 图47

如下图所示,将原始图像分成了20行15列小方块区域。
📃 目标检测 - 图48
YOLO-V3算法会在每个区域的中心,生成一系列锚框。为了展示方便,我们先在图中第10行第4列(序号从0开始)的小方块位置附近画出生成的锚框。

锚框生成规则如下:

  • 中心点:小方块区域为中心
  • 大小:3中[w,h],分别为[116,90]、[156,198]、[373,326]

生成的锚框如下图所示。
📃 目标检测 - 图49

下图展示在每个区域附近都生成3个锚框,很多锚框堆叠在一起可能不太容易看清楚,但过程跟上面类似,只是需要以每个区域的中心点为中心,分别生成3个锚框。
📃 目标检测 - 图50
最终这张图中生成的锚框总数为:20153=900

生成预测框

在前面已经指出,锚框的位置都是固定好的,不可能刚好跟物体边界框重合,需要在锚框的基础上进行位置的微调以生成预测框。预测框相对于在锚框的基础上进行微调,可以调整中心坐标和宽度、高度。

比如上面图中在第10行第4列的小方块区域中心生成的一个锚框,如下图绿色虚线框所示。以小方格的宽度为单位长度,此小方块区域左上角的位置坐标是:
📃 目标检测 - 图51
📃 目标检测 - 图52
此锚框的区域中心坐标是:
📃 目标检测 - 图53
📃 目标检测 - 图54
可以通过下面的方式生成预测框的中心坐标:
📃 目标检测 - 图55
📃 目标检测 - 图56

其中📃 目标检测 - 图57📃 目标检测 - 图58为实数,📃 目标检测 - 图59是我们之前学过的Sigmoid函数,其定义如下:
📃 目标检测 - 图60

由于Sigmoid的函数值总是在📃 目标检测 - 图61之间,所以由上式计算出来的预测框中心点总是落在第十行第四列的小区域内部。

📃 目标检测 - 图62时,📃 目标检测 - 图63📃 目标检测 - 图64,预测框中心与锚框中心重合,都是小区域的中心。

锚框的大小是预先设定好的,在模型中可以当作是超参数,下图中画出的锚框尺寸是:
📃 目标检测 - 图65
📃 目标检测 - 图66

通过下面的公式生成预测框的大小:
📃 目标检测 - 图67
📃 目标检测 - 图68

如果📃 目标检测 - 图69,则预测框跟锚框重合。

如果给📃 目标检测 - 图70随机赋值如下:
📃 目标检测 - 图71

则可以得到预测框的坐标(采用xywh的格式)是(154.98, 357.44, 276.29, 310.42),如下图中蓝色框所示。
📃 目标检测 - 图72
这里我们会问:当📃 目标检测 - 图73取值为多少的时候,预测框能够跟真实框重合?为了回答问题,只需要将上面预测框坐标中的📃 目标检测 - 图74设置为真实框的位置,即可求解出t的数值。

令:
📃 目标检测 - 图75
可以求解出 📃 目标检测 - 图76

如果📃 目标检测 - 图77是网络预测的输出值,将📃 目标检测 - 图78作为目标值,以他们之间的差距作为损失函数,则可以建立起一个回归问题,通过学习网络参数,使得📃 目标检测 - 图79足够接近📃 目标检测 - 图80,从而能够求解出预测框的位置坐标跟大小。

预测框可以看作是在锚框基础上的一个微调,每个锚框会有一个跟它对应的预测框,我们需要确定上面计算式中的📃 目标检测 - 图81,从而计算出与锚框对应的预测框的位置和形状。

对候选区域进行标注

每个区域可以产生3种不同形状的锚框,每个锚框都是一个可能的候选区域,对这些候选区域我们希望知道这么几件事情:

  • 锚框是否包含了物体,这可以看成是一个二分类问题,包含了物体和没有包含物体,我们使用objectness标签来表示。当锚框包含了物体时,objectness=1,表示预测框属于正类;当锚框不包含物体时,设置objectness=0,表示锚框属于负类。
  • 如果锚框包含了物体,那么它对应的预测框的中心位置和大小应该是多少,或者说上面计算式中的📃 目标检测 - 图82应该是多少(location标签)。
  • 如果锚框包含了物体,那么具体类别是什么,这里使用变量label来表示其所属类别的标签(classification标签)。

现在对于任意一个锚框,我们需要对它进行标注,也就是需要确定其对应的objectness, 📃 目标检测 - 图83和label,下面将分别讲述如何确定这三个标签的值。

标注锚框是否包含物体的objectness标签

如下图所示,这里一共有3个目标,以最左边的人像为例,其真实框是📃 目标检测 - 图84
📃 目标检测 - 图85
真实框的中心点坐标是:
📃 目标检测 - 图86
📃 目标检测 - 图87
📃 目标检测 - 图88
📃 目标检测 - 图89

它落在了第10行第4列的小方块内,如图(b)所示。此小方块区域可以生成3个不同形状的锚框,其在图上的编号和大小分别是📃 目标检测 - 图90

用这3个不同形状的锚框跟真实框计算IoU,选出IoU最大的锚框。这里为了简化计算,只考虑锚框的形状,不考虑其跟真实框中心之间的偏移,具体计算结果如下图所示。
📃 目标检测 - 图91
其中跟真实框IoU最大的是锚框📃 目标检测 - 图92,形状是📃 目标检测 - 图93,将它所对应的预测框的objectness标签设置为1,其所包括的物体类别就是真实框里面的物体所属类别。

依次可以找出其他几个真实框对应的IoU最大的锚框,然后将它们的预测框的objectness标签也都设置为1。这里一共有📃 目标检测 - 图94个锚框,只有3个预测框会被标注为正。

由于每个真实框只对应一个objectness标签为正的预测框,如果有些预测框跟真实框之间的IoU很大,但并不是最大的那个,那么直接将其objectness标签设置为0当作负样本,可能并不妥当。为了避免这种情况,YOLO-V3算法设置了一个IoU阈值iou_thresh(超参数,YOLO-V3中设置为0.7),当预测框的objectness不为1,但是其与某个真实框的IoU大于iou_thresh时,就将其objectness标签设置为-1,不参与损失函数的计算。

所有其他的预测框,其objectness标签均设置为0,表示负类。

对于objectness=1的预测框,需要进一步确定其位置和包含物体的具体分类标签,但是对于objectness=0或者-1的预测框,则不用管他们的位置和类别。

标注预测框的location位置坐标标签

当锚框objectness=1时,需要确定预测框位置相对于它微调的幅度,也就是锚框的位置标签。

在前面我们已经问过这样一个问题:当📃 目标检测 - 图95取值为多少的时候,预测框能够跟真实框重合?其做法是将预测框坐标中的📃 目标检测 - 图96设置为真实框的坐标,即可求解出t的数值。

令:
📃 目标检测 - 图97📃 目标检测 - 图98

对于📃 目标检测 - 图99📃 目标检测 - 图100,由于Sigmoid的反函数不好计算,我们直接使用📃 目标检测 - 图101📃 目标检测 - 图102作为回归的目标

📃 目标检测 - 图103

如果📃 目标检测 - 图104是网络预测的输出值,将📃 目标检测 - 图105作为📃 目标检测 - 图106的目标值,以它们之间的差距作为损失函数,则可以建立起一个回归问题,通过学习网络参数,使得📃 目标检测 - 图107足够接近📃 目标检测 - 图108,从而能够求解出预测框的位置。

标注锚框包含物体类别的classification标签

对于objectness=1的锚框,需要确定其具体类别。正如上面所说,objectness标注为1的锚框,会有一个真实框跟它对应,该锚框所属物体类别,即是其所对应的真实框包含的物体类别。这里使用one-hot向量来表示类别标签label。比如一共有10个分类,而真实框里面包含的物体类别是第2类,则label为📃 目标检测 - 图109

对上述步骤进行总结,标注的流程如下图所示。
📃 目标检测 - 图110
通过这种方式,我们在每个小方块区域都生成了一系列的锚框作为候选区域,并且根据图片上真实物体的位置,标注出了每个候选区域对应的objectness标签、位置需要调整的幅度以及包含的物体所属的类别。位置需要调整的幅度由4个变量描述📃 目标检测 - 图111,objectness标签需要用一个变量描述📃 目标检测 - 图112,描述所属类别的变量长度等于类别数C。

对于每个锚框,模型需要预测输出📃 目标检测 - 图113,其中📃 目标检测 - 图114是锚框是否包含物体的概率,📃 目标检测 - 图115则是锚框包含的物体属于每个类别的概率。接下来让我们一起学习如何通过卷积神经网络输出这样的预测值。

每个锚框需要的标注数据个数为:
📃 目标检测 - 图116

对于m*n个方块的区域,每个区域K个锚框,则标注数据的个数是📃 目标检测 - 图117