导语
从2016年 Joseph Redmon 发布第一代YOLO开始,YOLO已经更新四代了,凭借着在一个网络模型中完成对图像中所有对象边界框和类别预测的独特创新,成为现今使用最广泛也是最快的对象检测算法之一。在本文中我们将详细介绍 YOLO v3 算法的目标检测原理,检测步骤,神经网络结构及代码实现。
YOLO 是一种使用全卷积神经网络的实时目标检测算法,它是 You Only Look Once的缩写。与其他目标检测的算法相比,YOLO在一个网络模型中完成对图像中所有对象边界框和类别预测,避免了花费大量时间生成候选区域。它的强项是检测速度和识别能力,而不是完美地定位对象。
与目标识别算法不同,目标检测算法不仅需要预测目标的类标签,而且需要提供检测目标的位置。YOLO 算法对整个图像使用全卷积神经网络,将图像划分为多个网格区域,并预测每个区域目标的边界框和概率,而目标预测的概率则会随即用来对边界框的精确度加权,从而获得准确的边界框位置和尺寸。
现实生活中存在大量如封面街道图中的场景,无人驾驶汽车必须实时检测到周围所有对象的位置,才能让系统做出正确的决策和控制。YOLO 算法能够快速定位并分类不同的对象,并且让每个对象周围都有一个边界框和相应的分类标签。
layer filters size input output 0 conv 32 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 32 1 conv 64 3 x 3 / 2 416 x 416 x 32 -> 208 x 208 x 64 2 conv 32 1 x 1 / 1 208 x 208 x 64 -> 208 x 208 x 32 3 conv 64 3 x 3 / 1 208 x 208 x 32 -> 208 x 208 x 64 4 shortcut 1 5 conv 128 3 x 3 / 2 208 x 208 x 64 -> 104 x 104 x 128 6 conv 64 1 x 1 / 1 104 x 104 x 128 -> 104 x 104 x 64 7 conv 128 3 x 3 / 1 104 x 104 x 64 -> 104 x 104 x 128 8 shortcut 5 9 conv 64 1 x 1 / 1 104 x 104 x 128 -> 104 x 104 x 64 10 conv 128 3 x 3 / 1 104 x 104 x 64 -> 104 x 104 x 128 11 shortcut 8 12 conv 256 3 x 3 / 2 104 x 104 x 128 -> 52 x 52 x 256 13 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 14 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 15 shortcut 12 16 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 17 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 18 shortcut 15 19 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 20 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 21 shortcut 18 22 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 23 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 24 shortcut 21 25 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 26 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 27 shortcut 24 28 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 29 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 30 shortcut 27 31 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 32 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 33 shortcut 30 34 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 35 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 36 shortcut 33 37 conv 512 3 x 3 / 2 52 x 52 x 256 -> 26 x 26 x 512 38 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 39 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 40 shortcut 37 41 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 42 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 43 shortcut 40 44 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 45 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 46 shortcut 43 47 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 48 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 49 shortcut 46 50 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 51 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 52 shortcut 49 53 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 54 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 55 shortcut 52 56 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 57 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 58 shortcut 55 59 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 60 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 61 shortcut 58 62 conv 1024 3 x 3 / 2 26 x 26 x 512 -> 13 x 13 x1024 63 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 64 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 65 shortcut 62 66 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 67 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 68 shortcut 65 69 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 70 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 71 shortcut 68 72 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 73 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 74 shortcut 71 75 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 76 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 77 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 78 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 79 conv 512 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 512 80 conv 1024 3 x 3 / 1 13 x 13 x 512 -> 13 x 13 x1024 81 conv 255 1 x 1 / 1 13 x 13 x1024 -> 13 x 13 x 255 82 detection 83 route 79 84 conv 256 1 x 1 / 1 13 x 13 x 512 -> 13 x 13 x 256 85 upsample * 2 13 x 13 x 256 -> 26 x 26 x 256 86 route 85 61 87 conv 256 1 x 1 / 1 26 x 26 x 768 -> 26 x 26 x 256 88 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 89 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 90 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 91 conv 256 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 256 92 conv 512 3 x 3 / 1 26 x 26 x 256 -> 26 x 26 x 512 93 conv 255 1 x 1 / 1 26 x 26 x 512 -> 26 x 26 x 255 94 detection 95 route 91 96 conv 128 1 x 1 / 1 26 x 26 x 256 -> 26 x 26 x 128 97 upsample * 2 26 x 26 x 128 -> 52 x 52 x 128 98 route 97 36 99 conv 128 1 x 1 / 1 52 x 52 x 384 -> 52 x 52 x 128 100 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 101 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 102 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 103 conv 128 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 128 104 conv 256 3 x 3 / 1 52 x 52 x 128 -> 52 x 52 x 256 105 conv 255 1 x 1 / 1 52 x 52 x 256 -> 52 x 52 x 255 106 detection
我们可以看到3条预测路径都是使用全卷积的结构,根据不同的输入尺寸,会得到3个不同大小的输出特征图,以YOLO v3的标准输入416x416x3
为例,输出的特征图为13x13x255, 26x26x255, 52x52x255
。你可能对这个感到疑惑,为什么输出的维度都是255。
原因在与YOLO v3特征图的每个网格,都配备了3个不同宽高比的先验框。对每个网格预测的输出结果是网络对3个先验框分别回归之后得到的3个边界框的信息的集合,每个边界框包括边界框位置(4维)、检测置信度(1维)、类别概率(80维)一共85维信息,因此一共有3×85 = 255
个参数。
三个特征图一共可以解码出(52 x 52) + (26 x 26) + (13 x 13)) x 3= 10647
个检测盒以及相应的类别、置信度。
再说YOLO v3的先验框。相对于YOLO v1直接回归边界框的实际宽、高,YOLO v2回归基于给定先验框的变化值,YOLO v3在YOLO v2的基础上,增加先验框的数量(5->9),并使用k-means对COCO数据集中的标签框进行聚类,得到类别中心点的9个框,宽高尺寸分别是 (10×13),(16×30),(33×23),(30×61),(62×45),(59× 119), (116 × 90), (156 × 198),(373 × 326) ,它们就是先验框的由来。值得注意的是,如果你想在自己独有的数据集上训练YOLO v3模型,你需要根据目标的大小,宽高比,以及图像的输入尺寸修改自己的先验框参数,实际操作中一般先使用k-means对标签框进行聚类,计算先验框的大小。
YOLO v3网络预测对象的边界框是通过对先验框进行线性回归(微调)实现。
上文中我们提到,图像中边界框的bx 和b y参数代表对象中心位置距离当前网格单元的左上角的偏差量。而边界框的bw 和b h, 确定的是边界框相对于整个图像的宽和高比例。值得注意的是YOLO v3网络训练以及输出的前四项参数不是边界框的位置和尺寸bx,by, bw, bh,而是标准化的tx, ty, tw, th,需要经过下面的解码函数转换。
回归函数:
在上图中σ(tx), σ(ty)是基于矩形框中心点距离左上角网格点坐标的偏移量, sigma是激活函数,论文中作者使用sigmoid。pw, ph 是先验框的宽、高,通过上述公式,计算出实际预测框的宽高 bw, bh。
举个例子,假设在输入尺寸为416x416x3
的情况下,我们在第二个特征图26x26x3x255
中检测到一个对象的边界框为[4,3,2]
, 第二个特征图对应先验框为(30×61),(62×45),(59× 119)
,先验框中Cx=3,Cy=4, index=1
,则取第二个先验框62x45
的宽高作为pw,ph求出bw,bh。对于计算出来的bx,by需要乘以第二个特征图的降采样率16(416/26),就可以得到在416x416尺寸原图中真实的边界框x,y了。
也许有读者会疑问,为什么需要这个额外的解码转换函数?直接将真实的偏移量以及宽高的尺度比例输出不行吗?我们分两块解释。
偏移量
首先讨论为什么不用绝对坐标:边界框属性由彼此堆叠的单元格预测得出。绝对坐标对于目标置信度的阈值计算,添加对中心的网格偏移,应用锚点等都不方便。因此YOLO v3/v2不会预测边界框中心的绝对坐标,而是预测的偏移量。(备注:YOLO v1使用绝对坐标和尺寸,后续版本已经改进成偏移量和尺寸比例)
那为什么要对使用边界框使用解码函数?
YOLO v3神经网络通过学习偏移量,就可以通过预设的先验框去回归真实边界框。
但是偏移量并不是直接累加到当前网格的左上角上,而是首先需要根据特征图中的网格的尺寸进行 sigma标准化。这是为了防止预测得到的对象中心位置位于当前网格外,将位置值归一化到[0,1]区间。
我们可以看下面的例子,假设我们正在对红色网格进行预测,模型的预测输出值为(1.2,0.7),狗的中心点是(6.0,6.0),通过累加偏移量到原点的坐标,得到边界框中心位置位于13 x 13特征图上的(7.2,6.7),这不符合YOLO v3的设定。因此我们需要使用 sigma函数将输出压缩到0到1的范围内,从而有效的将对象中心保存在所预测的网格中。
对数尺度空间
在实际预测中, 由于图像中边界框的尺寸和长宽比变化很大,如果我们用卷积网络强行预测边界框的绝对尺寸,那么网络将很难收敛。因此我们使用相对原图宽高的比例来描述边界框的宽高。
那为什么我们还需要对数尺度空间呢?
原因在与将边界框的尺度缩放到对数空间,有助于降低训练的不稳定梯度。比如,原本我们可以通过对先验框的tw,th进行缩放微调得到bw,bh,由于bw,bh>0,那么必然要求卷积神经模型的输出tw, th>0,这就意味着给卷积神经模型加上了不等式约束。一个不等式约束的优化问题是无法用SGD,Adam解决的。
加上指数函数之后,我们可以看到不管预测的tw, th大于或者小于0,bw, bh永远大于0,这就可以有效的降低训练的不稳定梯度。
通过对输出应用对数空间转换,然后与先验框的宽高相乘就可以得到边界框的预测尺寸。
YOLO v3网络对于边界框的回归其实就是找到映射f使得先验框能够通过回归(平移和缩放)逼近真实值G。
Objectness Score 代表这个边界框中存在对象的概率。Class Confidences是指被检测的对象属于特定类别(如猫狗,汽车,行人等)的概率。在老的YOLO版本中,作者使用softmax来预测类别分数。然而在YOLO v3中作者使用了sigmoid激活函数,使得网络更加灵活。
这是由于softmax具有类别互斥性,即假设一个对象属于某一类,那必然不属于其他类。比如说如果数据集中同一网格预测对象有人和女人时,softmax就会失败,而sigmoid使用逻辑回归来预测,凡是高于给定阈值的任意类别的标签都可以被加入到边界框的属性中。换句话说,softmax所有类别的预测概率和为1,sigmoid可以不为1。
在 YOLO v3中,检测是通过在网络结构中三个不同位置的三种不同大小的特征图上应用1 × 1卷积内核来完成的。
除了此处之外模型中,存在大量的1x1卷积内核。其实这是为了实现跨通道的交互和信息整合,其次1x1卷积内核可以方便进行卷积核通道数的降维和升维,比如YOLO v3中先使用1x1 的卷积核将通道拉低,然后再用3x3的卷积核将通道拉回来,最后是1x1卷积内核可以在保持特征图尺寸不变(即不损失分辨率)的前提下大幅增加非线性特性。这种方式可以把网络做得很深。深层网络的特征提取和检测的效果相对浅层网络更好。而3x3卷积可以减少参数量,提高实时性。
三次检测分别都在原图进行32倍降采样(52x52x256),16倍降采样(26x26x512),8倍降采样(13x13x1024)后进行检测。降采样有助于防止由于池化导致的低层级特征损失。
众所周知,深层网络的特征表达效果很好。然而在32倍降采样后,特征图尺度变得很小,在这种情况下对于中小尺寸对象的识别上效果不好。因此我们在86层,97层分别使用了步长为2的上采样,逐步将特征图的尺寸拉回到了第61层,第36层的尺寸大小,随后融合了深层网络与浅层网络的信息,从而保留了一些原图中细小的特征,实现了对中小尺度物体的精确检测。如在第三条路径中,第36层的特征图(52x52x256)拼接第97层的特征图(52x52x128),最后得到第98层的特征图(52x52x128)
在这里我用封面街景图的例子来讲解YOLO v3目标检测的流程。
我们来看看 YOLO 如何接受输入图像并检测多个对象,假设有个 CNN 被训练成识别多个类别,包括交通信号灯 汽车,行人和卡车。我们为其提供多种不同类型的锚点框,如一个高的锚点框和一个宽的锚点框,使其能够处理形状不同的重叠对象。CNN 被训练后 我们可以向其提供新的测试图像并检测图像中的对象,测试图像首先被划分为网格,然后网络生成输出向量。每个网格单元对应一个向量,这些向量告诉我们某个单元中是否有对象,对象的类别是什么以及对象的边界框是什么。我们在每个尺度使用三个先验框,因此每个尺度下的各个网格单元都将有三个预测边界框,某些预测边界框的 Pc 值将很低,生成这些输出向量后 我们使用非最大值抑制删除不太有用的边界框。对于每个类别,非最大值抑制都会删除 Pc 值低于某个给定阈值的边界框,然后选择 Pc 值最高的边界框,并删除与该边界框非常相似的边界框。它将重复这一流程,直到删除所有类别的非最大值抑制边界框。
最终结果将如下图所示,可以看出 YOLO v3 有效地检测出图像中包括汽车和行人在内的很多对象。
我们来看下YOLO v3的目标预测函数(基于pytorch):
def nms(model, img, iou_thresh, nms_thresh): start = time.time() # Set the model to evaluation mode. model.eval() # Convert the image from a NumPy ndarray to a PyTorch Tensor # of the correct shape. The image is transposed, then converted # to a FloatTensor of dtype float32, then normalized to values # between 0 and 1, and finally unsqueezed to have the correct # shape of 1 x 3 x 416 x 416 img = torch.from_numpy(img.transpose(2,0,1)).float().div(255.0).unsqueeze(0) # Feed the image to the neural network with the corresponding # NMS threshold. The first step in NMS is to remove all bounding # boxes that have a very low probability of detection. All # predicted bounding boxes with a value less than the given NMS # threshold will be removed. list_boxes = model(img, nms_thresh) # Perform the second step of NMS on the bounding boxes returned # by the neural network. In this step, we only keep the best # bounding boxes by eliminating all the bounding boxes whose # IOU value is higher than the given IOU threshold. boxes = list_boxes[0][0] + list_boxes[1][0] + list_boxes[2][0] boxes = nms(boxes, iou_thresh) # Stop the time. finish = time.time() # Print the time it took to detect objects print('nnIt took {:.3f}'.format(finish - start), 'seconds to detect the objects in the image.n') # Print the number of objects detected print('Number of Objects Detected:', len(boxes), 'n') return boxes
再让我们看下NMS非最大值抑制的实现:
def nms(boxes, iou_thresh): # If there are no bounding boxes do nothing if len(boxes) == 0: return boxes # Create a PyTorch Tensor to keep track of the detection confidence # of each predicted bounding box det_confs = torch.zeros(len(boxes)) # Get the detection confidence of each predicted bounding box for i in range(len(boxes)): det_confs[i] = boxes[i][4] # Sort the indices of the bounding boxes by detection confidence value # in descending order. # We ignore the first returned element since we are only interested in # the sorted indices _,sortIds = torch.sort(det_confs, descending = True) # Create an empty list to hold the best bounding boxes after # Non-Maximal Suppression (NMS) is performed best_boxes = [] # Perform Non-Maximal Suppression for i in range(len(boxes)): # Get the bounding box with the highest detection confidence first box_i = boxes[sortIds[i]] # Check that the detection confidence is not zero if box_i[4] > 0: # Save the bounding box best_boxes.append(box_i) # Go through the rest of the bounding boxes in the # list and calculate their IOU with respect to the # previous selected box_i. for j in range(i + 1, len(boxes)): box_j = boxes[sortIds[j]] # If the IOU of box_i and box_j is higher than # the given IOU threshold set box_j's detection # confidence to zero. if boxes_iou(box_i, box_j) > iou_thresh: box_j[4] = 0 return best_boxes
详细的网络结构和预测代码可以查看我的代码库。
YOLO v3 具有极强的速度优势,改进的Darknet-53模型大幅提升了模型精度,同时在多个尺度上对目标进行检测,增强了对小目标及重叠遮挡目标的识别,在速度和精度上的表现都很均衡,因此被广泛应用在工业界。
YOLO v3 有很多细节和思想都值得计算机视觉领域的工程师去思考和借鉴。本文只是引入,还需要感兴趣的读者们自己去深入。
今年年初 Joseph Redmon这位YOLO、SSD 等知名 AI 算法的发明者宣布出于道德上的考虑,他决定退出有关计算机视觉的研究。不得不说,YOLO和SSD在各行各业的广泛应用,确实给隐私,安全等领域带来了很多的危险。技术发展必然伴随着风险,但是技术发展带来的光明未来值得大家继续去为之努力和创新!
尽管YOLO的原作者 Joseph Redmon退出了CV界,但是好在还有人接下了YOLO的大棒。我在下篇文章会介绍目标检测领域又一篇集大成之作 :YOLO v4 !作者是Alexey Bochkovskiy,堪称吾等软件工程师的工程集成和应用典范~
已完成
数据加载中