计算机视觉PyTorch实现目标检测:SSD

简介: 计算机视觉PyTorch实现目标检测:SSD

目标检测算法概况


目前目标检测点主流算法分为二种类型

二阶段方法:如R-CNN系算法,其主要思路是先通过启发式方法(selective search)或者CNN网络(RPN)产生一系列稀疏的候选框,然后对这些候选框进行分类与回归,two-stage方法的优势是准确度高

单阶段方法:如Yolo和SSD,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度快


eeaf308f1b86404c86cb830f8d23401f.png

目标检测知识点


目标检测的算法,通常都是对图片上的四个参数做处理:分别是中心点x轴、y轴坐标,框的高和宽。


对图片做目标检测,通常是通过卷积,将原始图片,卷积成不同尺寸大小的图片,例如一张cat的图片,通过卷积可以生成如下尺寸的图像。



通常尺寸越小的越容易检测大的物体,尺寸越大适合检测小的物体。


SSD算法知识点


先验框


先验框功能通常是帮助我们定好常见目标的宽和高,在进行预测的时候,我们可以利用这个已经定好的宽和高处理,可以帮助我们进行预测。


在进行模型训练的时候,通过分割网格,来生成不同的先验框,并先验框对图像进行处理,通过训练调整先验框位置,来正确框出图像中的事物。


每一层网格都对应一层先验框对图像中的事物做处理,可以理解为每一层卷积后对图像中的事物做一次先验框框出目标事物,随着更深层的网格训练,先验框也在不断调整位置和大小,使得框出的事物越来越精确。


过程大致如下图所示:

a1451bd564c4460bb04ec08226e001c5.png

0fe9db11440d4b9fabf9c66b244f345f.png

6affd806039849ed90f0a7188e97488d.png

对于检测COCO数据集,它输出的就是一个(13x13,(80+5)*5)的数据,13x13对应图像的网格点,*5表示每个网格点上有五个先验框,每个先验框有85个参数,其中80对应着有80个网格通道数,5分别对应着先验框中心坐标x、中心点坐标y、先验框高h和宽先验框w、置信度(即分类结果属于那种类型的概率)。


91d777bead5e44c1bc92eaa0e352b572.pnga61af8e4c43f4e51bf8c3ad652bf9a96.png


SSD算法原理


SSD算法是一种单阶段的目标检测算法,通过输入一张图片,神经网络可以预测出对应物体的边框以及对应边框下物体的种类信息。


在图像分类网络构架中,通常使用VGG16算法作为特征提取的骨架网络,而SSD模型与VGG16模型在网络架构有很多相似的,

VGG模型架构:

e173cc3b73e9463c9e2a4eacb2a55b90.png


不同的是:


SSD的输入大小与VGG不一样,SSD模型采用了不同的输入图像尺寸,常用的模型有SSD300和SSD512,分别代表300x300和512x512对输入图像大小。

在网络架构上SSD采用VGG16作为基础模型,然后在VGG16的基础上新增了卷积层来获得更多的特征图以用于检测。SSD的网络结构如下图所示


SSD架构:

87f21f5112ce4f84a670e342bcc36424.png

SSD算法网络中,通过输入图像进过VGG16的conv1~conv5计算,并保留conv4,conv5的中间特征输出用于后续的预测。


卷积过程


输入图像为(300x300x3)

Conv1:经过二次 大小为(3x3x64),步长为1的卷积核,输出为图像为 (300x300x64)。再经过一次(2x2),步长为2的最大池化层,输出图像为(150x150x64)

Conv2:经过二次 大小为(3x3x128),步长为1的卷积核,输出为图像为 (150x150x128)。再经过一次(2x2),步长为2的最大池化层,输出图像为(75x75x128)

Conv3:经过三次 大小为(3x3x256),步长为1的卷积核,输出为图像为 (75x75x256)。再经过一次(2x2),步长为2的最大池化层,输出图像为(38x38x256)

Conv4:经过三次 大小为(3x3x512),步长为1的卷积核,输出为图像为 (38x38x512)。再经过一次(2x2),步长为2的最大池化层,输出图像为(19x19x512)

Conv5:经过三次 大小为(3x3x512),步长为1的卷积核,输出为图像为 (19x19x512)。再经过一次(3x3),步长为1的最大池化层,输出图像为(19x19x512)

利用卷积代替全连接层:进行了一次(3x3x1024)卷积网络和一次(1x1x1024)卷积网络,分别为fc6和fc7,输出的通道数为1024,因此输出的net为(19,19,1024)。(从这里往前都是VGG的结构)

Conv6:经过一次(1x1x256)步长为1的卷积核,再经过一次(3x3x512),步长为2的卷积核,输出图像为(10x10x512)

Conv7:经过一次(1x1x128)步长为1的卷积核,再经过一次(3x3x256),步长为2的卷积核,输出图像为(5x5x256)

Conv8:经过一次(1x1x128)步长为1的卷积核,再经过一次(3x3x256),步长为2的卷积核,输出图像为(3x3x256)

Conv9:经过一次(1x1x128)步长为1的卷积核,再经过一次(3x3x256),步长为2的卷积核,输出图像为(1x1x256)


回溯到上面的先验框知识点,这里在SSD算法网络中利用Conv4、fc7、Conv6、Conv7、

先验框操作


进行一次(1 x 1x(num x 4))卷积处理,其中num表示每个层的先验框数量,其值在Conv4到Conv9中对应为(4、6、6、6、4、4),num x 4 表示对应的卷积核数量

进行一次(1x1x(num x classes))卷积处理,其中classes对应分类的类别,num x classes表示卷积核数量

计算对应的先验框


先验框流程


Conv4到Conv9中对应为(4、6、6、6、4、4)

classes为 21


Conv4:输入图像为(38x38x512),经过一次(1x1x(4x4))步长为1的卷积,得到(38x38x16),再经过一次(1x1x(4x21)),步长为1的卷积,得到(38x38x84),最后获得先验框(38x38x16)。

fc7:输入图像为(19x19x1024),经过一次(1x1x(4x6))步长为1的卷积,得到(19x19x24),再经过一次(1x1x(6x21)),步长为1的卷积,得到(19x19x126),最后获得先验框(19x19x24)。

Conv6:输入图像为(10x10x512),经过一次(1x1x(4x6))步长为1的卷积,得到(10x10x24),再经过一次(1x1x(6x21)),步长为1的卷积,得到(10x10x126),最后获得先验框(10x10x24)。

Conv7:输入图像为(5x5x256),经过一次(1x1x(4x6))步长为1的卷积,得到(5x5x24),再经过一次(1x1x(6x21)),步长为1的卷积,得到(5x5x126),最后获得先验框(5x5x24)。

Conv8:输入图像为(3x3x256),经过一次(1x1x(4x4))步长为1的卷积,得到(3x3x16),再经过一次(1x1x(4x21)),步长为1的卷积,得到(3x3x84),最后获得先验框(3x3x16)。

Conv9:输入图像为(1x1x256),经过一次(1x1x(4x4))步长为1的卷积,得到(1x1x16),再经过一次(1x1x(4x21)),步长为1的卷积,得到(1x1x84),最后获得先验框(1x1x16)。


最后根据得到的预测置信度,进行得分排序与非极大抑制筛选(去除多余的边框)


Pytorch实现SSD算法


VGG-16代码实现


这里只实现Conv1~Conv4层代码


class VGG16(nn.Module):
      def __init__(self):
          super(VGG16,self).__init__()
          self.layers=self.make_layers()
      def forward(self,x):
          y=self.layers(x)
          return y
      def make_layers(self):
          cfg=[64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512,'M',512, 512, 512]
          layers=[]
          in_channels=3
          for x in cfg:
              if x=='M':
                 layers+=[nn.MaxPool2d(kernel_size=2,stride=2)]
              elif x=='c':
                 layers=[nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
              else:
                 layers+=[nn.Conv2d(in_channels,x,kernel_size=3,padding=1),nn.ReLU(True)]
                 in_channels=x
          return nn.Sequential(*layers)         


SSD模型特征提取代码实现


这里实现Conv5~Conv9代码


class SSD(nn.Module):
      def __init__(self):
          super(SSD,self).__init__()
          #导入上面的模块
          self.features=VGG16()
          self.norm4=L2Norm(512,20)
          self.conv5_1=nn.Conv2d(512,512,kernel_size=3,padding=1,dilation=1)
          self.conv5_2=nn.Conv2d(512,512,kernel_size=3,padding=1,dilation=1)
          self.conv5_3=nn.Conv2d(512,512,kernel_size=3,padding=1,dilation=1)
          self.fc6=nn.Conv2d(512,1024,kernel_size=3,padding=6,dilation=6)
          self.fc7=nn.Conv2d(1024,1024,kernel_size=1)
          self.conv6_1=nn.Conv2d(1024,256,kernel_size=1,stride=1)
          self.conv6_2=nn.Conv2d(256,512,kernel_size=3,stride=2,padding=1)
          self.conv7_1=nn.Conv2d(512,128,kernel_size=1, stride=1)
          self.conv7_2=nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1))
          self.conv8_1=nn.Conv2d(256, 128, kernel_size=1, stride=1)
          self.conv8_2=nn.Conv2d(128, 256, kernel_size=3, stride=1)
          self.conv9_1=nn.Conv2d(256, 128, kernel_size=1, stride=1)
          self.conv9_2=nn.Conv2d(128, 256, kernel_size=3, stride=1)
      def forward(self,x):
          hs=[]
          h=self.features(x)
          hs.append(self.norm4(h))
          h=F.max_pool2d(kernel_size=2, stride=2, ceil_mode=True)
          h=F.relu(self.conv5_1(h))
          h=F.relu(self.conv5_2(h))
          h=F.relu(self.conv5_3(h))
          h=F.max_pool2d(h,kernel_size=3, stride=1, padding=1,ceil_mode=True)
          h=F.relu(self.fc6(h))
          h=F.relu(self.fc7(h))
          hs.append(h) #第一个先验框
          h=F.relu(self.conv6_1(h))
          h=F.relu(self.conv6_2(h))
          hs.append(h) #第二个先验框
          h=F.relu(self.conv7_1(h))
          h=F.relu(self.conv7_2(h))
          hs.append(h) #第三个先验框
          h=F.relu(self.conv8_1(h))
          h=F.relu(self.conv8_2(h))
          hs.append(h) #第四个先验框
          h=F.relu(self.conv9_1(h))
          h=F.relu(self.conv9_2(h))
          hs.append(h) #第五个先验框
          return hs
class L2Norm(nn.Module):
      def __init__(self,in_features,scale):
          super(L2Norm,self).__init__()
          self.weight=nn.Parameter(torch.Tensor(in_features))
          self.reset_parameters(scale)
      def reset_parameters(self,scale):
          nn.init.constant(self.weight,scale)
      def forward(self,x):
          x=F.normalize(x,dim=1)
          scale=self.weight(None,:,None,None)
          return scale*x            


先验框代码实现(获取特征)


class get_ssd(nn.Module):
      #定义步长
      steps=(8,16,32,64,100,300)
      #定义先验框的基础大小
      box_sizes=(30,60,111,162,213,264,315)
      #定义先验框的高宽比
      aspect_ratios=((2,),(2,3),(2,3),(2,3),(2,),(2,))
      fm_sizes=(38,19,10,5,3,1)
      def __init__(self,num_classes):
          super(get_ssd,self).__init()
          self.num_classes=num_classes
          self.num_anchors=(4,6,6,6,4,4)
          self.in_channels=(512,1024,512,256,256,256)
          self.extractor=SSD()
          self.loc_layers=nn.ModuleList()
          self.cls_layers=nn.ModuleList()
          for i in range(len(self.in_channels)):
              self.loc_layers+=[nn.Conv2d(self.in_channels[i],self.num_anchors[i]*4,kernel_size=3,padding=1)]
              self.cls_layers+=[nn.Conv2d(self.in_channels[i],self.num_anchors[i]*self.num_classes,kernel_size=3,padding=1)]
       def forward(self,x):
           loc_preds=[]
           cls_preds=[]
           xs=self.extractor(x)
           for i,x in enumerate(xs):
               loc_pred=self.loc_layers[i][x]
               loc_pred=loc_pred.permute(0,2,3,1).contiguous()
               loc_preds.append(loc_pred.view(loc_pred.size(0),-1,4))
               cls_pred=self.cls_layers[i][x]
               cls_pred=cls_pred.permute(0,2,3,1).contiguous()
               cls_preds.append(cls_pred.view(cls_pred.size(0),-1,self.num_classes))
           los_preds=torch.cat(loc_preds,1)
           cls_preds=torch.cat(cls_preds,1)
           return loc_preds,cls_preds


先验框函数


def default_prior_box():
    mean_layer = []
    for k,f in enumerate(Config.feature_map):
        mean = []
        for i,j in product(range(f),repeat=2):
            f_k = Config.image_size/Config.steps[k]
            cx = (j+0.5)/f_k
            cy = (i+0.5)/f_k
            s_k = Config.sk[k]/Config.image_size
            mean += [cx,cy,s_k,s_k]
            s_k_prime = sqrt(s_k * Config.sk[k+1]/Config.image_size)
            mean += [cx,cy,s_k_prime,s_k_prime]
            for ar in Config.aspect_ratios[k]:
                mean += [cx, cy, s_k * sqrt(ar), s_k/sqrt(ar)]
                mean += [cx, cy, s_k / sqrt(ar), s_k * sqrt(ar)]
        if Config.use_cuda:
            mean = torch.Tensor(mean).cuda().view(Config.feature_map[k], Config.feature_map[k], -1).contiguous()
        else:
            mean = torch.Tensor(mean).view( Config.feature_map[k],Config.feature_map[k],-1).contiguous()
        mean.clamp_(max=1, min=0)
        mean_layer.append(mean)
    return mean_layer


损失函数计算


class LossFun(nn.Module):
    def __init__(self):
        super(LossFun,self).__init__()
    def forward(self, prediction,targets,priors_boxes):
        loc_data , conf_data = prediction
        loc_data = torch.cat([o.view(o.size(0),-1,4) for o in loc_data] ,1)
        conf_data = torch.cat([o.view(o.size(0),-1,21) for o in conf_data],1)
        priors_boxes = torch.cat([o.view(-1,4) for o in priors_boxes],0)
        if Config.use_cuda:
            loc_data = loc_data.cuda()
            conf_data = conf_data.cuda()
            priors_boxes = priors_boxes.cuda()
        # batch_size
        batch_num = loc_data.size(0)
        # default_box数量
        box_num = loc_data.size(1)
        # 存储targets根据每一个prior_box变换后的数据
        target_loc = torch.Tensor(batch_num,box_num,4)
        target_loc.requires_grad_(requires_grad=False)
        # 存储每一个default_box预测的种类
        target_conf = torch.LongTensor(batch_num,box_num)
        target_conf.requires_grad_(requires_grad=False)
        if Config.use_cuda:
            target_loc = target_loc.cuda()
            target_conf = target_conf.cuda()
        # 因为一次batch可能有多个图,每次循环计算出一个图中的box,即8732个box的loc和conf,存放在target_loc和target_conf中
        for batch_id in range(batch_num):
            target_truths = targets[batch_id][:,:-1].data
            target_labels = targets[batch_id][:,-1].data
            if Config.use_cuda:
                target_truths = target_truths.cuda()
                target_labels = target_labels.cuda()
            # 计算box函数,即公式中loc损失函数的计算公式
            utils.match(0.5,target_truths,priors_boxes,target_labels,target_loc,target_conf,batch_id)
        pos = target_conf > 0
        pos_idx = pos.unsqueeze(pos.dim()).expand_as(loc_data)
        # 相当于论文中L1损失函数乘xij的操作
        pre_loc_xij = loc_data[pos_idx].view(-1,4)
        tar_loc_xij = target_loc[pos_idx].view(-1,4)
        # 将计算好的loc和预测进行smooth_li损失函数
        loss_loc = F.smooth_l1_loss(pre_loc_xij,tar_loc_xij,size_average=False)
        batch_conf = conf_data.view(-1,21)
        # 参照论文中conf计算方式,求出ci
        loss_c = utils.log_sum_exp(batch_conf) - batch_conf.gather(1, target_conf.view(-1, 1))
        loss_c = loss_c.view(batch_num, -1)
        # 将正样本设定为0
        loss_c[pos] = 0
        # 将剩下的负样本排序,选出目标数量的负样本
        _, loss_idx = loss_c.sort(1, descending=True)
        _, idx_rank = loss_idx.sort(1)
        num_pos = pos.long().sum(1, keepdim=True)
        num_neg = torch.clamp(3*num_pos, max=pos.size(1)-1)
        # 提取出正负样本
        neg = idx_rank < num_neg.expand_as(idx_rank)
        pos_idx = pos.unsqueeze(2).expand_as(conf_data)
        neg_idx = neg.unsqueeze(2).expand_as(conf_data)
        conf_p = conf_data[(pos_idx+neg_idx).gt(0)].view(-1, 21)
        targets_weighted = target_conf[(pos+neg).gt(0)]
        loss_c = F.cross_entropy(conf_p, targets_weighted, size_average=False)
        N = num_pos.data.sum().double()
        loss_l = loss_loc.double()
        loss_c = loss_c.double()
        loss_l /= N
        loss_c /= N
        return loss_l, loss_c
相关文章
|
算法 计算机视觉
计算机视觉目标检测性能指标
目标检测是计算机视觉领域中的一个重要任务,其目标是在图像或视频中识别出物体的位置和类别。为了评估目标检测算法的性能,需要使用一系列指标来量化模型的准确性、召回率、精确率以及对不同类别的处理能力。本文将详细介绍常见的目标检测性能指标,包括精确率、召回率、F1分数、IoU、AP、mAP、P-R曲线等,同时提供相关公式和案例。
900 0
|
机器学习/深度学习 编解码 监控
计算机视觉实战项目4(单目测距与测速+摔倒检测+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A_路径规划+行人车辆计数+动物识别等)-1
计算机视觉实战项目4(单目测距与测速+摔倒检测+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A_路径规划+行人车辆计数+动物识别等)-1
|
机器学习/深度学习 监控 算法
计算机视觉实战项目(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别)
计算机视觉实战项目(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别)
|
机器学习/深度学习 算法 计算机视觉
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A路径规划+单目测距与测速+行人车辆计数等)
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A路径规划+单目测距与测速+行人车辆计数等)
280 2
|
机器学习/深度学习 算法 数据可视化
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)-2
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)
|
机器学习/深度学习 算法 计算机视觉
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)
|
机器学习/深度学习 Ubuntu Linux
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)-1
计算机视觉+深度学习+机器学习+opencv+目标检测跟踪+一站式学习(代码+视频+PPT)
|
机器学习/深度学习 编解码 算法
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)-2
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)-2
|
机器学习/深度学习 监控 算法
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)-1
计算机视觉实战项目3(图像分类+目标检测+目标跟踪+姿态识别+车道线识别+车牌识别+无人机检测+A*路径规划+单目测距与测速+行人车辆计数等)-1
|
机器学习/深度学习 PyTorch 算法框架/工具
聊一聊计算机视觉中常用的注意力机制以及Pytorch代码实现
本文介绍了几种常用的计算机视觉注意力机制及其PyTorch实现,包括SENet、CBAM、BAM、ECA-Net、SA-Net、Polarized Self-Attention、Spatial Group-wise Enhance和Coordinate Attention等,每种方法都附有详细的网络结构说明和实验结果分析。通过这些注意力机制的应用,可以有效提升模型在目标检测任务上的性能。此外,作者还提供了实验数据集的基本情况及baseline模型的选择与实验结果,方便读者理解和复现。
991 0
聊一聊计算机视觉中常用的注意力机制以及Pytorch代码实现

热门文章

最新文章

推荐镜像

更多