YOLO v1 计算流程--基于pytorch

YOLO v1 计算流程–基于pytorch
  • 个人理解TOLO v1的计算有如下几个关键部分:
    • 1.图像预处理
      • YOLO v1要求图像的大小是一致的448 * 448 因此读取图像后需要对图像进行预处理
    • 2.图像的前向传播
      • 前向传播部分由两部分组成:特征提取和输出构建
      • 特征提取可以使用原文章中基于DartNet的特征提取方式,也可以采用其他网络诸如VGG或者ResNet等
      • 输出构建时YOLO v1的精华,是YOLO网络的主要思想核心,基于划分好的网格构建bounding box、confidence和class维度,每个网格构建一套
    • 3.损失函数的计算:
      • 损失函数均基于均方根误差计算
      • YOLO v1的损失函数包括三部分:bounding box坐标位置损失、置信度损失(网格中是否包含object中心),class损失(基于one hot编码计算)
图像预处理和划分
  • 将图像resize为 448 × 448 × 3 448\times 448 \times 3 448×448×3的大小,resize的目的是为了满足全连接层的固定输入维度的要求
    • 代码:
      • import cv2
        img = cv2.resize(img, (448,448))
        
    • 将448的图像划分为 7 × 7 7\times 7 7×7的格点,每个格点边长为64个像素点图解:
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7qmIDoI-1635915412647)(./1635745220607.png)]
将图像输入到YOLO v1网络前向传播:
  • 由于YOLO前边的网络主要作用是特征提取,因此使用resnet的全连接之前的部分代替,通过加载torchvision中的已经训练好的resnet34,并固定参数,后边跟YOLOv1后半部分卷积结构和全连接层变化结构
  • 每个网格要预测2个bounding box,每个bounding box除了要预测位置之外,还要附带预测一个confiden值,每个网格还要预测C个类别的分数
    • 网格有 7 × 7 7\times 7 7×7每个网格2个边界框
    • 也就是每个bounding box 预测5个值,其中四个为位置参数(相对值,相对于整个图像大小来说),confidence值,这里有两个,就是 2 × ( 4 + 1 ) 2 \times (4+1) 2×(4+1)
    • VOC数据集有20个类别,则类别数为20
    • 所以一共有参数 7 × 7 × ( 20 + 2 × ( 4 + 1 ) ) 7 \times 7 \times(20+2\times (4+1)) 7×7×(20+2×(4+1))
      *在这里插入图片描述
from torchvision.models import resnet34
import torch.nn as nn
import torch

class YOLOv1_resnet(nn.Module):
    def __init__(self, num_box, class_num):
        super(YOLOv1_resnet,self).__init__()
        self.num_box = num_box
        self.class_num = class_num
        resnet = resnest_model()
        # 调用torchvision里的resnet34预训练模型
        resnet_out_channel = resnet.fc.in_features 
        # 记录resnet全连接层之前的网络输出通道数,方便连入后续卷积网络中
        self.resnet = nn.Sequential(*list(resnet.children())[:-2])  
        # 去除resnet的最后两层
        '''定义YOLO的最后的卷积层'''
        self.conv_layer = nn.Sequential(
            nn.Conv2d(resnet_out_channel,1024,3,padding=1),
            nn.BatchNorm2d(1024),
            nn.LeakyReLU(),
            nn.Conv2d(1024,1024,3,stride=2,padding=1),
            nn.BatchNorm2d(1024),
            nn.LeakyReLU(),
            nn.Conv2d(1024, 1024, 3, padding=1),
            nn.BatchNorm2d(1024),
            nn.LeakyReLU(),
            )
        '''全连接层,三维变量拉平进行处理'''
        self.dense_layers = nn.Sequential(
            nn.Linear(7*7*1024,4096),
            nn.LeakyReLU(),
            nn.Linear(4096,7*7*30))
        
    def forward(self, x):
        out = self.resnet(x)
        out = self.conv_layer(out)
        out = out.view(out.size()[0],-1)
        out = self.dense_layers(out)
        return out.reshape(-1, (5*self.num_box+self.class_num), 7, 7)

def set_parameter_requires_grad(model):
    
    for param in model.parameters():
        param.requires_grad = False

# ResNeSt模型
def resnest_model():
    model_ft = resnet34(pretrained=True)
    set_parameter_requires_grad(model_ft)
    num_ftrs = model_ft.fc.in_features  
    ### 问题:冻结参数是对已有的层,新定义的层不冻结?
    return model_ft

if __name__ == '__main__':
    x = torch.randn((1,3,448,448))
    net = YOLOv1_resnet(2, 20)
    print(net)
    y = net(x)
    print(y.size())
3)损失函数的计算
  • 数据前向传播后,要计算损失才能后向传播,因此需要根据定义计算损失函数,在YOLO v1中损失函数由三个部分组成,均使用平方和计算:
  • 使用误差平方和计算
  • bounding box损失
    • L l o c ( l , g ) = ∑ i = 0 s 2 ∑ j = 0 B 1 i , j o b j [ ( x i − x ^ i ) 2 + ( y i − y ^ i ) 2 ] + λ c o o r d ∑ i = 0 s 2 ∑ j = 0 B 1 i , j o b j [ ( ( w i ) − w ^ i ) 2 + ( ( h i ) − h ^ i ) 2 ] L_{loc}(l,g)=\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(x_i-\hat x_i)^2+(y_i-\hat y_i)^2]+\lambda_{coord}\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(\sqrt{(w_i)}-\sqrt{\hat w_i})^2+(\sqrt{(h_i)}-\sqrt{\hat h_i})^2] Lloc(l,g)=i=0s2j=0B1i,jobj[(xix^i)2+(yiy^i)2]+λcoordi=0s2j=0B1i,jobj[((wi) w^i )2+((hi) h^i )2]
  • confidence 损失
    • L c o n f ( o , c ) = ∑ i = 0 s 2 ∑ j = 0 B 1 i , j o b j [ ( C i − C ^ i ) 2 + λ n o o b j ∑ i = 0 s 2 ∑ j = 0 B 1 i , j n o o b j [ ( C i − C ^ i ) 2 L_{conf}(o,c)=\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(C_i-\hat C_i)^2+\lambda_{noobj}\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{noobj}_{i,j}[(C_i-\hat C_i)^2 Lconf(o,c)=i=0s2j=0B1i,jobj[(CiC^i)2+λnoobji=0s2j=0B1i,jnoobj[(CiC^i)2
  • classes 损失
    • L c l a ( O , C ) = ∑ i = 0 s 2 1 i o b j ∑ c ∈ c l a s s e s ( p i ( c ) − p i ^ ( c ) ) L_{cla(O,C)}=\sum^{s^2}_{i=0}1^{obj}_{i}\sum_{c \in classes(p_i(c)-\hat {p_i}(c))} Lcla(O,C)=i=0s21iobjcclasses(pi(c)pi^(c))
    • 总的损失为所有三种损失的损失相加
    • L ( o , c , O , C , l , g ) = λ 1 L c o n f ( o , c ) + λ 2 L c l a ( O , C ) + λ 3 L l o c ( l , g ) L(o,c,O,C,l,g)=\lambda_1L_{conf}(o,c)+\lambda_2L_{cla(O,C)}+\lambda_3L_{loc}(l,g) L(o,c,O,C,l,g)=λ1Lconf(o,c)+λ2Lcla(O,C)+λ3Lloc(l,g)
      • 其中 λ \lambda λ为平衡系数
  • 在计算bounding box损失的时候涉及到一个问题:anchors的选取
    • anchors生成的过程如下:
      • 锚定过程:
        • 对于输入图像的每个对象,先找到其中心点,寻找中心点所在的划分好的网格(7*7),则该包含物体中心点的网格中confidence=1,其他48个网格中confidence为0,在YOLO中称为中心点所在的网格对预测该对象负责
        • bounding box坐标:
          • YOLO中并不会预设anchors坐标或者高宽比,而是锚定中心点之后,在结果中直接生成,组成输出结果的8个特征,不过每个网格cell生成两组boxes坐标,每组坐标有4个,分别为相对中心点坐标(x,y),相对高度和宽度为(w, h),其中相对中心坐标时相对于中心所在的子网格,w,h为相对于整幅图的宽度和高度比例,如下图:
            • 在这里插入图片描述
          • 由于有两个boxes,因此需要对boxes进行筛选,选取最优的boxes的坐标计算误差,筛选采用IoU 来进行:
      • IoU
      • I o U = i n t e r s e c t i o n ( A , B ) u n i o n ( A , B ) IoU =\frac{intersection(A,B)}{union(A,B)} IoU=union(A,B)intersection(A,B)
      • 物理意义为:IoU为交集部分面积与并集部分面积之比,当两个Box完全重合时IoU=1,不相交时IoU=0
  • 本部分代码采用沐神课程中的代码,在我之前的blog中有详细解析,链接https://blog.csdn.net/qq_34992900/article/details/120705041?spm=1001.2014.3001.5501:
def box_iou(boxes1, boxes2):
    '''
    args:
        boxes1: tensor
            [num_boxes, 4]
        boxes2: 同上
    '''
    box_area = lambda boxes:((boxes[:,2] - boxes[:,0]) * (boxes[:,3] - boxes[:,1]))
    areas1 = box_area(boxes1)
    areas2 = box_area(boxes2)
    inter_upperlefts = torch.max(boxes1[:, None, :2], boxes2[:, :2])
    inter_lowerrights = torch.min(boxes1[:, None, 2:], boxes2[: 2:])
    inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)
    inter_areas = inters[:, :, 0] * inters[:, :, 1]
    union_areas = areas1[:, None] + areas2 - inter_areas
    return inter_areas / union_areas
  • 定义Loss函数
  • 为了方便计算Loss,将网络输出为 7 × 7 × 30 7 \times 7 \times 30 7×7×30的数据格式与label计算,label转化为与网络输出一致的格式 7 × 7 × 30 7 \times 7 \times 30 7×7×30得到;
  • 在计算中先计算格点预设两个边框与真实边框的IoU值,选择最大IoU边框,进一步计算边框坐标误差
  • 上述步骤如下:
    • 根据confidence判断网格中是否包含object的中心
    • 若存在中心,则将中心–高宽的形式,转为左上角和右下角坐标的形式
    • 通过上述IoU计算大赛分别计算两个bounding box与label的大小
    • 选取IoU最大的计算边框坐标误差,IoU小的bounding box则计入到网格没有object的误差,计算分类误差,概率值为IoU
    • 计算置信度误差
    • 计算分类误差
  • 代码如下:
class Loss_yolov1(nn.Module):
    def __init__(self):
        super(Loss_yolov1,self).__init__()

    def box_center_to_conner(self, boxes, n, m, grid_num):
        """转换 YOLO v1的bounding box
        从中间,宽度,高度)转换(左上,右下)"""
        
        cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
        x1 = (cx + n)/grid_num - w/2
        y1 = (cy + m)/grid_num - h/2
        x2 = (cx + n)/grid_num + w/2
        y2 = (cy + m)/grid_num + w/2
        boxes = torch.stack((x1, y1, x2, y2), axis=-1)
        return boxes

    def forward(self, pred, labels):
        """
        :param pred: (batchsize,30,7,7)的网络输出数据
        :param labels: (batchsize,30,7,7)的样本标签数据
        :return: 当前批次样本的平均损失
        """
        num_gridx, num_gridy = labels.size()[-2:]  # 划分网格数量
        num_b = 2  # 每个网格的bbox数量
        num_cls = 20  # 类别数量
        noobj_confi_loss_2 = 0.  # 不含目标的网格损失(只有置信度损失)
        coor_loss = 0.  # 含有目标的bbox的坐标损失
        obj_confi_loss = 0.  # 含有目标的bbox的置信度损失
        class_loss = 0.  # 含有目标的网格的类别损失
        n_batch = labels.size()[0]  # batchsize的大小
        iloss_1 = 0.
        for i in range(n_batch):  # batchsize循环
            for n in range(7):  # x方向网格循环
                for m in range(7):  # y方向网格循环
                    if labels[i,4,m,n]==1:#如果包含物体
                        bbox1_pred_xyxy = self.box_center_to_conner(
                                            pred[i,:4,m,n], n, m, num_gridx)
                        bbox2_pred_xyxy = self.box_center_to_conner(
                                            pred[i,4:8,m,n], n, m, num_gridx) 
                        bbox_gt_xyxy =  self.box_center_to_conner(
                                            labels[i,:4,m,n], n, m, num_gridx)
                        iou1 = box_iou(bbox1_pred_xyxy,bbox_gt_xyxy)
                        iou2 = box_iou(bbox2_pred_xyxy,bbox_gt_xyxy)
                        # 选择iou大的bbox作为负责物体
                        box_pred = pred[i, :4, m, n] if iou1 >= iou2 else pred[i, 4:8, m, n]
                        icoor_loss = (
                            torch.sum((box_pred[0:2,:] - labels[i,0:2,m,n])**2) + torch.sum((pred[i,2:4,m,n].sqrt()-labels[i,2:4,m,n].sqrt())**2))
                        iobj_confi_loss = (pred[i,4,m,n] - iou1)**2
                        # iou比较小的bbox不负责预测物体,因此confidence loss算在noobj中,注意,对于标签的置信度应该是iou2
                        inoobj_confi_loss_1 = ((pred[i,9,m,n]-iou2)**2)
                        iclass_loss = torch.sum((pred[i,10:,m,n] - labels[i,10:,m,n])**2)
                        iloss_1 += 5 * icoor_loss + iobj_confi_loss + iclass_loss + 0.5*inoobj_confi_loss_1
                    else:  # 如果不包含物体
                        noobj_confi_loss_2 += 0.5 * torch.sum(pred[i,[4,9],m,n]**2)

        loss = iloss_1 + 0.5 * noobj_confi_loss_2
        # 此处可以写代码验证一下loss的大致计算是否正确,这个要验证起来比较麻烦,比较简洁的办法是,将输入的pred置为全1矩阵,再进行误差检查,会直观很多。
        return loss/n_batch
###############
### 验证代码 ###
###############
loss_fn = Loss_yolov1()
pred = torch.randn((4,30,7,7))
label = torch.randn((4,30,7,7))
loss = loss_fn(pred, label)
print(loss)
参考:
  • 大量参考了该仓代码:https://github.com/lavendelion/YOLOv1-from-scratch
  • 部分参考了李沐深度学习课程代码
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用pytorch-yolo时,为了使用模型进行目标检测,我们需要下载预训练的权重文件和相关的数据集。 首先,我们需要下载YOLOv3预训练权重文件。可以通过执行以下命令来下载: ``` wget https://pjreddie.com/media/files/yolov3.weights ``` 下载完成后,我们可以使用权重文件进行目标检测。但是,请记住,这个权重文件是基于Darknet架构训练的,而pytorch-yolo是基于PyTorch框架的。因此,我们需要将Darknet权重文件转换为PyTorch的权重文件格式。可以使用pytorch-yolo提供的`darknet2pytorch.py`脚本来进行转换: ``` python darknet2pytorch.py yolov3.cfg yolov3.weights yolov3.pt ``` 请确保将`yolov3.cfg`和`yolov3.weights`替换为下载的权重文件的路径。转换后,将生成一个`yolov3.pt`文件,我们可以使用这个文件进行目标检测。 除了预训练权重文件,我们还需要下载用于训练和测试的数据集。pytorch-yolo支持许多常见的目标检测数据集,如COCO、VOC等。可以通过执行以下命令来下载相应的数据集: COCO数据集: ``` sh data/scripts/COCO/download.sh ``` VOC数据集: ``` sh data/scripts/VOC/download.sh ``` 这些命令将自动下载并提取相应的数据集。 总结起来,为了使用pytorch-yolo进行目标检测,我们需要下载预训练权重文件和相关的数据集。通过执行相应的下载命令,我们可以获取所需的文件,并开始进行目标检测。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值