TensorFlow 2 和 Keras 高级深度学习:11~13(1)

简介: TensorFlow 2 和 Keras 高级深度学习:11~13(1)

十一、对象检测

目标检测是计算机视觉最重要的应用之一。 对象检测是同时定位和识别图像中存在的对象的任务。 为了使自动驾驶汽车安全地在街道上行驶,该算法必须检测到行人,道路,车辆,交通信号灯,标志和意外障碍物的存在。 在安全方面,入侵者的存在可以用来触发警报或通知适当的当局。

尽管很重要,但是对象检测一直是计算机视觉中的一个长期存在的问题。 已经提出了许多算法,但是通常很慢,并且精度和召回率很低。 与 AlexNet [1]在 ImageNet 大规模图像分类问题中所取得的成就类似,深度学习显着提高了对象检测领域。 最新的对象检测方法现在可以实时运行,并且具有更高的精度和召回率。

在本章中,我们重点介绍实时对象检测。 特别是,我们讨论了tf.keras单发检测SSD)[2]的概念和实现。 与其他深度学习检测算法相比,SSD 可在现代 GPU 上实现实时检测速度,而表现不会显着下降。 SSD 还易于端到端训练。

总之,本章的目的是介绍:

  • 对象检测的概念
  • 多尺度目标检测的概念
  • SSD 作为多尺度目标检测算法
  • tf.keras中 SSD 的实现

我们将从介绍对象检测的概念开始。

1. 对象检测

在对象检测中,目标是在图像中定位和识别物体。“图 11.1.1”显示了目标汽水罐的目标物检测。 本地化意味着必须估计对象的边界框。 使用左上角像素坐标和右下角像素坐标是用于描述边界框的通用约定。 在“图 11.1.1”中,左上角像素具有坐标(x_min, y_min),而右下角像素的坐标为(x_max, y_max)。像素坐标系的原点(0, 0)位于整个图像的左上角像素。

在执行定位时,检测还必须识别对象。 识别是计算机视觉中的经典识别或分类任务。 至少,对象检测必须确定边界框是属于已知对象还是背景。 可以训练对象检测网络以仅检测一个特定对象,例如“图 11.1.1”中的汽水罐。 其他所有内容均视为背景,因此无需显示其边界框。 同一对象的多个实例,例如两个或多个汽水罐,也可以通过同一网络检测到,如图“图 11.1.2”所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0QWKYMw-1681704403407)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_01.png)]

图 11.1.1 说明了对象检测是在图像中定位和识别对象的过程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yr25EewR-1681704403408)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_02.png)]

图 11.1.2 被训练为检测一个对象实例的同一网络可以检测到同一对象的多个实例。

如果场景中存在多个对象,例如在“图 11.1.3”中,则对象检测方法只能识别在其上训练的一个对象。 其他两个对象将被分类为背景,并且不会分配边界框。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LFN8USt6-1681704403408)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_03.png)]

图 11.1.3 如果仅在检测汽水罐方面训练了对象检测,它将忽略图像中的其他两个对象。

但是,如果重新训练了网络以检测三个对象:1)汽水罐,2)果汁罐和 3)水瓶会同时定位和识别,如图“图 11.1.4”所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0RMai6Ir-1681704403409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_04.png)]

图 11.1.4 即使背景杂乱或照明发生变化,也可以重新训练对象检测网络以检测所有三个对象。

一个好的对象检测器必须在现实环境中具有鲁棒性。“图 11.1.4”显示了一个好的对象检测网络,即使背景杂乱甚至在弱光条件下,也可以定位和识别已知对象。 对象检测器必须具有鲁棒性的其他因素是物体变换(旋转和/或平移),表面反射,纹理变化和噪声。

总之,对象检测的目标是针对图像中每个可识别的对象同时预测以下内容:

  • y_cls或单热向量形式的类别或类
  • y_box = ((x_min, y_min), (x_max, y_max))或像素坐标形式的边界框坐标

通过解释了对象检测的基本概念,我们可以开始讨论对象检测的某些特定机制。 我们将从介绍锚框开始。

2. 锚框

从上一节的讨论中,我们了解到,对象检测必须预测边界框区域以及其中的对象类别。 假设与此同时,我们的重点是边界框坐标估计。

网络如何预测坐标(x_min, y_min)(x_max, y_max)? 网络可以做出与图像的左上角像素坐标和右下角像素坐标相对应的初始猜测,例如(0, 0)(w, h)w是图像宽度,而h是图像高度。 然后,网络通过对地面真实边界框坐标执行回归来迭代地校正估计。

由于可能的像素值存在较大差异,因此使用原始像素估计边界框坐标不是最佳方法。 SSD 代替原始像素,将地面真值边界框和预测边界框坐标之间的像素误差值最小化。 对于此示例,像素的误差值为(x_min, y_min)(x_max - w, y_max - h)。 这些值称为offsets

为了帮助网络找出正确的边界框坐标,将图像划分为多个区域。 每个区域称为定位框。 然后,网络估计每个锚框的偏移。 这样得出的预测更接近于基本事实。

例如,如图“图 11.2.1”所示,将普通图像尺寸640 x 480分为2 x 1个区域,从而产生两个锚框。 与2 x 2的大小不同,2 x 1的划分创建了近似方形的锚框。 在第一个锚点框中,新的偏移量是(x_min, y_min){x_max - w/2, y_max - h},它们比没有锚框的像素误差值更小。 第二个锚框的偏移量也较小。

在“图 11.2.2”中,图像被进一步分割。 这次,锚框为3 x 2。第二个锚框偏移为{x_min - w/3, y_min}{x_max - 2w/3, y_max - h/2},这是迄今为止最小的。 但是,如果将图像进一步分为5 x 4,则偏移量开始再次增加。 主要思想是,在创建各种尺寸的区域的过程中,将出现最接近地面真值边界框的最佳锚框大小。 使用多尺度锚框有效地检测不同大小的对象将巩固多尺度对象检测算法的概念。

找到一个最佳的锚框并不是零成本。 尤其是,有些外部锚框的偏移量比使用整个图像还要差。 在这种情况下,SSD 建议这些锚定框不应对整个优化过程有所帮助,而应予以抑制。 在以下各节中,将更详细地讨论排除非最佳锚框的算法。

到目前为止,我们已经有三套锚框。

第一个创建一个2 x 1的锚框网格,每个锚框的尺寸为(w/2, h)

第二个创建一个3 x 2的锚框网格,每个锚框的尺寸为(w/3, h/2)

第三个创建一个5 x 4的锚框网格,每个锚框的尺寸为(w/5, h/4)

我们还需要多少套锚盒? 它取决于图像的尺寸和对象最小边框的尺寸。 对于此示例中使用的640 x 480图像,其他锚点框为:

10 x 8格的锚框,每个框的尺寸为(w/10, h/8)

20 x 15格的锚框,每个锚框的尺寸为(w/20, h/15)

40 x 30格的锚框,每个框的尺寸为(w/40, h/30)

对于具有40 x 30网格的锚框的640 x 480图像,最小的锚框覆盖输入图像的16 x 16像素斑块,也称为接收域。 到目前为止,包围盒的总数为 1608。对于所有尺寸,最小的缩放因子可以总结为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oaJBrmYO-1681704403409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_010.png)] (Equation 11.2.1)

锚框如何进一步改进? 如果我们允许锚框具有不同的纵横比,则可以减少偏移量。 每个调整大小的锚点框的质心与原始锚点框相同。 除宽高比 1 外,SSD [2]包括其他宽高比:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYrgozfl-1681704403409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_011.png)] (Equation 11.2.2)

对于每个纵横比a[i],对应的锚框尺寸为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9YigF3Dn-1681704403409)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_013.png)] (Equation 11.2.3)

(s[xj], s[yj])是“公式 11.2.1”中的第j个比例因子。

使用每个锚框五个不同的长宽比,锚框的总数将增加到1,608 x 5 = 8,040。“图 11.2.3”显示了(s[x4], s[y4]) = (1/3, 1/2)a[i ∈ {0, 1, 3}] = 1, 2, 1/2情况下的锚框。

请注意,为了达到一定的纵横比,我们不会使锚框变形。 而是调整锚框的宽度和高度。

对于a[0] = 1,SSD 建议使用其他尺寸的锚框:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CrAJxyOz-1681704403410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_018.png)] (Equation 11.2.4)

现在每个区域有六个锚定框。 有五个是由于五个纵横比,另外还有一个纵横比为 1。新的锚框总数增加到 9,648。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6np5yWp-1681704403410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_05.png)]

图 11.2.1 将图像划分为多个区域(也称为锚框),使网络可以进行更接近地面真实情况的预测。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHAv3ROI-1681704403410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_06.png)]

图 11.2.2 使用较小的锚框可以进一步减少偏移。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNYjynKc-1681704403410)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_07.png)]

图 11.2.3 具有比例因子(s[x4], s[y4]) = (1/3, 1/2)和纵横比a[i ∈ {0, 1, 3}] = 1, 2, 1/2的一个区域的锚框。

下面的“列表 11.2.1”显示了锚框生成函数anchor_boxes()。 给定输入的图像形状(image_shape),纵横比(aspect_ratios)和缩放因子(sizes),将计算不同的锚框大小并将其存储在名为width_height的列表中。 从给定的特征映射形状(feature_shape(h_fmap, w_fmap)width_height, 生成具有尺寸(h_fmap, w_fmap, n_boxes, 4)n_boxes或每个特征映射点的锚点框数是基于纵横比和等于 1 的纵横比的一个附加大小计算的。

“列表 11.2.1”:锚框生成函数的layer_utils.py函数:

def anchor_boxes(feature_shape,
                 image_shape,
                 index=0,
                 n_layers=4,
                 aspect_ratios=(1, 2, 0.5)):
    """ Compute the anchor boxes for a given feature map.
    Anchor boxes are in minmax format
Arguments:
        feature_shape (list): Feature map shape
        image_shape (list): Image size shape
        index (int): Indicates which of ssd head layers
            are we referring to
        n_layers (int): Number of ssd head layers
Returns:
        boxes (tensor): Anchor boxes per feature map
    """
# anchor box sizes given an index of layer in ssd head
    sizes = anchor_sizes(n_layers)[index]
    # number of anchor boxes per feature map pt
    n_boxes = len(aspect_ratios) + 1
    # ignore number of channels (last)
    image_height, image_width, _ = image_shape
    # ignore number of feature maps (last)
    feature_height, feature_width, _ = feature_shape
# normalized width and height
    # sizes[0] is scale size, sizes[1] is sqrt(scale*(scale+1))
    norm_height = image_height * sizes[0]
    norm_width = image_width * sizes[0]
# list of anchor boxes (width, height)
    width_height = []
    # anchor box by aspect ratio on resized image dims
    # Equation 11.2.3
    for ar in aspect_ratios:
        box_width = norm_width * np.sqrt(ar)
        box_height = norm_height / np.sqrt(ar)
        width_height.append((box_width, box_height))
    # multiply anchor box dim by size[1] for aspect_ratio = 1
    # Equation 11.2.4
    box_width = image_width * sizes[1]
    box_height = image_height * sizes[1]
    width_height.append((box_width, box_height))
# now an array of (width, height)
    width_height = np.array(width_height)
# dimensions of each receptive field in pixels
    grid_width = image_width / feature_width
    grid_height = image_height / feature_height
# compute center of receptive field per feature pt
    # (cx, cy) format 
    # starting at midpoint of 1st receptive field
    start = grid_width * 0.5
    # ending at midpoint of last receptive field
    end = (feature_width - 0.5) * grid_width
    cx = np.linspace(start, end, feature_width)
start = grid_height * 0.5
    end = (feature_height - 0.5) * grid_height
    cy = np.linspace(start, end, feature_height)
# grid of box centers
    cx_grid, cy_grid = np.meshgrid(cx, cy)
# for np.tile()
    cx_grid = np.expand_dims(cx_grid, -1)
    cy_grid = np.expand_dims(cy_grid, -1)
# tensor = (feature_map_height, feature_map_width, n_boxes, 4)
    # aligned with image tensor (height, width, channels)
    # last dimension = (cx, cy, w, h)
    boxes = np.zeros((feature_height, feature_width, n_boxes, 4))
# (cx, cy)
    boxes[..., 0] = np.tile(cx_grid, (1, 1, n_boxes))
    boxes[..., 1] = np.tile(cy_grid, (1, 1, n_boxes))
# (w, h)
    boxes[..., 2] = width_height[:, 0]
    boxes[..., 3] = width_height[:, 1]
# convert (cx, cy, w, h) to (xmin, xmax, ymin, ymax)
    # prepend one dimension to boxes 
    # to account for the batch size = 1
    boxes = centroid2minmax(boxes)
    boxes = np.expand_dims(boxes, axis=0)
    return boxes
def centroid2minmax(boxes):
    """Centroid to minmax format 
    (cx, cy, w, h) to (xmin, xmax, ymin, ymax)
Arguments:
        boxes (tensor): Batch of boxes in centroid format
Returns:
        minmax (tensor): Batch of boxes in minmax format
    """
    minmax= np.copy(boxes).astype(np.float)
    minmax[..., 0] = boxes[..., 0] - (0.5 * boxes[..., 2])
    minmax[..., 1] = boxes[..., 0] + (0.5 * boxes[..., 2])
    minmax[..., 2] = boxes[..., 1] - (0.5 * boxes[..., 3])
    minmax[..., 3] = boxes[..., 1] + (0.5 * boxes[..., 3])
    return minmax

我们已经介绍了锚框如何协助对象检测以及如何生成它们。 在下一节中,我们将介绍一种特殊的锚点框:真实情况锚点框。 给定图像中的对象,必须将其分配给多个锚点框之一。 这就是,称为真实情况锚定框。

3. 真实情况锚框

从“图 11.2.3”看来,给定一个对象边界框,有许多可以分配给对象的真实情况锚定框。 实际上,仅出于“图 11.2.3”中的说明,已经有 3 个锚定框。 如果考虑每个区域的所有锚框,则仅针对(s[x4], s[y4]) = (1/3, 1/2)就有6 x 6 = 36个地面真实框。 使用所有 9,648 个锚点框显然过多。 所有锚定框中只有一个应与地面真值边界框相关联。 所有其他锚点框都是背景锚点框。 选择哪个对象应被视为图像中对象的真实情况锚定框的标准是什么?

选择锚框的基础称为交并比IoU)。 IoU 也称为 Jaccard 指数。 在“图 11.3.1”中说明了 IoU。 给定 2 个区域,对象边界框B[0]和锚定框A[1],IoU 等于重叠除以合并区域的面积:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZIjlJRy-1681704403411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_022.png)] (Equation 11.3.1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ClluQ9bE-1681704403411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_08.png)]

图 11.3.1 IoU 等于(左)候选锚点框A[1]与(右)对象边界框B[0]之间的相交面积除以并集面积。

我们删除了该等式的下标。 对于给定的对象边界框B[i],对于所有锚点框A[j],地面真值锚点框A[j(gt)]是具有最大 IoU 的一个:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iRYHLbjy-1681704403411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_023.png)] (Equation 11.3.2)

请注意,对于每个对象,只有一个基于“公式 11.3.2”的地面真值锚定框。 此外,必须在所有比例因子和尺寸(长宽比和附加尺寸)中对所有锚框进行最大化。 在“图 11.3.1”中,在 9,648 个锚框中仅显示了一个比例因子大小。

为了说明“公式 11.3.2”,假设考虑了“图 11.3.1”中纵横比为 1 的锚框。 对于每个锚框,估计的 IoU 均显示在“表 11.3.1”中。 由于边界框B[0]的最大 IoU 为 0.32,因此带有锚框A[1]A[1]被分配为地面真值边界框B[0]A[1]也被称为正锚框

正锚定框的类别和偏移量是相对于其地面真值边界框确定的。 正锚定框的类别与其地面真值边界框相同。 同时,可以将正锚框偏移量计算为等于地面真实边界框坐标减去其自身的边界框坐标。

其余锚框发生了什么,A[0]A[2]A[3]A[4],和A[5]? 我们可以通过找到他们的 IoU 大于某个阈值的边界框来给他们第二次机会。

例如,如果阈值为 0.5,则没有可分配给它们的地面真理边界框。 如果阈值降低到 0.25,则A[4]也分配有地面真值边界框B[0],因为其 IoU 为 0.30 。 将A[4]添加到肯定锚框列表中。 在这本书中,A[4]被称为额外的正面锚盒。 没有地面边界框的其余锚框称为负锚框

在以下有关损失函数的部分中,负锚框不构成偏移损失函数。

B[0]
A[0] 0
A[1] 0.32
A[2] 0
A[3] 0
A[4] 0.30
A[5] 0

“表 11.3.1”每个锚框A[j ∈ 0 .. 5]的 IoU,带有对象边界框B[0],如“图 11.3.1”所示。

如果加载了另一个带有 2 个要检测的对象的图像,我们将寻找 2 个正 IoU,最大 IoU,并带有边界框B[0]B[1]。 然后,我们使用边界框B[0]B[1]寻找满足最小 IoU 准则的额外正锚框。

为了简化讨论,我们只考虑每个区域一个锚框。 实际上,应该考虑代表不同缩放比例,大小和纵横比的所有锚框。 在下一节中,我们讨论如何制定损失函数,这些损失函数将通过 SSD 网络进行优化。

“列表 11.3.1”显示了get_gt_data()的实现,该实现计算锚定框的真实情况标签。

“列表 11.3.1”:layer_utils.py

def get_gt_data(iou,
                n_classes=4,
                anchors=None,
                labels=None,
                normalize=False,
                threshold=0.6):
    """Retrieve ground truth class, bbox offset, and mask
    Arguments:
        iou (tensor): IoU of each bounding box wrt each anchor box
        n_classes (int): Number of object classes
        anchors (tensor): Anchor boxes per feature layer
        labels (list): Ground truth labels
        normalize (bool): If normalization should be applied
        threshold (float): If less than 1.0, anchor boxes>threshold
            are also part of positive anchor boxes
Returns:
        gt_class, gt_offset, gt_mask (tensor): Ground truth classes,
            offsets, and masks
    """
    # each maxiou_per_get is index of anchor w/ max iou
    # for the given ground truth bounding box
    maxiou_per_gt = np.argmax(iou, axis=0)
# get extra anchor boxes based on IoU
    if threshold < 1.0:
        iou_gt_thresh = np.argwhere(iou>threshold)
        if iou_gt_thresh.size > 0:
            extra_anchors = iou_gt_thresh[:,0]
            extra_classes = iou_gt_thresh[:,1]
            extra_labels = labels[extra_classes]
            indexes = [maxiou_per_gt, extra_anchors]
            maxiou_per_gt = np.concatenate(indexes,
                                           axis=0)
            labels = np.concatenate([labels, extra_labels],
                                    axis=0)
# mask generation
    gt_mask = np.zeros((iou.shape[0], 4))
    # only indexes maxiou_per_gt are valid bounding boxes
    gt_mask[maxiou_per_gt] = 1.0
# class generation
    gt_class = np.zeros((iou.shape[0], n_classes))
    # by default all are background (index 0)
    gt_class[:, 0] = 1
    # but those that belong to maxiou_per_gt are not
    gt_class[maxiou_per_gt, 0] = 0
    # we have to find those column indexes (classes)
    maxiou_col = np.reshape(maxiou_per_gt,
                            (maxiou_per_gt.shape[0], 1))
    label_col = np.reshape(labels[:,4],
                           (labels.shape[0], 1)).astype(int)
    row_col = np.append(maxiou_col, label_col, axis=1)
    # the label of object in maxio_per_gt
    gt_class[row_col[:,0], row_col[:,1]]  = 1.0
# offsets generation
    gt_offset = np.zeros((iou.shape[0], 4))
#(cx, cy, w, h) format
    if normalize:
        anchors = minmax2centroid(anchors)
        labels = minmax2centroid(labels)
        # bbox = bounding box
        # ((bbox xcenter - anchor box xcenter)/anchor box width)/.1
        # ((bbox ycenter - anchor box ycenter)/anchor box height)/.1
        # Equation 11.4.8 Chapter 11
        offsets1 = labels[:, 0:2] - anchors[maxiou_per_gt, 0:2]
        offsets1 /= anchors[maxiou_per_gt, 2:4]
        offsets1 /= 0.1
# log(bbox width / anchor box width) / 0.2
        # log(bbox height / anchor box height) / 0.2
        # Equation 11.4.8 Chapter 11
        offsets2 = np.log(labels[:, 2:4]/anchors[maxiou_per_gt, 2:4])
        offsets2 /= 0.2
offsets = np.concatenate([offsets1, offsets2], axis=-1)
# (xmin, xmax, ymin, ymax) format
    else:
        offsets = labels[:, 0:4] - anchors[maxiou_per_gt]
gt_offset[maxiou_per_gt] = offsets
return gt_class, gt_offset, gt_mask
def minmax2centroid(boxes):
    """Minmax to centroid format
    (xmin, xmax, ymin, ymax) to (cx, cy, w, h)
Arguments:
        boxes (tensor): Batch of boxes in minmax format
Returns:
        centroid (tensor): Batch of boxes in centroid format
    """
    centroid = np.copy(boxes).astype(np.float)
    centroid[..., 0] = 0.5 * (boxes[..., 1] - boxes[..., 0])
    centroid[..., 0] += boxes[..., 0]
    centroid[..., 1] = 0.5 * (boxes[..., 3] - boxes[..., 2])
    centroid[..., 1] += boxes[..., 2]
    centroid[..., 2] = boxes[..., 1] - boxes[..., 0]
    centroid[..., 3] = boxes[..., 3] - boxes[..., 2]
    return centroid

maxiou_per_gt = np.argmax(iou, axis=0)实现了“公式 11.3.2”。 额外的阳性锚框是基于由iou_gt_thresh = np.argwhere(iou>threshold)实现的用户定义的阈值确定的。

仅当阈值小于 1.0 时,才会查找额外的正锚框。 所有带有地面真值边界框的锚框(即组合的正锚框和额外的正锚框)的索引成为真实情况掩码的基础:

gt_mask[maxiou_per_gt] = 1.0

所有其他锚定框(负锚定框)的掩码为 0.0,并且不影响偏移损失函数的优化。

每个锚定框的类别gt_class被分配为其地面实况边界框的类别。 最初,为所有锚框分配背景类:

# class generation
    gt_class = np.zeros((iou.shape[0], n_classes))
    # by default all are background (index 0)
    gt_class[:, 0] = 1

然后,将每个正面锚点框的类分配给其非背景对象类:

# but those that belong to maxiou_per_gt are not
    gt_class[maxiou_per_gt, 0] = 0
    # we have to find those column indexes (classes)
    maxiou_col = np.reshape(maxiou_per_gt,
                            (maxiou_per_gt.shape[0], 1))
    label_col = np.reshape(labels[:,4],
                           (labels.shape[0], 1)).astype(int)
    row_col = np.append(maxiou_col, label_col, axis=1)
    # the label of object in maxio_per_gt
    gt_class[row_col[:,0], row_col[:,1]]  = 1.0

row_col[:,0]是正锚框的索引,而row_col[:,1]是它们的非背景对象类的索引。 请注意,gt_class是单热点向量的数组。 这些值都为零,除了锚点框对象的索引处。 索引 0 是背景,索引 1 是第一个非背景对象,依此类推。 最后一个非背景对象的索引等于n_classes-1

例如,如果锚点框 0 是负锚点框,并且有 4 个对象类别(包括背景),则:

gt_class[0] = [1.0, 0.0, 0.0, 0.0]

如果锚定框 1 是正锚定框,并且其地面真值边界框包含带有标签 2 的汽水罐,则:

gt_class[1] = [0.0, 0.0, 1.0, 0.0]

最后,偏移量只是地面真实边界框坐标减去锚框坐标:

# (xmin, xmax, ymin, ymax) format
    else:
        offsets = labels[:, 0:4] - anchors[maxiou_per_gt]

注意,我们仅计算正锚框的偏移量。

如果选择了该选项,则可以将偏移量标准化。 下一部分将讨论偏移量归一化。 我们将看到:

#(cx, cy, w, h) format
    if normalize:
anchors = minmax2centroid(anchors)
        labels = minmax2centroid(labels)
        # bbox = bounding box
        # ((bbox xcenter - anchor box xcenter)/anchor box width)/.1
        # ((bbox ycenter - anchor box ycenter)/anchor box height)/.1
        # Equation 11.4.8 
        offsets1 = labels[:, 0:2] - anchors[maxiou_per_gt, 0:2]
        offsets1 /= anchors[maxiou_per_gt, 2:4]
        offsets1 /= 0.1
# log(bbox width / anchor box width) / 0.2
        # log(bbox height / anchor box height) / 0.2
        # Equation 11.4.8 
        offsets2 = np.log(labels[:, 2:4]/anchors[maxiou_per_gt, 2:4])
        offsets2 /= 0.2
offsets = np.concatenate([offsets1, offsets2], axis=-1)

只是“公式 11.4.8”的实现,下一节将进行讨论,为方便起见,在此处显示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KeaT0TTn-1681704403411)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_025.png)] (Equation 11.4.8)

现在我们已经了解了地面真锚框的作用,我们将继续研究对象检测中的另一个关键组件:损失函数。

4. 损失函数

在 SSD 中,有数千个锚定框。 如本章前面所述,对象检测的目的是预测每个锚框的类别和偏移量。 我们可以对每个预测使用以下损失函数:

  • L_cls - y_cls的分类交叉熵损失
  • L_off - L1 或 L2,用于y_cls。 请注意,只有正锚框有助于L_off L1,也称为平均绝对误差MAE)损失,而 L2 也称为均方误差MSE)损失。

总的损失函数为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G8C3QxEy-1681704403412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_081.png)] (Equation 11.4.1)

对于每个定位框,网络都会预测以下内容:

  • y_cls或单热向量形式的类别或类
  • y_off = ((x_omin, y_omin), (x_omax, y_omax))或相对于锚框的像素坐标形式的偏移。

为了方便计算,可以将偏移量更好地表示为以下形式:

y_off = ((x_omin, y_omin), (x_omax, y_omax)) (Equation 11.4.2)

SSD 是一种监督对象检测算法。 可以使用以下基本真值:

  • y_label或要检测的每个对象的类标签
  • y_gt = (x_gmin, x_gmax, y_gmin, y_gmax)或地面真实偏差,其计算公式如下:

y_gt = (x_bmin – x_amin, x_bmax – x_amax, y_bmin – y_amin, y_bmax – y_amax) (Equation 11.4.3)

换句话说,将地面真实偏移量计算为对象包围盒相对于锚定框的地面真实偏移量。 为了清楚起见,y_box下标中的细微调整。 如上一节所述,基本真值是通过get_gt_data()函数计算的。

但是,SSD 不建议直接从预测原始像素误差值y_off。 而是使用归一化的偏移值。 地面真值边界框和锚点框坐标首先以质心尺寸格式表示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IzEvl1fo-1681704403412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_026.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ITSI8PBT-1681704403412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_027.png)]

(Equation 11.4.4)

哪里:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q45JPu0F-1681704403412)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_028.png)] (Equation 11.4.5)

是边界框中心的坐标,并且:

(w[b], h[b]) = (x_max – x_min, y_max - y_min) (Equation 11.4.6)

分别对应于宽度和高度。 锚框遵循相同的约定。 归一化的真实情况偏移量表示为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSiOvctw-1681704403413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_029.png)] (Equation 11.4.7)

通常,y_gt的元素值很小,||y_gt|| << 1.0。 较小的梯度会使网络训练更加难以收敛。

为了缓解该问题,将每个元素除以其估计的标准差。 由此产生的基本事实抵消了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nI54Gmr-1681704403413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_025.png)] (Equation 11.4.8)

推荐值为:σ[x] = σ[y] = 0.1σ[w] = σ[h] = 0.2。 换句话说,沿着xy轴的像素误差的预期范围是± 10%,而对于宽度和高度,则是`± 20%。 这些值纯粹是任意的。

“列表 11.4.1”:loss.py L1 和平滑 L1 损失函数

from tensorflow.keras.losses import Huber
def mask_offset(y_true, y_pred): 
    """Pre-process ground truth and prediction data"""
    # 1st 4 are offsets
    offset = y_true[..., 0:4]
    # last 4 are mask
    mask = y_true[..., 4:8]
    # pred is actually duplicated for alignment
    # either we get the 1st or last 4 offset pred
    # and apply the mask
    pred = y_pred[..., 0:4]
    offset *= mask 
    pred *= mask 
    return offset, pred
def l1_loss(y_true, y_pred):
    """MAE or L1 loss
    """ 
    offset, pred = mask_offset(y_true, y_pred)
    # we can use L1
    return K.mean(K.abs(pred - offset), axis=-1)
def smooth_l1_loss(y_true, y_pred):
    """Smooth L1 loss using tensorflow Huber loss
    """
    offset, pred = mask_offset(y_true, y_pred)
    # Huber loss as approx of smooth L1
    return Huber()(offset, pred)

此外,代替y_cls的 L1 损失,SSD 受 Fast-RCNN [3]启发,使用平滑 L1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Wqb047p-1681704403413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_035.png)] (Equation 11.4.9)

其中u代表地面真实情况与预测之间的误差中的每个元素:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzYHveII-1681704403413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_037.png)] (Equation 11.4.10)

与 L1 相比,平滑 L1 更健壮,并且对异常值的敏感性较低。 在 SSD 中,σ = 1。 作为σ -> ∞,平滑 L1 接近 L1。 L1 和平滑 L1 损失函数都在“列表 11.4.1”中显示。 mask_offset()方法可确保仅在具有地面真实边界框的预测上计算偏移量。 平滑的 L1 函数与σ = 1[8]时的 Huber 损失相同。

作为对损失函数的进一步改进,RetinaNet [3]建议将 CEy_cls的分类交叉熵函数替换为焦点损失 FL:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7P4PYkjh-1681704403413)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_041.png)] (Equation 11.4.11)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3G2QzOqo-1681704403414)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/adv-dl-tf2-keras/img/B14853_11_042.png)] (Equation 11.4.12)

区别在于额外因素α(1 - p[i])^γ。 在 RetinaNet 中,当γ = 2α = 0.25时,对象检测效果最好。 焦点损失在“列表 11.4.2”中实现。

“列表 11.4.2”:loss.py焦点损失

def focal_loss_categorical(y_true, y_pred):
    """Categorical cross-entropy focal loss"""
    gamma = 2.0
    alpha = 0.25
# scale to ensure sum of prob is 1.0
    y_pred /= K.sum(y_pred, axis=-1, keepdims=True)
# clip the prediction value to prevent NaN and Inf
    epsilon = K.epsilon()
    y_pred = K.clip(y_pred, epsilon, 1\. - epsilon)
    # calculate cross entropy
    cross_entropy = -y_true * K.log(y_pred)
# calculate focal loss
    weight = alpha * K.pow(1 - y_pred, gamma)
    cross_entropy *= weight
return K.sum(cross_entropy, axis=-1)

聚焦损失的动机是,如果我们检查图像,则大多数锚框应分类为背景或负锚框。 只有很少的正锚框是代表目标对象的良好候选对象。 负熵损失是造成交叉熵损失的主要因素。 因此,负锚框的贡献使优化过程中正锚框的贡献无法实现。 这种现象也称为类不平衡,其中一个或几个类占主导地位。 有关其他详细信息,Lin 等。 文献[4]讨论了对象检测中的类不平衡问题。

有了焦点损失,我们在优化过程的早期就确信负锚框属于背景。 因此,由于p[i] -> 1.0,项(1 - p[i])^γ减少了负锚框的贡献。 对于正锚框,其贡献仍然很大,因为p[i]远非 1.0。

既然我们已经讨论了锚定框,地面真值锚定框和损失函数的概念,我们现在准备介绍实现多尺度目标检测算法的 SSD 模型架构。

TensorFlow 2 和 Keras 高级深度学习:11~13(2)https://developer.aliyun.com/article/1426964

相关文章
|
12天前
|
机器学习/深度学习 人工智能 算法
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
垃圾识别分类系统。本系统采用Python作为主要编程语言,通过收集了5种常见的垃圾数据集('塑料', '玻璃', '纸张', '纸板', '金属'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对图像数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。然后使用Django搭建Web网页端可视化操作界面,实现用户在网页端上传一张垃圾图片识别其名称。
43 0
基于Python深度学习的【垃圾识别系统】实现~TensorFlow+人工智能+算法网络
|
12天前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
43 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
12天前
|
机器学习/深度学习 人工智能 算法
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
蔬菜识别系统,本系统使用Python作为主要编程语言,通过收集了8种常见的蔬菜图像数据集('土豆', '大白菜', '大葱', '莲藕', '菠菜', '西红柿', '韭菜', '黄瓜'),然后基于TensorFlow搭建卷积神经网络算法模型,通过多轮迭代训练最后得到一个识别精度较高的模型文件。在使用Django开发web网页端操作界面,实现用户上传一张蔬菜图片识别其名称。
52 0
基于深度学习的【蔬菜识别】系统实现~Python+人工智能+TensorFlow+算法模型
|
28天前
|
机器学习/深度学习 人工智能 算法
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
车辆车型识别,使用Python作为主要编程语言,通过收集多种车辆车型图像数据集,然后基于TensorFlow搭建卷积网络算法模型,并对数据集进行训练,最后得到一个识别精度较高的模型文件。再基于Django搭建web网页端操作界面,实现用户上传一张车辆图片识别其类型。
72 0
【车辆车型识别】Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+算法模型
|
1月前
|
机器学习/深度学习 人工智能 算法
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
玉米病害识别系统,本系统使用Python作为主要开发语言,通过收集了8种常见的玉米叶部病害图片数据集('矮花叶病', '健康', '灰斑病一般', '灰斑病严重', '锈病一般', '锈病严重', '叶斑病一般', '叶斑病严重'),然后基于TensorFlow搭建卷积神经网络算法模型,通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型文件。再使用Django搭建Web网页操作平台,实现用户上传一张玉米病害图片识别其名称。
59 0
【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练
|
1月前
|
机器学习/深度学习 移动开发 TensorFlow
深度学习之格式转换笔记(四):Keras(.h5)模型转化为TensorFlow(.pb)模型
本文介绍了如何使用Python脚本将Keras模型转换为TensorFlow的.pb格式模型,包括加载模型、重命名输出节点和量化等步骤,以便在TensorFlow中进行部署和推理。
83 0
|
2天前
|
机器学习/深度学习 自然语言处理 语音技术
Python在深度学习领域的应用,重点讲解了神经网络的基础概念、基本结构、训练过程及优化技巧
本文介绍了Python在深度学习领域的应用,重点讲解了神经网络的基础概念、基本结构、训练过程及优化技巧,并通过TensorFlow和PyTorch等库展示了实现神经网络的具体示例,涵盖图像识别、语音识别等多个应用场景。
16 8
|
6天前
|
机器学习/深度学习 人工智能 自然语言处理
深度学习中的卷积神经网络(CNN)及其在图像识别中的应用
本文旨在通过深入浅出的方式,为读者揭示卷积神经网络(CNN)的神秘面纱,并展示其在图像识别领域的实际应用。我们将从CNN的基本概念出发,逐步深入到网络结构、工作原理以及训练过程,最后通过一个实际的代码示例,带领读者体验CNN的强大功能。无论你是深度学习的初学者,还是希望进一步了解CNN的专业人士,这篇文章都将为你提供有价值的信息和启发。
|
6天前
|
机器学习/深度学习 数据采集 测试技术
深度学习在图像识别中的应用
本篇文章将探讨深度学习在图像识别中的应用。我们将介绍深度学习的基本原理,以及如何使用深度学习进行图像识别。我们将通过一个简单的代码示例来演示如何使用深度学习进行图像识别。这篇文章的目的是帮助读者理解深度学习在图像识别中的作用,并学习如何使用深度学习进行图像识别。
|
3天前
|
机器学习/深度学习 人工智能 自然语言处理
探索深度学习中的卷积神经网络(CNN)及其在现代应用中的革新
探索深度学习中的卷积神经网络(CNN)及其在现代应用中的革新
下一篇
无影云桌面