week5 8月7日

本文介绍了物体检测算法的发展,从早期的R-CNN到FastR-CNN的改进,特别是通过启发式搜索和动态规划思想优化锚框生成与特征提取。同时,SSD和YOLO的单发多框检测方法也被详细讲解,展示了如何减少计算量并提高检测精度。
摘要由CSDN通过智能技术生成

物体检测算法

RCNN

之前的show锚框、nms筛选最合适的锚框的学习,都是基于假设已知样本。对于未知样本,可以通过模型训练的方式,选择出合适的锚框。

rcnn从输入的图像选取若干锚框,并标注类别和边界框,任何用卷积神经网络进行前向传播,以提取其特征。 

每次选择的锚框大小是不一样的。

 怎么让锚框大小一样,好批量化处理?

问题对每张图片的锚框都抽取特征,相当于,一张图片分成了上千张小图片,每张小图片都单独做特征提取,由于很多区域都是有重叠的,会导致计算量太大且重复计算

改进之后:不对每个锚框,从零开始做特征抽取。而是一开始就得到一张全图像的特征图。

 卷积的输入是一整张图片,而不是各个锚框。此外,这个网络还会参与训练

cnn之后,得到一张整个图像的特征图。

比如,在原图上通过启发式搜索,选择了2个锚框,将这两个锚框的位置映射到特征图上,提取出属于这两个锚框的特征。经由rol池化,得到了两个形状相同的特征。然后通过全连接层。

启发式搜索算法就是在状态空间中先对每一条搜索分支进行评估,得到最好的分支,再从这个分支继续搜索从而到达目标,这样可以有效省略大量无谓的搜索路径,大大提高了搜索效率。在启发式搜索中,对分支的评估是非常重要的,启发式搜索算法定义了一个估价函数 f(x),与问题相关的启发式信息都被计算为一定的 f(x) 值,引入到搜索过程中。f(x) = g(x) + h(x),其中 f(x) 是节点 x的估价函数,g(x)是在状态空间中从初始节点到节点 x的实际代价,h(x)是从节点 x到目标节点的最佳路径的估计代价。g(x)是已知的,所以在这里主要是 h(x) 体现了搜索的启发信息,h(x)专业的叫法是启发函数。换句话说,g(x)代表了搜索的广度的优先趋势。但是当 h(x) >> g(x)时,可以忽略g(x),从而提高效率。

就是动态规划的思想

 faster rcnn 使用一个区域提议网络,减少提议区域(比如,锚框的生成数量,并确保目标检测的精确度

步骤1、使用1*1且填充为1的卷积层,转换卷积神经网络的输出,保持输出通道数为c。这样,cnn为图像提取的特征图的每个单元均得到一个长度为c的新特征

步骤2、以特征图的每个像素为中心,生成多个不同比例、宽高比的锚框,并标注

步骤3、使用锚框中心单元长度为c的特征,分别预测该锚框的二元类别(背景还是含目标)和边界框

步骤4、使用非极大值抑制,只得到最相似的边界框。

 

 SSD 单发多框检测 

import torch
import d2l
from torch import nn
import d2l.torch
import torchvision
#类别预测层
#使用一个保持宽度和高度不变的卷积层,让输入输出在特征图宽和高上的空间坐标一一对应
def class_predictor(num_inputs,num_anchors_per_pixel,num_class):
    return nn.Conv2d(
        in_channels=num_inputs,out_channels=num_anchors_per_pixel*(num_class+1),
        kernel_size=3,padding=1)
#对于一个坐标(x,y),其通道包括以(x,y)为中心的所有锚框的类别预测
#所以,输出通道数就是每个锚框的所有类别可能数(1是背景)


#边界预测框
def bbox_offset_predictor(num_inputs,num_anchors_per_pixel):
    return nn.Conv2d(
        in_channels=num_inputs,
        out_channels=num_anchors_per_pixel*4,
        kernel_size=3,padding=1)
#为每个锚框预测4个偏移量,左上两个,右下两个
#输出通道数是,以每个像素为中心的锚框数*4


#连接多尺度的预测
def forward(x,block):
    return block(x)


#例子
Y1 = forward(torch.zeros(size=(2,8,20,20)),class_predictor(8,5,10))
#生成2个不一样的特征图,通道数8,大小20*20
#类别预测8个输入通道数,5个输出通道数(锚框数),类别10种
Y2 = forward(torch.zeros(size=(2,16,10,10)),class_predictor(16,3,10))
Y1.shape,Y2.shape#输出(批量大小,通道数,高,宽)


#由于y1和y2大小不一样,可以知道,两个输出预测没法连接,提高效率
#将张量格式转化成2d。除了一样的批量大小,剩下的几个维度拉到同一维度
def flatten_pred(pred):
    return torch.flatten(pred.permute(0,2,3,1),start_dim=1)
#把通道数放到最后,从1号维度开始,拉成一个向量
def concat_preds(preds):
    return torch.cat([flatten_pred(pred) for pred in preds],dim=1)
#把向量维度转化格式之后,连接


#构建的高和宽减半块会更改输入通道的数量,并将输入特征图的高度和宽度减半
def down_sample_block(in_channels,out_channels):
    #VGG模块设计
    blk = []
    for _ in range(2):
        #卷积、批量归一化、激活
        blk.append(nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=3,padding=1))
        blk.append(nn.BatchNorm2d(num_features=out_channels))
        blk.append(nn.ReLU())
        in_channels = out_channels
    blk.append(nn.MaxPool2d(kernel_size=2,stride=2))
    return nn.Sequential(*blk)
Y3 = forward(torch.zeros(size=(2,10,20,20)),down_sample_block(10,50))
Y3.shape



#基础网络块
#用于从图像中抽取特征,就是3个高宽减半块串联在一起 ,并逐步通道数翻倍
def base_net():
    blk = []
    num_filters = [3,16,32,64]
    for i in range(len(num_filters)-1):
        blk.append(down_sample_block(in_channels=num_filters[i],out_channels=num_filters[i+1]))
    return nn.Sequential(*blk)
Y4 = forward(torch.zeros(size=(2,3,256,256)),base_net())
Y4.shape


#完整的单发多框检测模型由5个模块组成
 
def get_block(i):
    if i == 0:
        block = base_net()#第1块基础网络块
        #用于从图像中抽取特征,就是3个高宽减半块串联在一起 ,并逐步通道数翻倍
    elif i ==1 :
        block = down_sample_block(in_channels=64,out_channels=128)
        #通道数从64变128,但是大小减半
    elif i == 4:
        block = nn.AdaptiveAvgPool2d(output_size=(1,1))
        #第5个块,最终压缩成1*1大小
    else:
        block = down_sample_block(in_channels=128,out_channels=128)
        #通道数不变,大小减半
    return block



#为每个模块定义前向传播
def block_forward(X,block,size,ratio,block_class_predictor,block_bbox_offset_predictor):
    Y = block(X)
    #对图像x生成特征图y
    anchors = d2l.torch.multibox_prior(data=Y,sizes=size,ratios=ratio)
    #对特征图y,生成若干锚框
    class_predictor = block_class_predictor(Y)
    #每个锚框的类别
    bbox_offset_predictor = block_bbox_offset_predictor(Y)
    #每个锚框的偏移
    return (Y,anchors,class_predictor,bbox_offset_predictor)



class TinySSD(nn.Module):#完整的ssd网络模型
    def __init__(self,num_classes):
        super(TinySSD,self).__init__()
        self.num_classes = num_classes
        idx_to_in_channels = [64,128,128,128,128]
        for i in range(5):
            # 即赋值语句self.blk_i=get_blk(i)
            setattr(self,f'block_{i}',get_block(i))
            
            setattr(self,f'block_class_predictor_{i}',
                    class_predictor(
                                     num_inputs=idx_to_in_channels[i],
                                     num_anchors_per_pixel=num_anchors_per_pixel,
                                     num_class=num_classes))
            
            setattr(self,f'block_bbox_offset_predictor_{i}',
                    bbox_offset_predictor(
                                          num_inputs=idx_to_in_channels[i],
                                          num_anchors_per_pixel=num_anchors_per_pixel))
    def forward(self,X):
        anchors,class_predictors,bbox_offset_predictors=[None]*5,[None]*5,[None]*5
        for i in range(5):
            # getattr(self,'blk_%d'%i)即访问self.blk_i
            X,anchors[i],class_predictors[i],bbox_offset_predictors[i] = 
            block_forward(
                          X,getattr(self,f'block_{i}'),
                          sizes[i],ratios[i],
                          getattr(self,f'block_class_predictor_{i}'),
                          getattr(self,f'block_bbox_offset_predictor_{i}'))
        anchors = torch.cat(anchors,dim=1)
        class_predictors = concat_preds(class_predictors)
        class_predictors = class_predictors.reshape(
                            class_predictors.shape[0],-1,self.num_classes+1)
        bbox_offset_predictors = concat_preds(bbox_offset_predictors)
        return anchors,class_predictors,bbox_offset_predictors




batch_size = 32
train_iter,_ = d2l.torch.load_data_bananas(batch_size)
#香蕉检测数据集中,目标的类别数为1,定义好模型后,需要初始化其参数并定义优化算法。
#定义网络和优化算法
device,net = d2l.torch.try_gpu(0),TinySSD(num_classes=1)
optim = torch.optim.SGD(params=net.parameters(),lr=0.2,weight_decay=5e-4)


#定义损失函数
class_loss = nn.CrossEntropyLoss(reduction='none')
bbox_offset_loss = nn.L1Loss(reduction='none')
#不用l2损失,是因为有可能预测和真实相差很大,l2会让差距更大
def calculate_loss(class_preds,class_labels,bbox_offset_preds,bbox_offset_labels,bbox_mask):
    batch_sizes,num_classes = class_preds.shape[0],class_preds.shape[2]
    cla_ls = class_loss(class_preds.reshape(-1,num_classes),
                        class_labels.reshape(-1)).reshape(batch_sizes,-1).mean(dim=1)
    bbox_offset_ls = bbox_offset_loss(bbox_offset_preds*bbox_mask,
                                      bbox_offset_labels*bbox_mask).mean(dim=1)
    return cla_ls+bbox_offset_ls


#定义分类评价函数、偏移评价函数
def class_eval(class_preds,class_labels):
    # 由于类别预测结果放在最后一维,argmax需要指定最后一维。
    return float((torch.argmax(class_preds,dim=-1).type(class_labels.dtype) == class_labels).sum())
def offset_eval(bbox_offset_preds,bbox_offset_labels,bbox_mask):
    return float((torch.abs((bbox_offset_labels-bbox_offset_preds)*bbox_mask)).sum())




#模型训练
#在模型的前向传播过程中,生成多尺度锚框,并预测其类别和偏移量
#然后根据标签信息y为生成的锚框标注类别和偏移量
#根据预测和真实计算损失 
num_epochs,timer = 20,d2l.torch.Timer()
animator = d2l.torch.Animator(xlabel='epoch',xlim=[1,num_epochs],legend=['class error','bbox mae'])
net = net.to(device)
for epoch in range(num_epochs):
    # 批量类别损失和,批量样本数
    # 批量偏移损失和,批量样本数
    accumulator = d2l.torch.Accumulator(4)
    net.train()
    for X,Y in train_iter:
        timer.start()
        X,Y = X.to(device),Y.to(device)
        optim.zero_grad()
        # 生成多尺度的锚框,为每个锚框预测类别和偏移量
        anchors,class_preds,bbox_offset_preds = net(X)
        # 为每个锚框标注类别和偏移量
        bboxes_offset_labels, bboxes_masks, bboxes_class_labels = d2l.torch.multibox_target(anchors,Y)
        # 根据类别和偏移量的预测和标注值计算损失函数
        loss = calculate_loss(class_preds,bboxes_class_labels,bbox_offset_preds,bboxes_offset_labels,bboxes_masks)
        loss.mean().backward()
        optim.step()
        accumulator.add(class_eval(class_preds,bboxes_class_labels),bboxes_class_labels.numel(),offset_eval(bbox_offset_preds,bboxes_offset_labels,bboxes_masks),bboxes_offset_labels.numel())
    class_error,offset_mae = 1-accumulator[0]/accumulator[1],accumulator[2]/accumulator[3]
    animator.add(epoch+1,[class_error,offset_mae])
print('class_error:',class_error,'offset_mae:',offset_mae)
print(f'{len(train_iter.dataset)/timer.stop()}examples/s,on ',str(device))


训练结果

 

YOLO

  如果我们让每个框只能识别出一个物体,且要求这个物体必须在这个框之内,那YOLO就变成了很蠢的滑窗法了。

YOLO的聪明之处在于,它只要求这个物体的中心落在这个框框之中。

这意味着,我们不用设计非常非常的框,因为我们只需要让物体的中心在这个框中就可以了,而不是必须要让整个物体都在这个框中。

具体怎么实现呢?

我们要让这个 s^{2} 个框每个都预测出B个bounding box,这个bounding box有5个量,分别是物体的中心位置(x,y)和它的高(h)宽(w),以及这次预测的置信度

每个框框不仅只预测B个bounding box,它还要负责预测这个框框中的物体是什么类别的,这里的类别用one-hot编码表示。

注意,虽然一个框框有多个bounding boxes,但是只能识别出一个物体,因此每个框框需要预测物体的类别,而bounding box不需要

也就是说,如果我们有s^{2}个框框,每个框框的bounding boxes个数为B,分类器可以识别出C种不同的物体,那么所有整个ground truth的长度为:s*s*(5*B+c)

写给小白的YOLO介绍 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/94986199

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值