机器视觉笔记4——DarkNet53模型详细解析

本文详细介绍了DarkNet53在YOLOv3目标检测中的作用,包括其网络结构、残差块的设计及其在模型框架中的应用。文章还比较了DarkNet53与DarkNet19和ResNet的性能,强调了YOLOv3在速度和准确性方面的优势。
摘要由CSDN通过智能技术生成

目录

引言

一、DarkNet53概述

二、模型框架详细解析

三、YOLOv3论文初窥

四、代码实现


引言

        Joseph Redmon在2018年YOLOv3论文《YOLOv3: An Incremental Improvement》提出了DarkNet53框架,作为YOLOv3目标检测算法的主干网络(backbone),顾名思义有53层网络层数,深化、丰富了神经网络模型,具有较强特征提取能力。

        可参见机器视觉笔记3——卷积残差及代码实现文章中相关论述。

一、DarkNet53概述

        DarkNet53深度卷积神经网络由卷积层、池化层和残差块组成,设计灵感来源于ResNet和初代DarkNet网络,包括53个卷积层和5个池化层组成。

        DarkNet53网络结构图如下。

二、模型框架详细解析

        从DarkNet53模型网络框架图中可以看出,对于输入图像(1, 3, 416, 416)(批次1张图像,每张图像3个通道,像素大小416x416 ),先进行卷积conv1(3,32,3,1,1)(注:输入为RGB三通道图片,输出32通道,卷积核为3x3,步幅为1,填充为1),得到具有32个通道的特征图。再进行卷积conv2 = Conv(32, 64, 3, 2, 1),输出64通道特征图。

        再进行conv3_4 = ConvResidual(64)残差块计算,在残差块中先采用“c = c_in // 2 ”对输入特征图通道数减半,可以在降低模型计算的复杂度的同时尽可能地保留输入的主要特征;接着残差块采用1×1 卷积降低输入通道的维度,然后1×1 卷积对降维度后的特征图进行特征提取;最终,通过残差连接将提取到的特征与原始输入特征相加,从而保持特征图的维度不变。

        代码实现类的定义如下:

class Conv(nn.Module):
	def __init__(self, c_in, c_out, k, s, p, bias=True):
		"""
		自定义一个卷积块,一次性完成卷积+归一化+激活,这在类似于像DarkNet53这样的深层网络编码上可以节省很多代码
		:param c_in: in_channels
		:param c_out: out_channels
		:param k: kernel_size
		:param s:  stride
		:param p: padding
		:param bias: …
		"""
		super(Conv, self).__init__()
		self.conv = nn.Sequential(
			nn.Conv2d(c_in, c_out, k, s, p, bias=bias),
			nn.BatchNorm2d(c_out),
			nn.LeakyReLU(0.1),
		)

	def forward(self, entry):
		return self.conv(entry)

class ConvResidual(nn.Module):
	def __init__(self, c_in):		# converlution * 2 + residual
		"""
		自定义残差单元,只需给出通道数,该单元完成两次卷积,并进行加残差后返回相同维度的特征图
		:param c_in: 通道数
		"""
		c = c_in // 2
		super(ConvResidual, self).__init__()
		# 采用 1*1 + 3*3 的形式加深网络深度,加强特征抽象
		self.conv = nn.Sequential(
			Conv(c_in, c, 1, 1, 0),		 # kernel_size = 1进行降通道
			Conv(c, c_in, 3, 1, 1),		 # 再用kernel_size = 3把通道升回去
		)

	def forward(self, entry):
		return entry + self.conv(entry)	 # 加残差,既保留原始信息,又融入了提取到的特征

        上述代码中先定义了class Conv(nn.Module),在残差块class ConvResidual(nn.Module)类中调用卷积函数。由于Conv(c_in, c, 1, 1, 0)和Conv(c, c_in, 3, 1, 1)完成了先减少通道数再恢复原来的通道数,所以残差块处理保证输出特征图与输入特征图的尺寸保持不变。

        接着进行第5次卷积conv5 = Conv(64, 128, 3, 2, 1),输入为上述残差块输出的64通道特征图,conv5卷积处理后输出128通道的特征图。

        然后又是残差块的计算,“2×ConvResidual()”表明在该过程中进行了两次的残差块运算,并且第一次残差块输出的特征图作为第二次残差块运算的输入。conv6_9 = nn.Sequential(      ConvResidual(128),ConvResidual(128)),采用nn.Sequential函数顺序包括系列神经网络顺序运行ConvResidual(128),输入为conv5输出的128通道特征图,输出128通道特征图。可知,残差块运算对特征图像的通道数没有影响,旨在对特征图进行特征提取和特征融合。

        如图所示,后续操作类似,依次进行:

        conv10 = Conv(128, 256, 3, 2, 1)、conv11_26=8×ConvResidual(256)、conv27 = Conv(256, 512, 3, 2, 1)、conv28_43=8×ConvResidual(512)、conv44 = Conv(512, 1024, 3, 2, 1)、conv45_52=4×ConvResidual(1024)。

        上述便是6个单独的卷积层和23个Residual(),共计52层卷积,还有最后一层是在YOLOv3中

在前52层特征提取基础上进行预测值,在这个模型为最后一个Residual()模块输出,即残差块中forward(self, entry)函数return。共计53层,这便是DarkNet53由来。

        这里DarkNet53模型框架输出conv45_52、conv28_43和conv11_26三个特征图用于检测不同尺度地目标,通过后续的处理解码来预测目标位置和类别信息。在解码的过程,主要是将特征图与预定义的锚框(anchor boxes)进行匹配,并使用目标检测算法(如非极大值抑制)来筛选和优化检测结果。最终的输出结果通常是检测到的目标的位置坐标、类别预测以及置信度得分。

        匹配过程分两步:

  1. 锚框生成(Anchor Box Generation):在特征图的每个位置生成一组锚框。这些锚框通常由多个尺寸和比例的框组成,以覆盖不同尺度和宽高比的目标。对于每个特征图上的位置,生成的锚框通常覆盖了整个图像。

  2. 锚框匹配(Anchor Box Matching):将生成的锚框与真实目标框进行匹配,以确定每个锚框应该负责检测图像中的哪些目标。匹配通常基于锚框与真实目标框之间的重叠程度(如IoU),如果一个锚框与某个目标框的重叠超过了一定阈值,则将该锚框标记为正样本;如果没有与任何目标框重叠或者与目标框重叠不足,则将其标记为负样本。

        在YOLOv3中,conv45_52、conv28_43和conv11_26分别代表了不同分辨率级别下的特征图。例如,conv45_52是一个尺度较大的特征图,适合检测较大尺寸的目标;而conv11_26是一个尺度较小的特征图,适合检测较小尺寸的目标。因此,通过在不同分辨率级别下使用不同尺寸的锚框,并将它们与真实目标框进行匹配,模型可以更好地学习和检测不同尺寸的目标。

三、YOLOv3论文初窥

        首先看一下Abstract,

        摘要是论文的核心,这里主要意思就是Joseph Redmon对YOLO算法进行了一些更新,版本稍微大一些、但更加准确。表现在在320×320的分辨率下,YOLOv3的运行时间为22毫秒,mAP为28.2,与SSD一样准确,但速度快三倍。当我们查看旧的0.5 IOU mAP检测度量时,YOLOv3表现得相当不错。在Titan X上,它在51毫秒内达到了57.9的AP50,而RetinaNet在198毫秒内达到了57.5的AP50,性能相似但快3.8倍。并且所有的代码都是开源的。

        相比较YOLOv2的DarkNet19升级到了DarkNet53,加深了网络的层数,并且创新性的引入了ResNet中残差块方法,给出了各网络精度性能对比图。

       从准确度、十亿次操作、每秒十亿次浮点运算和各种网络的FPS, 列举出了backbone的对比。得出“This new network is much more powerful than Darknet-19 but still more efficient than ResNet-101 or ResNet-152.”这样一个结论。

        其中,Darknet-53处理速度每秒78张图,比Darknet-19慢不少,但是比同精度的ResNet快很多,Yolov3依然保持了高性能。也就是能够达到速度保持的前提下,又快又准。

        并且饶有兴趣地给出个执行时间、FPS对比图夸夸YOLOv3。具体详细解释等笔者详细研究再拓展。

四、代码实现

        这里采用输入随机数据(1,3,416,416),经过Darknet53模型处理后,反馈conv45_52、conv28_43和conv11_26三个特征图形的shapes。

import torch
from torch import nn

class Conv(nn.Module):
	def __init__(self, c_in, c_out, k, s, p, bias=True):
		"""
		自定义一个卷积块,一次性完成卷积+归一化+激活,这在类似于像DarkNet53这样的深层网络编码上可以节省很多代码
		:param c_in: in_channels
		:param c_out: out_channels
		:param k: kernel_size
		:param s:  stride
		:param p: padding
		:param bias: …
		"""
		super(Conv, self).__init__()
		self.conv = nn.Sequential(
			nn.Conv2d(c_in, c_out, k, s, p, bias=bias),
			nn.BatchNorm2d(c_out),
			nn.LeakyReLU(0.1),
		)

	def forward(self, entry):
		return self.conv(entry)

class ConvResidual(nn.Module):
	def __init__(self, c_in):		# converlution * 2 + residual
		"""
		自定义残差单元,只需给出通道数,该单元完成两次卷积,并进行加残差后返回相同维度的特征图
		:param c_in: 通道数
		"""
		c = c_in // 2
		super(ConvResidual, self).__init__()
		# 采用 1*1 + 3*3 的形式加深网络深度,加强特征抽象
		self.conv = nn.Sequential(
			Conv(c_in, c, 1, 1, 0),		 # kernel_size = 1进行降通道
			Conv(c, c_in, 3, 1, 1),		 # 再用kernel_size = 3把通道升回去
		)

	def forward(self, entry):
		return entry + self.conv(entry)	 # 加残差,既保留原始信息,又融入了提取到的特征


class Darknet53(nn.Module):
	def __init__(self):
		super(Darknet53, self).__init__()
		self.conv1 = Conv(3, 32, 3, 1, 1)			# 一个卷积块 = 1层卷积
		self.conv2 = Conv(32, 64, 3, 2, 1)
		self.conv3_4 = ConvResidual(64)				# 一个残差块 = 2层卷积
		self.conv5 = Conv(64, 128, 3, 2, 1)
		self.conv6_9 = nn.Sequential(				# = 4层卷积
			ConvResidual(128),
			ConvResidual(128),
		)
		self.conv10 = Conv(128, 256, 3, 2, 1)
		self.conv11_26 = nn.Sequential(				# = 16层卷积
			ConvResidual(256),
			ConvResidual(256),
			ConvResidual(256),
			ConvResidual(256),
			ConvResidual(256),
			ConvResidual(256),
			ConvResidual(256),
			ConvResidual(256),
		)
		self.conv27 = Conv(256, 512, 3, 2, 1)
		self.conv28_43 = nn.Sequential(				# = 16层卷积
			ConvResidual(512),
			ConvResidual(512),
			ConvResidual(512),
			ConvResidual(512),
			ConvResidual(512),
			ConvResidual(512),
			ConvResidual(512),
			ConvResidual(512),
		)
		self.conv44 = Conv(512, 1024, 3, 2, 1)
		self.conv45_52 = nn.Sequential(				# = 8层卷积
			ConvResidual(1024),
			ConvResidual(1024),
			ConvResidual(1024),
			ConvResidual(1024),
		)

	def forward(self, entry):
		conv1 = self.conv1(entry)
		conv2 = self.conv2(conv1)
		conv3_4 = self.conv3_4(conv2)
		conv5 = self.conv5(conv3_4)
		conv6_9 = self.conv6_9(conv5)
		conv10 = self.conv10(conv6_9)
		conv11_26 = self.conv11_26(conv10)
		conv27 = self.conv27(conv11_26)
		conv28_43 = self.conv28_43(conv27)
		conv44 = self.conv44(conv28_43)
		conv45_52 = self.conv45_52(conv44)
		return conv45_52, conv28_43, conv11_26		# YOLOv3用,所以输出了3次特征

class Darknet53(nn.Module):
    def __init__(self):
        super(Darknet53, self).__init__()
        self.conv1 = Conv(3, 32, 3, 1, 1)       # 一个卷积块 = 1层卷积
        self.conv2 = Conv(32, 64, 3, 2, 1)
        self.conv3_4 = ConvResidual(64)         # 一个残差块 = 2层卷积
        self.conv5 = Conv(64, 128, 3, 2, 1)
        self.conv6_9 = nn.Sequential(           # = 4层卷积
            ConvResidual(128),
            ConvResidual(128),
        )
        self.conv10 = Conv(128, 256, 3, 2, 1)
        self.conv11_26_2 = nn.Sequential(*[ConvResidual(256) for i in range(8)])    # = 16层卷积
        self.conv27 = Conv(256, 512, 3, 2, 1)
        self.conv28_43_2 = nn.Sequential(*[ConvResidual(512) for i in range(8)])    # = 16层卷积
        self.conv44 = Conv(512, 1024, 3, 2, 1)
        self.conv45_52_2 = nn.Sequential(*[ConvResidual(1024) for i in range(4)])   # = 8层卷积

    def forward(self, entry):
        conv1 = self.conv1(entry)
        conv2 = self.conv2(conv1)
        conv3_4 = self.conv3_4(conv2)
        conv5 = self.conv5(conv3_4)
        conv6_9 = self.conv6_9(conv5)
        conv10 = self.conv10(conv6_9)
        conv11_26 = self.conv11_26_2(conv10)
        conv27 = self.conv27(conv11_26)
        conv28_43 = self.conv28_43_2(conv27)
        conv44 = self.conv44(conv28_43)
        conv45_52 = self.conv45_52_2(conv44)
        return conv45_52, conv28_43, conv11_26  # YOLOv3用,所以输出了3次特征

# 示例输入
input_tensor = torch.randn(1, 3, 416, 416)

# 实例化模型
model = Darknet53()

# 模型推断
out3, out4, out5 = model(input_tensor)
print("Output shapes:\n{}\n{}\n{}".format(out3.shape, out4.shape, out5.shape))

        运行代码后结果如下:

  • 50
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值