DCAMnet网络复现与讲解

news2024/11/15 8:43:36

距论文阅读完毕已经过了整整一周多。。。终于抽出时间来写这篇辣!~

论文阅读笔记放这里:

基于可变形卷积和注意力机制的带钢表面缺陷快速检测网络DCAM-Net(论文阅读笔记)-CSDN博客


 为了方便观看,我把结构图也拿过来了。

Overall architecture of the DCAM-Net.

众所周知,DCAMnet是在yolox的基础上改进、升级的。(应该只有博主这个憨憨对着yolov5网络改了两天,被学长一语道破天机后才幡然醒悟的吧?)

既然这样,我们就需要先对照着原yolox的网络结构,找出新增的或改动的结构,下面是yolox的网络结构图(温馨提示,在csdn页面观看图片很难受,下载后打开方式选择照片可以看得很清楚)。

yolox的项目请自行去github上获取,下面推荐一篇->

GitHub - renyuehe/bilibili-yolox_simple_voc_coco: yolox 详细注释-简化版,已配置好 voc + coco 数据集

YOLOx网络结构图

改动点

一 ,CSPLayer

相对于yolox,DCAMnet在所有CSPLayer层的右支路都加上了一个EDE-block,而EDE-block是论文作者新提出的一个模块,结构不算复杂,可以通过开头放的传送门去论文阅读笔记学一学。

EDE-block中的DFMConv即可变形卷积模块,本模块的代码如下:

此模块的代码放在该位置->yolox\models\network_blocks.py

class DeformConv(nn.Module):
    def __init__(self, in_channel, out_channel, kernel_size=3, padding=1, stride=1, bias=None, modulation=False):
        """
        Args:
            modulation (bool, optional): If True, Modulated Defomable Convolution (Deformable ConvNets v2).
        """
        super(DeformConv, self).__init__()
        self.kernel_size = kernel_size
        self.padding = padding
        self.stride = stride
        self.zero_padding = nn.ZeroPad2d(padding)
        # conv则是实际进行的卷积操作,注意这里步长设置为卷积核大小,因为与该卷积核进行卷积操作的特征图是由输出特征图中每个点扩展为其对应卷积核那么多个点后生成的。
        self.conv = nn.Conv2d(in_channel, out_channel, kernel_size=kernel_size, stride=kernel_size, bias=bias)
        # p_conv是生成offsets所使用的卷积,输出通道数为卷积核尺寸的平方的2倍,代表对应卷积核每个位置横纵坐标都有偏移量。
        self.p_conv = nn.Conv2d(in_channel, 2 * kernel_size * kernel_size, kernel_size=3, padding=1, stride=stride)
        nn.init.constant_(self.p_conv.weight, 0)
        self.p_conv.register_full_backward_hook(self._set_lr)

        self.modulation = modulation  # modulation是可选参数,若设置为True,那么在进行卷积操作时,对应卷积核的每个位置都会分配一个权重。
        if modulation:
            self.m_conv = nn.Conv2d(in_channel, kernel_size * kernel_size, kernel_size=3, padding=1, stride=stride)
            nn.init.constant_(self.m_conv.weight, 0)
            self.m_conv.register_full_backward_hook(self._set_lr)

    @staticmethod
    def _set_lr(module, grad_input, grad_output):
        grad_input = (grad_input[i] * 0.1 for i in range(len(grad_input)))
        grad_output = (grad_output[i] * 0.1 for i in range(len(grad_output)))

    def forward(self, x):
        offset = self.p_conv(x)
        if self.modulation:
            m = torch.sigmoid(self.m_conv(x))

        dtype = offset.data.type()
        ks = self.kernel_size
        N = offset.size(1) // 2

        if self.padding:
            x = self.zero_padding(x)

        # (b, 2N, h, w)
        p = self._get_p(offset, dtype)

        # (b, h, w, 2N)
        p = p.contiguous().permute(0, 2, 3, 1)
        q_lt = p.detach().floor()
        q_rb = q_lt + 1

        q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2) - 1), torch.clamp(q_lt[..., N:], 0, x.size(3) - 1)],
                         dim=-1).long()
        q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2) - 1), torch.clamp(q_rb[..., N:], 0, x.size(3) - 1)],
                         dim=-1).long()
        q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], dim=-1)
        q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], dim=-1)

        # clip p
        p = torch.cat([torch.clamp(p[..., :N], 0, x.size(2) - 1), torch.clamp(p[..., N:], 0, x.size(3) - 1)], dim=-1)

        # bilinear kernel (b, h, w, N)
        g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:]))
        g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:]))
        g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:]))
        g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:]))

        # (b, c, h, w, N)
        x_q_lt = self._get_x_q(x, q_lt, N)
        x_q_rb = self._get_x_q(x, q_rb, N)
        x_q_lb = self._get_x_q(x, q_lb, N)
        x_q_rt = self._get_x_q(x, q_rt, N)

        # (b, c, h, w, N)
        x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \
                   g_rb.unsqueeze(dim=1) * x_q_rb + \
                   g_lb.unsqueeze(dim=1) * x_q_lb + \
                   g_rt.unsqueeze(dim=1) * x_q_rt

        # modulation
        if self.modulation:
            m = m.contiguous().permute(0, 2, 3, 1)
            m = m.unsqueeze(dim=1)
            m = torch.cat([m for _ in range(x_offset.size(1))], dim=1)
            x_offset *= m

        x_offset = self._reshape_x_offset(x_offset, ks)
        out = self.conv(x_offset)

        return out

    def _get_p_n(self, N, dtype):
        # 由于卷积核中心点位置是其尺寸的一半,于是中心点向左(上)方向移动尺寸的一半就得到起始点,向右(下)方向移动另一半就得到终止点
        p_n_x, p_n_y = torch.meshgrid(
            torch.arange(-(self.kernel_size - 1) // 2, (self.kernel_size - 1) // 2 + 1),
            torch.arange(-(self.kernel_size - 1) // 2, (self.kernel_size - 1) // 2 + 1),
            indexing='ij'
        )
        # (2N, 1)
        p_n = torch.cat([torch.flatten(p_n_x), torch.flatten(p_n_y)], 0)
        p_n = p_n.view(1, 2 * N, 1, 1).type(dtype)

        return p_n

    def _get_p_0(self, h, w, N, dtype):
        # p0_y、p0_x就是输出特征图每点映射到输入特征图上的纵、横坐标值。
        p_0_x, p_0_y = torch.meshgrid(
            torch.arange(1, h * self.stride + 1, self.stride),
            torch.arange(1, w * self.stride + 1, self.stride),
            indexing='ij'
        )

        p_0_x = torch.flatten(p_0_x).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0_y = torch.flatten(p_0_y).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0 = torch.cat([p_0_x, p_0_y], 1).type(dtype)

        return p_0

    # 输出特征图上每点(对应卷积核中心)加上其对应卷积核每个位置的相对(横、纵)坐标后再加上自学习的(横、纵坐标)偏移量。
    # p0就是将输出特征图每点对应到卷积核中心,然后映射到输入特征图中的位置;
    # pn则是p0对应卷积核每个位置的相对坐标;
    def _get_p(self, offset, dtype):
        N, h, w = offset.size(1) // 2, offset.size(2), offset.size(3)

        # (1, 2N, 1, 1)
        p_n = self._get_p_n(N, dtype)
        # (1, 2N, h, w)
        p_0 = self._get_p_0(h, w, N, dtype)
        p = p_0 + p_n + offset
        return p

    def _get_x_q(self, x, q, N):
        # 计算双线性插值点的4邻域点对应的权重
        b, h, w, _ = q.size()
        padded_w = x.size(3)
        c = x.size(1)
        # (b, c, h*w)
        x = x.contiguous().view(b, c, -1)

        # (b, h, w, N)
        index = q[..., :N] * padded_w + q[..., N:]  # offset_x*w + offset_y
        # (b, c, h*w*N)
        index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)

        x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)

        return x_offset

    @staticmethod
    def _reshape_x_offset(x_offset, ks):
        b, c, h, w, N = x_offset.size()
        x_offset = torch.cat([x_offset[..., s:s + ks].contiguous().view(b, c, h, w * ks) for s in range(0, N, ks)],
                             dim=-1)
        x_offset = x_offset.contiguous().view(b, c, h * ks, w * ks)

        return x_offset

有了DFMConv模块后,就可以完成EDE-block,EDE-block的结构图和代码如下:

此模块的代码放在该位置->yolox\models\network_blocks.py

(其实我就放在可变形卷积的下面)

 

class EDE_block(nn.Module):
    def __init__(self, in_channel, out_channel):
        super(EDE, self).__init__()
        self.branch0 = nn.Sequential(
            BaseConv(in_channel, out_channel, ksize=1, stride=1),
            BaseConv(out_channel, out_channel, ksize=3, stride=1)
        )#左支路分别经过1*1和3*3的两个BaseConv。
        self.branch1 = nn.Sequential(
            DeformConv(in_channel, out_channel),
            nn.BatchNorm2d(out_channel),
            nn.SiLU(inplace=True),
            BaseConv(out_channel, out_channel, ksize=3, stride=1)
        )#中支路首先经过(可变形卷积,BN归一化,SiLU激活)这三块看作DFMConv模块,然后经过一个3*3的BaseConv。
        self.conv_cat = BaseConv(3*out_channel, out_channel, ksize=1, stride=1)#最后将左、中、和右(右支路就是原特征层)完成拼接后的结果压缩回需要的通道数即out_channel

    def forward(self, x):
        x0 = self.branch0(x)
        x1 = self.branch1(x)
        x_cat = torch.cat((x, x0, x1), dim=1)#在维度1即通道上进行拼接
        return self.conv_cat(x_cat)

有了EDE-block后,可以完成CSPLayer层,结构图和代码如下:

#因为取的名字和原yolox网络相同,所以请注意修改或注释原网络的CSPLayer层,以便网络正确调用修改过后的层!~

此模块的代码放在该位置->yolox\models\network_blocks.py

 

 

class CSPLayer(nn.Module):
    """C3 in yolov5, CSP Bottleneck with 3 convolutions"""

    def __init__(
        self,
        in_channels,
        out_channels,
        n=1,
        shortcut=True,
        expansion=0.5,
        depthwise=False,
        act="silu",
    ):
        """
        Args:
            in_channels (int): input channels.
            out_channels (int): output channels.
            n (int): number of Bottlenecks. Default value: 1.
        """
        # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        hidden_channels = int(out_channels * expansion)  # hidden channels
        self.conv1 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)
        self.conv2 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)

        self.edeblock = EDE_block(hidden_channels, hidden_channels)#与源代码唯一的区别,在右支路加了一个EDE-block。
        self.conv3 = BaseConv(2 * hidden_channels, out_channels, 1, stride=1, act=act)

    def forward(self, x):
        x_1 = self.conv1(x)
        x_1 = self.edeblock(x_1)

        x_2 = self.conv2(x)
        x = torch.cat((x_1, x_2), dim=1)#在维度1即通道上进行拼接
        return self.conv3(x)#拼接后再压缩回原通道

至此CSPLayer的改动就完成了!


二,backbone部分的dark5处

在此处添加了一个CA注意力模块来替换了源码的SPP结构,替换的原因请通过开头传送门移动论文阅读笔记自行学习。

CA即Coordinate Attention注意力模块,代码如下:

此模块的代码放在该位置->yolox\models\darknet.py

(其实就是backbone源码部分的上面,当然你放network_blocks.py文件里面也是没问题的,但是你就得在darknet.py导入写好的CA模块)

 

#CA注意模块
class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)

    def forward(self, x):
        return self.relu(x + 3) / 6


class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)

    def forward(self, x):
        return x * self.sigmoid(x)


class CA(nn.Module):
    def __init__(self, inp, oup, reduction=32):
        super(CA, self).__init__()
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))

        mip = max(8, inp // reduction)

        self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()

        self.conv_h = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        identity = x

        n, c, h, w = x.size()
        x_h = self.pool_h(x)
        x_w = self.pool_w(x).permute(0, 1, 3, 2)

        y = torch.cat([x_h, x_w], dim=2)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y)

        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)

        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()

        out = identity * a_w * a_h

        return out

有了CA模块就能在dark上进行添加了,如下:

就加这一行代码,替换原来的spp结构->

CA(base_channels * 16, base_channels * 16),

 三,检测头

DCAMnet把Head的右支路上的第一个BaseConv替换成了DFMConv,结构图如下:

 ​​​​

yolox的检测头
DCAMnet的检测头

 

所以先在->yolox\models\yolo_head.py

中导入DFMConv模块,如下图:

然后在reg_convs模块中用DFMConv模块替换原来的BaseConv模块,如下图:

DeformConv(int(256 * width),
           int(256 * width)),
nn.BatchNorm2d(int(256 * width)),
nn.SiLU(inplace=True),

 


至此,DCAMnet就完成复现了,祝愿各位代码成功跑通

o(* ̄▽ ̄*)ブ

博主自己对着自己写的教程,从头做了一遍,是没有任何问题的!

因为博主也是才学了不到2月半的小白,所以难免出现问题,如果有问题欢迎在评论区指出!~ 

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

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

相关文章

java+springboot物流管理系统设计与实现wl-ssmj+jsp

物流管理系统的开发和综合性的物流信息网站平台的建设。研究的重点是运输管理信息系统.本系统是一套基于运输作业流程的管理系统,该系统以运输任务、货品、商务三大线索设计开发。运输任务是该管理系统的核心,系统通过对运输任务中的接收、调…

智能优化算法应用:基于树种算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于树种算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于树种算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.树种算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

渗透测试|HW蓝队

公众号:老油条运维 记录某个对某个钓鱼事件中获取的钓鱼样本进行分析,以及简单的制作学习 样本行为分析 首先看到是 qq 邮箱发来的某个压缩包大概本身是带密码的,反手就丢到虚拟机先看下大概文件,解压后是这样的一个快捷方式 然…

[iOS开发]UITableView的性能优化

一些基础的优化 (一)CPU 1. 用轻量级对象 比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView CALayer * imageLayer [CALayer layer]; imageLayer.bounds CGRectMake(0,0,200,100); imageLayer.position CGPointMake(200,200…

windows文件删除权限

一、普通文件 这里指的是所有可以被随意删除的文件。 二、可更改权限的文件 如果想要删除的文件无法被删除,那大概是权限不够,这时候:鼠标右键、属性、安全、编辑、选择相应的组或用户(如果不知道哪个可以全选,反正…

MMdetection3.0 问题

MMdetection3.0 问题 希望各位路过的大佬指教一下: 问题: 1、NWPU-VHR-10有标注的数据一共650张,我将其分为了455张训练集,195张验证集。 2、然后使用MMdetection3.0框架中的Faster-rcnn网络进行训练,设置训练参数b…

【vue实战项目】通用管理系统:信息列表,信息录入

本文为博主的vue实战小项目系列中的第六篇,很适合后端或者才入门的小伙伴看,一个前端项目从0到1的保姆级教学。前面的内容: 【vue实战项目】通用管理系统:登录页-CSDN博客 【vue实战项目】通用管理系统:封装token操作…

Python的换行和转义:深入理解代码排版与字符串处理

更多Python学习内容:ipengtao.com 大家好,我是涛哥,今天为大家分享 Python的换行和转义:深入理解代码排版与字符串处理,全文2700字,阅读大约8分钟。 在Python编程中,正确使用换行和转义字符是保…

spring框架的事务传播级别经典篇

一 spring事务传播级别 1.1 总结概述 方法A:外围方法,方法B:内部方法,在A中调用B 1.事务级别PROPAGATION_REQUIRED: 如果A为PROPAGATION_REQUIRED:B 不管有没有设置事务级别,都会加入到A的事务级别中。如…

ssm+vue的仓库在线管理系统的设计与实现(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频: ssmvue的仓库在线管理系统的设计与实现(有报告)。Javaee项目,ssm vue前后端分离项目。 项目介绍: 采用M(model)V(view)C(controller)三…

5、DMA Demo(STM32F407)

DMA简介 DMA 全称Direct Memory Access,即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。 DMA传输方式无需CPU直接控制传输,也没有中断处理方式那…

倒计时(JS计时器)

<script>function countDown() {document.body.innerHTML ;//清空页面内容var nowTimer new Date(); //现在时间的毫秒数var valueTimer new Date("2024-1-1 12:00"); //用户输入年份倒计时时间毫秒数var timer (valueTimer - nowTimer) / 1000; //倒计时秒…

网工内推 | 云计算运维,云相关认证优先,最高30K,带薪年假

01 安畅网络 招聘岗位&#xff1a;云计算运维工程师 职责描述&#xff1a; 1、负责对公有云平台的计算、存储、网络资源等IAAS/SAAS/PAAS层产品组件日常交付部署运维工作&#xff0c;包括调试、配置、维护、监控、优化等工作&#xff1b; 2、负责对操作系统及应用日常运行维护…

springboot+jsp+java人才招聘网站4f21r

本基于springboot的人才招聘网站主要满足3种类型用户的需求&#xff0c;这3种类型用户分别为求职者、企业和管理员&#xff0c;他们分别实现的功能如下。 &#xff08;1&#xff09;求职者进入网站后可查看职位信息、企业信息以及职位新闻等&#xff0c;注册登录后可实现申请职…

获取焦点后,样式异常的处理方法

问题 在使用monaco-editor 设置代码提示未正常显示&#xff0c;提示框出现&#xff0c;看不到内容&#xff0c;如图 看不到内容&#xff0c;有两种情况&#xff1a; 情况一&#xff1a;没有得到数据&#xff0c;所以没有展示&#xff1b; 情况二&#xff1a;得到了数据&#x…

用户注册这样玩,保你平安

前言 基本上每个系统系统都包含用户注册、发送验证码等基本操作。在前些年&#xff0c;我还记得我在逛 csdn、贴吧、网易新闻等网站的时候是可以不登陆也能浏览完网页内容的&#xff0c;但是近几年这些网站已经改成了不登陆不让用&#xff0c;浏览网页时不时提醒你要进行登录&…

基于B/S架构的医院一体化电子病历编辑器源码

电子病历在线制作、管理和使用的一体化电子病历解决方案&#xff0c;通过一体化的设计&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。电子病历系统将临床医护需要的诊疗资料以符合临床思维的方法展示。建立以病人为中心&#xff0c;以临床诊疗信息为主线…

【前端开发】Next.js与Nest.js之间的差异2023

在快节奏的网络开发领域&#xff0c;JavaScript已成为构建可靠且引人入胜的在线应用程序的标准语言。然而&#xff0c;随着对适应性强、高效的在线服务的需求不断增加&#xff0c;开发人员通常不得不从广泛的库和框架中进行选择&#xff0c;以满足其项目的要求。Next.js和Nest.…

Electron+Ts+Vue+Vite桌面应用系列:TypeScript常用语法详解

文章目录 1️⃣ TypeScript常用讲解1.1 使用1.2 字符串1.3 数字1.3 布尔1.4 数组1.5 元组1.6 枚举1.7 any1.8 void1.9 object1.10 函数指定返回值的类型1.11 联合类型1.12 类型断言1.13 接口1.14 函数类型1.15 类类型1.16 泛型 2️⃣ 类2.1 类的基本写法2.2 类的继承2.3 类的修…

指数退避和抖动

目录 引入 OCC 添加退避机制 添加抖动机制 小结 引入 OCC 乐观并发控制&#xff08;Optimistic Concurrency Control&#xff0c;OCC&#xff09;是一种既能保证多个写入者安全地修改单个对象又能避免丢失写入的古老方法OCC具有三个优点&#xff1a;只要底层存储可用&#…