1.算法评估相关概念
2.目标检测与YOLO
2.1目标检测
发展历程:R-CNN >>SPP NET >>Fast R-CNN>>Faster R-CNN>> 最终实现YOLO
2.1.1目标检测实例说明
下面用一个实例来说明目标检测的相关步骤:可以看到图像左边是一只狗,右边是一只猫。它们是这张图像里的两个主要目标。
首先导入必要的包和模块:
%matplotlib inline
from PIL import Image
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
d2l.set_figsize()
img = Image.open('img/catdog.jpg') d2l.plt.imshow(img);
# 加分号只显示图
2.1.2边界框的实现
在目标检测中,我们使用边界框(bounding box)来描述目标位置。边界框通常是一个矩形框,可以由矩形左上角的(x, y)坐标与右下角的(x, y)坐标确定。我们可以根据图中的坐标信息来定义图中猫和狗的边界框。图中坐标原点在图像左上角,原点往右和往下分别定义为x轴和y轴的正方向:
dog_bbox, cat_bbox = [60, 45, 378, 516], [400, 112, 655, 493]
我们可以在图中将边界框画出来检测其是否准确。我们定义一个辅助函数bbox_to_rect,通过其将边界框表示成matplotlib的边界框格式:
def bbox_to_rect(bbox, color):
# 将边界框(左上x, 左上y, 右下x, 右下y)格式转换成matplotlib格式:
# ((左上x, 左上y), 宽, 高)
return d2l.plt.Rectangle(
xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0],
height=bbox[3]-bbox[1], fill=False, edgecolor=color, linewidth=2
)
我们将边界框加载在图像上:
fig = d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'red'))
从输出结果可看出,猫和狗两个主要目标的轮廓都在边界框内。
2.1.3 锚框的实现
目标检测算法通常会在输入图像中采样大量区域,然后判断这些区域是否包含我们所要检测的目标,并调整区域边缘从而更准确地预测目标的真实边界框(ground-truth bounding box)。
不同模型使用的区域采样方法可能不同,若以每个像素为中心生成多个大小和宽高比(aspect ratio)不同的边界框,这些边界框就被称为锚框(anchor box)。
先导入相关包和库:
%matplotlib inline
from PIL import Image
import numpy as np
import math
import torch
import sys sys.path.append("..")
import d2lzh_pytorch as d2l
假设输入图像高为h,宽为w。我们分别以图像的每个像素为中心生成不同形状的锚框。设大小为s∈(0,1]且宽高比为r >0,那么锚框的宽和高将分别 为𝑤𝑠𝑟^0.5 和h𝑠𝑟^0.5。当中心位置给定时,已知宽和高的锚框是确定的。
下面我们分别设定好一组大小s1,...,sn和一组宽高比r1,...,rm。如果以每个像素为中心时使用所有的大小与宽高比的组合,输入图像将一共得到 w*h*n*m个锚框。虽然这些锚框可能覆盖了所有的真实边界框,但计算复杂度容易过高。因此,以相同像素为中心的锚框的数量为n+m−1。对于整个输入图像,我们将一共生成w*h(n+m−1)个锚框。
以上生成锚框的方法通过下述的MultiBoxPrior函数实现。当制定输入一组大小和一组宽高比时,该函数将返回输入的所有锚框:
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])
pairs = [] # pair of (size, sqrt(ration))
for r in ratios:
pairs.append([sizes[0], math.sqrt(r)])
for s in sizes[1:]:
pairs.append([s, math.sqrt(ratios[0])])
pairs = np.array(pairs)
ss1 = pairs[:, 0] * pairs[:, 1] # size * sqrt(ration)
ss2 = pairs[:, 0] / pairs[:, 1] # size / sqrt(ration)
base_anchors = np.stack([-ss1, -ss2, ss1, ss2], axis=1) / 2
h, w = feature_map.shape[-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_x和shifts_y是将宽高进行归一化处理然后用meshgrid函数生成一个向量矩阵, 最后reshape成一行向量。
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))
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])
将reshape之后的向量进行stack操作,之后将得到的shift与原始的base_anchors相加从而自动生成所有的anchor。
下面的例子里 我们访问以(250,250)为中心的第一个锚框。它有4个元素,分别是锚框 左上角的x和y轴坐标和右下角的x和y轴坐标,其中x和y轴的坐标值分别已除 以图像的宽和高,因此值域均为0和1之间。
boxes = Y.reshape((h, w, 5, 4))
boxes[250, 250, 0, :]# * torch.tensor([w, h, w, h],
dtype=torch.float32)
输出如下:
tensor([-0.0316, 0.0706, 0.7184, 0.8206])
为了描绘图像中以某个像素为中心的所有锚框,我们先定义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'])
为了描绘图像中以某个像素为中心的所有锚框,我们先定义show_bboxes函 数以便在图像上画出多个边界框:
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)
)
变量boxes中xx和yy轴的坐标值分别已除以图像的宽和高。在 绘图时,我们需要恢复锚框的原始坐标值,并因此定义了变量bbox_scale,我们可以画出图像中以(250, 250)为中心的所有锚框了。可以看到,大小为 0.75且宽高比为1的锚框较好地覆盖了图像中的狗:
d2l.set_figsize()
fig = d2l.plt.imshow(img)
bbox_scale = torch.tensor([[x, y, 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.2YOLO
网络结构
网络结构包含24个卷积层和2个全连接层;其中前20个卷积层用来做预训 练,后面4个是随机初始化的卷积层,和2个全连接层。
2.2.1YOLO包围框
我们有 𝑠 2个框,每个框的bb个数为𝐵,分类器可以识别出𝐶种不同的物体, 那么所有整个ground truth的长度为𝑆 × 𝑆 × (𝐵 × 5 + 𝐶) YOLO v1中,这个数量是30 YOLO v2和以后版本使用了自聚类的anchor box为bb, v2版本为𝐵 = 5, v3中 𝐵 =9。
2.2.2归一化
四个关于位置的值,分别是𝑥, 𝑦, ℎ和𝑤,均为整数,实际预测中收敛慢 因此,需要对数据进行归一化,在0-1之间。例子是一个448*448的图 片,有3*3的grid,每个 cell是149。 目标中心点是(220,190)。
2.2.3置信度
𝐼𝑂𝑈:图中绿框为真实标注,其余五个颜色框为预测值,可计算对应𝐼𝑂U。
训练值(ground truth):Pr 𝑜𝑏𝑗 的ground truth:三个目标中点对应格子为1,其它为0。
训练数据与网络输出
2.2.4YOLO损失函数
YOLO损失函数——边界框对应损失项
第2项要开根号,让误差更显著,保证回归精度。
YOLO损失函数——𝝀取值