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为yolo用c还是python yolo v1 pytorch_深度学习的大小,resize的目的是为了满足全连接层的固定输入维度的要求
  • 代码:
import cv2
img = cv2.resize(img, (448,448))
  • 将448的图像划分为yolo用c还是python yolo v1 pytorch_pytorch_02的格点,每个格点边长为64个像素点图解:
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7qmIDoI-1635915412647)(./1635745220607.png)]
将图像输入到YOLO v1网络前向传播:
  • 由于YOLO前边的网络主要作用是特征提取,因此使用resnet的全连接之前的部分代替,通过加载torchvision中的已经训练好的resnet34,并固定参数,后边跟YOLOv1后半部分卷积结构和全连接层变化结构
  • 每个网格要预测2个bounding box,每个bounding box除了要预测位置之外,还要附带预测一个confiden值,每个网格还要预测C个类别的分数
  • 网格有yolo用c还是python yolo v1 pytorch_pytorch_02每个网格2个边界框
  • 也就是每个bounding box 预测5个值,其中四个为位置参数(相对值,相对于整个图像大小来说),confidence值,这里有两个,就是yolo用c还是python yolo v1 pytorch_2d_04
  • VOC数据集有20个类别,则类别数为20
  • 所以一共有参数yolo用c还是python yolo v1 pytorch_计算机视觉_05
    *
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损失
  • yolo用c还是python yolo v1 pytorch_2d_06
  • confidence 损失
  • yolo用c还是python yolo v1 pytorch_深度学习_07
  • classes 损失
  • yolo用c还是python yolo v1 pytorch_2d_08
  • 总的损失为所有三种损失的损失相加
  • yolo用c还是python yolo v1 pytorch_深度学习_09
  • 其中yolo用c还是python yolo v1 pytorch_2d_10为平衡系数
  • 在计算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
  • yolo用c还是python yolo v1 pytorch_pytorch_11
  • 物理意义为:IoU为交集部分面积与并集部分面积之比,当两个Box完全重合时IoU=1,不相交时IoU=0
  • 本部分代码采用沐神课程中的代码,在我之前的blog中有详细解析,链接:
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,将网络输出为yolo用c还是python yolo v1 pytorch_pytorch_12的数据格式与label计算,label转化为与网络输出一致的格式yolo用c还是python yolo v1 pytorch_pytorch_12得到;
  • 在计算中先计算格点预设两个边框与真实边框的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:///lavendelion/YOLOv1-from-scratch