pytorch yolo复现

pytorch yolo复现

想着入门pytorch,用pytorch复现一下yolo算法,其实yolo的原理一天左右就完全搞懂了,但是真正写起代码来,就是会有各种细节不太清除,我是先从吴恩达的视频开始,然后参考着两位大佬的复现代码eriklindernoren的代码bubbliiiing的代码,可能是我对pytorch还不太熟悉,陆陆续续搞了一个星期才写完了自己的yolo复现,并在coco2014voc2007的数据集进行了训练,代码同步github

搭建YOLOv3模型

其实yolov3的模型十分简单,看起来很吓唬人,其实就一个主干网络darknet53加卷积块convolutional set与简单卷积核和上采样时的特征合并,下面这张图很全面低反映了整个网络的细节,因为网上的很多图片并没有把具体的细节,如通道数与padding展示出来,而且在非主干网络上也没有标明输出,因此我在图上用蓝色字添加上了通道数 输出大小的信息,padding的话除了核为1*1的padding为0,其余的padding都为1,也就是说,只有步长为2的时候,才不是“same”的padding

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cGmH6fJd-1637741609883)(README.assets/2019040211084050.png)]

从图上可以看出,其实整个几个部分:darknet53convolutional set最后的输出层上采样层,但是他们中很多部分都由**普通的卷积层(带激活与正则化)**组成。

1.普通的卷积层(带激活与正则化)

#==================================#
#          简单的卷积层                      
#==================================#
class Conv(nn.Module):
    def __init__(self, inputC, outputC, keralSize, stride = 1, padding = "same") -> None:
        super(Conv, self).__init__()
        self.m = nn.Sequential(
            nn.Conv2d(inputC, outputC, keralSize, stride, padding, bias=False),
            nn.BatchNorm2d(outputC),
            nn.LeakyReLU(0.1)
        )
    def forward(self, x):
        return self.m(x)

卷积—>正则化---->激活

2.残差块

其实残差块也是由普通卷积层构成,只是有跳连接的部分。

#==================================#
#             残差块                      
#==================================#
class Residual(nn.Module):
    def __init__(self, inputC) -> None:
        super(Residual, self).__init__()
        tempC = inputC // 2
        self.m = nn.Sequential(
            Conv(inputC, tempC, 1, 1, 0),
            Conv(tempC, inputC, 3, 1, 1)
        )
    def forward(self, x):
        return x + self.m(x)

3.convolutional set

按照上图中的结构,可以很容易写出convolutional set,也是由普通残差块组成而已

#==================================#
#           convSet                    
#==================================#
class convSet(nn.Module):
    def __init__(self, inputC, outputC, midC) -> None:
        super(convSet, self).__init__()
        self.m = nn.Sequential(
            Conv(inputC, outputC, 1),
            Conv(outputC, midC, 3),
            Conv(midC, outputC, 1),
            Conv(outputC, midC, 3),
            Conv(midC, outputC, 1),
        )
    def forward(self, x):
        return self.m(x)

4.输出层

输出层有三个,分别输出13*13、26*26、52*52的图像,这里我添加了创建对应格子的网络grid的过程,后面会用到,对于模型来说没有影响

#==================================#
#           lastLayer                   
#==================================#
class LastLayer(nn.Module):
    def __init__(self, inputC, outputC, anchor=None) -> None:
        super(LastLayer, self).__init__()
        self.grid = None
        self.anchor = np.array(anchor)
        self.anchorScaled = []
        self.stride = 1
        self.shape = None
        self.m = nn.Sequential(
            Conv(inputC, inputC * 2, 3),
            nn.Conv2d(inputC * 2, outputC, 1)
        )
    def forward(self, x):
        o = self.m(x)
        if self.grid is None:
            self._createGrid(o.shape)
        return o
    def _createGrid(self, shape):
        b,c,h,w = shape
        self.shape = (h, w)
        self.stride = CONST.inputShape[0] / h
        self.anchorScaled = torch.tensor(self.anchor / self.stride, device=CONST.device)
        grid = torch.ones((b,len(self.anchor),h,w,4),device=CONST.device)
        gridY, gridX = torch.meshgrid(torch.arange(h), torch.arange(w), indexing="ij")
        grid[...,0] *= gridX.to(CONST.device).unsqueeze(0)
        grid[...,1] *= gridY.to(CONST.device).unsqueeze(0)
        grid[...,2] *= self.anchorScaled[:,0].view(1,len(self.anchor),1,1)
        grid[...,3] *= self.anchorScaled[:,1].view(1,len(self.anchor),1,1)
        self.grid = grid

5.主干的darknet53

#==================================#
#           darknet53                       
#==================================#
class Darknet53(nn.Module):
    def __init__(self) -> None:
        super(Darknet53, self).__init__()
        # 定义darknet53的层数
        self.layoutNumber = [1, 2, 8, 8, 4]
        self.layerA = nn.Sequential(
            Conv(3, 32, 3, 1, 1),
            self.MultiResidual(32, 64, 1),
            self.MultiResidual(64, 128, 2),
            self.MultiResidual(128, 256, 8)
        )
        self.layerB = self.MultiResidual(256, 512, 8)
        self.layerC = self.MultiResidual(512, 1024, 4)

        # 进行权值初始化
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
    
    def forward(self, x):
        out1 = self.layerA(x)
        out2 = self.layerB(out1)
        out3 = self.layerC(out2)
        return out1, out2, out3

    # 多层的残差网络
    def MultiResidual(self, inputC, outputC, count):
        t = [Conv(inputC, outputC, 3, 2, 1) if i == 0 else Residual(outputC) for i in range(count + 1)]
        return nn.Sequential(*t)

6.最终的yolo模型

将上面的几个部分和在一起,就是最终的yolo模型了

#==================================#
#           定义yolo模型                        
#==================================#
class MyYOLO(nn.Module):
    def __init__(self) -> None:
        super(MyYOLO, self).__init__()
        # 得到 1024*13*13
        self.darknet53 = Darknet53()
        # 得到 512*13*13
        self.convSet1 = convSet(1024, 512, 1024)
        # 得到 256*26*26, 但是后面要和另一层的输出合起来,得到的应该是 (512+256)*26*26
        self.layerA = nn.Sequential(
            Conv(512, 256, 1),
            nn.Upsample(scale_factor=2, mode='nearest')
        )
        # 得到 256*26*26
        self.convSet2 = convSet(256 + 512, 256, 512)
        
        # 得到 128*52*52, 但是后面要和另一层的输出合起来,得到的应该是 (128+256)*52*52
        self.layerB = nn.Sequential(
            Conv(256, 128, 1),
            nn.Upsample(scale_factor=2, mode='nearest')
        )
        # 得到 256*26*26
        self.convSet3 = convSet(128 + 256, 128, 256)

        # 得到 75*13*13
        self.predict1 = LastLayer(512, CONST.anchorNumber * (5 + CONST.classNumber), anchor=CONST.anchor[0])
        # 得到 75*26*26
        self.predict2 = LastLayer(256, CONST.anchorNumber * (5 + CONST.classNumber), anchor=CONST.anchor[1])
        # 得到 75*52*52
        self.predict3= LastLayer(128, CONST.anchorNumber * (5 + CONST.classNumber), anchor=CONST.anchor[2])
        self.lastLayers = [self.predict1, self.predict2, self.predict3]
    def forward(self, x):
        x1, x2, x3 = self.darknet53(x)
        convOut1 = self.convSet1(x3)
        out1 = self.predict1(convOut1)
        layerOut = self.layerA(convOut1)
        additon = torch.cat([layerOut, x2], 1)
        convOut2 = self.convSet2(additon)
        out2 = self.predict2(convOut2)
        layerOut = self.layerB(convOut2)
        additon = torch.cat([layerOut, x1], 1)
        convOut3 = self.convSet3(additon)
        out3 = self.predict3(convOut3)
        return out1, out2, out3

到目前为止,整个yolo模型构建完成

模型输出的是一个长度为3的list。每个list的大小分别是

  • [batch_size,先验框数量,13,13,5+类别数]
  • [batch_size,先验框数量,26,26,5+类别数]
  • [batch_size,先验框数量,52,52,5+类别数]

训练

1.获取数据集

使用pytorch自带的datasetdataloader进行数据集的加载,先从网上下载coco数据集,在data文件夹里如下图所示进行放置。

─coco
  ├─images
  │  ├─train2014
  │  └─val2014
  └─instances_train2014.json
  └─instances_val2014.json

然后使用handleData.py来进行数据集的预处理,生成标签与对应的目标框,并将目标框变为[x,y,w,h],中心点与宽高模式。在label文件夹下生成对应的label。

16 0.6066874999999999 0.3413807531380753 0.54415625 0.51

2.数据增强

利用imgaug模块进行数据的预处理与增强,包括将图片保持宽高比地缩放为(416,416)大小,位移,镜像等操作

# 应用iaa的图像数据增强类
class imgAug():
    def __init__(self) -> None:
        self.argument = None
    def __call__(self, data):
        img, boxes = data
        bbs = []
        for item in boxes:
            bbs.append(BoundingBox(*item[1:], label=item[0]))
        bbs = BoundingBoxesOnImage(bbs, shape=img.shape)
        img, bbs = self.argument(image = img, bounding_boxes=bbs)
        bbs = bbs.clip_out_of_image()
        for i, item in enumerate(bbs):
            boxes[i,:] = np.array([item.label, item.x1, item.y1, item.x2, item.y2])
        return img, boxes
# 训练集数据增强
TRAIN_TRANSFORMS = transforms.Compose([
    AbsoluteLabel(),
    xywh2xyxy(),
    ImgUp(),
    CenterPlcae(),
    ReSize(),
    RelativeLabel(),
    xyxy2xywh(),
    ToTensor()
])

3.损失函数

损失函数实际上就是:期望yolo模型的输出与实际yolo模型输出的损失,分别计算输出的x/y/w/h/置信度/分类的交叉熵损失,合并,再反向传播即可。

  • 处理目标框,通过交并比得到究竟是哪个先验框负责预测这个目标框,将先验框的索引记录下来,将所有目标框处理成格式如下的二维数组
    • [对应batch中第几张图,类别,x,y,w,h,先验框索引]
    • 并附上对应的格子索引ij
  • 通过目标框处理后的数据可以得到期望yolo模型的输出
  • 预测值与期望值做交叉熵损失
#==================================#
#           损失函数       
#==================================#
def getLoss(yoloOut, yolo,bboxes):
    BCELoss = nn.BCELoss()
    MSELoss = nn.MSELoss()
    bboxes = torch.cat([bboxes, torch.zeros(bboxes.shape[0],1,device=CONST.device)], 1)
    anchorRelate = torch.tensor(CONST.anchor, device=CONST.device).view(-1,2) / 416
    anchorRelate = torch.cat([torch.zeros_like(anchorRelate), anchorRelate], 1)
    boxesWH = torch.cat([torch.zeros_like(bboxes[:,4:6]), bboxes[:,4:6]], 1)
    for i,item in enumerate(boxesWH):
        bboxes[i][6] = torch.argmax(iou(item, anchorRelate)) # [bs, cls, x,y,w,h,an]
    # print(bboxes)
    loss = 0
    for l,output in enumerate(yoloOut):
        lastLayer = yolo.lastLayers[l]
        ba,c,h,w = output.shape
        output = output.view(ba,len(lastLayer.anchor),-1,h,w).permute(0,1,3,4,2).contiguous()
        b, cls, boxesScaled, an, i, j = buildTarget(bboxes, lastLayer, l)
        tConf = torch.zeros_like(output[..., 4], device=CONST.device)
        xLoss,yLoss,wLoss,hLoss,clsLoss = [0,0,0,0,0]
        if b.shape[0] != 0:
            pr = output[b, an, i, j] # type:torch.Tensor
            tConf[b, an, i, j] = 1
            pr[:,:2] = pr[:,:2].sigmoid()
            xLoss = BCELoss(pr[..., 0], boxesScaled[...,0])
            yLoss = BCELoss(pr[..., 1], boxesScaled[...,1])
            wLoss = MSELoss(pr[..., 2], boxesScaled[...,2]) * 0.5
            hLoss = MSELoss(pr[..., 3], boxesScaled[...,3]) * 0.5
            clsLoss = BCELoss(pr[:,5:].sigmoid(), cls)
        confLoss = BCELoss(output[..., 4].sigmoid(),tConf)
        loss = loss + xLoss + yLoss + wLoss + hLoss + clsLoss + confLoss
    return loss

4.训练

利用pytorch提供的optim进行训练即可

预测

  • 读取图片,进行预处理成训练时一样的格式
  • 放入网络前向传播,将输出整理成(batch_size,10647,类别数+5)
  • 将所有输出的框与其先验框进行合并,整理为绝对值的框
  • 对整理后的框进行置信度的筛选后,进行非极大值抑制
  • 抑制后的结果既为预测结果,在原图上绘制

非极大值抑制

#==================================#
#        一个框与多个框的交并比                        
#==================================#
def iou(box1: torch.Tensor, box2:torch.Tensor, isleftT2rightD = True) -> torch.Tensor:
    # box1 的shape为(1, 4), box2的shape为(None, 4)
    # 防止输入错误
    box1 = box1.view(-1,4)
    box2 = box2.view(-1,4)
    box1 = box1.repeat((box2.shape[0], 1))
    if not isleftT2rightD:
        box1 = torch.concat([box1[:,:2] - box1[:,2:4] / 2, box1[:,:2] + box1[:,2:4] / 2], 1).cuda()
        box2 = torch.concat([box2[:,:2] - box2[:,2:4] / 2, box2[:,:2] + box2[:,2:4] / 2], 1).cuda()
    # 交集左上角的点
    lu = torch.max(box1[:, :2], box2[:, :2])
    # 交集右下角的点
    rd = torch.min(box1[:, 2:], box2[:, 2:])
    rectsN = rd - lu
    rectsN[rectsN < 0] = 0#没有重叠区域设置为0
    rectsN = rectsN[:,0] * rectsN[:,1]
    rectsU = (box1[:,2] - box1[:,0]) * (box1[:,3] - box1[:,1]) + (box2[:,2] - box2[:,0]) * (box2[:,3] - box2[:,1])
    return rectsN / (rectsU - rectsN)
#==================================#
#           非极大值抑制                        
#==================================#
def nms(box: torch.Tensor = None, score: torch.Tensor = None,threshold: float = 0.3) -> None:
    _, sortIndex =  score.sort(0, descending = True)
    res = []
    while sortIndex.size(0):
        if sortIndex.size(0) == 1:
            res.append(sortIndex[0].item())
            break
        res.append(sortIndex[0].item())
        ious = iou(box[sortIndex[0]], box[sortIndex[1:]])
        sortIndex = sortIndex[1:][ious < threshold]
    return  res

验证

对模型进行验证,使用的指标为RPMP

  • 先进行预测

  • 遍历所有框,将预测框与真实框的交并比大于阈值,并且类别正确的标记为1,其余标记为0,整理成list,并且以置信度进行排序

  • 对list梯形求和,总和除以总数为p,总和除以真实标签的总和为R,对多个pr求面积为map

#==================================#
#           计算模型参数               
#==================================#
def calMap(isCor, preConf, preLabels, targetLabels):
    sI = np.argsort(-preConf)
    isCor = isCor[sI]
    preConf = preConf[sI]
    preLabels = preLabels[sI]
    uClasses = np.unique(targetLabels)
    R = []
    P = []
    AP = []
    for oneCls in uClasses:
        sI = preLabels == oneCls
        isCorOneCls = isCor[sI]

        targetLabelsOneCls = targetLabels[targetLabels == oneCls]
        tarTrueC = targetLabelsOneCls.size # 目标框为该类的数量
        preTrueC = isCorOneCls.size # 预测框为该类的数量

        if preTrueC == 0:
            R.append(0)
            P.append(0)
            AP.append(0)
            continue
        tpC = isCorOneCls.cumsum()
        fpC = (1 - isCorOneCls).cumsum()

        r = tpC / tarTrueC
        p = tpC / (tpC + fpC)
        R.append(r[-1])
        P.append(p[-1])
        # 在前面添加是往前取矩形,在后面添加是让召回率可以达到1
        r = np.concatenate(([0.0], r, [1.0]))
        p = np.concatenate(([0.0], p, [0.0]))
        # 保证p单调递减
        for i in range(p.size - 1, 0, -1):
            p[i - 1] = max(p[i], p[i - 1])
        # 删除重复项
        i = np.where(r[1:] != r[:-1])[0]
        ap = np.sum((r[i+1] - r[i]) * p[i+1])
        AP.append(ap)
    return R,P,AP,uClasses
        
#==================================#
#           show MP            
#==================================#
def showMap(R,P,AP,uClasses):
    res = [["class","AP", "R", "P"]]
    for i,_ in enumerate(uClasses):
        res.append([CONST.classes[int(uClasses[i])], "%.4f" % AP[i], "%.4f" % R[i], "%.4f" % P[i]])
    res.append([])
    res.append(["MAP", "%.4f" % np.average(AP)])
    print(AsciiTable(res).table)
  • 10
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 您好,以下是使用PyTorchYOLOv4的代码: 1. 首先,您需要下载YOLOv4的权重文件,可以从以下链接中下载: https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights 2. 接下来,您需要安装PyTorch和OpenCV库,可以使用以下命令进行安装: ``` pip install torch pip install opencv-python ``` 3. 然后,您可以使用以下代码加载YOLOv4的权重文件并进行目标检测: ``` import cv2 import torch import numpy as np # 加载YOLOv4的权重文件 model = torch.hub.load('ultralytics/yolov5', 'yolov4', pretrained=True) # 加载图像 img = cv2.imread('test.jpg') # 进行目标检测 results = model(img) # 显示检测结果 results.show() ``` 其中,'test.jpg'是您要进行目标检测的图像文件名。 希望以上代码能够帮助到您。 ### 回答2: YOLOv4是目前最先进的物体检测算法之一,它结合了YOLOv3 和 Scaled-YOLOv4的优势,具有更高的召回率和准确度。PyTorch是一种常用的深度学习框架,可以快速实深度学习算法。 实YOLOv4的方法有多种,其中一种是基于开源代码进行。 首先,需要准备数据集。数据集应包含图片和对应的标签,标签一般以XML或JSON格式保存。YOLOv4要求输入的图片尺寸是416x416,因此需要对原始图片进行缩放。可以使用OpenCV库来实。 然后,需要下载YOLOv4的预训练模型文件。可以从互联网上下载Darknet和PyTorch版本的模型文件。Darknet版本的模型文件通常以.weights格式保存,而PyTorch版本的模型文件通常以.pt格式保存。这两种格式都可以通过相应的代码进行加载和转换。 接下来,需要编写Python程序来实YOLOv4的物体检测功能。可以使用PyTorch和其它相关库来实。具体的代码实可以分为以下几步: 1. 加载预训练模型 利用PyTorch中的torch.load函数,将预训练模型加载到内存中。 2. 定义模型 定义模型的结构,包括卷积层、池化层、残差块和最终的检测层。 3. 加载图片和标签 使用相应的库读取图片和标签,并将其转换为张量形式。 4. 图像预处理 对图片进行预处理,包括缩放、裁剪、归一化和转换为张量形式。 5. 模型推理 将图片输入模型,获取物体检测的结果。检测结果包括物体类别、位置和置信度。 6. 后处理 将检测结果进行后处理,包括筛选、非极大值抑制和将边界框转换为坐标形式。 最后,运行程序即可实YOLOv4的物体检测功能。需要注意的是,YOLOv4的精度和速度都非常高,但是在硬件资源和算法优化方面的要求也比较高。为了实最佳的检测效果,需要进行一系列的细节优化。 ### 回答3: YOLOv4是一个非常流行的目标检测模型,由于其高效性能和较高的准确率,成为了许多研究人员的选择。在本文中,我们将介绍如何使用PyTorchYOLOv4的代码。 1. 数据集的准备: 在YOLOv4模型之前,我们需要准备一个数据集来训练模型。数据集应该包含许多训练图片和对应的标注信息。通常情况下,标注信息包括每张图片中目标的类别、位置和大小信息。 2. 下载YOLOv4的模型代码: 使用以下命令从GitHub上下载YOLOv4的代码: ``` !git clone https://github.com/WongKinYiu/yolov4-pytorch.git ``` 3. 修改文件夹结构: 为了更好地适应我们的数据集,我们需要修改YOLOv4代码库的文件夹结构。在代码库的“data”目录下新建“custom”目录,将训练数据集拷贝至该目录下,并在“data/custom”目录下创建“images”和“labels”两个子目录,分别存放数据集的图片和标注信息。 4. 修改配置文件: 在“cfg”目录下找到“yolov4.cfg”文件,修改该文件以适应我们的数据集。具体来说,需要将模型的输入大小修改为我们训练数据集的大小,修改检测头的类别数量为我们数据集中目标的种类数。 5. 训练模型: 要训练模型,我们需要运行如下命令: ``` !python train.py --cfg cfg/yolov4.cfg --data data/custom/custom.data --weights '' --name yolov4-custom ``` 其中,“--cfg”指定配置文件路径,“--data”指定数据集信息文件路径,“--name”指定训练模型的名称。 6. 应用模型: 我们可以使用训练得到的模型来检测新的图片。运行以下命令即可进行检测: ``` !python detect.py --weights checkpoints/yolov4-custom.pth --img-size 416 --conf-thres 0.3 --source data/samples/ ``` 其中,“--weights”指定使用的模型路径,“--img-size”指定输入图片的大小,“--conf-thres”指定过滤掉置信度较低的边界框,“--source”指定检测的图片路径。 在以上过程中,我们可以根据实际情况进行修改以适应我们的数据集。通过修改数据集、配置文件和运行命令,我们可以使用PyTorchYOLOv4模型,从而实目标检测的应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值