【pytorch】自己实现精简版YOLOV3【二】,通过darknet_53输出的特征图:得到预测框位置、置信度以及目标类别

89 篇文章 11 订阅
65 篇文章 3 订阅
这篇博客详细介绍了YOLOv3网络如何从三个不同尺度的特征图中预测目标框,包括预测值的含义、坐标转换和归一化处理。还展示了PyTorch实现这一过程的代码,涉及sigmoid激活函数、特征图解析和先验框的应用。
摘要由CSDN通过智能技术生成

接上篇博文
YOLOv3网络在三个特征图中预测出( 4 + 1 + c ) × k 个值,即特征图上每个点对应4个边界坐标(实际为偏移量),1个置信度值,C个类别值(C个类别置信度);数量k为特征图上每个点预测出的边框数量,默认为3:
下图先验框由聚类算法得到:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0719fa9b4d704cc19d45d561a730b0b2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6aKi5biI5YKF,size_20,color_FFFFFF,t_70,g_se,x_16
特征图上的预测目标为:
在这里插入图片描述
上图中蓝色字样为最终预测目标,tx及ty为网格中心至网格左上角的偏移量;bx及by为预测看中心点坐标;etw及wth为边框长度缩放因子,pw及ph为先验框的高宽,bw,bh为最终预测框的高宽。sigmod函数将预测框中心坐标偏移量限制在当前网格中(坐标范围为0至1之间),加速收敛。
这样,cx及cy,pw及ph已知,只用预测出tw,th及tx及ty四个值,就能通过计算确定最终目标框的位置。注意,预测出的所有的b值均是经过归一化处理的。
对于分类的类别,使用logistic激活函数(将特征值代入sigmoid函数内部),而不使用softmax激活函数,logistic激活函数彼此相互独立,不同类别之间不会相互抑制,这就方便了对同一目标的多标签检测,比如一个目标既是人,又是男人。
代码实现的主要难点集中在tensor的多维运算。
使用pytorch实现如下:

import os.path
from typing import Iterator
import numpy as np
import torch
import cv2
import matplotlib.pyplot as plt
from PIL import Image
from torch.utils.data import Dataset, DataLoader, Subset, random_split
import re
from functools import reduce
from torch.utils.tensorboard import SummaryWriter as Writer
from torchvision import transforms, datasets
import torchvision as tv
from torch import nn
import torch.nn.functional as F
import time
import math
class DecodeBoxGeneratedFromFeatures():
    #   13x13的特征层对应的anchor是[116,90],[156,198],[373,326]
    #   26x26的特征层对应的anchor是[30,61],[62,45],[59,119]
    #   52x52的特征层对应的anchor是[10,13],[16,30],[33,23]
    anchors=\
    {  13:[[116,90],[156,198],[373,326]],
       26: [[30,61],[62,45],[59,119]],
       52: [[10,13],[16,30],[33,23]],
    }
    '''由特征图生成对应的预测结果,FeaturesMap为darknet35输出的其中一种特征图
    形状为:(batchsize,(5+classNum)*3,width,hight)
    '''
    def __init__(self,FeaturesMap,classNum):
        self.__dict__.update(self.anchors)
        self.FeaturesMap=FeaturesMap
        #目前高宽总是相等的
        self.imgH=FeaturesMap.shape[2]
        self.imgW = FeaturesMap.shape[2]
        if self.imgH not in [13,26,52]:
            raise Exception('输入的特征图尺寸只能是13,26,52中的一种')
        self.batchSize=FeaturesMap.shape[0]
        #预测的属性
        self.predictAttars=FeaturesMap.shape[1]
        self.classNum=classNum
        #变换原特征图至理想输出向量,默认特征图上每个点输出3个预测框
        self.FeaturesMap=self.FeaturesMap.view(self.batchSize,3,5+self.classNum,self.imgH,self.imgH)
        #为了便于解析,将输出预测值换到最后一维:
        self.FeaturesMap=self.FeaturesMap.permute(0, 1, 3, 4, 2).contiguous()
        #提取出相关的预测值:最后一维按顺序为 中心偏移量(tx,ty),宽高缩放比(tw,th),置信度conf,以及classnum的分类向量
        '''训练前的原始输出不具有实际意义,需要人为进行指定:同一尺寸特征图的每一个点,都有3个对应的预测输出值,下面
        torch.Size([1, 3, 26, 26])中的3,是指每个特征图上的点有3个预测,而不是3通道图像
        '''
        #tx的形状为:torch.Size([1, 3, 26, 26]),该特征图共计3*26*26个tx值;
        self.tx=torch.sigmoid(self.FeaturesMap[...,0])
        self.ty = torch.sigmoid(self.FeaturesMap[..., 1])
        self.tw=self.FeaturesMap[..., 2]
        self.th = self.FeaturesMap[..., 3]
        #下面两者配合损失函数使用sigmoid函数:
        self.conf = torch.sigmoid(self.FeaturesMap[..., 4])
        self.classPrediction=torch.sigmoid(self.FeaturesMap[..., 5:])
        #坐标公式中,所有值是以特征图长宽为依据的,下面要对原始尺寸归一化:
        #特征图上每点相对于原图来看是多长:
        self.scale=416.0/self.imgH
        #由聚类产生的Anchor是相对于原图的,将其也相对于特征图:
        self.anchorChoised=[[anchor[0]/self.scale,anchor[1]/self.scale] for anchor in self.anchors[self.imgH]]
        #生成网格值:即图中的cx及cy:生成类似(1,3,26,26)一样的结构,便于同位置相加
        '''其中torch.linspace(start, end, steps=100);repeat将通道1复制self.imgH个,通道2不变,再通过repeat新增第一维,为
        self.batchSize * 3,再通过view的方法,将该维分散成self.batchSize,3,同self.tx的形状一致
        '''
        self.gridx=torch.linspace(0, self.imgW - 1, self.imgW).repeat(self.imgH, 1).repeat(
                self.batchSize * 3, 1, 1).view(self.tx.shape).type(torch.FloatTensor)
        self.gridy=torch.linspace(0, self.imgW - 1, self.imgW).repeat(self.imgH, 1).repeat(
                self.batchSize * 3, 1, 1).view(self.ty.shape).type(torch.FloatTensor)
        '''
        将self.anchorChoised也处理成与self.tw一致,即torch.Size([1, 3, 26, 26]),方便与缩放系数self.tw相乘:
        (注:index_select用法:
            dim:表示从第几维挑选数据,类型为int值;
            index:表示从第一个参数维度中的哪个位置挑选数据,类型为torch.Tensor类的实例;
            使用后原tensor不降维。)
        待操作anchorChoised有0维行和1维列,形如:[[116,90],[156,198],[373,326]]
        对于生成与self.tw同shape的tensor,先取出第1维中的下标为0的元素:
        [[116],[156],[373]],size(3,1):
        #'''
        step1=torch.FloatTensor(self.anchorChoised).index_select(1, torch.LongTensor([0]))
        '''
        按行复制self.batchSize个,size(self.batchSize*3,1)
        '''
        step2=step1.repeat(self.batchSize, 1)
        '''
        扩充至3维,将列扩充self.imgH * self.imgW倍,size(1,self.batchSize*3,self.imgH * self.imgW)
        '''
        step3=step2.repeat(1, 1, self.imgH * self.imgW)
        '''
        调整至size(batchsize,3,imgH, imgW):view函数是将待变换tensor从第0维至第一维展平成1维后再从最后一维开始向第0维按目标shape依次排列而成的,
        由于矩阵元素都一致,变换只用关注shape,即可按维度顺序进行重分配,即(1,self.batchSize*3,self.imgH * self.imgW)-》(batchsize,3,imgH, imgW)
        '''
        anchor_w=step3.view(self.tw.shape)
        #下面相同的手法得到anchor_h:
        step1 = torch.FloatTensor(self.anchorChoised).index_select(1, torch.LongTensor([1]))
        step2 = step1.repeat(self.batchSize, 1)
        step3 = step2.repeat(1, 1, self.imgH * self.imgW)
        anchor_h = step3.view(self.th.shape)

        #盒子位置:x,y,w,h
        #首先创建大小一致的容器
        self.boxPosition=torch.FloatTensor(self.FeaturesMap[...,0:4].shape)
        print(self.boxPosition.shape)
        self.boxPosition[...,0]=self.tx+self.gridx
        self.boxPosition[..., 1] = self.ty + self.gridy
        self.boxPosition[..., 2] = anchor_w*torch.exp(self.tw)
        self.boxPosition[..., 3] = anchor_h*torch.exp(self.th)
        #输出该尺寸特征图的所有预测Boxs
    def givePredictedBoxs(self):
        '''注1:self.boxPosition.view(self.batchSize, -1, 4) / _scale这里输出的x.y.w.h为这四个点坐标值相对于特征图的比例值,该比例值乘以特征图长度,就为特征图中box大小位置。
        该比例乘以原图,就能得到原图图上box的大小位置。这是因为网络输出特征图和原图差了一个缩放比self.scale,而比例值在各自图中具有不变性,
        '''
        _scale = torch.Tensor([self.imgH, self.imgW,self.imgH, self.imgW]).type(torch.FloatTensor)
         '''注2:这里使用了pytorch的广播机制:
         (self.batchSize, -1, 4) / (1,4),从最后一维开始对照,相同维度取最大的一维,再逐元素相乘。
         (self.batchSize, -1, 4)
         (                 1, 4)
         按最大取值:
         (self.batchSize, -1, 4)
         将待乘两向量对应元素相除。
         得出最终结果。
        '''
        output = torch.cat((self.boxPosition.view(self.batchSize, -1, 4) / _scale,
                            self.conf.view(self.batchSize, -1, 1), self.classPrediction.view(self.batchSize, -1, self.classNum)), -1)
        return output.detach()

#测试:用k模拟特征图
k=torch.rand(2,24,26,26)
#设为3分类
a=DecodeBoxGeneratedFromFeatures(k,3)
print(a.givePredictedBoxs().shape)

测试输出结果为:

torch.Size([2, 2028, 8])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颢师傅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值