深度学习|目标检测与YOLO算法

一、目标检测

1.1 目标检测概念

目标检测(object detection)是在给定的图片中精确找到物体所在位置,并标注出物体的类别。物体的尺寸变化范围很大,摆放物体的角度、姿态不确定,而且可以出现在图片任何地方,同时物体也可是多个类别的。

目标检测在多个领域中被广泛使用。例如,在无人驾驶领域,系统需要通过识别拍摄到的视频图像中车辆、行人、道路和障碍的位置来规划行进路线;在安保领域,系统需要检测异常目标,如歹徒或者危险品。

在这里插入图片描述

 目标检测在目标定位基础上进一步开发,其与图片分类、目标定位的主要区别如下:

Image ClassificationClassification with LocalizationObject Detection
图片特点包含比较大的单个物体包含比较大的单个物体包含多类物体
处理方法将图片输入到多层卷积神经网络中在图片分类基础上,使神经网络多输出4个单元用来表示物体的边界框(如红框所示)在分类定位基础上,使神经网络多输出n个单元,代表物体是否属于该类别的物体
输出结果是/否属于该类别是/否属于该类别+该物体的位置信息每个物体是否属于该类别+该物体的位置+是否属于n个类别
具体结果1个代表属于某类别的值1个代表属于某类别的值+4个代表边框的值1个代表物体存在的值+n个属于某类别的值+4个代表边框的值

1.2 目标检测原理

1.2.1 基本原理

目标检测通常采用滑动窗口来进行,即使用一个小窗口,窗口截取到图片上的区域,将该区域输入到卷积神经网络,输出1或0代表该区域内是否有目标,并按照一定的步长滑动窗口,直到遍历整张图片。

在这里插入图片描述

这种方法相当于对每个窗口的图片做了一个图片分类,但带来的是低计算成本和高精度难以同时满足的现实问题。

1.2.2 滑动窗口卷积实现

滑动窗口的卷积实现即在卷积层上应用这个算法。首先把神经网络的全连接层转化成卷积层, 全连接层可看作是进行一次卷积操作,但在把全连接层转化成卷积层之后,每个窗口截取到的区域存在共享的部分,将神经网络每层所有窗口共享的部分重叠在一起,相当于将整张图片输入神经网络得到的结果。

所以,不必将图片每个区域分别输入神经网络进行检测,直接将整张图片输入进行卷积操作,神经网络就可以识别出对象的位置。

1.3 目标检测算法改善

1.3.1 交并比

交并比(IoU:Intersection over Union)函数是用于计算两个边界框交集和并集之比,即下图中S(绿色)/S(蓝色+绿色+黄色)的数值,常用来评价目标检测算法的精度。

一般来说,IoU大于等于0.5时说明结果可以接受,即检测结果可接受。如果预测器和实际边界框完美重叠,loU就是1,因为交集就等于并集。通常规定0.5是阈值(threshold),用来判断预测的边界框是否正确,loU越高,边界框越精确。 

1.3.2 非极大值抑制

当目标检测窗口格子划分较小时,会出现同时有几个格子里检测到有目标的情况。即图中的一辆汽车可能被检测到多次:

可以采用非极大值抑制(Non-max suppression)的方法解决此问题。首先比较所有格子输出结果中检测到物体的概率,由大到小排序,保留概率最大的预测框,然后依次与比它小的预测框求交并比,若交并比很大,则说明这两个预测框很有可能检测的是同一个物体,于是把存在物体概率较低的那个预测框抑制掉。

下图显示的是抑制后的结果: 

1.3.3 锚框

目标检测中存在一个问题就是每个格子只能预测一个对象,如果想让一个格子检测出多个对象,就要使用到锚框(Anchor Box)。 

 如上图所示,我们使用3×3的网格,可以观察到人和汽车的几何中心几乎在同一个网格,然而传统方法中一个格子只能预测一个对象,而且对于y输出的向量可以检测这三个类别:人、汽车和摩托车,他将无法输出检测结果,所以我必须从两个检测结果中选择一个,这便影响了模型性能,导致一些对象被丢弃无法检测出来。

而引入锚框后,预先定义两个不同形状的锚框,把预测结果和这两个锚框关联起来:

因此,定义类别标签时用的向量不再是:

y=[pc,bx,by,bh,bw,c1,c2,c3]^T

而是重复两次:

y=[pc,bx,by,bh,bw,c1,c2,c3,pc,bx,by,bh,bw,c1,c2,c3]^T

前面8个是和Anchor box1相关联,后面8个是和Anchor box2相关联。行人一般符合anchor box1形状,所以用Anchor box1来预测行人回达到很好的效果,这么编码𝑝𝑐 = 1,代表有个行人,用𝑏𝑥, 𝑏𝑦, 𝑏ℎ和𝑏𝑤来编码包住行人的边界框,然后用𝑐1, 𝑐2, 𝑐3(𝑐1 = 1, 𝑐2 = 0, 𝑐3 = 0)来说明这个对象是行人。 汽车一般符合Anchor box2形状,所以用Anchor box2来预测汽车会有很好的效果。

1.4 目标检测程序实现

为说明目标检测基本原理,我们通过一个案例加以阐述。假设有如下一张图(左边是一只狗,右边是一只猫),他们共同构成了图像里的两个主要目标,我们需要对这两个目标进行识别并进行标注

首先导入必要的包和模块:

%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);
# 加分号只显示图

1.4.1 边界框的实现

在目标检测中,我们使用边界框(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'))

 从输出结果可看出,猫和狗两个主要目标的轮廓都在边界框内。

1.4.2 锚框的实现

目标检测算法通常会在输入图像中采样大量区域,然后判断这些区域是否包含我们所要检测的目标,并调整区域边缘从而更准确地预测目标的真实边界框(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'])

输出如下: 

二、YOLO算法

2.1 YOLO算法简述

YOLO(You Only Look Once)是一种基于深度学习的目标检测算法,由Joseph Redmon等人在2015年提出,其最新版本为YOLOv5。

与传统目标检测算法相比,YOLO算法具有以下优点:

  1. 速度快:YOLO算法采用全卷积神经网络实现,可以实现实时目标检测;
  2. 准确率高:YOLO算法采用单个网络结构,能够同时检测多个目标并获得更准确的边界框;
  3. 应用广泛:YOLO算法可处理各种尺寸的物体,不需要对图像进行缩放和裁剪,能够处理任意大小的物体。

YOLO算法的基本思路是将输入图像分割成多个网格,然后对每个网格预测物体的类别和位置。具体来说,YOLO算法采用单个卷积神经网络,在图像的全局信息和局部信息之间进行平衡,并直接预测出每个物体的边界框和类别。

YOLO算法的主要步骤如下:

  1. 将输入图像分成S×S个网格;
  2. 对于每个网格,预测该网格中是否包含物体,以及该物体的边界框和类别;
  3. 计算每个预测边界框与真实边界框之间的损失,更新网络参数;
  4. 在测试阶段,将所有预测边界框进行筛选,得到最终的检测结果。

虽然YOLO算法具有很高的速度和准确率,但其也存在一些缺点,如对小物体的检测效果较差,对于密集目标的检测效果也不尽如人意。此外,YOLO算法的训练过程也较为复杂,需要大量的训练数据和计算资源。

2.2 YOLO v1算法

学习该算法前,首先要分清预测阶段训练阶段。预测阶段是用已经训练好的、现成的网络,去对图片做预测,即做目标检测;训练阶段是利用梯度下降和反向传播来迭代地微调神经元中的权重,并使损失函数最小化,来训练得到一个预测效果更好的模型。

2.2.1 预测阶段

预测阶段使用训练好的卷积神经网络结构如下:

输入卷积层(24层)全连接层(2层)输出
448×448×3的图像与7×7的过滤器、3×3的过滤器做卷积,最终得到7×7×1024的特征图拉平填充输入4096个神经元中,再输入到1470个神经元中将全连接层的输出重塑为7×7×30的张量

最后输出的7×7×30的张量中,7×7的原因是yolo将输入的图片划分为7×7的网格,每一个网格需要输出的是一个1×30维的信息,所以最后的输出为7×7×30,输出可视化如下图:

YOLO v1是一个单阶段的、端到端的算法,7×7×30的输出包括所有目标的置信度、预测框坐标、类别,上图中以框的粗细代表置信度的高低。 对于每个网格,它都会产生两个预测框,这两个预测框的中心点都落在该网格中,所以YOLO v1最后一共会产生98个预测框。

对于这98个预测框,并不是所有预测框都会被保留,需要进行一系列后处理,将最终结果表现为上图的结果那样。后处理步骤主要包括:置信度过滤(去掉低置信度的预测框)和非极大值抑制(去掉对同一个物体重复检测的预测框)。

首先是置信度过滤,它指的是置信度×每个类别的概率。对于这98个1×20维向量来说,每个向量中同一个位置的值都代表该预测框预测到某种类别的概率,我们可以设定一个阈值,小于这个阈值的全部设为0。

其次是非极大值抑制,当得到的每个预测框对某些种类预测的置信度都比较高时,对于某一种类,我们将该种类所在位置的概率从高到低排,再对排序之后的结果进行非极大值抑制。 

具体步骤为:除去最高概率,剩下的每一个值代表的预测框都与最高值的预测框计算IoU,如果IoU大于我们设定的阈值,我们就认为这两个框检测的是同一个目标,那么就将较低的概率值置0,否则保留。

第一轮比较完成后,除去最高的概率和第二高的概率,剩下概率代表的预测框与第二高的概率的预测框计算IoU,重复上述步骤,直至到达最后一个概率。 

对于每个类别都重复上面步骤,完成所有输出的后处理并可视化在图上,即为目标检测的结果。

2.2.2 训练阶段

在训练阶段,每个网格会产生出两个预测框,我们将与ground truth中IoU较大的预测框作为负责拟合ground truth的框,与ground truth的IoU较小的预测框的置信度越低越好。

然后设计一个损失函数,来使负责拟合ground truth的框作为损失函数的最大组成,另一个框的让它的影响尽量的小。损失函数主要由三大部分、五小部分组成:坐标回归误差(主预测框中心点坐标误差+主预测框宽高误差)+置信度回归误差(主预测框置信度误差+另一预测框置信度误差)+类别预测误差(主预测框分类误差)。

2.2.3 优缺点分析

YOLO v1的优缺点主要如下:

优点缺点
单阶段模型,速度很快输入图像分辨率低,准确率低,定位性能差
能获取全图信息,更好辨别前景和背景每个网络只能预测一个物体
捕获全图信息,学习能力强所有网格只能预测7×7=49个物体,小目标和密集目标识别性能差
  • 9
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值