.. _sec_anchor:
Caixas de Âncora
================
Os algoritmos de detecção de objetos geralmente amostram um grande
número de regiões na imagem de entrada, determinam se essas regiões
contêm objetos de interesse e ajustam as bordas das regiões de modo a
prever a caixa delimitadora da verdade terrestre do alvo com mais
precisão. Diferentes modelos podem usar diferentes métodos de amostragem
de região. Aqui, apresentamos um desses métodos: ele gera várias caixas
delimitadoras com diferentes tamanhos e proporções de aspecto, enquanto
é centralizado em cada pixel. Essas caixas delimitadoras são chamadas de
caixas de âncora. Praticaremos a detecção de objetos com base em caixas
de âncora nas seções a seguir.
.. raw:: html
.. raw:: html
Primeiro, importe os pacotes ou módulos necessários para esta seção.
Aqui, modificamos a precisão de impressão do NumPy. Como os tensores de
impressão, na verdade, chamam a função de impressão de NumPy, os números
de ponto flutuante nos tensores impressos nesta seção são mais concisos.
.. code:: python
%matplotlib inline
from mxnet import gluon, image, np, npx
from d2l import mxnet as d2l
np.set_printoptions(2)
npx.set_np()
.. raw:: html
.. raw:: html
Primeiro, importe os pacotes ou módulos necessários para esta seção.
Aqui, modificamos a precisão de impressão do PyTorch. Como os tensores
de impressão, na verdade, chamam a função de impressão de PyTorch, os
números de ponto flutuante nos tensores impressos nesta seção são mais
concisos.
.. code:: python
%matplotlib inline
import torch
from d2l import torch as d2l
torch.set_printoptions(2)
.. raw:: html
.. raw:: html
Gerando Várias Caixas de Âncora
-------------------------------
Suponha que a imagem de entrada tenha uma altura de :math:`h` e uma
largura de :math:`w`. Geramos caixas de âncora com diferentes formas
centralizadas em cada pixel da imagem. Suponha que o tamanho seja
:math:`s\in (0, 1]`, a proporção da imagem é :math:`r > 0` e a largura e
a altura da caixa de âncora são :math:`ws\sqrt{r}` e
:math:`hs/\sqrt{r}`, respectivamente. Quando a posição central é
fornecida, uma caixa de âncora com largura e altura conhecidas é
determinada.
Abaixo, definimos um conjunto de tamanhos :math:`s_1,\ldots, s_n` e um
conjunto de relações de aspecto :math:`r_1,\ldots, r_m`. Se usarmos uma
combinação de todos os tamanhos e proporções com cada pixel como o
centro, a imagem de entrada terá um total de :math:`whnm` caixas de
âncora. Embora essas caixas de âncora possam abranger todas as caixas
delimitadoras da verdade, a complexidade computacional costuma ser
excessiva. Portanto, normalmente estamos interessados apenas em uma
combinação contendo :math:`s_1` ou :math:`r_1` tamanhos e proporções,
isto é:
.. math:: (s_1, r_1), (s_1, r_2), \ldots, (s_1, r_m), (s_2, r_1), (s_3, r_1), \ldots, (s_n, r_1).
Ou seja, o número de caixas de âncora centradas no mesmo pixel é
:math:`n+m-1`. Para toda a imagem de entrada, geraremos um total de
:math:`wh(n+m-1)` caixas de âncora.
O método acima para gerar caixas de âncora foi implementado na função
``multibox_prior``. Especificamos a entrada, um conjunto de tamanhos e
um conjunto de proporções, e esta função retornará todas as caixas de
âncora inseridas.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def multibox_prior(data, sizes, ratios):
in_height, in_width = data.shape[-2:]
device, num_sizes, num_ratios = data.ctx, len(sizes), len(ratios)
boxes_per_pixel = (num_sizes + num_ratios - 1)
size_tensor = np.array(sizes, ctx=device)
ratio_tensor = np.array(ratios, ctx=device)
# Offsets are required to move the anchor to center of a pixel
# Since pixel (height=1, width=1), we choose to offset our centers by 0.5
offset_h, offset_w = 0.5, 0.5
steps_h = 1.0 / in_height # Scaled steps in y axis
steps_w = 1.0 / in_width # Scaled steps in x axis
# Generate all center points for the anchor boxes
center_h = (np.arange(in_height, ctx=device) + offset_h) * steps_h
center_w = (np.arange(in_width, ctx=device) + offset_w) * steps_w
shift_x, shift_y = np.meshgrid(center_w, center_h)
shift_x, shift_y = shift_x.reshape(-1), shift_y.reshape(-1)
# Generate boxes_per_pixel number of heights and widths which are later
# used to create anchor box corner coordinates (xmin, xmax, ymin, ymax)
# concat (various sizes, first ratio) and (first size, various ratios)
w = np.concatenate((size_tensor * np.sqrt(ratio_tensor[0]),
sizes[0] * np.sqrt(ratio_tensor[1:])))\
* in_height / in_width # handle rectangular inputs
h = np.concatenate((size_tensor / np.sqrt(ratio_tensor[0]),
sizes[0] / np.sqrt(ratio_tensor[1:])))
# Divide by 2 to get half height and half width
anchor_manipulations = np.tile(np.stack((-w, -h, w, h)).T,
(in_height * in_width, 1)) / 2
# Each center point will have boxes_per_pixel number of anchor boxes, so
# generate grid of all anchor box centers with boxes_per_pixel repeats
out_grid = np.stack([shift_x, shift_y, shift_x, shift_y],
axis=1).repeat(boxes_per_pixel, axis=0)
output = out_grid + anchor_manipulations
return np.expand_dims(output, axis=0)
.. raw:: html
.. raw:: html
.. code:: python
#@save
def multibox_prior(data, sizes, ratios):
in_height, in_width = data.shape[-2:]
device, num_sizes, num_ratios = data.device, len(sizes), len(ratios)
boxes_per_pixel = (num_sizes + num_ratios - 1)
size_tensor = torch.tensor(sizes, device=device)
ratio_tensor = torch.tensor(ratios, device=device)
# Offsets are required to move the anchor to center of a pixel
# Since pixel (height=1, width=1), we choose to offset our centers by 0.5
offset_h, offset_w = 0.5, 0.5
steps_h = 1.0 / in_height # Scaled steps in y axis
steps_w = 1.0 / in_width # Scaled steps in x axis
# Generate all center points for the anchor boxes
center_h = (torch.arange(in_height, device=device) + offset_h) * steps_h
center_w = (torch.arange(in_width, device=device) + offset_w) * steps_w
shift_y, shift_x = torch.meshgrid(center_h, center_w)
shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)
# Generate boxes_per_pixel number of heights and widths which are later
# used to create anchor box corner coordinates (xmin, xmax, ymin, ymax)
# cat (various sizes, first ratio) and (first size, various ratios)
w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),
sizes[0] * torch.sqrt(ratio_tensor[1:])))\
* in_height / in_width # handle rectangular inputs
h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]),
sizes[0] / torch.sqrt(ratio_tensor[1:])))
# Divide by 2 to get half height and half width
anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(
in_height * in_width, 1) / 2
# Each center point will have boxes_per_pixel number of anchor boxes, so
# generate grid of all anchor box centers with boxes_per_pixel repeats
out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],
dim=1).repeat_interleave(boxes_per_pixel, dim=0)
output = out_grid + anchor_manipulations
return output.unsqueeze(0)
.. raw:: html
.. raw:: html
Podemos ver que a forma da variável de caixa de âncora retornada ``y`` é
(tamanho do lote, número de caixas de âncora, 4).
.. raw:: html
.. raw:: html
.. code:: python
img = image.imread('../img/catdog.jpg').asnumpy()
h, w = img.shape[0:2]
print(h, w)
X = np.random.uniform(size=(1, 3, h, w)) # Construct input data
Y = multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape
.. parsed-literal::
:class: output
561 728
.. parsed-literal::
:class: output
(1, 2042040, 4)
.. raw:: html
.. raw:: html
.. code:: python
img = d2l.plt.imread('../img/catdog.jpg')
h, w = img.shape[0:2]
print(h, w)
X = torch.rand(size=(1, 3, h, w)) # Construct input data
Y = multibox_prior(X, sizes=[0.75, 0.5, 0.25], ratios=[1, 2, 0.5])
Y.shape
.. parsed-literal::
:class: output
561 728
.. parsed-literal::
:class: output
torch.Size([1, 2042040, 4])
.. raw:: html
.. raw:: html
Depois de alterar a forma da variável da caixa de âncora ``y`` para
(altura da imagem, largura da imagem, número de caixas de âncora
centradas no mesmo pixel, 4), podemos obter todas as caixas de âncora
centradas em uma posição de pixel especificada. No exemplo a seguir,
acessamos a primeira caixa de âncora centrada em (250, 250). Ele tem
quatro elementos: as coordenadas do eixo :math:`x, y` no canto superior
esquerdo e as coordenadas do eixo :math:`x, y` no canto inferior direito
da caixa de âncora. Os valores das coordenadas dos eixos :math:`x` e
:math:`y` são divididos pela largura e altura da imagem,
respectivamente, portanto, o intervalo de valores está entre 0 e 1.
.. raw:: html
.. raw:: html
.. code:: python
boxes = Y.reshape(h, w, 5, 4)
boxes[250, 250, 0, :]
.. parsed-literal::
:class: output
array([0.06, 0.07, 0.63, 0.82])
.. raw:: html
.. raw:: html
.. code:: python
boxes = Y.reshape(h, w, 5, 4)
boxes[250, 250, 0, :]
.. parsed-literal::
:class: output
tensor([0.06, 0.07, 0.63, 0.82])
.. raw:: html
.. raw:: html
Para descrever todas as caixas de âncora centralizadas em um pixel na
imagem, primeiro definimos a função ``show_bboxes`` para desenhar várias
caixas delimitadoras na imagem.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def show_bboxes(axes, bboxes, labels=None, colors=None):
"""Show bounding boxes."""
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.asnumpy(), 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=9, color=text_color,
bbox=dict(facecolor=color, lw=0))
.. raw:: html
.. raw:: html
.. code:: python
#@save
def show_bboxes(axes, bboxes, labels=None, colors=None):
"""Show bounding boxes."""
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().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=9, color=text_color,
bbox=dict(facecolor=color, lw=0))
.. raw:: html
.. raw:: html
Como acabamos de ver, os valores das coordenadas dos eixos :math:`x` e
:math:`y` na variável ``caixas`` foram divididos pela largura e altura
da imagem, respectivamente. Ao desenhar imagens, precisamos restaurar os
valores das coordenadas originais das caixas de âncora e, portanto,
definir a variável ``bbox_scale``. Agora, podemos desenhar todas as
caixas de âncora centralizadas em (250, 250) na imagem. Como você pode
ver, a caixa de âncora azul com um tamanho de 0,75 e uma proporção de 1
cobre bem o cão na imagem.
.. raw:: html
.. raw:: html
.. code:: python
d2l.set_figsize()
bbox_scale = np.array((w, h, w, h))
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, boxes[250, 250, :, :] * bbox_scale,
['s=0.75, r=1', 's=0.5, r=1', 's=0.25, r=1', 's=0.75, r=2',
's=0.75, r=0.5'])
.. figure:: output_anchor_f592d1_50_0.svg
.. raw:: html
.. raw:: html
.. code:: python
d2l.set_figsize()
bbox_scale = torch.tensor((w, h, w, h))
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, boxes[250, 250, :, :] * bbox_scale,
['s=0.75, r=1', 's=0.5, r=1', 's=0.25, r=1', 's=0.75, r=2',
's=0.75, r=0.5'])
.. figure:: output_anchor_f592d1_53_0.svg
.. raw:: html
.. raw:: html
Interseção sobre União
----------------------
Acabamos de mencionar que a caixa de âncora cobre bem o cachorro na
imagem. Se a caixa delimitadora da verdade básica do alvo é conhecida,
como o “bem” pode ser quantificado aqui? Um método intuitivo é medir a
semelhança entre as caixas de âncora e a caixa delimitadora da verdade
absoluta. Sabemos que o índice de Jaccard pode medir a semelhança entre
dois conjuntos. Dados os conjuntos :math:`\mathcal{A}` e
:math:`\mathcal{B}`, seu índice de Jaccard é o tamanho de sua interseção
dividido pelo tamanho de sua união:
.. math:: J(\mathcal{A},\mathcal{B}) = \frac{\left|\mathcal{A} \cap \mathcal{B}\right|}{\left| \mathcal{A} \cup \mathcal{B}\right|}.
Na verdade, podemos considerar a área de pixels de uma caixa
delimitadora como uma coleção de pixels. Dessa forma, podemos medir a
similaridade das duas caixas delimitadoras pelo índice de Jaccard de
seus conjuntos de pixels. Quando medimos a similaridade de duas caixas
delimitadoras, geralmente nos referimos ao índice de Jaccard como
interseção sobre união (IoU), que é a razão entre a área de interseção e
a área de união das duas caixas delimitadoras, conforme mostrado em
:numref:`fig_iou`. O intervalo de valores de IoU está entre 0 e 1: 0
significa que não há pixels sobrepostos entre as duas caixas
delimitadoras, enquanto 1 indica que as duas caixas delimitadoras são
iguais.
.. _fig_iou:
.. figure:: ../img/iou.svg
IoU é a razão entre a área de interseção e a área de união de duas
caixas delimitadoras.
Para o restante desta seção, usaremos IoU para medir a semelhança entre
as caixas de âncora e as caixas delimitadoras de verdade terrestre e
entre as diferentes caixas de âncora.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def box_iou(boxes1, boxes2):
"""Compute IOU between two sets of boxes of shape (N,4) and (M,4)."""
# Compute box areas
box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) *
(boxes[:, 3] - boxes[:, 1]))
area1 = box_area(boxes1)
area2 = box_area(boxes2)
lt = np.maximum(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
rb = np.minimum(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]
wh = (rb - lt).clip(min=0) # [N,M,2]
inter = wh[:, :, 0] * wh[:, :, 1] # [N,M]
unioun = area1[:, None] + area2 - inter
return inter / unioun
.. raw:: html
.. raw:: html
.. code:: python
#@save
def box_iou(boxes1, boxes2):
"""Compute IOU between two sets of boxes of shape (N,4) and (M,4)."""
# Compute box areas
box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) *
(boxes[:, 3] - boxes[:, 1]))
area1 = box_area(boxes1)
area2 = box_area(boxes2)
lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]
wh = (rb - lt).clamp(min=0) # [N,M,2]
inter = wh[:, :, 0] * wh[:, :, 1] # [N,M]
unioun = area1[:, None] + area2 - inter
return inter / unioun
.. raw:: html
.. raw:: html
Rotulagem de Treinamento para Definir Caixas de Âncora
------------------------------------------------------
No conjunto de treinamento, consideramos cada caixa de âncora como um
exemplo de treinamento. Para treinar o modelo de detecção de objetos,
precisamos marcar dois tipos de rótulos para cada caixa de âncora:
primeiro, a categoria do alvo contido na caixa de âncora (categoria) e,
em segundo lugar, o deslocamento da caixa delimitadora da verdade básica
em relação à caixa de âncora (deslocamento). Na detecção de objetos,
primeiro geramos várias caixas de âncora, predizemos as categorias e
deslocamentos para cada caixa de âncora, ajustamos a posição da caixa de
âncora de acordo com o deslocamento previsto para obter as caixas
delimitadoras a serem usadas para previsão e, finalmente, filtramos as
caixas delimitadoras de predição que precisam ser produzidos.
Sabemos que, no conjunto de treinamento de detecção de objetos, cada
imagem é rotulada com a localização da caixa delimitadora da verdade
terrestre e a categoria do alvo contido. Depois que as caixas de âncora
são geradas, rotulamos principalmente as caixas de âncora com base na
localização e nas informações de categoria das caixas delimitadoras de
verdade terrestre semelhantes às caixas de âncora. Então, como
atribuímos caixas delimitadoras de verdade terrestre a caixas de
ancoragem semelhantes a elas?
Suponha que as caixas de ancoragem na imagem sejam
:math:`A_1, A_2, \ldots, A_{n_a}` e as caixas delimitadoras de verdade
são :math:`B_1, B_2, \ldots, B_{n_b}` e :math:`n_a \geq n_b`. Defina a
matriz :math:`\mathbf{X} \in \mathbb{R}^{n_a \times n_b}`, onde o
elemento :math:`x_{ij}` na linha :math:`i^\mathrm{th}` e coluna
:math:`j^\mathrm{th}` é a IoU da caixa de âncora :math:`A_i` para a
caixa delimitadora da verdade básica :math:`B_j`. Primeiro, encontramos
o maior elemento na matriz :math:`\mathbf{X}` e registramos o índice da
linha e o índice da coluna do elemento como :math:`i_1,j_1`. Atribuímos
a caixa delimitadora da verdade básica :math:`B_{j_1}` à caixa âncora
:math:`A_{i_1}`. Obviamente, a caixa de âncora :math:`A_{i_1}` e a caixa
delimitadora da verdade básica :math:`B_{j_1}` têm a maior similaridade
entre todos os pares “caixa de âncora - caixa delimitadora da verdade”.
A seguir, descarte todos os elementos da :math:`i_1`\ ª linha e da
:math:`j_1`\ ª coluna da matriz :math:`\mathbf{X}`. Encontre o maior
elemento restante na matriz :math:`\mathbf{X}` e registre o índice da
linha e o índice da coluna do elemento como :math:`i_2,j_2`. Atribuímos
a caixa delimitadora de verdade básica :math:`B_{j_2}` à caixa de
ancoragem :math:`A_{i_2}` e, em seguida, descartamos todos os elementos
na :math:`i_2`\ ª linha e na :math:`j_2`\ ª coluna na matriz
:math:`\mathbf{X}`. Neste ponto, os elementos em duas linhas e duas
colunas na matriz :math:`\mathbf{X}` foram descartados.
Prosseguimos até que todos os elementos da coluna :math:`n_b` da matriz
:math:`\mathbf{X}` sejam descartados. Neste momento, atribuímos uma
caixa delimitadora de verdade terrestre a cada uma das caixas de âncora
:math:`n_b`. Em seguida, percorremos apenas as caixas de âncora
:math:`n_a - n_b` restantes. Dada a caixa de âncora :math:`A_i`,
encontre a caixa delimitadora :math:`B_j` com o maior IoU com
:math:`A_i` de acordo com a :math:`i^\mathrm{th}` linha da matriz
:math:`\mathbf{X}`, e apenas atribua o terreno -caixa delimitadora da
verdade :math:`B_j` para ancorar a caixa :math:`A_i` quando o IoU é
maior do que o limite predeterminado.
Conforme mostrado em :numref:`fig_anchor_label` (esquerda), assumindo
que o valor máximo na matriz :math:`\mathbf{X}` é :math:`x_{23}`, iremos
atribuir a caixa delimitadora da verdade básica :math:`B_3` à caixa de
âncora :math:`A_2`.. Em seguida, descartamos todos os elementos na linha
2 e coluna 3 da matriz, encontramos o maior elemento :math:`x_{71}` da
área sombreada restante e atribuímos a caixa delimitadora de verdade
básica :math:`B_1` à caixa de ancoragem :math:`A_7`. Então, como
mostrado em :numref:`fig_anchor_label` (meio), descarte todos os
elementos na linha 7 e coluna 1 da matriz, encontre o maior elemento
:math:`x_{54}` da área sombreada restante e atribua a caixa delimitadora
de verdade fundamental :math:`B_4` para a caixa âncora :math:`A_5`.
Finalmente, como mostrado em :numref:`fig_anchor_label` (direita),
descarte todos os elementos na linha 5 e coluna 4 da matriz, encontre o
maior elemento :math:`x_{92}` da área sombreada restante e atribua a
caixa delimitadora da verdade fundamental :math:`B_2` para a caixa
âncora :math:`A_9`. Depois disso, só precisamos atravessar as caixas de
âncora restantes de :math:`A_1, A_3, A_4, A_6, A_8` e determinar se
devemos atribuir caixas delimitadoras de verdade fundamental às caixas
de âncora restantes de acordo com o limite.
.. _fig_anchor_label:
.. figure:: ../img/anchor-label.svg
Atribua caixas delimitadoras de base de verdade às caixas de
ancoragem.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def match_anchor_to_bbox(ground_truth, anchors, device, iou_threshold=0.5):
"""Assign ground-truth bounding boxes to anchor boxes similar to them."""
num_anchors, num_gt_boxes = anchors.shape[0], ground_truth.shape[0]
# Element `x_ij` in the `i^th` row and `j^th` column is the IoU
# of the anchor box `anc_i` to the ground-truth bounding box `box_j`
jaccard = box_iou(anchors, ground_truth)
# Initialize the tensor to hold assigned ground truth bbox for each anchor
anchors_bbox_map = np.full((num_anchors,), -1, dtype=np.int32, ctx=device)
# Assign ground truth bounding box according to the threshold
max_ious, indices = np.max(jaccard, axis=1), np.argmax(jaccard, axis=1)
anc_i = np.nonzero(max_ious >= 0.5)[0]
box_j = indices[max_ious >= 0.5]
anchors_bbox_map[anc_i] = box_j
# Find the largest iou for each bbox
col_discard = np.full((num_anchors,), -1)
row_discard = np.full((num_gt_boxes,), -1)
for _ in range(num_gt_boxes):
max_idx = np.argmax(jaccard)
box_idx = (max_idx % num_gt_boxes).astype('int32')
anc_idx = (max_idx / num_gt_boxes).astype('int32')
anchors_bbox_map[anc_idx] = box_idx
jaccard[:, box_idx] = col_discard
jaccard[anc_idx, :] = row_discard
return anchors_bbox_map
.. raw:: html
.. raw:: html
.. code:: python
#@save
def match_anchor_to_bbox(ground_truth, anchors, device, iou_threshold=0.5):
"""Assign ground-truth bounding boxes to anchor boxes similar to them."""
num_anchors, num_gt_boxes = anchors.shape[0], ground_truth.shape[0]
# Element `x_ij` in the `i^th` row and `j^th` column is the IoU
# of the anchor box `anc_i` to the ground-truth bounding box `box_j`
jaccard = box_iou(anchors, ground_truth)
# Initialize the tensor to hold assigned ground truth bbox for each anchor
anchors_bbox_map = torch.full((num_anchors,), -1, dtype=torch.long,
device=device)
# Assign ground truth bounding box according to the threshold
max_ious, indices = torch.max(jaccard, dim=1)
anc_i = torch.nonzero(max_ious >= 0.5).reshape(-1)
box_j = indices[max_ious >= 0.5]
anchors_bbox_map[anc_i] = box_j
# Find the largest iou for each bbox
col_discard = torch.full((num_anchors,), -1)
row_discard = torch.full((num_gt_boxes,), -1)
for _ in range(num_gt_boxes):
max_idx = torch.argmax(jaccard)
box_idx = (max_idx % num_gt_boxes).long()
anc_idx = (max_idx / num_gt_boxes).long()
anchors_bbox_map[anc_idx] = box_idx
jaccard[:, box_idx] = col_discard
jaccard[anc_idx, :] = row_discard
return anchors_bbox_map
.. raw:: html
.. raw:: html
Agora podemos rotular as categorias e deslocamentos das caixas de
âncora. Se uma caixa de âncora :math:`A` for atribuída a uma caixa
delimitadora de verdade fundamental :math:`B`, a categoria da caixa de
âncora :math:`A` será definida como a categoria de :math:`B`. E o
deslocamento da caixa âncora :math:`A` é definido de acordo com a
posição relativa das coordenadas centrais de :math:`B` e :math:`A` e os
tamanhos relativos das duas caixas. Como as posições e tamanhos de
várias caixas no conjunto de dados podem variar, essas posições e
tamanhos relativos geralmente requerem algumas transformações especiais
para tornar a distribuição de deslocamento mais uniforme e fácil de
ajustar. Suponha que as coordenadas centrais da caixa de âncora
:math:`A` e sua caixa delimitadora de verdade fundamental $ B $ sejam
:math:`(x_a, y_a), (x_b, y_b)`, as larguras de :math:`A` e :math:`B` são
:math:`w_a, w_b`, e suas alturas são :math:`h_a, h_b`, respectivamente.
Neste caso, uma técnica comum é rotular o deslocamento de :math:`A` como
.. math::
\left( \frac{ \frac{x_b - x_a}{w_a} - \mu_x }{\sigma_x},
\frac{ \frac{y_b - y_a}{h_a} - \mu_y }{\sigma_y},
\frac{ \log \frac{w_b}{w_a} - \mu_w }{\sigma_w},
\frac{ \log \frac{h_b}{h_a} - \mu_h }{\sigma_h}\right),
Os valores padrão da constante são
:math:`\mu_x = \mu_y = \mu_w = \mu_h = 0, \sigma_x=\sigma_y=0.1, \text{ e } \sigma_w=\sigma_h=0.2`.
Esta transformação é implementada abaixo na função ``offset_boxes``. Se
uma caixa de âncora não for atribuída a uma caixa delimitadora de
verdade, só precisamos definir a categoria da caixa de âncora como
segundo plano. As caixas de âncora cuja categoria é o plano de fundo
costumam ser chamadas de caixas de âncora negativas e o restante é
chamado de caixas de âncora positivas.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def offset_boxes(anchors, assigned_bb, eps=1e-6):
c_anc = d2l.box_corner_to_center(anchors)
c_assigned_bb = d2l.box_corner_to_center(assigned_bb)
offset_xy = 10 * (c_assigned_bb[:, :2] - c_anc[:, :2]) / c_anc[:, 2:]
offset_wh = 5 * np.log(eps + c_assigned_bb[:, 2:] / c_anc[:, 2:])
offset = np.concatenate([offset_xy, offset_wh], axis=1)
return offset
#@save
def multibox_target(anchors, labels):
batch_size, anchors = labels.shape[0], anchors.squeeze(0)
batch_offset, batch_mask, batch_class_labels = [], [], []
device, num_anchors = anchors.ctx, anchors.shape[0]
for i in range(batch_size):
label = labels[i, :, :]
anchors_bbox_map = match_anchor_to_bbox(label[:, 1:], anchors, device)
bbox_mask = np.tile((np.expand_dims((anchors_bbox_map >= 0),
axis=-1)), (1, 4)).astype('int32')
# Initialize class_labels and assigned bbox coordinates with zeros
class_labels = np.zeros(num_anchors, dtype=np.int32, ctx=device)
assigned_bb = np.zeros((num_anchors, 4), dtype=np.float32, ctx=device)
# Assign class labels to the anchor boxes using matched gt bbox labels
# If no gt bbox is assigned to an anchor box, then let the
# class_labels and assigned_bb remain zero, i.e the background class
indices_true = np.nonzero(anchors_bbox_map >= 0)[0]
bb_idx = anchors_bbox_map[indices_true]
class_labels[indices_true] = label[bb_idx, 0].astype('int32') + 1
assigned_bb[indices_true] = label[bb_idx, 1:]
# offset transformations
offset = offset_boxes(anchors, assigned_bb) * bbox_mask
batch_offset.append(offset.reshape(-1))
batch_mask.append(bbox_mask.reshape(-1))
batch_class_labels.append(class_labels)
bbox_offset = np.stack(batch_offset)
bbox_mask = np.stack(batch_mask)
class_labels = np.stack(batch_class_labels)
return (bbox_offset, bbox_mask, class_labels)
.. raw:: html
.. raw:: html
.. code:: python
#@save
def offset_boxes(anchors, assigned_bb, eps=1e-6):
c_anc = d2l.box_corner_to_center(anchors)
c_assigned_bb = d2l.box_corner_to_center(assigned_bb)
offset_xy = 10 * (c_assigned_bb[:, :2] - c_anc[:, :2]) / c_anc[:, 2:]
offset_wh = 5 * torch.log(eps + c_assigned_bb[:, 2:] / c_anc[:, 2:])
offset = torch.cat([offset_xy, offset_wh], axis=1)
return offset
#@save
def multibox_target(anchors, labels):
batch_size, anchors = labels.shape[0], anchors.squeeze(0)
batch_offset, batch_mask, batch_class_labels = [], [], []
device, num_anchors = anchors.device, anchors.shape[0]
for i in range(batch_size):
label = labels[i, :, :]
anchors_bbox_map = match_anchor_to_bbox(label[:, 1:], anchors, device)
bbox_mask = ((anchors_bbox_map >= 0).float().unsqueeze(-1)).repeat(1, 4)
# Initialize class_labels and assigned bbox coordinates with zeros
class_labels = torch.zeros(num_anchors, dtype=torch.long,
device=device)
assigned_bb = torch.zeros((num_anchors, 4), dtype=torch.float32,
device=device)
# Assign class labels to the anchor boxes using matched gt bbox labels
# If no gt bbox is assigned to an anchor box, then let the
# class_labels and assigned_bb remain zero, i.e the background class
indices_true = torch.nonzero(anchors_bbox_map >= 0)
bb_idx = anchors_bbox_map[indices_true]
class_labels[indices_true] = label[bb_idx, 0].long() + 1
assigned_bb[indices_true] = label[bb_idx, 1:]
# offset transformations
offset = offset_boxes(anchors, assigned_bb) * bbox_mask
batch_offset.append(offset.reshape(-1))
batch_mask.append(bbox_mask.reshape(-1))
batch_class_labels.append(class_labels)
bbox_offset = torch.stack(batch_offset)
bbox_mask = torch.stack(batch_mask)
class_labels = torch.stack(batch_class_labels)
return (bbox_offset, bbox_mask, class_labels)
.. raw:: html
.. raw:: html
Abaixo, demonstramos um exemplo detalhado. Definimos caixas
delimitadoras de verdade para o gato e o cachorro na imagem lida, onde o
primeiro elemento é a categoria (0 para cachorro, 1 para gato) e os
quatro elementos restantes são as coordenadas do eixo :math:`x, y` no
canto superior esquerdo canto e coordenadas do eixo :math:`x, y` no
canto inferior direito (o intervalo de valores está entre 0 e 1). Aqui,
construímos cinco caixas de âncora para serem rotuladas pelas
coordenadas do canto superior esquerdo e do canto inferior direito, que
são registradas como :math:`A_0, \ldots, A_4`, respectivamente (o índice
no programa começa em 0) . Primeiro, desenhe as posições dessas caixas
de âncora e das caixas delimitadoras da verdade fundamental na imagem.
.. raw:: html
.. raw:: html
.. code:: python
ground_truth = np.array([[0, 0.1, 0.08, 0.52, 0.92],
[1, 0.55, 0.2, 0.9, 0.88]])
anchors = np.array([[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']);
.. figure:: output_anchor_f592d1_86_0.svg
.. raw:: html
.. raw:: html
.. code:: python
ground_truth = torch.tensor([[0, 0.1, 0.08, 0.52, 0.92],
[1, 0.55, 0.2, 0.9, 0.88]])
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']);
.. figure:: output_anchor_f592d1_89_0.svg
.. raw:: html
.. raw:: html
Podemos rotular categorias e deslocamentos para caixas de âncora usando
a função ``multibox_target``. Esta função define a categoria de fundo
para 0 e incrementa o índice inteiro da categoria de destino de zero por
1 (1 para cachorro e 2 para gato).
.. raw:: html
.. raw:: html
Adicionamos dimensões de exemplo às caixas de âncora e às caixas
delimitadoras de verdade e construímos resultados preditos aleatórios
com uma forma de (tamanho do lote, número de categorias incluindo plano
de fundo, número de caixas de âncora) usando a função ``expand_dims``.
.. code:: python
labels = multibox_target(np.expand_dims(anchors, axis=0),
np.expand_dims(ground_truth, axis=0))
.. raw:: html
.. raw:: html
Adicionamos dimensões de exemplo às caixas de âncora e caixas
delimitadoras de verdade e construímos resultados preditos aleatórios
com uma forma de (tamanho do lote, número de categorias incluindo fundo,
número de caixas de âncora) usando a função ``unsqueeze``.
.. code:: python
labels = multibox_target(anchors.unsqueeze(dim=0),
ground_truth.unsqueeze(dim=0))
.. raw:: html
.. raw:: html
Existem três itens no resultado retornado, todos no formato tensor. O
terceiro item é representado pela categoria rotulada para a caixa de
âncora.
.. raw:: html
.. raw:: html
.. code:: python
labels[2]
.. parsed-literal::
:class: output
array([[0, 1, 2, 0, 2]], dtype=int32)
.. raw:: html
.. raw:: html
.. code:: python
labels[2]
.. parsed-literal::
:class: output
tensor([[0, 1, 2, 0, 2]])
.. raw:: html
.. raw:: html
Analisamos essas categorias rotuladas com base nas posições das caixas
de âncora e das caixas delimitadoras de informações básicas na imagem.
Em primeiro lugar, em todos os pares de “caixa de âncora - caixa
delimitadora de verdade básica”, a IoU da caixa de ancoragem :math:`A_4`
para a caixa delimitadora de verdade básica do gato é a maior, então a
categoria de caixa de ancoragem :math:`A_4` é rotulada como gato. Sem
considerar a caixa de âncora :math:`A_4` ou a caixa delimitadora de
verdade do solo do gato, nos pares restantes “caixa de âncora - caixa de
ligação de verdade”, o par com a maior IoU é a caixa de âncora
:math:`A_1` e a a caixa delimitadora da verdade do cachorro, portanto, a
categoria da caixa de âncora :math:`A_1` é rotulada como cachorro. Em
seguida, atravesse as três caixas de âncora restantes sem etiqueta. A
categoria da caixa delimitadora de verdade básica com o maior IoU com
caixa de âncora :math:`A_0` é cahcorro, mas o IoU é menor que o limite
(o padrão é 0,5), portanto, a categoria é rotulada como plano de fundo;
a categoria da caixa delimitadora de verdade básica com a maior IoU com
caixa de âncora :math:`A_2` é gato e a IoU é maior que o limite,
portanto, a categoria é rotulada como gato; a categoria da caixa
delimitadora de verdade básica com a maior IoU com caixa de âncora
:math:`A_3` é cat, mas a IoU é menor que o limite, portanto, a categoria
é rotulada como plano de fundo.
O segundo item do valor de retorno é uma variável de máscara, com a
forma de (tamanho do lote, quatro vezes o número de caixas de âncora).
Os elementos na variável de máscara correspondem um a um com os quatro
valores de deslocamento de cada caixa de âncora. Como não nos importamos
com a detecção de fundo, os deslocamentos da classe negativa não devem
afetar a função de destino. Multiplicando por elemento, o 0 na variável
de máscara pode filtrar os deslocamentos de classe negativos antes de
calcular a função de destino.
.. raw:: html
.. raw:: html
.. code:: python
labels[1]
.. parsed-literal::
:class: output
array([[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]],
dtype=int32)
.. raw:: html
.. raw:: html
.. code:: python
labels[1]
.. parsed-literal::
:class: output
tensor([[0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 1., 1.,
1., 1.]])
.. raw:: html
.. raw:: html
O primeiro item retornado são os quatro valores de deslocamento
rotulados para cada caixa de âncora, com os deslocamentos das caixas de
âncora de classe negativa rotulados como 0.
.. raw:: html
.. raw:: html
.. code:: python
labels[0]
.. parsed-literal::
:class: output
array([[-0.00e+00, -0.00e+00, -0.00e+00, -0.00e+00, 1.40e+00, 1.00e+01,
2.59e+00, 7.18e+00, -1.20e+00, 2.69e-01, 1.68e+00, -1.57e+00,
-0.00e+00, -0.00e+00, -0.00e+00, -0.00e+00, -5.71e-01, -1.00e+00,
4.17e-06, 6.26e-01]])
.. raw:: html
.. raw:: html
.. code:: python
labels[0]
.. parsed-literal::
:class: output
tensor([[-0.00e+00, -0.00e+00, -0.00e+00, -0.00e+00, 1.40e+00, 1.00e+01,
2.59e+00, 7.18e+00, -1.20e+00, 2.69e-01, 1.68e+00, -1.57e+00,
-0.00e+00, -0.00e+00, -0.00e+00, -0.00e+00, -5.71e-01, -1.00e+00,
4.17e-06, 6.26e-01]])
.. raw:: html
.. raw:: html
Caixas Delimitadoras para Previsão
----------------------------------
Durante a fase de previsão do modelo, primeiro geramos várias caixas de
âncora para a imagem e, em seguida, predizemos categorias e
deslocamentos para essas caixas de âncora, uma por uma. Em seguida,
obtemos caixas delimitadoras de previsão com base nas caixas de âncora e
seus deslocamentos previstos.
Abaixo, implementamos a função ``offset_inverse`` que leva âncoras e
previsões de deslocamento como entradas e aplica transformações de
deslocamento inversas para retornar as coordenadas da caixa delimitadora
prevista.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def offset_inverse(anchors, offset_preds):
c_anc = d2l.box_corner_to_center(anchors)
c_pred_bb_xy = (offset_preds[:, :2] * c_anc[:, 2:] / 10) + c_anc[:, :2]
c_pred_bb_wh = np.exp(offset_preds[:, 2:] / 5) * c_anc[:, 2:]
c_pred_bb = np.concatenate((c_pred_bb_xy, c_pred_bb_wh), axis=1)
predicted_bb = d2l.box_center_to_corner(c_pred_bb)
return predicted_bb
.. raw:: html
.. raw:: html
.. code:: python
#@save
def offset_inverse(anchors, offset_preds):
c_anc = d2l.box_corner_to_center(anchors)
c_pred_bb_xy = (offset_preds[:, :2] * c_anc[:, 2:] / 10) + c_anc[:, :2]
c_pred_bb_wh = torch.exp(offset_preds[:, 2:] / 5) * c_anc[:, 2:]
c_pred_bb = torch.cat((c_pred_bb_xy, c_pred_bb_wh), axis=1)
predicted_bb = d2l.box_center_to_corner(c_pred_bb)
return predicted_bb
.. raw:: html
.. raw:: html
Quando há muitas caixas de âncora, muitas caixas delimitadoras de
predição semelhantes podem ser geradas para o mesmo alvo. Para
simplificar os resultados, podemos remover caixas delimitadoras de
predição semelhantes. Um método comumente usado é chamado de supressão
não máxima (NMS).
Vamos dar uma olhada em como o NMS funciona. Para uma caixa delimitadora
de previsão :math:`B`, o modelo calcula a probabilidade prevista para
cada categoria. Suponha que a maior probabilidade prevista seja
:math:`p`, a categoria correspondente a essa probabilidade é a categoria
prevista de :math:`B`. Também nos referimos a :math:`p` como o nível de
confiança da caixa delimitadora de predição :math:`B`. Na mesma imagem,
classificamos as caixas delimitadoras de previsão com categorias
previstas diferentes do plano de fundo por nível de confiança de alto a
baixo e obtemos a lista :math:`L`. Selecionamos a caixa delimitadora de
predição :math:`B_1` com o nível de confiança mais alto de :math:`L`
como linha de base e remova todas as caixas delimitadoras de predição
não comparativas com um IoU com :math:`B_1` maior que um determinado
limite de :math:`L`. O limite aqui é um hiperparâmetro predefinido.
Nesse ponto, :math:`L` retém a caixa delimitadora de predição com o
nível de confiança mais alto e remove outras caixas delimitadoras de
predição semelhantes a ela. Em seguida, selecionamos a caixa
delimitadora de predição :math:`B_2` com o segundo nível de confiança
mais alto de :math:`L` como linha de base e removemos todas as caixas
delimitadoras de predição não comparativas com um IoU com :math:`B_2`
maior que um determinado limite de :math:`L`. Repetimos esse processo
até que todas as caixas delimitadoras de previsão em :math:`L` tenham
sido usadas como linha de base. Neste momento, a IoU de qualquer par de
caixas delimitadoras de predição em :math:`L` é menor que o limite.
Finalmente, produzimos todas as caixas delimitadoras de predição na
lista :math:`L`.
.. raw:: html
.. raw:: html
.. code:: python
#@save
def nms(boxes, scores, iou_threshold):
# sorting scores by the descending order and return their indices
B = scores.argsort()[::-1]
keep = [] # boxes indices that will be kept
while B.size > 0:
i = B[0]
keep.append(i)
if B.size == 1: break
iou = box_iou(boxes[i, :].reshape(-1, 4),
boxes[B[1:], :].reshape(-1, 4)).reshape(-1)
inds = np.nonzero(iou <= iou_threshold)[0]
B = B[inds + 1]
return np.array(keep, dtype=np.int32, ctx=boxes.ctx)
#@save
def multibox_detection(cls_probs, offset_preds, anchors, nms_threshold=0.5,
pos_threshold=0.00999999978):
device, batch_size = cls_probs.ctx, cls_probs.shape[0]
anchors = np.squeeze(anchors, axis=0)
num_classes, num_anchors = cls_probs.shape[1], cls_probs.shape[2]
out = []
for i in range(batch_size):
cls_prob, offset_pred = cls_probs[i], offset_preds[i].reshape(-1, 4)
conf, class_id = np.max(cls_prob[1:], 0), np.argmax(cls_prob[1:], 0)
predicted_bb = offset_inverse(anchors, offset_pred)
keep = nms(predicted_bb, conf, 0.5)
# Find all non_keep indices and set the class_id to background
all_idx = np.arange(num_anchors, dtype=np.int32, ctx=device)
combined = np.concatenate((keep, all_idx))
unique, counts = np.unique(combined, return_counts=True)
non_keep = unique[counts == 1]
all_id_sorted = np.concatenate((keep, non_keep))
class_id[non_keep] = -1
class_id = class_id[all_id_sorted].astype('float32')
conf, predicted_bb = conf[all_id_sorted], predicted_bb[all_id_sorted]
# threshold to be a positive prediction
below_min_idx = (conf < pos_threshold)
class_id[below_min_idx] = -1
conf[below_min_idx] = 1 - conf[below_min_idx]
pred_info = np.concatenate((np.expand_dims(class_id, axis=1),
np.expand_dims(conf, axis=1),
predicted_bb), axis=1)
out.append(pred_info)
return np.stack(out)
.. raw:: html
.. raw:: html
.. code:: python
#@save
def nms(boxes, scores, iou_threshold):
# sorting scores by the descending order and return their indices
B = torch.argsort(scores, dim=-1, descending=True)
keep = [] # boxes indices that will be kept
while B.numel() > 0:
i = B[0]
keep.append(i)
if B.numel() == 1: break
iou = box_iou(boxes[i, :].reshape(-1, 4),
boxes[B[1:], :].reshape(-1, 4)).reshape(-1)
inds = torch.nonzero(iou <= iou_threshold).reshape(-1)
B = B[inds + 1]
return torch.tensor(keep, device=boxes.device)
#@save
def multibox_detection(cls_probs, offset_preds, anchors, nms_threshold=0.5,
pos_threshold=0.00999999978):
device, batch_size = cls_probs.device, cls_probs.shape[0]
anchors = anchors.squeeze(0)
num_classes, num_anchors = cls_probs.shape[1], cls_probs.shape[2]
out = []
for i in range(batch_size):
cls_prob, offset_pred = cls_probs[i], offset_preds[i].reshape(-1, 4)
conf, class_id = torch.max(cls_prob[1:], 0)
predicted_bb = offset_inverse(anchors, offset_pred)
keep = nms(predicted_bb, conf, 0.5)
# Find all non_keep indices and set the class_id to background
all_idx = torch.arange(num_anchors, dtype=torch.long, device=device)
combined = torch.cat((keep, all_idx))
uniques, counts = combined.unique(return_counts=True)
non_keep = uniques[counts == 1]
all_id_sorted = torch.cat((keep, non_keep))
class_id[non_keep] = -1
class_id = class_id[all_id_sorted]
conf, predicted_bb = conf[all_id_sorted], predicted_bb[all_id_sorted]
# threshold to be a positive prediction
below_min_idx = (conf < pos_threshold)
class_id[below_min_idx] = -1
conf[below_min_idx] = 1 - conf[below_min_idx]
pred_info = torch.cat((class_id.unsqueeze(1),
conf.unsqueeze(1),
predicted_bb), dim=1)
out.append(pred_info)
return torch.stack(out)
.. raw:: html
.. raw:: html
A seguir, veremos um exemplo detalhado. Primeiro, construa quatro caixas
de âncora. Para simplificar, assumimos que os deslocamentos previstos
são todos 0. Isso significa que as caixas delimitadoras de previsão são
caixas de âncora. Finalmente, construímos uma probabilidade prevista
para cada categoria.
.. raw:: html
.. raw:: html
.. code:: python
anchors = np.array([[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 = np.array([0] * d2l.size(anchors))
cls_probs = np.array([[0] * 4, # Predicted probability for background
[0.9, 0.8, 0.7, 0.1], # Predicted probability for dog
[0.1, 0.2, 0.3, 0.9]]) # Predicted probability for cat
.. raw:: html
.. raw:: html
.. code:: python
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] * anchors.numel())
cls_probs = torch.tensor([[0] * 4, # Predicted probability for background
[0.9, 0.8, 0.7, 0.1], # Predicted probability for dog
[0.1, 0.2, 0.3, 0.9]]) # Predicted probability for cat
.. raw:: html
.. raw:: html
Imprima caixas delimitadoras de previsão e seus níveis de confiança na
imagem.
.. raw:: html
.. raw:: html
.. code:: python
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
.. figure:: output_anchor_f592d1_160_0.svg
.. raw:: html
.. raw:: html
.. code:: python
fig = d2l.plt.imshow(img)
show_bboxes(fig.axes, anchors * bbox_scale,
['dog=0.9', 'dog=0.8', 'dog=0.7', 'cat=0.9'])
.. figure:: output_anchor_f592d1_163_0.svg
.. raw:: html
.. raw:: html
Usamos a função ``multibox_detection`` para executar NMS e definir o
limite para 0,5. Isso adiciona uma dimensão de exemplo à entrada do
tensor. Podemos ver que a forma do resultado retornado é (tamanho do
lote, número de caixas de âncora, 6). Os 6 elementos de cada linha
representam as informações de saída para a mesma caixa delimitadora de
previsão. O primeiro elemento é o índice de categoria previsto, que
começa em 0 (0 é cachorro, 1 é gato). O valor -1 indica fundo ou remoção
no NMS. O segundo elemento é o nível de confiança da caixa delimitadora
de previsão. Os quatro elementos restantes são as coordenadas do eixo
:math:`x, y` do canto superior esquerdo e as coordenadas do eixo
:math:`x, y` do canto inferior direito da caixa delimitadora de previsão
(o intervalo de valores está entre 0 e 1).
.. raw:: html
.. raw:: html
.. code:: python
output = multibox_detection(
np.expand_dims(cls_probs, axis=0),
np.expand_dims(offset_preds, axis=0),
np.expand_dims(anchors, axis=0),
nms_threshold=0.5)
output
.. parsed-literal::
:class: output
array([[[ 1. , 0.9 , 0.55, 0.2 , 0.9 , 0.88],
[ 0. , 0.9 , 0.1 , 0.08, 0.52, 0.92],
[-1. , 0.8 , 0.08, 0.2 , 0.56, 0.95],
[-1. , 0.7 , 0.15, 0.3 , 0.62, 0.91]]])
.. raw:: html
.. raw:: html
.. code:: python
output = multibox_detection(cls_probs.unsqueeze(dim=0),
offset_preds.unsqueeze(dim=0),
anchors.unsqueeze(dim=0),
nms_threshold=0.5)
output
.. parsed-literal::
:class: output
tensor([[[ 0.00, 0.90, 0.10, 0.08, 0.52, 0.92],
[ 1.00, 0.90, 0.55, 0.20, 0.90, 0.88],
[-1.00, 0.80, 0.08, 0.20, 0.56, 0.95],
[-1.00, 0.70, 0.15, 0.30, 0.62, 0.91]]])
.. raw:: html
.. raw:: html
Removemos as caixas delimitadoras de predição da categoria -1 e
visualizamos os resultados retidos pelo NMS.
.. raw:: html
.. raw:: html
.. code:: python
fig = d2l.plt.imshow(img)
for i in output[0].asnumpy():
if i[0] == -1:
continue
label = ('dog=', 'cat=')[int(i[0])] + str(i[1])
show_bboxes(fig.axes, [np.array(i[2:]) * bbox_scale], label)
.. figure:: output_anchor_f592d1_178_0.svg
.. raw:: html
.. raw:: html
.. code:: python
fig = d2l.plt.imshow(img)
for i in output[0].detach().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)
.. figure:: output_anchor_f592d1_181_0.svg
.. raw:: html
.. raw:: html
Na prática, podemos remover caixas delimitadoras de predição com níveis
de confiança mais baixos antes de executar NMS, reduzindo assim a
quantidade de computação para NMS. Também podemos filtrar a saída de
NMS, por exemplo, retendo apenas os resultados com níveis de confiança
mais altos como saída final.
Resumo
------
- Geramos várias caixas de âncora com diferentes tamanhos e proporções
de aspecto, centralizadas em cada pixel.
- IoU, também chamado de índice de Jaccard, mede a similaridade de duas
caixas delimitadoras. É a proporção entre a área de intersecção e a
área de união de duas caixas delimitadoras.
- No conjunto de treinamento, marcamos dois tipos de rótulos para cada
caixa de âncora: um é a categoria do alvo contido na caixa de âncora
e o outro é o deslocamento da caixa delimitadora de verdade em
relação à caixa de âncora.
- Ao prever, podemos usar supressão não máxima (NMS) para remover
caixas delimitadoras de previsão semelhantes, simplificando assim os
resultados.
Exercícios
----------
1. Altere os valores de ``sizes`` e\ ``ratios`` na função
``multibox_prior`` e observe as alterações nas caixas de âncora
geradas.
2. Construa duas caixas delimitadoras com uma IoU de 0,5 e observe sua
coincidência.
3. Verifique a saída de offset ``labels[0]`` marcando os offsets da
caixa de âncora conforme definido nesta seção (a constante é o valor
padrão).
4. Modifique a variável ``anchors`` nas seções" Rotulando Caixas de
Âncora de Conjunto de Treinamento “e” Caixas Limitadoras de Saída
para Previsão ". Como os resultados mudam?
.. raw:: html
.. raw:: html
`Discussões `__
.. raw:: html
.. raw:: html
`Discussões `__
.. raw:: html
.. raw:: html
.. raw:: html