【从零开始学习深度学习】46. 目标检测中锚框的概念、计算方法、样本锚框标注方式及如何选取预测边界框

简介: 【从零开始学习深度学习】46. 目标检测中锚框的概念、计算方法、样本锚框标注方式及如何选取预测边界框

1. 锚框介绍

目标检测算法中通常会在输入图像中采样大量的区域,然后判断这些区域中是否包含我们感兴趣的目标,并调整区域边缘位置从而更准确地预测目标的真实边界框(ground-truth bounding box)。不同的模型使用的区域采样方法可能不同。此处介绍其中一种采样方法:它以每个像素为中心生成多个大小和宽高比(aspect ratio)不同的边界框。这些边界框被称为锚框(anchor box)。后续我们将基于锚框实践目标检测。

先导入一下相关包。

%matplotlib inline
from PIL import Image
import numpy as np
import math
import torch
import sys
import d2lzh_pytorch as d2l
print(torch.__version__) # 1.10.2+cpu

1.1 生成多个锚框


image.png

以上生成锚框的方法实现在下面的MultiBoxPrior函数中。指定输入、一组大小和一组宽高比,该函数将返回输入的所有锚框。

注: PyTorch官方在torchvision.models.detection.rpn里有一个AnchorGenerator类也可以用来生成anchor。

d2l.set_figsize()
img = Image.open('./img/catdog.jpg')
w, h = img.size
print("w = %d, h = %d" % (w, h)) # w = 728, h = 561
def MultiBoxPrior(feature_map, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5]):
    """
    # 生成多个锚框, anchor表示成(xmin, ymin, xmax, ymax).
    https://zh.d2l.ai/chapter_computer-vision/anchor.html
    Args:
        feature_map: torch tensor, Shape: [N, C, H, W].
        sizes: List of sizes (0~1) of generated MultiBoxPriores. 
        ratios: List of aspect ratios (non-negative) of generated MultiBoxPriores. 
    Returns:
        anchors of shape (1, num_anchors, 4). 由于batch里每个都一样, 所以第一维为1
    """
    h, w = feature_map.shape[-2:]
    
    pairs = [] # pair of (size, sqrt(ration))
    
    for r in ratios:
        pairs.append([math.sqrt(sizes[0]*h*w), math.sqrt(r)])
    for s in sizes[1:]:
        pairs.append([math.sqrt(s*h*w), math.sqrt(ratios[0])])
    
    pairs = np.array(pairs)
    
    # 坐标值分别除以图像的宽和高,因此值域均为0和1之间
    ss1 = pairs[:, 0] * pairs[:, 1] / w # sqrt(size*h*w) * sqrt(ration)/w
    ss2 = pairs[:, 0] / pairs[:, 1] / h # sqrt(size*h*w) / sqrt(ration)/h
    
    base_anchors = np.stack([-ss1, -ss2, ss1, ss2], axis=1) / 2
    
    shifts_x = np.arange(0, w) / w
    shifts_y = np.arange(0, h) / h
    shift_x, shift_y = np.meshgrid(shifts_x, shifts_y)
    shift_x = shift_x.reshape(-1)
    shift_y = shift_y.reshape(-1)
    shifts = np.stack((shift_x, shift_y, shift_x, shift_y), axis=1)
    
    anchors = shifts.reshape((-1, 1, 4)) + base_anchors.reshape((1, -1, 4))
    
    # 返回的坐标已经分别除以了w,h,因此都是小于1的数
    return torch.tensor(anchors, dtype=torch.float32).view(1, -1, 4)
X = torch.Tensor(1, 3, h, w)  # 构造输入数据
Y = MultiBoxPrior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape # torch.Size([1, 2042040, 4])

我们看到,返回锚框变量y的形状为(1,锚框个数,4),4表示左上角和右下角两点的x,y坐标。将锚框变量y的形状变为(图像高,图像宽,以相同像素为中心的锚框个数n+m-1,4)后,我们就可以通过指定像素位置来获取所有以该像素为中心的锚框了。下面的例子里我们访问以(250,250)为中心的第一个锚框。它有4个元素,分别是锚框左上角的xy轴坐标和右下角的xy轴坐标,其中x xxy yy轴的坐标值分别已除以图像的宽和高,因此值域均为0和1之间。

boxes = Y.reshape((h, w, 5, 4))
# 以(250,250)为中心的第一个锚框
boxes[250, 250, 0, :]
# 输出结果形状: torch.tensor([w, h, w, h], dtype=torch.float32)

输出:

tensor([-0.0367, -0.0476,  0.7235,  0.9389])

为了描绘图像中以某个像素为中心的所有锚框,我们先定义show_bboxes函数以便在图像上画出多个边界框。

def show_bboxes(axes, bboxes, labels=None, colors=None):
    def _make_list(obj, default_values=None):
        if obj is None:
            obj = default_values
        elif not isinstance(obj, (list, tuple)):
            obj = [obj]
        return obj
    labels = _make_list(labels)
    colors = _make_list(colors, ['b', 'g', 'r', 'm', 'c'])
    for i, bbox in enumerate(bboxes):
        color = colors[i % len(colors)]
        rect = d2l.bbox_to_rect(bbox.detach().cpu().numpy(), color)
        axes.add_patch(rect)
        if labels and len(labels) > i:
            # 标注信息
            text_color = 'k' if color == 'w' else 'w'
            axes.text(rect.xy[0], rect.xy[1], labels[i],
                      va='center', ha='center', fontsize=6, color=text_color,
                      bbox=dict(facecolor=color, lw=0))

刚刚我们看到,变量boxesxy轴的坐标值分别已除以图像的宽和高。在绘图时,我们需要恢复锚框的原始坐标值,并因此定义了变量bbox_scale。现在,我们可以画出图像中以(250, 250)为中心的所有锚框了。可以看到,大小为0.75且宽高比为1的锚框较好地覆盖了图像中的狗。

d2l.set_figsize()
fig = d2l.plt.imshow(img)
bbox_scale = torch.tensor([[w, h, w, h]], dtype=torch.float32)
show_bboxes(fig.axes, boxes[250, 250, :, :] * bbox_scale,
            ['s=0.75, r=1', 's=0.75, r=2', 's=0.55, r=0.5', 's=0.5, r=1', 's=0.25, r=1'])

2. 交并比–Jaccard系数

为了较好的衡量锚框和真实边界框之间的相似度,定义了一个Jaccard系数(Jaccard index)【也称交并比】来衡量两个集合的相似度。给定集合AB,它们的Jaccard系数即二者交集大小除以二者并集大小:


image.png

实际上,我们可以把边界框内的像素区域看成是像素的集合。如此一来,我们可以用两个边界框的像素集合的Jaccard系数衡量这两个边界框的相似度。当衡量两个边界框的相似度时,我们通常将Jaccard系数称为交并比(Intersection over Union,IoU),即两个边界框相交面积与相并面积之比,如下图所示。交并比的取值范围在0和1之间:0表示两个边界框无重合像素,1表示两个边界框相等。

下面我们对其进行实现。

def compute_intersection(set_1, set_2):
    """
    计算anchor之间的交集
    set_1为n1个锚点的集合,形状 (n1, 4),set_2为n2个锚点的集合形状 (n2, 4)
    return: n1个锚点与n2个锚点的交集,形状(n1, n2)
    Args:
        set_1: a tensor of dimensions (n1, 4), anchor表示成(xmin, ymin, xmax, ymax)
        set_2: a tensor of dimensions (n2, 4), anchor表示成(xmin, ymin, xmax, ymax)
    Returns:
        intersection of each of the boxes in set 1 with respect to each of the boxes in set 2, shape: (n1, n2)
    """
    # PyTorch auto-broadcasts singleton dimensions
    lower_bounds = torch.max(set_1[:, :2].unsqueeze(1), set_2[:, :2].unsqueeze(0))  # (n1, n2, 2)
    upper_bounds = torch.min(set_1[:, 2:].unsqueeze(1), set_2[:, 2:].unsqueeze(0))  # (n1, n2, 2)
    intersection_dims = torch.clamp(upper_bounds - lower_bounds, min=0)  # (n1, n2, 2)
    return intersection_dims[:, :, 0] * intersection_dims[:, :, 1]  # (n1, n2)
def compute_jaccard(set_1, set_2):
    """
    计算anchor之间的Jaccard系数(IoU)
    Args:
        set_1: a tensor of dimensions (n1, 4), anchor表示成(xmin, ymin, xmax, ymax)
        set_2: a tensor of dimensions (n2, 4), anchor表示成(xmin, ymin, xmax, ymax)
    Returns:
        Jaccard Overlap of each of the boxes in set 1 with respect to each of the boxes in set 2, shape: (n1, n2)
    """
    # Find intersections
    intersection = compute_intersection(set_1, set_2)  # (n1, n2)
    # Find areas of each box in both sets
    areas_set_1 = (set_1[:, 2] - set_1[:, 0]) * (set_1[:, 3] - set_1[:, 1])  # (n1)
    areas_set_2 = (set_2[:, 2] - set_2[:, 0]) * (set_2[:, 3] - set_2[:, 1])  # (n2)
    # Find the union
    # PyTorch auto-broadcasts singleton dimensions
    union = areas_set_1.unsqueeze(1) + areas_set_2.unsqueeze(0) - intersection  # (n1, n2)
    return intersection / union  # (n1, n2)

后续我们将使用交并比来衡量锚框与真实边界框以及锚框与锚框之间的相似度。

3. 标注训练集的锚框

在训练集中,我们将每个锚框视为一个训练样本。为了训练目标检测模型,我们需要为每个锚框标注两类标签:一是锚框所含目标的类别,简称类别;二是真实边界框相对锚框的偏移量,简称偏移量(offset)

在目标检测时,我们首先生成多个锚框,然后为每个锚框预测类别以及偏移量,接着根据预测的偏移量调整锚框位置从而得到预测边界框,最后筛选需要输出的预测边界框。

我们知道,在目标检测的训练集中,每个图像已标注了真实边界框的位置以及所含目标的类别。在生成锚框之后,我们主要依据与锚框相似的真实边界框的位置和类别信息为锚框标注。那么,该如何为锚框分配与其相似的真实边界框呢?


image.png


image.png

bbox_scale = torch.tensor((w, h, w, h), dtype=torch.float32)
# 真实边界框
ground_truth = torch.tensor([[0, 0.1, 0.08, 0.52, 0.92],
                            [1, 0.55, 0.2, 0.9, 0.88]])
# 5个需要标注的锚框
anchors = torch.tensor([[0, 0.1, 0.2, 0.3], [0.15, 0.2, 0.4, 0.4],
                    [0.63, 0.05, 0.88, 0.98], [0.66, 0.45, 0.8, 0.8],
                    [0.57, 0.3, 0.92, 0.9]])
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, ground_truth[:, 1:] * bbox_scale, ['dog', 'cat'], 'k')
show_bboxes(fig.axes, anchors * bbox_scale, ['0', '1', '2', '3', '4']);

下面实现MultiBoxTarget函数来为锚框标注类别和偏移量。该函数将背景类别设为0,并令从零开始的目标类别的整数索引自加1(1为狗,2为猫)。

def assign_anchor(bb, anchor, jaccard_threshold=0.5):
    """
    # 为每个anchor分配真实的bb, anchor表示成归一化(xmin, ymin, xmax, ymax).
    Args:
        bb: 真实边界框(bounding box), shape:(nb, 4)
        anchor: 待分配的anchor, shape:(na, 4)
        jaccard_threshold: 预先设定的阈值
    Returns:
        assigned_idx: shape: (na, ), 每个anchor分配的真实bb对应的索引, 若未分配任何bb则为-1
    """
    na = anchor.shape[0]
    nb = bb.shape[0]
    jaccard = compute_jaccard(anchor, bb).detach().cpu().numpy() # shape: (na, nb)
    assigned_idx = np.ones(na) * -1  # 初始全为-1
    
    # 先为每个bb分配一个anchor(不要求满足jaccard_threshold)
    jaccard_cp = jaccard.copy()
    for j in range(nb):
        i = np.argmax(jaccard_cp[:, j])
        assigned_idx[i] = j
        jaccard_cp[i, :] = float("-inf") # 赋值为负无穷, 相当于去掉这一行
     
    # 处理还未被分配的anchor, 要求满足jaccard_threshold
    for i in range(na):
        if assigned_idx[i] == -1:
            j = np.argmax(jaccard[i, :])
            if jaccard[i, j] >= jaccard_threshold:
                assigned_idx[i] = j
    
    return torch.tensor(assigned_idx, dtype=torch.long)
def xy_to_cxcy(xy):
    """
    将(x_min, y_min, x_max, y_max)形式的anchor转换成(center_x, center_y, w, h)形式的.
    Args:
        xy: bounding boxes in boundary coordinates, a tensor of size (n_boxes, 4)
    Returns: 
        bounding boxes in center-size coordinates, a tensor of size (n_boxes, 4)
    """
    return torch.cat([(xy[:, 2:] + xy[:, :2]) / 2,  # c_x, c_y
                      xy[:, 2:] - xy[:, :2]], 1)  # w, h
def MultiBoxTarget(anchor, label):
    """
    #  anchor表示成归一化(xmin, ymin, xmax, ymax).
    Args:
        anchor: torch tensor, 输入的锚框, 一般是通过MultiBoxPrior生成, shape:(1,锚框总数,4)
        label: 真实标签, shape为(bn, 每张图片最多的真实锚框数, 5)
               第二维中,如果给定图片没有这么多锚框, 可以先用-1填充空白, 最后一维中的元素为[类别标签, 四个坐标值]
    Returns:
        列表, [bbox_offset, bbox_mask, cls_labels]
        bbox_offset: 每个锚框的标注偏移量,形状为(bn,锚框总数*4)
        bbox_mask: 形状同bbox_offset, 每个锚框的掩码, 一一对应上面的偏移量, 负类锚框(背景)对应的掩码均为0, 正类锚框的掩码均为1
        cls_labels: 每个锚框的标注类别, 其中0表示为背景, 形状为(bn,锚框总数)
    """
    assert len(anchor.shape) == 3 and len(label.shape) == 3
    bn = label.shape[0]
    
    def MultiBoxTarget_one(anc, lab, eps=1e-6):
        """
        MultiBoxTarget函数的辅助函数, 处理batch中的一个
        Args:
            anc: shape of (锚框总数, 4)
            lab: shape of (真实锚框数, 5), 5代表[类别标签, 四个坐标值]
            eps: 一个极小值, 防止log0
        Returns:
            offset: (锚框总数*4, )
            bbox_mask: (锚框总数*4, ), 0代表背景, 1代表非背景
            cls_labels: (锚框总数, 4), 0代表背景
        """
        an = anc.shape[0]
        assigned_idx = assign_anchor(lab[:, 1:], anc) # (锚框总数, )
        bbox_mask = ((assigned_idx >= 0).float().unsqueeze(-1)).repeat(1, 4) # (锚框总数, 4)
        cls_labels = torch.zeros(an, dtype=torch.long) # 0表示背景
        assigned_bb = torch.zeros((an, 4), dtype=torch.float32) # 所有anchor对应的bb坐标
        for i in range(an):
            bb_idx = assigned_idx[i]
            if bb_idx >= 0: # 即非背景
                cls_labels[i] = lab[bb_idx, 0].long().item() + 1 # 注意要加一
                assigned_bb[i, :] = lab[bb_idx, 1:]
        center_anc = xy_to_cxcy(anc) # (center_x, center_y, w, h)
        center_assigned_bb = xy_to_cxcy(assigned_bb)
        offset_xy = 10.0 * (center_assigned_bb[:, :2] - center_anc[:, :2]) / center_anc[:, 2:]
        offset_wh = 5.0 * torch.log(eps + center_assigned_bb[:, 2:] / center_anc[:, 2:])
        offset = torch.cat([offset_xy, offset_wh], dim = 1) * bbox_mask # (锚框总数, 4)
        return offset.view(-1), bbox_mask.view(-1), cls_labels
    
    batch_offset = []
    batch_mask = []
    batch_cls_labels = []
    for b in range(bn):
        offset, bbox_mask, cls_labels = MultiBoxTarget_one(anchor[0, :, :], label[b, :, :])
        
        batch_offset.append(offset)
        batch_mask.append(bbox_mask)
        batch_cls_labels.append(cls_labels)
    
    bbox_offset = torch.stack(batch_offset)
    bbox_mask = torch.stack(batch_mask)
    cls_labels = torch.stack(batch_cls_labels)
    
    return [bbox_offset, bbox_mask, cls_labels]

我们通过unsqueeze函数为锚框和真实边界框添加样本维。

labels = MultiBoxTarget(anchors.unsqueeze(dim=0),
                        ground_truth.unsqueeze(dim=0))

返回的结果里有3项,均为Tensor。第三项表示为锚框标注的类别。

labels[2] 

输出:

tensor([[0, 1, 2, 0, 2]])

我们根据锚框与真实边界框在图像中的位置来分析这些标注的类别。首先,在所有的“锚框—真实边界框”的配对中,锚框A4与猫的真实边界框的交并比最大,因此锚框A4的类别标注为猫。不考虑锚框A4或猫的真实边界框,在剩余的“锚框—真实边界框”的配对中,最大交并比的配对为锚框A1和狗的真实边界框,因此锚框A1的类别标注为狗。接下来遍历未标注的剩余3个锚框:与锚框A0交并比最大的真实边界框的类别为狗,但交并比小于阈值(默认为0.5),因此类别标注为背景;与锚框A2交并比最大的真实边界框的类别为猫,且交并比大于阈值,因此类别标注为猫;与锚框A3交并比最大的真实边界框的类别为猫,但交并比小于阈值,因此类别标注为背景。

返回值的第二项为掩码(mask)变量,形状为(批量大小, 锚框个数*4)。掩码变量中的元素与每个锚框的4个偏移量一一对应。

由于我们不关心对背景的检测,有关负类的偏移量不应影响目标函数。通过按元素乘法,掩码变量中的0可以在计算目标函数之前过滤掉负类的偏移量。

labels[1]

输出:

tensor([[0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 1., 1.,
         1., 1.]])

返回的第一项是为每个锚框标注的四个偏移量,其中负类锚框的偏移量标注为0。

labels[0]

输出:

tensor([[-0.0000e+00, -0.0000e+00, -0.0000e+00, -0.0000e+00,  1.4000e+00,
          1.0000e+01,  2.5940e+00,  7.1754e+00, -1.2000e+00,  2.6882e-01,
          1.6824e+00, -1.5655e+00, -0.0000e+00, -0.0000e+00, -0.0000e+00,
         -0.0000e+00, -5.7143e-01, -1.0000e+00,  4.1723e-06,  6.2582e-01]])

4. 输出预测边界框—非极大值抑制方法

在模型预测阶段,我们先为图像生成多个锚框,并为这些锚框一一预测类别和偏移量。随后,我们根据锚框及其预测偏移量得到预测边界框。当锚框数量较多时,同一个目标上可能会输出较多相似的预测边界框。为了使结果更加简洁,我们可以移除相似的预测边界框。常用的方法叫作非极大值抑制(non-maximum suppression,NMS)

我们来描述一下非极大值抑制的工作原理。对于一个预测边界框B BB,模型会计算各个类别的预测概率。设其中最大的预测概率为p,该概率所对应的类别即B的预测类别。我们也将p称为预测边界框B的置信度。在同一图像上,我们将预测类别非背景的预测边界框按置信度从高到低排序,得到列表L。从L中选取置信度最高的预测边界框B1作为基准,将所有与B1的交并比大于某阈值的非基准预测边界框从L中移除。这里的阈值是预先设定的超参数。此时,L保留了置信度最高的预测边界框并移除了与其相似的其他预测边界框。

接下来,从L中选取置信度第二高的预测边界框B2作为基准,将所有与B2的交并比大于某阈值的非基准预测边界框从L中移除。重复这一过程,直到L中所有的预测边界框都曾作为基准。此时L中任意一对预测边界框的交并比都小于阈值。最终,输出列表L中的所有预测边界框。

下面来看一个具体的例子。先构造4个锚框。简单起见,我们假设预测偏移量全是0:预测边界框即锚框。最后,我们构造每个类别的预测概率。

anchors = torch.tensor([[0.1, 0.08, 0.52, 0.92], [0.08, 0.2, 0.56, 0.95],
                        [0.15, 0.3, 0.62, 0.91], [0.55, 0.2, 0.9, 0.88]])
offset_preds = torch.tensor([0.0] * (4 * len(anchors)))
cls_probs = torch.tensor([[0., 0., 0., 0.,],  # 背景的预测概率
                          [0.9, 0.8, 0.7, 0.1],  # 狗的预测概率
                          [0.1, 0.2, 0.3, 0.9]])  # 猫的预测概率

在图像上打印预测边界框和它们的置信度。

fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
            ['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])

下面我们实现MultiBoxDetection函数来执行非极大值抑制。

from collections import namedtuple
Pred_BB_Info = namedtuple("Pred_BB_Info", ["index", "class_id", "confidence", "xyxy"])
def non_max_suppression(bb_info_list, nms_threshold = 0.5):
    """
    非极大抑制处理预测的边界框
    Args:
        bb_info_list: Pred_BB_Info的列表, 包含预测类别、置信度等信息
        nms_threshold: 阈值
    Returns:
        output: Pred_BB_Info的列表, 只保留过滤后的边界框信息
    """
    output = []
    # 先根据置信度从高到低排序
    sorted_bb_info_list = sorted(bb_info_list, key = lambda x: x.confidence, reverse=True)
    while len(sorted_bb_info_list) != 0:
        best = sorted_bb_info_list.pop(0)
        output.append(best)
        
        if len(sorted_bb_info_list) == 0:
            break
        bb_xyxy = []
        for bb in sorted_bb_info_list:
            bb_xyxy.append(bb.xyxy)
        
        iou = compute_jaccard(torch.tensor([best.xyxy]), 
                              torch.tensor(bb_xyxy))[0] # shape: (len(sorted_bb_info_list), )
        
        n = len(sorted_bb_info_list)
        sorted_bb_info_list = [sorted_bb_info_list[i] for i in range(n) if iou[i] <= nms_threshold]
    return output
def MultiBoxDetection(cls_prob, loc_pred, anchor, nms_threshold = 0.5):
    """
    # anchor表示成归一化(xmin, ymin, xmax, ymax).
    Args:
        cls_prob: 经过softmax后得到的各个锚框的预测概率, shape:(bn, 预测总类别数+1, 锚框个数)
        loc_pred: 预测的各个锚框的偏移量, shape:(bn, 锚框个数*4)
        anchor: MultiBoxPrior输出的默认锚框, shape: (1, 锚框个数, 4)
        nms_threshold: 非极大抑制中的阈值
    Returns:
        所有锚框的信息, shape: (bn, 锚框个数, 6)
        每个锚框信息由[class_id, confidence, xmin, ymin, xmax, ymax]表示
        class_id=-1 表示背景或在非极大值抑制中被移除了
    """
    assert len(cls_prob.shape) == 3 and len(loc_pred.shape) == 2 and len(anchor.shape) == 3
    bn = cls_prob.shape[0]
    
    def MultiBoxDetection_one(c_p, l_p, anc, nms_threshold = 0.5):
        """
        MultiBoxDetection的辅助函数, 处理batch中的一个
        Args:
            c_p: (预测总类别数+1, 锚框个数)
            l_p: (锚框个数*4, )
            anc: (锚框个数, 4)
            nms_threshold: 非极大抑制中的阈值
        Return:
            output: (锚框个数, 6)
        """
        pred_bb_num = c_p.shape[1]
        anc = (anc + l_p.view(pred_bb_num, 4)).detach().cpu().numpy() # 加上偏移量
        
        confidence, class_id = torch.max(c_p, 0)
        confidence = confidence.detach().cpu().numpy()
        class_id = class_id.detach().cpu().numpy()
        
        pred_bb_info = [Pred_BB_Info(
                            index = i,
                            class_id = class_id[i] - 1, # 正类label从0开始
                            confidence = confidence[i],
                            xyxy=[*anc[i]]) # xyxy是个列表
                        for i in range(pred_bb_num)]
        
        # 正类的index
        obj_bb_idx = [bb.index for bb in non_max_suppression(pred_bb_info, nms_threshold)]
        
        output = []
        for bb in pred_bb_info:
            output.append([
                (bb.class_id if bb.index in obj_bb_idx else -1.0),
                bb.confidence,
                *bb.xyxy
            ])
            
        return torch.tensor(output) # shape: (锚框个数, 6)
    
    batch_output = []
    for b in range(bn):
        batch_output.append(MultiBoxDetection_one(cls_prob[b], loc_pred[b], anchor[0], nms_threshold))
    
    return torch.stack(batch_output)

然后我们运行MultiBoxDetection函数并设阈值为0.5。这里为输入都增加了样本维。我们看到,返回的结果的形状为(批量大小, 锚框个数, 6)。其中每一行的6个元素代表同一个预测边界框的输出信息。第一个元素是索引从0开始计数的预测类别(0为狗,1为猫),其中-1表示背景或在非极大值抑制中被移除。第二个元素是预测边界框的置信度。剩余的4个元素分别是预测边界框左上角的xy轴坐标以及右下角的xy轴坐标(值域在0到1之间)。

output = MultiBoxDetection(
    cls_probs.unsqueeze(dim=0), offset_preds.unsqueeze(dim=0),
    anchors.unsqueeze(dim=0), nms_threshold=0.5)
output

输出:

tensor([[[ 0.0000,  0.9000,  0.1000,  0.0800,  0.5200,  0.9200],
         [-1.0000,  0.8000,  0.0800,  0.2000,  0.5600,  0.9500],
         [-1.0000,  0.7000,  0.1500,  0.3000,  0.6200,  0.9100],
         [ 1.0000,  0.9000,  0.5500,  0.2000,  0.9000,  0.8800]]])

我们移除掉类别为-1的预测边界框,并可视化非极大值抑制保留的结果。

fig = d2l.plt.imshow(img)
for i in output[0].detach().cpu().numpy():
    if i[0] == -1:
        continue
    label = ('dog=', 'cat=')[int(i[0])] + str(i[1])
    show_bboxes(fig.axes, [torch.tensor(i[2:]) * bbox_scale], label)

实践中,我们可以在执行非极大值抑制前将置信度较低的预测边界框移除,从而减小非极大值抑制的计算量。我们还可以筛选非极大值抑制的输出,例如,只保留其中置信度较高的结果作为最终输出。

总结

  • 以每个像素为中心,生成多个大小和宽高比不同的锚框。
  • 交并比是两个边界框相交面积与相并面积之比。
  • 在训练集中,为每个锚框标注两类标签:一是锚框所含目标的类别;二是真实边界框相对锚框的偏移量。
  • 预测时,可以使用非极大值抑制来移除相似的预测边界框。
相关文章
|
16天前
|
机器学习/深度学习 数据采集 人工智能
AI赋能教育:深度学习在个性化学习系统中的应用
【10月更文挑战第26天】随着人工智能的发展,深度学习技术正逐步应用于教育领域,特别是个性化学习系统中。通过分析学生的学习数据,深度学习模型能够精准预测学生的学习表现,并为其推荐合适的学习资源和规划学习路径,从而提供更加高效、有趣和个性化的学习体验。
71 9
|
1月前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
56 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
1月前
|
机器学习/深度学习 传感器 人工智能
深度学习之自主学习和任务规划
基于深度学习的自主学习和任务规划,是指通过深度学习算法使人工智能(AI)系统能够自主地从环境中学习,并根据特定的目标和任务,规划出有效的解决方案。
46 3
|
24天前
|
机器学习/深度学习 调度 计算机视觉
深度学习中的学习率调度:循环学习率、SGDR、1cycle 等方法介绍及实践策略研究
本文探讨了多种学习率调度策略在神经网络训练中的应用,强调了选择合适学习率的重要性。文章介绍了阶梯式衰减、余弦退火、循环学习率等策略,并分析了它们在不同实验设置下的表现。研究表明,循环学习率和SGDR等策略在提高模型性能和加快训练速度方面表现出色,而REX调度则在不同预算条件下表现稳定。这些策略为深度学习实践者提供了实用的指导。
33 2
深度学习中的学习率调度:循环学习率、SGDR、1cycle 等方法介绍及实践策略研究
|
15天前
|
机器学习/深度学习 算法 编译器
Python程序到计算图一键转化,详解清华开源深度学习编译器MagPy
【10月更文挑战第26天】MagPy是一款由清华大学研发的开源深度学习编译器,可将Python程序一键转化为计算图,简化模型构建和优化过程。它支持多种深度学习框架,具备自动化、灵活性、优化性能好和易于扩展等特点,适用于模型构建、迁移、部署及教学研究。尽管MagPy具有诸多优势,但在算子支持、优化策略等方面仍面临挑战。
41 3
|
25天前
|
机器学习/深度学习 存储 自然语言处理
深度学习之少样本学习
少样本学习(Few-Shot Learning, FSL)是深度学习中的一个重要研究领域,其目标是在只有少量标注样本的情况下,训练出能够很好地泛化到新类别或新任务的模型。
24 2
|
1月前
|
机器学习/深度学习 自然语言处理 计算机视觉
深度学习中的迁移学习技术
【10月更文挑战第11天】 本文探讨了深度学习中的迁移学习技术,并深入分析了其原理、应用场景及实现方法。通过实例解析,展示了迁移学习如何有效提升模型性能和开发效率。同时,文章也讨论了迁移学习面临的挑战及其未来发展方向。
|
1月前
|
机器学习/深度学习 监控 数据可视化
深度学习中实验、观察与思考的方法与技巧
在深度学习中,实验、观察与思考是理解和改进模型性能的关键环节。
39 5
|
16天前
|
安全 搜索推荐 机器学习/深度学习
AI赋能教育:深度学习在个性化学习系统中的应用
【10月更文挑战第26天】在人工智能的推动下,个性化学习系统逐渐成为教育领域的重要趋势。深度学习作为AI的核心技术,在构建个性化学习系统中发挥关键作用。本文探讨了深度学习在个性化推荐系统、智能辅导系统和学习行为分析中的应用,并提供了代码示例,展示了如何使用Keras构建模型预测学生对课程的兴趣。尽管面临数据隐私和模型可解释性等挑战,深度学习仍有望为教育带来更个性化和高效的学习体验。
43 0
|
29天前
|
机器学习/深度学习 分布式计算 安全
深度学习之安全多方计算
基于深度学习的安全多方计算(Secure Multi-Party Computation,简称MPC)是一种密码学技术,旨在让多个参与方在不暴露各自数据的前提下,协作完成一个计算任务。
42 0