0


3.7.物体检测算法

物体检测算法

1.R-CNN

在这里插入图片描述

​ 首先使用启发式搜索算法来选择锚框,使用预训练模型对每个锚框抽取特征,训练一个SVM来对类别分类,最后训练一个线性回归模型来预测边缘框偏移。

​ R-CNN比较早,所以使用的是SVM

1.1 兴趣区域(RoI)池化层

​ 给定一个锚框,均匀分割成

     n 
    
   
     × 
    
   
     m 
    
   
  
    n\times m 
   
  
n×m块,输出每块里的最大值,不管锚框多大,总是输出 
 
  
   
   
     n 
    
   
     m 
    
   
  
    nm 
   
  
nm个值。

在这里插入图片描述

     3 
    
   
     × 
    
   
     3 
    
   
  
    3\times 3 
   
  
3×3不好被 
 
  
   
   
     2 
    
   
     × 
    
   
     2 
    
   
  
    2\times 2 
   
  
2×2均分,所以会取一下整。

1.2 Fast RCNN

​ R-CNN每张图片都要抽取一次特征,如果每张图片锚框很多,就可能要抽取很多次特征,很麻烦,Fast RCNN在次基础上做快:

​ 使用CNN对整张图片抽取特征,再使用RoI池化层对每个锚框生成固定长度的特征

在这里插入图片描述

​ 就是先抽取特征后,将原图的锚框按比例的在特征图中找出锚框,然后再做。CNN这一层不对每个锚框抽取特征,而是对整个图片抽取特征,那么对于锚框重复的地方,就只用抽取一次了,变快了很多。

1.3 Faster R-CNN

​ 使用一个区域提议网络来替代启发式搜索来获得更好的锚框 ?

在这里插入图片描述

​ 大概就是训练一个神经网络,判断这些锚框是不是框住了(一个二分类问题),如果框住了,与真实边界框的偏移是多少,训练好后,会输出比较好的锚框。先做一个粗糙的预测,再做一个精准的预测。

1.4 Mask R-CNN

在这里插入图片描述

​ 其余部分和Faster R-CNN相同,新增了一个对像素的神经网络,假设有每个像素的编号,可以对像素进行预测。并且将pooling改为了align,对像素分类更准确,得到的是一个加权,而不是简单的切割。

​ R-CNN是最早、也是最有名的一类基于锚框和CNN的目标检测算法。Faster R-CNN和Mask R-CNN是在追求最高精度场景下的常用算法,并且Mask R-CNN需要每个像素的标号,会有一些限制

2.单发多框检测 (SSD)

​ 对于每个像素,生成多个以它为中心的锚框(上一节的生成锚框方法),

在这里插入图片描述

​ 首先使用一个基础网络块来抽取特征,然后使用多个卷积层块来减半高宽,在每个阶段都生成锚框,底部段来拟合小物体,顶部段来拟合大物体,对每个锚框都预测类别和真实边缘框

​ 接近顶部的多尺度特征图较小,但具有较大的感受野,它们适合检测较少但较大的物体。 简而言之,通过多尺度特征块,单发多框检测生成不同大小的锚框,并通过预测边界框的类别和偏移量来检测大小不同的目标,因此这是一个多尺度目标检测模型。

​ SSD通过单神经网络来检测模型,以每个像素为中心产生多个锚框,在多个段的输出上进行多尺度的检测。

2.1 多尺度目标检测

​ 动机是减少图像上的锚框数量,可以在输入图像中均匀采样一小部分像素,并以它们为中心生成锚框。在不同尺度下,我们可以生成不同数量和不同大小的锚框。

​ 因为一般来说,较小的物体在图像中出现的可能性更多样,例如

     1 
    
   
     × 
    
   
     1 
    
   
     , 
    
   
     1 
    
   
     × 
    
   
     2 
    
   
     , 
    
   
     2 
    
   
     × 
    
   
     2 
    
   
  
    1\times1,1\times2,2\times2 
   
  
1×1,1×2,2×2的目标,分别以4、2和1种可能的方式出现在 
 
  
   
   
     2 
    
   
     × 
    
   
     2 
    
   
  
    2\times 2 
   
  
2×2图像上。那么当检测较小的物体时,可以采样更多的区域,对于较大的物体,可以采样较少的区域。

import torch
from d2l import torch as d2l

img = d2l.plt.imread('../img/catdog.jpg')
h, w = img.shape[:2]print(h, w)defdisplay_anchors(fmap_w, fmap_h, s):
    d2l.set_figsize()# 前两个维度上的值不影响输出
    fmap = torch.zeros((1,10, fmap_h, fmap_w))# multibox_prior的data是四维的
    anchors = d2l.multibox_prior(fmap, sizes=s, ratios=[1,2,0.5])# 生成多个锚框,形状为(1,num_anchors,4)
    bbox_scale = torch.tensor((w, h, w, h))
    d2l.show_bboxes(d2l.plt.imshow(img).axes,
                    anchors[0]* bbox_scale)#小锚框检测小目标
display_anchors(fmap_w=4, fmap_h=4, s=[0.15])# 分成4 *4 的区域
d2l.plt.show()#大锚框检测大目标
display_anchors(fmap_w=2, fmap_h=2, s=[0.4])# 分成4 *4 的区域
d2l.plt.show()

在这里插入图片描述

在这里插入图片描述

2.2 SSD

​ 具体请看注释:

import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

'''类别预测层,索引为i(q+1)+j的通道代表了索引为i的锚框有关类别索引为j的预测'''defcls_predictor(num_inputs, num_anchors, num_classes):# num_inputs 是输入的像素点个数,对每个像素都要预测return nn.Conv2d(num_inputs, num_anchors *(num_classes +1),
                     kernel_size=3, padding=1)# 加1是因为还要预测背景类,对于每个锚框都要进行分类,所以输出通道有这么多'''边界框预测层(bound box),为每个锚框预测4个偏移量(x,y,w,h)上的偏移'''defbbox_predictor(num_inputs, num_anchors):return nn.Conv2d(num_inputs, num_anchors *4, kernel_size=3, padding=1)'''连接多尺度的预测'''defforward(x, block):return block(x)# 举个例子# 分别生成5*(10+1) =55 个和 3*(10+1)=33个锚框,输出形状是(批量大小,通道数,高度,宽度)
Y1 = forward(torch.zeros((2,8,20,20)), cls_predictor(8,5,10))
Y2 = forward(torch.zeros((2,16,10,10)), cls_predictor(16,3,10))print(Y1.shape, Y2.shape)# 把4 D转换成2D的# permute将维度调整,将通道数挪到最后,然后从dim=1开始拉平,即后三维拉平# 把通道数放到最后是为了让预测值连续,好用一些,可以想象一下# 将通道数放在第三维,那么纵深就是通道,每个平面是(高,宽),拉平是每个平面每个平面的拉平,这样才是连续的。defflatten_pred(pred):return torch.flatten(pred.permute(0,2,3,1), start_dim=1)# 拉平后连接:20 * 20 *55 +10 * 10 *33= 25300defconcat_preds(preds):return torch.cat([flatten_pred(p)for p in preds], dim=1)print(concat_preds([Y1, Y2]).shape)'''高宽减半块'''defdown_sample_blk(in_channels, out_channels):
    blk =[]for _ inrange(2):
        blk.append(nn.Conv2d(in_channels, out_channels,
                             kernel_size=3, padding=1))# 高宽不变
        blk.append(nn.BatchNorm2d(out_channels))
        blk.append(nn.ReLU())
        in_channels = out_channels
    blk.append(nn.MaxPool2d(2))# 高宽减半return nn.Sequential(*blk)# 示例20*20 减半为 10*10print('高宽减半块:', forward(torch.zeros((2,3,20,20)), down_sample_blk(3,10)).shape)'''基本网络块'''# 该网络块输入图像的形状为256*256,输出的特征图为32*32defbase_net():
    blk =[]
    num_filters =[3,16,32,64]# 输入是3个维度,然后增加到16,32,64for i inrange(len(num_filters)-1):
        blk.append(down_sample_blk(num_filters[i], num_filters[i +1]))# 每个块高宽减半,有三个,减8倍return nn.Sequential(*blk)print('基本网络块:', forward(torch.zeros((2,3,256,256)), base_net()).shape)'''完整的模型'''# 5个模块,每个模块既用于生成锚框,又用于预测这些锚框的类别和偏移量# 第一个是基本网络块,第2到4个都是高宽减半块,最后一个使用全局最大池化将高度和宽度都降到1# 可以自己找其他神经网络,比如resnet,将down_sample_blk换成resnet?defget_blk(i):if i ==0:
        blk = base_net()elif i ==1:
        blk = down_sample_blk(64,128)# 第一个减半块扩大一下输出通道elif i ==4:
        blk = nn.AdaptiveMaxPool2d((1,1))else:
        blk = down_sample_blk(128,128)# 后续不用扩大输出通道return blk

'''块前向传播'''# 与图像分类任务不同,此处的输出包括:CNN特征图Y,当前尺度下根据Y生产的锚框,预测的这些锚框的类别和偏移量(基于Y)defblk_forward(X, blk, size, ratio, cls_predictor, bbox_predictor):
    Y = blk(X)
    anchors = d2l.multibox_prior(Y, sizes=size, ratios=ratio)# 生成锚框
    cls_preds = cls_predictor(Y)# 类别预测,不需要把锚框传进去,只需要知道有多少个锚框,多少个类就行
    bbox_preds = bbox_predictor(Y)# 边界框预测return(Y, anchors, cls_preds, bbox_preds)# 超参数,有5个层,size逐渐增加,实际面积= s^2 *原图面积 ,第二个值是几何平均# 0.272 = \sqrt{0.2 *0.37}
sizes =[[0.2,0.272],[0.37,0.447],[0.54,0.619],[0.71,0.79],[0.88,0.961]]
ratios =[[1,2,0.5]]*5# 常用的组合
num_anchors =len(sizes[0])+len(ratios[0])-1'''完整的模型'''classTinySSD(nn.Module):def__init__(self, num_classes,**kwargs):super(TinySSD, self).__init__(**kwargs)
        self.num_classes = num_classes
        idx_to_in_channels =[64,128,128,128,128]# 每个块的输出通道数for i inrange(5):# 即赋值语句self.blk_i=get_blk(i)# 设定属性值,有属性.blk_0,.cls_0,.bbox_0等一系列属性了setattr(self,f'blk_{i}', get_blk(i))setattr(self,f'cls_{i}', cls_predictor(idx_to_in_channels[i],
                                                    num_anchors, num_classes))setattr(self,f'bbox_{i}', bbox_predictor(idx_to_in_channels[i],
                                                      num_anchors))defforward(self, X):
        anchors, cls_preds, bbox_preds =[None]*5,[None]*5,[None]*5for i inrange(5):# getattr(self,'blk_%d'%i)即访问self.blk_i,获取这一属性的值
            X, anchors[i], cls_preds[i], bbox_preds[i]= blk_forward(
                X,getattr(self,f'blk_{i}'), sizes[i], ratios[i],getattr(self,f'cls_{i}'),getattr(self,f'bbox_{i}'))
        anchors = torch.cat(anchors, dim=1)# 输出的后三个都是三维的,第一个都是类似批量大小的# 将预测结果全部连接起来
        cls_preds = concat_preds(cls_preds)# reshape成(输出通道数,anchors,类别),-1就表示由其他参数决定,因为我们想预测类别,# 重构成这样方便读
        cls_preds = cls_preds.reshape(
            cls_preds.shape[0],-1, self.num_classes +1)
        bbox_preds = concat_preds(bbox_preds)return anchors, cls_preds, bbox_preds

net = TinySSD(num_classes=1)
X = torch.zeros((32,3,256,256))
anchors, cls_preds, bbox_preds = net(X)print('output anchors:', anchors.shape)print('output class preds:', cls_preds.shape)print('output bbox preds:', bbox_preds.shape)

2.3 训练模型

​ 具体看注释:

'''训练模型'''# 读取数据和初始化
batch_size =32
train_iter, _ = d2l.load_data_bananas(batch_size)

device, net = torch_directml.device(), TinySSD(num_classes=1)
trainer = torch.optim.SGD(net.parameters(), lr=0.2, weight_decay=5e-4)'''损失和评价函数'''

cls_loss = nn.CrossEntropyLoss(reduction='none')
bbox_loss = nn.L1Loss(reduction='none')# 当预测特别差时,L1也不会特别大,如果用L2可能会特别大defcalc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels, bbox_masks):
    batch_size, num_classes = cls_preds.shape[0], cls_preds.shape[2]
    cls = cls_loss(cls_preds.reshape(-1, num_classes),
                   cls_labels.reshape(-1)).reshape(batch_size,-1).mean(dim=1)
    bbox = bbox_loss(bbox_preds * bbox_masks,
                     bbox_labels * bbox_masks).mean(dim=1)#mask表示,对应背景时取0,不算损失了return cls + bbox # 损失值就是锚框类别的损失值加上偏移量的损失defcls_eval(cls_preds, cls_labels):# 由于类别预测结果放在最后一维,argmax需要指定最后一维。returnfloat((cls_preds.argmax(dim=-1).type(
        cls_labels.dtype)== cls_labels).sum())defbbox_eval(bbox_preds, bbox_labels, bbox_masks):returnfloat((torch.abs((bbox_labels - bbox_preds)* bbox_masks)).sum())

num_epochs, timer =20, d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                        legend=['class error','bbox mae'])
net = net.to(device)for epoch inrange(num_epochs):# 训练精确度的和,训练精确度的和中的示例数# 绝对误差的和,绝对误差的和中的示例数
    metric = d2l.Accumulator(4)
    net.train()for features, target in train_iter:
        timer.start()
        trainer.zero_grad()
        X, Y = features.to(device), target.to(device)# 生成多尺度的锚框,为每个锚框预测类别和偏移量
        anchors, cls_preds, bbox_preds = net(X)# 为每个锚框标注类别和偏移量,Y是真实标签
        bbox_labels, bbox_masks, cls_labels = d2l.multibox_target(anchors, Y)# 根据类别和偏移量的预测和标注值计算损失函数
        l = calc_loss(cls_preds, cls_labels, bbox_preds, bbox_labels,
                      bbox_masks)
        l.mean().backward()
        trainer.step()
        metric.add(cls_eval(cls_preds, cls_labels), cls_labels.numel(),
                   bbox_eval(bbox_preds, bbox_labels, bbox_masks),
                   bbox_labels.numel())# 累加器记录(预测正确数,总预测数,)
    cls_err, bbox_mae =1- metric[0]/ metric[1], metric[2]/ metric[3]
    animator.add(epoch +1,(cls_err, bbox_mae))print(f'class err {cls_err:.2e}, bbox mae {bbox_mae:.2e}')print(f'{len(train_iter.dataset)/ timer.stop():.1f} examples/sec on 'f'{str(device)}')

d2l.plt.show()

​ cpu训练(因为是A卡,后面会说为什么要用cpu):

在这里插入图片描述

​ 使用torch_directml有问题,似乎是某个操作不支持(repeat_interleave和AdaptiveMaxPool2d),只能在CPU上计算,导致训练结果非常差,但应该只影响训练时间啊?不是很明白:

在这里插入图片描述

UserWarning: The operator ‘aten::repeat_interleave.Tensor’ is not currently supported on the DML backend and will fall back to run on the CPU. This may have performance implications. (Triggered internally at D:\a_work\1\s\pytorch-directml-plugin\torch_directml\csrc\dml\dml_cpu_fallback.cpp:17.)
out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],

在这里插入图片描述

3. YOLO

​ You Only Look Once

​ SSD中锚框有大量重叠(生成锚框的方法导致的),因此浪费了很多计算,YOLO将图片分成

     S 
    
   
     × 
    
   
     S 
    
   
  
    S\times S 
   
  
S×S个锚框,每个锚框预测 
 
  
   
   
     B 
    
   
  
    B 
   
  
B个边缘框(如果只预测一个可能会丢失某些物体,因为可能有多个物体)


本文转载自: https://blog.csdn.net/shiki217_/article/details/140858626
版权归原作者 lsslcj 所有, 如有侵权,请联系我们删除。

“3.7.物体检测算法”的评论:

还没有评论