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

用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 = xout = 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 = backboneself.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 -> 32out = 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 = outout = self.conv5(out) # 下采样out = self.residual_block4(out) # fea2 : 16倍下采样fea2 = outout = self.conv6(out) # 下采样out = self.residual_block5(out) # fea3 : 32倍下采样fea3 = outout = self.global_avg_pooling(out)out = out.view(-1, 1024)out = self.fc(out)if self.backbone: # 返回最后3个尺度特征图features = [fea1, fea2, fea3]return featuresreturn 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_sizesself.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*1self.conv = nn.Conv2d(channels * (1 + self.num_pools), channels, kernel_size=(1,1), stride=(1,1),padding=0)def forward(self, x):# 保存 h,winput_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) # 通道维度拼接# convspp_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 nnclass 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 nnfrom 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_channelsself.num_anchors = num_anchorsself.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 scalesself.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# 多尺度## neckx[0] = self.neck1(x[0])x[1] = self.neck2(x[1])x[2] = self.neck3(x[2])## headx[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.mzph.cn/news/206238.shtml

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

相关文章

思维模型 色彩心理效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知色彩影响情绪。 1 色彩心理效应的应用 1.1 色彩心理效应在营销中的应用 1 可口可乐公司的“红色”营销 可口可乐公司是全球最著名的饮料品牌之一&#xff0c;其标志性的红色包装已经成为了…

Constraining Async Clock Domain Crossing

Constraining Async Clock Domain Crossing 我们在normal STA中只会去check 同步clock之间的timing,但是design中往往会存在很多CDC paths,这些paths需要被正确约束才能保证design function正确,那么怎么去约束这些CDC paths呢? 以下面的design为例,如下图所示 这里clk…

小红书蒲公英平台开通后,有哪些注意的地方,以及如何进行报价?

今天来给大家聊聊当小红书账号过1000粉后&#xff0c;开通蒲公英需要注意的事项。 蒲公英平台是小红书APP中的一个专为内容创作者设计的平台。它为品牌和创作者提供了一个完整的服务流程&#xff0c;包括内容的创作、推广、互动以及转换等多个方面。 2.蒲公英平台的主要功能 &…

【C语言】vfprintf函数

vfprintf 是 C 语言中的一个函数&#xff0c;它是 fprintf 函数的变体&#xff0c;用于格式化输出到文件中。vfprintf 函数接受一个格式化字符串和一个指向可变参数列表的指针&#xff0c;这个列表通常是通过 va_list 类型来传递的。vfprintf 函数的主要用途是在需要处理不定数…

远传智能水表一般应用于哪些场景?

远传智能水表是一种在水表领域应用广泛的创新技术&#xff0c;它利用物联网和无线通信技术使水表具备了远程监测和数据传输的能力。这种智能水表的应用场景多种多样&#xff0c;可适用于各个领域和环境。那么&#xff0c;远传智能水表一般应用于哪些场景呢&#xff1f; 首先&am…

9.关于Java的程序设计-基于Springboot的家政平台管理系统设计与实现

摘要 随着社会的进步和生活水平的提高&#xff0c;家政服务作为一种重要的生活服务方式逐渐受到人们的关注。本研究基于Spring Boot框架&#xff0c;设计并实现了一种家政平台管理系统&#xff0c;旨在提供一个便捷高效的家政服务管理解决方案。系统涵盖了用户注册登录、家政服…

mybatis数据输出-map类型输出

1、建库建表 create table emp (empNo varchar(10) null,empName varchar(100) null,sal int null,deptno varchar(10) null ); 2、pom.xml <dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis<…

Elasticsearch 8.9 flush刷新缓存中的数据到磁盘源码

一、相关API的handler1、接收HTTP请求的hander2、每一个数据节点(node)执行分片刷新的action是TransportShardFlushAction 二、对indexShard执行刷新请求1、首先获取读锁&#xff0c;再获取刷新锁&#xff0c;如果获取不到根据参数决定是否直接返回还是等待2、在刷新之后transl…

Android Audio实战——音频链路分析(二十五)

在 Android 系统的开发过程当中,音频异常问题通常有如下几类:无声、调节不了声音、爆音、声音卡顿和声音效果异常(忽大忽小,低音缺失等)等。尤其声音效果这部分问题通常从日志上信息量较少,相对难定位根因。想要分析此类问题,便需要对声音传输链路有一定的了解,能够在链…

【论文解读】:大模型免微调的上下文对齐方法

本文通过对alignmenttuning的深入研究揭示了其“表面性质”&#xff0c;即通过监督微调和强化学习调整LLMs的方式可能仅仅影响模型的语言风格&#xff0c;而对模型解码性能的影响相对较小。具体来说&#xff0c;通过分析基础LLMs和alignment-tuned版本在令牌分布上的差异&#…

100多种视频转场素材|专业胶片,抖动,光效电影转场特效PR效果预设

100多种 Premiere Pro 效果预设&#xff0c;包含&#xff1a;“胶片框架”、“胶片烧录”、“彩色LUT”、“相机抖动”、“电影Vignette”和“胶片颗粒”。非常适合制作复古风格的视频&#xff0c;添加独特的色彩。包括视频教程。 来自PR模板网&#xff1a;https://prmuban.com…

git 本地有改动,远程也有改动,且文件是自动生成的配置文件

在改动过的地方 文件是.lock文件&#xff0c;自动生成的。想切到远程的分支&#xff0c;但是远程的分支也有改动过。这时候就要解决冲突&#xff0c;因为这是两个分支&#xff0c;代码都是不一样的&#xff0c;要先把这改动的代码提交在本地或者提交在本分支的远程才可以切到其…

ke13--10章-1数据库JDBC介绍

注册数据库(两种方式),获取连接,通过Connection对象获取Statement对象,使用Statement执行SQL语句。操作ResultSet结果集 ,回收数据库资源. 需要语句: 1Class.forName("DriverName");2Connection conn DriverManager.getConnection(String url, String user, String…

Qt国际化翻译Linguist使用

QT的国际化是非常方便的&#xff0c;简单的说就是QT有自带的翻译工具把我们源代码中的字符串翻译成任何语言文件&#xff0c;再把这个语言文件加载到项目中就可以显示不同的语言。下面直接上手&#xff1a; 步骤一&#xff1a;打开pro文件&#xff0c;添加&#xff1a;TRANSLA…

idea快速定位文件、自动定位文件位置

如何快速定位到Student类的位置 如下图&#xff1a;点击类似瞄准按钮的图标即可 自动定位到文件位置 打开设置&#xff0c;勾选这个Always Select Opened File 这样子你点击文件他就会自动追踪&#xff0c;切换一个追踪一个&#xff1b; 事半功倍 &#xff01;&#xff01…

[揭秘] 文件恢复工具背后的真相!所有删除的文件都可以恢复吗

许多数据恢复工具声称它们可以在您意外删除文件时为您提供帮助。然而&#xff0c;并非所有数据恢复工具都有相同的目的。有些是针对特定文件类型而设计的&#xff0c;而另一些则无法处理用户的请求。 当我们谈论数据恢复工具时&#xff0c;用户存在很多误解。每个人最常见的问…

Vue配置代理解决跨域

Network的status中报CORS error指在前端&#xff08;Vue.js&#xff09;发起跨域请求时&#xff0c;被服务器拒绝访问的错误 在本地开发环境中&#xff0c;Vue.js 将默认从 http://localhost:8080 启动服务器。如果浏览器访问服务器时使用的 URL 不是该地址&#xff0c;就可能…

编程创意汇聚地,打造个性作品集 | 开源日报 No.97

spring-projects/spring-boot Stars: 70.4k License: Apache-2.0 Spring Boot 是一个用于简化 Spring 应用程序开发的框架&#xff0c;它通过提供默认配置和约定大于配置的方式来减少开发者的工作量。Spring Boot 可以快速地创建独立的、生产级别的基于 Spring 框架的应用程序…

Element-ui框架完成vue2项目的vuex的增删改查

看效果图是否是你需要的 这是原来没有Element-ui框架的 首先&#xff0c;你要在你的项目里安装Element-ui yarn命令 yarn add element-uinpm命令 npm install element-ui --save好了现在可以粘贴代码 //main.js import Vue from vue import Vuex from vuex import VueRouter …

【react】动态页面转换成html文件下载,解决样式问题

需求 今天遇到一个需求&#xff0c;挺恶心人的&#xff0c;将一个在线文档页面&#xff0c;可以导出成为html页面查看。 看到网上有使用fs模块&#xff0c;通过react的ReactDOMServer.renderToStaticMarkup将组件转成html字符串&#xff0c;输出文件了。 但是我尝试了&#x…