【从零构建系列】YOLOv1

【从零构建系列】YOLOv1


(论文地址:https://arxiv.org/pdf/1506.02640.pdf)

模型介绍

在这里插入图片描述

以下为论文中的简述:

在YOLOv1提出之前,R-CNN系列算法在目标检测领域中独占鳌头。R-CNN系列检测精度高,但是由于其网络结构是双阶段(two-stage)的特点,使得它的检测速度不能满足实时性,饱受诟病。为了打破这一僵局,涉及一种速度更快的目标检测器是大势所趋。

2016年,Joseph Redmon、Santosh Divvala、Ross Girshick等人提出了一种单阶段(one-stage)的目标检测网络。它的检测速度非常快,每秒可以处理45帧图片,能够轻松地实时运行。由于其速度之快和其使用的特殊方法,作者将其取名为:You Only Look Once(也就是我们常说的YOLO的全称),并将该成果发表在了CVPR2016上,从而引起了广泛地关注。

YOLO的核心思想就是把目标检测转变成一个回归问题,利用整张图作为网络的输入,仅仅经过一个神经网络,得到bounding box(边界框)的位置及其所属的类别。


此处我们可以通过以下图点简单了解Faster-RCNNYOLO

在这里插入图片描述

总结:

  1. YOLOv1没了RPN这个结构,更加专注于在backbone中得到最终的结果。(从理论上说应该是没有anchor结构定义,采用的是网格结构,在后来的YOLOv2结构中仍旧是anchor结构)
  2. YOLOv1输出采用的是耦合头(分类+边框回归+置信度分数 在一起)

代码实现

要快速理解整个模型,必须先搞清楚它输入了什么,输出了什么(注意:目标检测中你不仅仅要知道我在上述图片中所表述的东西,也要知道正负样本划分,数据集的预处理),其次才是主干网络做了什么(这个时候你需要带入时代感,什么是时代感,就是说这个网络啥时候诞生的,这个时间点CNN、FCN、RCNN…都在干什么,为什么这样去思考,原因就是在于在后来的历史中主干网络其实有更多选择,比如编者在代码实现中主要使用MobileNetV3,此处就不解释了。最后你需要考虑如何训练,训练损失、优化器、是否启用衰减学习法等等…)

输入输出

输入数据展示(此处数据集来源于《动手学习深度学习》,目标为图中的香蕉,.jpg 格式)

注意:大小重要吗? 不重要,反正都可以统一Resize,除非你输入的图像宽高比很离谱

常见的处理方式:1、Resize 2、Pad

在这里插入图片描述

输出数据展示(.txt):

0 0.484375 0.152344 0.179688 0.164062

从左往右依次为“类别,真实框中心点坐标xy,宽,高”,都是按照占用原图宽高比例来计算的。(啊?你问为什么要这样做,因为如果是数值,那图片在进行数据增强的时候,岂不是惨了。)

然后如果图中有多个目标,则像如下内容生成即可。

0 0.484375 0.152344 0.179688 0.164062
0 0.484375 0.152344 0.179688 0.164062 # 此处仅仅是举例说明

数据差不多像这样

在这里插入图片描述

每一张图片对应它的标注

在这里插入图片描述

然后就是代码(此处不解释Dataset,DataLoader,看pytorch官网说明):

import numpy as np
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
import os
from PIL import Image
from torchvision.transforms import transforms


class YOLODataset(Dataset):
    def __init__(self, root_dir, s=7, b=2, c=1, transform=None):
        super().__init__()

        self.s = s  # 网格数量
        self.b = b  # bbox的数量
        self.c = c  # 分类数量
        self.transform = transform

        #  获取所有的图片地址
        images_path = os.path.join(root_dir, "images")
        image_names = os.listdir(images_path)
        self.image_urls = [os.path.join(images_path, name) for name in image_names]

        #  获取所有的图片标注信息
        labels_path = os.path.join(root_dir, "labels")
        label_names = os.listdir(labels_path)
        self.label_urls = [os.path.join(labels_path, name) for name in label_names]

    def __getitem__(self, index):
        image_path = self.image_urls[index]
        label_path = self.label_urls[index]

        with open(label_path, "r", encoding="utf-8") as f:
            bboxes = [[float(item) for item in line.strip().split()] for line in f.readlines()]
        bboxes = torch.tensor(bboxes)[:, [1, 2, 3, 4, 0]]

        img_pl = Image.open(image_path).convert("RGB")
        image = torch.from_numpy(np.array(img_pl)).permute([2, 0, 1])
        if self.transform:
            image = self.transform(image.float())

        target = torch.zeros((self.s, self.s, self.b * 5 + self.c))
        for bbox in bboxes:
            # 边框回归参数计算
            cx, cy, w, h, label = bbox.tolist()
            i = int(cx // (1 / self.s))
            j = int(cy // (1 / self.s))
            # 直接开方,一起前向传播。
            w_sqrt = w ** 0.5
            h_sqrt = h ** 0.5

            target[i, j, int(self.b * 5 + label)] = 1
            target[i, j, 0:5] = torch.tensor([cx, cy, w_sqrt, h_sqrt, 1.])

        return image, target

    def __len__(self):
        return len(self.image_urls)


def get_loader(root_dir, batch_size):
    transform = transforms.Compose([
        transforms.Resize((448, 448)),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    train_dataset = YOLODataset(os.path.join(root_dir, "train"), transform=transform)
    valid_dataset = YOLODataset(os.path.join(root_dir, "val"), transform=transform)

    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    valid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=True)
    return train_loader, valid_loader


# ================= 测试数据集加载的代码 ==================
if __name__ == '__main__':
    train_loader, valid_loader = get_loader("../data", 5)
    for image, target in train_loader:
        print(image.shape)
        print(target.shape)
        break

模型结构

官方模型结构如下:

在这里插入图片描述

我并没有采用官方说的那个模型,而是直接使用了mobilenetv3作为主干网络。(原因我已说明)

此处的主干网络,论文已有说明是预训练过后的网络,因此采用了torchvision.models的权重。

from torch import nn
from torchvision.models import mobilenet_v3_large, MobileNet_V3_Large_Weights


class YOLOv1(nn.Module):
    def __init__(self, s, b, c):
        super().__init__()

        self.s = s
        self.b = b
        self.c = c

        mobilenet = mobilenet_v3_large(weights=MobileNet_V3_Large_Weights.IMAGENET1K_V1)
        self.backbone = mobilenet.features

        self.conv = nn.Sequential(
            nn.Conv2d(960, 1024, 1),
            nn.BatchNorm2d(1024),
            nn.ReLU(),
            nn.Conv2d(1024, 1024, 3, 2, 1),
            nn.BatchNorm2d(1024),
            nn.ReLU(),
        )

        self.fc = nn.Sequential(
            nn.Flatten(),
            nn.Linear(1024 * 7 * 7, 1024),
            nn.ReLU(),
            nn.Linear(1024, int((self.b * 5 + self.c) * self.s * self.s))
        )

    def forward(self, x):
        x = self.backbone(x)
        x = self.conv(x)
        x = self.fc(x)
        return x.reshape(-1, int(self.b * 5 + self.c))

# ================= 测试模型结构的代码 ==================
if __name__ == '__main__':
    from torchsummary import summary
    model = YOLOv1(7, 2, 1)
    summary(model, (3, 448, 448), device="cpu")

模型训练

模型训练损失参照论文原图:

在这里插入图片描述

此处有几点内容你需要知道:

1、λ 参数是什么?λ coord 这是边框回归损失系数 设置为5, λ noobj 这是不包含对象框置信度损失系数 设置0.5

(为什么是5,0.5? 因为正负样本不平衡,负样本数量远大于正样本数量)

2、正负样本又是如何划分? 作者按照输出特征网格数量在原图中划分,如果预测中心在原图网格中则是正样本,反之则为负样本。如下图所示。

在这里插入图片描述

3、为什么w,h要开方,因为同样的均方差距离,对于小样本来说损失太大,大样本来说损失很小

在这里插入图片描述

from dataset.loader import get_loader
from backbone.mobilenet_yolo import YOLOv1
import torch
from torch import nn
from tqdm import tqdm

if __name__ == '__main__':
    train_loader, valid_loader = get_loader("./data", batch_size=5)
    s = 7
    b = 2
    c = 1
    device = torch.device("cuda")
    model = YOLOv1(s, b, c)
    model = model.to(device)

    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)

    lambda_coord = 5
    lambda_noobj = 0.5

    epochs = 1000
    for epoch in range(epochs):
        loss_list = []
        loop = tqdm(train_loader)
        for image, target in loop:
            image = image.to(device)
            target = target.reshape(-1, 5 * b + c)
            target = target.to(device)
            optimizer.zero_grad()
            predicts = model(image)

            # 包含目标对象的掩码
            mask_obj = target[:, 4] == 1
            # 不包含对象的掩码
            mask_noobj = target[:, 4] == 0

            # 位置损失
            loss1 = lambda_coord * criterion(predicts[mask_obj][:, 0:4], target[mask_obj][:, 0:4])
            # criterion(predicts[mask_obj][:, 5:9], target[mask_obj][:, 5:9])

            # 置信度损失
            loss2 = criterion(predicts[mask_obj][:, [4, 9]], target[mask_obj][:, [4, 9]]) + \
                    lambda_noobj * criterion(predicts[mask_noobj][:, [4, 9]], target[mask_noobj][:, [4, 9]])

            # 分类损失
            loss3 = criterion(predicts[mask_obj][:, 5 * b:], target[mask_obj][:, 5 * b:])

            loss = loss1 + loss2 + loss3
            loss.backward()
            optimizer.step()

            loop.set_postfix({"loss": f"{loss.item():.4f}"})
            loss_list.append(loss.item())

        avg_loss = sum(loss_list) / len(loss_list)
        print(f"epoch:{epoch + 1}/{epochs} -- loss:{avg_loss:.4f}")

性能表现

(1)优点

  1. YOLO检测速度非常快。标准版本的YOLO可以每秒处理45张图像;YOLO的极速版本每秒可以处理150帧图像。这就意味着YOLO可以小于25毫秒延迟,实时地处理视频。对于欠实时系统,在准确率保证的情况下,YOLO速度快于其他方法。
  2. YOLO实时检测的平均精度是其他实时监测系统的两倍。
  3. 迁移能力强,能运用到其他新的领域(比如艺术品目标检测)。

(2)局限性

  1. YOLO对相互靠近的物体,以及很小的群体检测效果不好,这是因为一个网格只预测了2个框,并且都只属于同一类。
  2. 由于损失函数的问题,定位误差是影响检测效果的主要原因,尤其是大小物体的处理上,还有待加强。(因为对于小的bounding boxes,small error影响更大)。
    5张图像;YOLO的极速版本每秒可以处理150帧图像。这就意味着YOLO可以小于25毫秒延迟,实时地处理视频。对于欠实时系统,在准确率保证的情况下,YOLO速度快于其他方法。
  3. YOLO实时检测的平均精度是其他实时监测系统的两倍。
  4. 迁移能力强,能运用到其他新的领域(比如艺术品目标检测)。

(2)局限性

  1. YOLO对相互靠近的物体,以及很小的群体检测效果不好,这是因为一个网格只预测了2个框,并且都只属于同一类。
  2. 由于损失函数的问题,定位误差是影响检测效果的主要原因,尤其是大小物体的处理上,还有待加强。(因为对于小的bounding boxes,small error影响更大)。
  3. YOLO对不常见的角度的目标泛化性性能偏弱。
### 构建和训练YOLOv8模型指南 #### 了解YOLO架构 YOLO(You Only Look Once)系列算法是目标检测领域的重要组成部分。YOLOv8作为最新版本,在保持实时性能的同时提高了准确性。为了从开始构建YOLOv8,理解其工作原理至关重要[^1]。 #### 准备环境 安装必要的依赖库对于启动项目非常重要。通常情况下,这涉及到Python及其科学计算包如NumPy、Pandas以及机器学习框架TensorFlow或PyTorch的选择。此外,还需要安装OpenCV用于图像处理操作。 ```bash pip install numpy pandas opencv-python torch torchvision torchaudio ``` #### 数据集准备 高质量的数据集决定了模型的好坏。收集并标注大量图片样本,确保类别分布均匀合理。常用工具LabelImg可以帮助完成这项任务。数据预处理阶段还包括增强技术的应用,比如随机裁剪、翻转等方法来增加多样性。 #### 模型定义 基于先前研究基础上设计网络结构。YOLOv8可能采用更深层次的卷积层组合方式,并引入注意力机制提升特征提取能力。下面是一个简化版的YOLOv8骨干网实现: ```python import torch.nn as nn class YOLOv8(nn.Module): def __init__(self, num_classes=80): super(YOLOv8, self).__init__() # 定义各部分组件... def forward(self, x): pass # 实现前向传播逻辑 def main(): model = YOLOv8() if __name__ == '__main__': main() ``` #### 训练过程配置 编写适合特定应用场景下的损失函数与优化器策略。考虑到多尺度预测特性,需特别注意锚框尺寸设定及正负样本比例调整等问题。同时利用迁移学习加速收敛速度也是一个不错的选择。 #### 测试评估指标 最后一步是对已训练好的模型进行全面测试验证。通过mAP(mean Average Precision)衡量整体表现;而针对具体需求还可以考虑其他补充性评价标准,例如召回率(recall),精确度(precision)等。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值