DCAMnet网络复现与讲解

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

论文阅读笔记放这里:

基于可变形卷积和注意力机制的带钢表面缺陷快速检测网络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月半的小白,所以难免出现问题,如果有问题欢迎在评论区指出!~ 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity 一些常用注解

在Unity中有一些比较常用的注解: 1、[SerializeField]:将私有字段或属性显示在 Unity 编辑器中,使其可以在 Inspector 窗口中进行编辑。 2、[Range(min, max)]:限制数值字段或属性的范围,在 Inspector 窗口中以滑动条…

【合集】MQ消息队列——Message Queue消息队列的合集文章 RabbitMQ入门到使用

前言 RabbitMQ作为一款常用的消息中间件,在微服务项目中得到大量应用,其本身是微服务中的重点和难点。本篇博客是Message Queue相关的学习博客文章的合集篇,目前主要是RabbitMQ入门到使用文章,后续会扩展其他MQ。 目录 前言一、R…

中国毫米波雷达产业分析3——毫米波雷达市场分析(四、五、六)

四、康养雷达市场 (一)市场背景 1、政府出台系列政策提升智慧健康养老产品供给和应用 康养雷达是一种以老年人为主要监测对象,可以实现人体感应探测、跌倒检测报警、睡眠呼吸心率监测等重要养老监护功能的新型智慧健康养老产品。 随着我国经…

C#常见的设计模式-创建型模式

引言 在软件开发过程中,设计模式是一种被广泛采用的思想和实践,可以提供一种标准化的解决方案,以解决特定问题。设计模式分为三种类型:创建型模式、结构型模式和行为型模式。本篇文章将重点介绍C#中常见的创建型模式。 目录 引言…

【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

文章目录 行为型模式1、模板方法模式(1)概述(2)结构(3)案例实现(4)优缺点(5)适用场景(6)JDK源码解析(7)模板方…

1-3、DOSBox环境搭建

语雀原文链接 文章目录 1、安装DOSBox2、Debug进入Debugrdeautq 1、安装DOSBox 官网下载下载地址:https://www.dosbox.com/download.php?main1此处直接下载这个附件(内部有8086的DEBUG.EXE环境)8086汇编工作环境.rar执行安装DOSBox0.74-wi…

pandas-profiling / ydata-profiling介绍与使用教程

文章目录 pandas-profilingydata-profilingydata-profiling实际应用iris鸢尾花数据集分析 pandas-profiling pandas_profiling 官网(https://pypi.org/project/pandas-profiling/)大概在23年4月前发出如下公告: Deprecated pandas-profilin…

keepalive路由缓存实现前进刷新后退缓存

1.在app.vue中配置全局的keepalive并用includes指定要缓存的组件路由name名字数组 <keep-alive :include"keepCachedViews"><router-view /></keep-alive>computed: {keepCachedViews() {console.log(this.$store.getters.keepCachedViews, this.…

提升技能素养,AMCAP做出合适的决策

近年来&#xff0c;智能配置投资与理财逐渐受到关注并走俏。这是一种简单快捷的智慧化理财方式&#xff0c;通过将个人和家族的闲置资金投入到低风险高流动性的产品中。 国际财富管理投资机构AMCAP集团金融分析师表示&#xff1a;智能配置投资与理财之所以持续走俏&#xff0c…

Redis String类型

String 类型是 Redis 最基本的数据类型&#xff0c;String 类型在 Redis 内部使用动态长度数组实现&#xff0c;Redis 在存储数据时会根据数据的大小动态地调整数组的长度。Redis 中字符串类型的值最大可以达到 512 MB。 关于字符串需要特别注意∶ 首先&#xff0c;Redis 中所…

Linux下查看目录大小

查看目录大小 Linux下查看当前目录大小&#xff0c;可用一下命令&#xff1a; du -h --max-depth1它会从下到大的显示文件的大小。

FastDFS+Nginx - 本地搭建文件服务器同时实现在外远程访问「内网穿透」

文章目录 前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx 2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.…

【MySQL】视图:简化查询

文章目录 create view … as创建视图更改或删除视图drop view 删除视图replace关键字&#xff1a;更改视图 可更新视图with check option子句&#xff1a;防止行被删除视图的其他优点简化查询减小数据库设计改动的影响使用视图限制基础表访问 create view … as创建视图 把常用…

Python 异常处理(try except)

文章目录 1 概述1.1 异常示例 2 异常处理2.1 捕获异常 try except2.2 抛出异常 raise 3 异常类型3.1 内置异常3.2 自定义异常 1 概述 1.1 异常示例 异常&#xff1a;程序执行中出现错误&#xff0c;若不处理&#xff0c;则程序终止 示例代码&#xff1a; v 6 / 0 # 除数不…

GPT带我学Openpyxl操作Excel

注&#xff1a;以下文字大部分文字和代码由GPT生成 一、openpyxl详细介绍 Openpyxl是一个用于读取和编写Excel 2010 xlsx/xlsm/xltx/xltm文件的Python库。它允许您使用Python操作Excel文件&#xff0c;包括创建新的工作簿、读取和修改现有工作簿中的数据、设置单元格格式以及编…

信贷专员简历模板

这份简历内容&#xff0c;以信贷专员招聘需求为背景&#xff0c;我们制作了1份全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴。 信贷专员简历在线编辑下载&#xff1a;百度幻主简历 求职意向 求职类型&#xff1a;全职 意向岗位&#xff1a;信贷专员 …

2002-2021年全国各省产业结构合理化高级化指数数据(含原始数据+计算过程+计算结果)

2002-2021年全国各省产业结构合理化高级化指数数据&#xff08;含原始数据计算过程计算结果&#xff09; 1、时间&#xff1a;2002-2021年 2、指标&#xff1a;地区、时间、就业总人数&#xff08;万人&#xff09;、第一产业就业人数&#xff08;万人&#xff09;、第二产业…

C++ ini配置文件的简单读取使用

ini文件就是简单的section 下面有对应的键值对 std::map<std::string, std::map<std::string, std::string>>MyIni::readIniFile() {std::ifstream file(filename);if (!file.is_open()) {std::cerr << "Error: Unable to open file " << …

代码随想录算法训练营第一天 | 704. 二分查找 27. 移除元素

class Solution { public:int search(vector<int>& nums, int target) {int l0;int rnums.size()-1;while(l<r){int mid(lr)>>1;if(targetnums[mid]) return mid;if(target>nums[mid]){lmid1;}else{rmid-1;}}return -1;} }; 之前就已经熟悉二分法了&am…

禁奥义·SQL秘籍

sql secret scripts sql 语法顺序、执行顺序、执行过程、要点解析、优化技巧。 1、语法顺序 如上图所示&#xff0c;为 sql 语法顺序与执行顺序对照图。其具体含义如下&#xff1a; 0、select&#xff1a; 用于从数据库中选取数据&#xff0c;即表示从数据库中查询到的数据的…