【目标检测从零开始】torch搭建yolov3模型

news2025/4/5 13:10:56

用torch从0简单实现一个的yolov3模型,主要分为Backbone、Neck、Head三部分

目录

  • Backbone:DarkNet53
    • 结构简介
    • 代码实现
      • Step1:导入相关库
      • Step2:搭建基本的Conv-BN-LeakyReLU
      • Step3:组成残差连接块
      • Step4:搭建DarkNet53
      • Step5: 测试
  • Neck:Spatial Pyramid Pooling(SPP)
    • 结构简介
    • 代码实现
      • Step1 导入相关库
      • Step2 搭建SPPLayer
      • Step3 测试
  • Head:Conv
    • 结构简介
    • 代码实现
      • Step1 模型搭建
      • Step2 测试
  • YOLOV3
    • 模型简介
    • 代码实现
      • Step1 导入上述三个模块和相关库
      • Step2 搭建YOLOV3模型
      • Step3 测试
  • 小结

Backbone:DarkNet53

结构简介

  • 输入层:接受图像数据作为模型输入,以长宽均为640像素的图像为例:(BatchSize,3,640,640)
  • 隐藏层:
    • 残差块:每个残差块由多个卷积(Conv)、批归一化(BN)、激活函数(Relu)组成
    • 下采样:利用 stride = 2 的卷积层来进行下采样,减小特征图尺寸
  • 输出层:保留最后3个尺度的特征图并返回

在这里插入图片描述

代码实现

Step1:导入相关库

import torch
import torch.nn as nn
from torchsummary import summary

Step2:搭建基本的Conv-BN-LeakyReLU

  • 一层卷积 + 一层B批归一化 + 一层激活函数
def ConvBnRelu(in_channels, out_channels, kernel_size=(3,3), stride=(1,1), padding=1):
    """
    一层卷积 + 一层 BatchNorm + 一层激活函数
    :param in_channels:  输入维度
    :param out_channels: 输出维度(卷积核个数)
    :param kernel_size:  卷积核大小
    :param stride:       卷积核步长
    :param padding:      填充
    :return:
    """
    return nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, bias=False),
        nn.BatchNorm2d(out_channels),
        nn.LeakyReLU())

Step3:组成残差连接块

  1. 小单元:ConvBNRelu
  2. 堆叠两个小单元
  3. 残差连接
class DarkResidualBlock(nn.Module):
    """
    DarkNet残差单元
    """
    def __init__(self, channels):
        super(DarkResidualBlock, self).__init__()
        reduced_channels = int(channels // 2)
        
        self.layer1 = ConvBnRelu(channels, reduced_channels, kernel_size=(1,1), padding=0)
        self.layer2 = ConvBnRelu(reduced_channels, channels)

    def forward(self, x):
        residual = x
        out = self.layer1(x)
        out = self.layer2(out)
        out += residual # 残差连接
        return out

Step4:搭建DarkNet53

  • 根据DarkNet各block的个数搭建网络模型
    • conv1用来把输入3维的图片卷积卷成32维
    • conv2 ~ conv6 都会用一个stride = 2的卷积层进行下采样
    • 每一个residual_block都是带有残差连接的2层ConvBNRelu
  • 原始DarkNet是用来做图像分类,作为Backbone只需要提取最后3个尺度特征图就好,不需要最后的池化层和全连接层
class DarkNet53(nn.Module):
    """
    Darknet53网络结构
    """
    def __init__(self, in_channels = 3, num_classes = 80, backbone = True):
        """
        :param in_channels:
        :param num_classes:
        :param backbone:
        """
        super(DarkNet53, self).__init__()
        self.backbone = backbone
        
        self.num_classes = num_classes
        
        # blocks : [1,2,8,8,4]
        self.conv1 = ConvBnRelu(in_channels,32)
        self.conv2 = ConvBnRelu(32,64,stride=2)
        self.residual_block1 = self.make_layer(DarkResidualBlock, in_channels=64, num_blocks=1)
        self.conv3 = ConvBnRelu(64,128,stride=2)
        self.residual_block2 = self.make_layer(DarkResidualBlock, in_channels=128, num_blocks=2)
        self.conv4 = ConvBnRelu(128,256,stride=2)
        self.residual_block3 = self.make_layer(DarkResidualBlock, in_channels=256, num_blocks=8)
        self.conv5 = ConvBnRelu(256,512,stride=2)
        self.residual_block4 = self.make_layer(DarkResidualBlock, in_channels=512, num_blocks=8)
        self.conv6 = ConvBnRelu(512,1024,stride=2)
        self.residual_block5 = self.make_layer(DarkResidualBlock, in_channels=1024, num_blocks=4)
        self.global_avg_pooling = nn.AdaptiveAvgPool2d((1,1))
        self.fc = nn.Linear(1024, self.num_classes)

    def make_layer(self, block, in_channels, num_blocks):
        """
        构建 num_blocks 个 conv_bn_relu, 组成residual_block
        :param block: 待堆叠的 block : DarkResidualBlock
        :param in_channels: 输入维度
        :param num_blocks: block个数
        :return:
        """
        layers = []
        for i in range(num_blocks):
            layers.append(block(in_channels))
        # print(*layers)
        return nn.Sequential(*layers)
    
    def forward(self, x):
        features = []
        
        out = self.conv1(x) # 3 -> 32
        
        out = self.conv2(out) # 下采样
        out = self.residual_block1(out)
        
        out = self.conv3(out) # 下采样
        out = self.residual_block2(out)
        
        out = self.conv4(out) # 下采样
        out = self.residual_block3(out) # fea1 : 8倍下采样
        fea1 = out
        
        out = self.conv5(out) # 下采样
        out = self.residual_block4(out) # fea2 : 16倍下采样
        fea2 = out

        out = self.conv6(out) # 下采样
        out = self.residual_block5(out) # fea3 : 32倍下采样
        fea3 = out

        out = self.global_avg_pooling(out)
        out = out.view(-1, 1024)
        out = self.fc(out)
        
        if self.backbone: # 返回最后3个尺度特征图
            features = [fea1, fea2, fea3]
            return features
            
        return out

Step5: 测试

if __name__ == '__main__':
    x = torch.randn(size=(4,3,640,640))
    model = DarkNet53(backbone=True)
    out = model(x)
    print(summary(model, (3,640,640), device='cpu'))
    for x in out:
        print(x.shape)

在这里插入图片描述

通过以上Backbone后提取到输入图片的3个尺度特征图,640*640的图片分别经过8倍、16倍、32倍下采样得到80×80,40×40,20×20的特征图,随着尺度的增加,通道维度也随之增加

Neck:Spatial Pyramid Pooling(SPP)

结构简介

采用空间金字塔池化层,用于处理不同尺度的特征信息

  • 输入层:DarkNet53出来的特征图
  • 隐藏层:
    • 空间金字塔池化(Spatial Pyramid Pooling):一种多尺度池化的策略,允许网络在处理不同尺寸的目标时更加灵活。通过使用不同大小的池化核或步幅,该模块将输入特征图划分为多个不同大小的网格,然后对每个网格进行池化操作。这些池化操作的结果被连接在一起,形成一个具有多尺度信息的特征向量。
    • 连接全局平均池化:除了空间金字塔池化,全局平均池化也通常被添加到模块的最后一层。这有助于捕捉整个特征图的全局上下文信息。全局平均池化将整个特征图降维为一个固定大小的特征向量。
    • 通道扩展:为了进一步提高语义信息的表达能力,通常会添加适量的卷积操作,增加特征图的通道数。
  • 输出层:输出特征图,保持与输入特征尺寸和维度一致
    在这里插入图片描述

代码实现

Step1 导入相关库

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

Step2 搭建SPPLayer

主要两个模块:

  • 池化层:根据pool_size进行多尺度池化
  • 卷积层:变换拼接后的特征图维度,保证前后特征图大小一致
class SPPLayer(nn.Module):
    def __init__(self, channels, pool_sizes = [1,2,4]):
        super(SPPLayer, self).__init__()
        self.pool_sizes = pool_sizes
        self.num_pools = len(pool_sizes)
        
        # num_pools个池化层
        self.pools = nn.ModuleList()
        for pool_size in pool_sizes:
            self.pools.append(nn.AdaptiveAvgPool2d(pool_size))
            
        # 卷积层 1*1
        self.conv = nn.Conv2d(channels * (1 + self.num_pools), channels, kernel_size=(1,1), stride=(1,1),padding=0)
        
    def forward(self, x):
        # 保存 h,w
        input_size = x.size()[2:]
        # 池化 + 插值
        # 池化后插值回原始特征图大小
        pool_outs = [F.interpolate(pool(x), size=input_size, mode='bilinear')for pool in self.pools] 
        # 拼接
        spp_out = torch.cat([x] + pool_outs, dim = 1) # 通道维度拼接
        # conv
        spp_out = self.conv(spp_out)
        return spp_out

Step3 测试

if __name__ == '__main__':
    x = torch.randn(size=(4,256,80,80)) # 取backbone出来的第一个尺度特征图
    model = SPPLayer(channels = 256, pool_sizes = [1,2,4,8]) # 4个不同尺度的池化层
    out = model(x)
    print('SppLayer output shape: ', out.shape) # (4,256,80,80)

在这里插入图片描述

Head:Conv

结构简介

head部分采用最简单的两层卷积结构,将每一个尺度的特征图维度变换为 (4 + 1 + num_classes) * num_anchors

  • 4:每个框有4个坐标值(xyxy或xywh),这四个值表示锚框的偏移量
  • 1:每个锚框包含目标的概率
  • num_classes:检测类别数量
  • num_anchors:锚框数量

代码实现

Step1 模型搭建

import torch
import torch.nn as nn

class BaseHead(nn.Module):
    def __init__(self,in_channels, num_anchors, num_classes):
        super(BaseHead, self).__init__()
        # 没有算上背景类
        self.predict = nn.Sequential(
            nn.Conv2d(in_channels, in_channels, kernel_size=(1, 1), stride=(1, 1), padding=0),
            nn.Conv2d(in_channels, num_anchors * (4 + 1 + num_classes), kernel_size=(1, 1), stride=(1, 1), padding=0)
        )
        
    def forward(self, x):
        x = self.predict(x)
        return x

Step2 测试

if __name__ == '__main__':
    # 示例:创建一个具有3个锚框和80个类别的检测头
    detection_head = BaseHead(in_channels=256, num_anchors=3, num_classes=80)
    # Neck部分出来的特征图(256通道,80*80的特征图)
    example_input = torch.randn((4, 256, 80, 80))
    # 前向传播
    output = detection_head(example_input)
    # 打印输出形状
    print('head out shape',output.shape)

在这里插入图片描述

255 = (4 + 1 + 80) * 3 <==> num_anchors * (4 + 1 + num_classes)

YOLOV3

模型简介

在搭建好backbone、neck、head后,拼成YOLOv3,注意3个尺度特征图分别检测。图片摘自此处
在这里插入图片描述

代码实现

Step1 导入上述三个模块和相关库

import torch
import torch.nn as nn

from models.backbone.darknet import DarkNet53
from models.head.conv_head import BaseHead
from models.neck.spp import SPPLayer

Step2 搭建YOLOV3模型

class YOLOv3(nn.Module):
    def __init__(self, in_channels, num_anchors, num_classes=80):
        super(YOLOv3, self).__init__()
        self.in_channels = in_channels
        self.num_anchors = num_anchors
        
        self.backbone = DarkNet53(in_channels=in_channels, backbone=True)
        
        # 单尺度
        self.neck = SPPLayer(channels=256)
        self.head = BaseHead(in_channels=256, num_anchors=num_anchors, num_classes=num_classes)
        
        # #多尺度
        self.neck1 = SPPLayer(channels=256)
        self.neck2 = SPPLayer(channels=512)
        self.neck3 = SPPLayer(channels=1024)
        # Detection heads for three scales
        self.head1 = BaseHead(in_channels=256, num_anchors=num_anchors, num_classes=num_classes)
        self.head2 = BaseHead(in_channels=512, num_anchors=num_anchors, num_classes=num_classes)
        self.head3 = BaseHead(in_channels=1024, num_anchors=num_anchors, num_classes=num_classes)

    def forward(self, x):
        output = []
        x = self.backbone(x) # DarkNet53提取3个尺度的特征:list
        
        # 单尺度
        # x = self.neck(x[0])
        # out = self.neck(x)
        # output.append(out)
        # return output
    
        # 多尺度
        ## neck
        x[0] = self.neck1(x[0])
        x[1] = self.neck2(x[1])
        x[2] = self.neck3(x[2])
        ## head
        x[0] = self.head1(x[0])
        x[1] = self.head2(x[1])
        x[2] = self.head3(x[2])
        
        for xi in x:
            output.append(xi)
            
        return output

Step3 测试

if __name__ == '__main__':
    yolov3_model = YOLOv3(in_channels=3, num_classes=80, num_anchors=3)
    x = torch.randn(size=(4,3,640,640))
    # 3 * (5 + num_classes)
    out = yolov3_model(x)
    for i, o in enumerate(out):
        print(f"output {i} shape: ", o.shape)

在这里插入图片描述

得到三个尺度的特征图,每个特征图对应着255维(head部分有介绍),后续通过通过解码得到锚框的位置和类别计算得到相应的loss和map等评价指标。

小结

  • 组织架构上:将目标检测模型分为backbone、neck、head三部分,简单使用darknet-spp-conv实现,如今可用的网络结构层出不穷,后续将进一步进行完善

  • 细节理解上:特别需要理解一下锚框的含义以及head出来后 dim = num_anchors * (4 + 1 + num_classes)。网络结构不是很难,实现起来主要注意一下尺度的变换

  • 从0开始写pipline中间的实现细节会理解的更透彻些,后续完善目标检测pipline。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1293255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea本地调试hadoop 遇到的几个问题

1.DEA对MapReduce的toString调用报错&#xff1a;Method threw ‘java.lang.IllegalStateException‘ exception. Cannot evaluate org.apache.hadoop.mapreduc 解决方法&#xff1a;关闭 IDEA 中的启用“ tostring() ”对象视图 2.代码和hdfs路径都对的情况下&#xff0c;程序…

如何使用ArcGIS Pro制作类似CAD的尺寸注记

经常使用CAD制图的朋友应该比较熟悉CAD内的尺寸标注&#xff0c;这样的标注看起来直观且简洁&#xff0c;那么在ArcGIS Pro内能不能制作这样尺寸注记呢&#xff0c;答案是肯定的&#xff0c;这里为大家介绍一下制作的方法&#xff0c;希望能对你有所帮助。 数据来源 本教程所…

(JAVA)OpenCV的安装与使用

本安装教程属于CLI安装&#xff0c;Windows用户可以使用Power Shell 官网教程点击查看 安装OpenCV # git克隆项目 git clone git://github.com/opencv/opencv.git cd opencv # 切换对应的版本分支 git checkout 4.x mkdir build && cd build # 生成Makefile cmake -D…

tomcat配置管理员And配置访问静态资源

配置管理员 打开 tomcat\conf\tomcat-users.xml <tomcat-users xmlns"http://tomcat.apache.org/xml"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://tomcat.apache.org/xml tomcat-users.xsd"version&qu…

UE Http笔记

c参考链接 UE4 开发如何使用 Http 请求_wx61ae2f5191643的技术博客_51CTO博客 虚幻引擎:UEC如何对JSON文件进行读写?-CSDN博客 UE4 HTTP使用 官方免费插件 VaRest 在代码插件创建的VaRest - 虚幻引擎商城 UE5在蓝图中使用Varest插件Get&#xff0c;Post两种常见请求方式…

No Chromedriver found that can automate Chrome ‘x.x.xxxx‘的解决办法

一、前置说明 在使用Appium对Android设备自动化测试时&#xff0c;切换WebView时抛出异常&#xff1a; selenium.common.exceptions.WebDriverException: Message: An unknown server-side error occurred while processing the command. Original error: No Chromedriver foun…

第54天:django学习(三)

页面上的增删改查 创建一个django项目&#xff08;使用django3版本&#xff09;day54——dj&#xff0c;并创建应用app01 在models.py文件中创建表 class UserInfo(models.Model):username models.CharField(max_length32)password models.CharField(max_length32)gender m…

删除PPT文件的备注内容

解决方案的工作经常汇报以及经常做ppt的回报工作&#xff0c;但是删除备注很痛苦。 在网上或者拿历史的ppt文件修改后&#xff0c;需要删除ppt备注内容以及删除ppt个人文件信息的办法&#xff1a; 现象&#xff1a;很多备注信息&#xff0c;需要删除 解决办法一、 文件--信息-…

Django + Matplotlib:实现数据分析显示与下载为PDF或SVG

写作背景 首先&#xff0c;数据分析在当前的信息时代中扮演着重要的角色。随着数据量的增加和复杂性的提高&#xff0c;人们对于数据分析的需求也越来越高。 其次&#xff0c;笔者也确确实实曾经接到过一个这样的开发需求&#xff0c;甲方是一个医疗方面的科研团队&#xff0…

最新版本——Hadoop3.3.6单机版完全部署指南

大家好&#xff0c;我是独孤风&#xff0c;大数据流动的作者。 本文基于最新的 Hadoop 3.3.6 的版本编写&#xff0c;带大家通过单机版充分了解 Apache Hadoop 的使用。本文更强调实践&#xff0c;实践是大数据学习的重要环节&#xff0c;也能在实践中对该技术有更深的理解&…

Excel 表列序号

题目链接 Excel 表列序号 题目描述 注意点 columnTitle 仅由大写英文组成1 < columnTitle.length < 7 解答思路 对于"CAB"&#xff0c;计算其序列号的思路&#xff1a;字母B的贡献值为2&#xff0c;字母A的贡献值为1 * 26&#xff0c;字母C的贡献值为3 * …

销售如何用企微SCRM加速潜客孵化,提升转化率?

营销推广与销售转化相互协同是势不可挡的趋势 直播带货、抖音房云店等玩法的兴起&#xff0c;是当前社交网络将营销与销售场景合二为一的缩影。因此企业在业务上&#xff0c;也要让营销推广与销售转化相互协同。以客户成交为中心&#xff0c;不局限于单项目获客、服务、转化的…

浅谈基于泛在电力物联网的综合能源管控平台设计及硬件选型

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;城区内一般都具有错综复杂的能源系统&#xff0c;且大部分能耗都集中于城区的各企、事业单位中。基于泛在电力物联网的综合能源管控平台将城区内从能源产生到能源消耗的整体流动情况采用大屏清晰展示&#xff…

Linux-进程之间的通信

目录 ​编辑 一.什么是进程之间的通信 二.进程之间的通信所访问的数据 三.进程之间的通信是如何做到的 四.基于内存文件级别的通信方式——管道 1.什么是管道 2.管道的建立过程——匿名管道 a.什么是匿名管道 b.匿名管道特点&#xff1a; c.使用匿名管道的…

IDEA中配置Git

Git 在IDEA中使用Git1 在IDEA中配置Git2 在IDEA中使用Git2.1在IDEA中创建工程并将工程添加至Git2.2 将文件添加到暂存区2.3 提交文件2.4 将代码推送到远程仓库2.5 从远程仓库克隆工程到本地2.6 从远程拉取代码2.7 版本对比2.8 创建分支2.9 切换分支2.10 分支合并 3 使用IDEA进行…

【开源】基于Vue.js的二手车交易系统

文末获取源码&#xff0c;项目编号&#xff1a; S 084 。 \color{red}{文末获取源码&#xff0c;项目编号&#xff1a;S084。} 文末获取源码&#xff0c;项目编号&#xff1a;S084。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 二手车档案管理模块…

教程篇(7.2) 01. 介绍和初始访问 ❀ FortiAnalyzer分析师 ❀ Fortinet 网络安全专家 NSE5

在本课中&#xff0c;你将了解FortiAnalyzer的关键功能和概念&#xff0c;以及如何最初访问FortiAnalyzer。 FortiAnalyzer将日志记录、分析和报告集成到一个系统中&#xff0c;因此你可以快速识别和响应。 在本课中&#xff0c;你将探索上图显示的主题。 通过展示FortiAnalyze…

算能 MilkV Duo开发板实战——opencv-mobile (迷你版opencv库)的移植和应用

前言 OpenCV是一种开源的计算机视觉和机器学习软件库&#xff0c;旨在提供一组通用的计算机视觉工具。它用于图像处理、目标识别、人脸识别、机器学习等领域&#xff0c;广泛应用于计算机视觉任务。 OpenCV-Mobile是OpenCV库的轻量版本&#xff0c;专为移动平台&#xff08;A…

基于ssm少儿编程管理系统源码和论文

idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 环境&#xff1a; jdk8 tomcat8.5 开发技术 ssm 基于ssm少儿编程管理系统源码和论文744 摘要 网络的广泛应用给生活带来了十分的便利。所以把少儿编程管理系统与现在网络相结合&#xff0c;利用java技术建设…

Kafka Connect :构建强大分布式数据集成方案

Kafka Connect 是 Apache Kafka 生态系统中的关键组件&#xff0c;专为构建可靠、高效的分布式数据集成解决方案而设计。本文将深入探讨 Kafka Connect 的核心架构、使用方法以及如何通过丰富的示例代码解决实际的数据集成挑战。 Kafka Connect 的核心架构 Kafka Connect 的核…