AMFF-YOLOX:基于YOLOX的工业缺陷检测注意机制与多特征融合研究(包含论文讲解、代码实现和可运行的源代码)

0 介绍

在工业制造领域,保证工业产品的质量是该领域的一项重要任务。对于工业产品,一个小的缺陷有时会危及整体影响。例如,印刷电路板上的断点会影响设备信号的稳定传导,而金属裂纹会影响产品的美观性和强度。通常,一般的工业产品质量检查是由人进行的,这也有一些缺点,包括需要进行大量的初始检查人员培训,这增加了人员培训的成本。随着值班人员检查时间的增加,因其原因导致的误检率上升。随着计算机视觉的发展,自动化质量检测的使用已成为该行业的解决方案。工业缺陷的视觉检测可以降低成本,提高效率。

本文将根据现有技术与存在问题,进行工业缺陷检测的应用研究。包括原文、实现过程与代码,详细的代码请访问我的GitHub,如有问题可在评论区提出问题或在此链接提出Issue。

1 网络整体结构

1.1 网络总览

YOLOX有6种不同的型号: YOLOX-nano、YOLOX-tinty、YOLOX-s、YOLOX-m、YOLOX-l和YOLOX-x。它使用CSP-Darknet和空间金字塔池(SPP)作为主干网络结构,路径聚合网络(PANet)作为网络的颈部部分,头部使用与上一代YOLO系列不同的解耦头部。

本文将选取YOLOX-s模型作为基准模型,如下图所示,本文改进了YOLOX的整体结构。改进后的网络结构分为主干网络、特征提取网络和检测网络。改进后的特征提取网络(蓝色部分)由主干网络注意提取层(红色部分)、带有注意模块的多尺度特征层和自适应空间特征融合层(紫色部分)组成。该编码器由底层主干网络和特征提取网络组成,检测解码器由三个解耦的检测头(绿色部分)组成。

网络总览

1.2 网络改进

1.2.1 特征提取网络PANet的改进

为了更好地关注工业缺陷,本文在主干网络的后三层和PANet的CSP层的输出位置添加了一个ECA(Efficient channel attention)模块,如下图所示。使用ECA模块并没有给模型添加太多的参数。同时,对不同特征映射的相关度分配加权系数,从而发挥强化重要特征的作用。本文在PANet后加入了自适应空间特征融合技术。它对特征提取网络后三层的三尺度特征信息输出进行加权和求和,以增强特征尺度的不变性。

特征提取网络PANet的改进

该部分实现代码如下

import torch
import torch.nn as nn

from .darknet import CSPDarknet
from .network_blocks import BaseConv, CSPLayer, DWConv
from .yolo_pafpn_attention import ECA
from .yolo_pafpn_asff import ASFF


class YOLOPAFPN(nn.Module):
    def __init__(
            self,
            depth=1.0,
            width=1.0,
            in_features=("dark3", "dark4", "dark5"),
            in_channels=[256, 512, 1024],
            depthwise=False,
            act="silu",
    ):
        super().__init__()
        self.backbone = CSPDarknet(depth, width, depthwise=depthwise, act=act)
        self.in_features = in_features
        self.in_channels = in_channels
        Conv = DWConv if depthwise else BaseConv

        self.upsample = nn.Upsample(scale_factor=2, mode="nearest")

        self.lateral_conv0 = BaseConv(
            int(in_channels[2] * width), int(in_channels[1] * width), 1, 1, act=act
        )
        self.C3_p4 = CSPLayer(
            int(2 * in_channels[1] * width),
            int(in_channels[1] * width),
            round(3 * depth),
            False,
            depthwise=depthwise,
            act=act,
        )  # cat

        self.reduce_conv1 = BaseConv(
            int(in_channels[1] * width), int(in_channels[0] * width), 1, 1, act=act
        )
        self.C3_p3 = CSPLayer(
            int(2 * in_channels[0] * width),
            int(in_channels[0] * width),
            round(3 * depth),
            False,
            depthwise=depthwise,
            act=act,
        )

        # bottom-up conv
        self.bu_conv2 = Conv(
            int(in_channels[0] * width), int(in_channels[0] * width), 3, 2, act=act
        )
        self.C3_n3 = CSPLayer(
            int(2 * in_channels[0] * width),
            int(in_channels[1] * width),
            round(3 * depth),
            False,
            depthwise=depthwise,
            act=act,
        )

        # bottom-up conv
        self.bu_conv1 = Conv(
            int(in_channels[1] * width), int(in_channels[1] * width), 3, 2, act=act
        )
        self.C3_n4 = CSPLayer(
            int(2 * in_channels[1] * width),
            int(in_channels[2] * width),
            round(3 * depth),
            False,
            depthwise=depthwise,
            act=act,
        )

        # 注意力通达大小
        self.neck_channels = [512, 256, 512, 1024]

        # ECA
        # dark5 1024
        self.eca_1 = ECA(int(in_channels[2] * width))
        # dark4 512
        self.eca_2 = ECA(int(in_channels[1] * width))
        # dark3 256
        self.eca_3 = ECA(int(in_channels[0] * width))
        # FPN CSPLayer 512
        self.eca_fp1 = ECA(int(self.neck_channels[0] * width))
        # PAN CSPLayer pan_out2 下采样 256
        self.eca_pa1 = ECA(int(self.neck_channels[1] * width))
        # PAN CSPLayer pan_out1 下采样 512
        self.eca_pa2 = ECA(int(self.neck_channels[2] * width))
        # PAN CSPLayer pan_out0 1024
        self.eca_pa3 = ECA(int(self.neck_channels[3] * width))

        # ASFF
        self.asff_1 = ASFF(level=0, multiplier=width)
        self.asff_2 = ASFF(level=1, multiplier=width)
        self.asff_3 = ASFF(level=2, multiplier=width)

    def forward(self, input):
        """
        Args:
            inputs: 输入图像

        Returns:
            Tuple[Tensor]: 特征信息.
        """

        # 主干网络
        out_features = self.backbone(input)
        features = [out_features[f] for f in self.in_features]
        [x2, x1, x0] = features

        # ECA
        x0 = self.eca_1(x0)
        x1 = self.eca_2(x1)
        x2 = self.eca_3(x2)

        # FPN

        # dark5
        fpn_out0 = self.lateral_conv0(x0)  # 1024->512/32
        # 上采样
        f_out0 = self.upsample(fpn_out0)  # 512/16
        # dark4 + 上采样
        f_out0 = torch.cat([f_out0, x1], 1)  # 512->1024/16
        f_out0 = self.C3_p4(f_out0)  # 1024->512/16
        # ECA
        f_out0 = self.eca_fp1(f_out0)

        fpn_out1 = self.reduce_conv1(f_out0)  # 512->256/16
        # 上采样
        f_out1 = self.upsample(fpn_out1)  # 256/8
        # dark3 + 上采样
        f_out1 = torch.cat([f_out1, x2], 1)  # 256->512/8
        #YOLO HEAD
        pan_out2 = self.C3_p3(f_out1)  # 512->256/8
        # ECA
        pan_out2 = self.eca_pa1(pan_out2)

        # PAN

        # pan_out2 下采样
        p_out1 = self.bu_conv2(pan_out2)  # 256->256/16
        # pan_out1 下采样 + CSPLayer fpn_out1
        p_out1 = torch.cat([p_out1, fpn_out1], 1)  # 256->512/16
        # YOLO HEAD
        pan_out1 = self.C3_n3(p_out1)  # 512->512/16
        # ECA
        pan_out1 = self.eca_pa2(pan_out1)

        # 下采样
        p_out0 = self.bu_conv1(pan_out1)  # 512->512/32
        # p_out0 下采样 + fpn_out0
        p_out0 = torch.cat([p_out0, fpn_out0], 1)  # 512->1024/32
        # YOLO HEAD
        pan_out0 = self.C3_n4(p_out0)  # 1024->1024/32
        # ECA
        pan_out0 = self.eca_pa3(pan_out0)

        outputs = (pan_out2, pan_out1, pan_out0)

        # ASFF
        pan_out0 = self.asff_1(outputs)
        pan_out1 = self.asff_2(outputs)
        pan_out2 = self.asff_3(outputs)
        outputs = (pan_out2, pan_out1, pan_out0)

        return outputs

1.2.2 注意力模块

ECA(Efficient channel attention)是一种轻量级的注意机制,它简单、有效,易于集成到现有的网络中,而不需要降维。使用一维卷积可以有效地捕获局部跨通道交互来提取通道间的依赖关系,允许在不向网络添加更多参数的情况下增强聚焦特征。为了使网络每次都能学习到所需的特征,本文在改进后的模型中添加了一个ECA模块,如下图所示。每个注意力组由一个CSP(Cross-Stage Partial )层、一个ECA模块和一个基本卷积块组成。CSP层增强了整个网络学习特征的能力,并将特征提取的结果传递到ECA模块中。ECA模块的第一步是对传入的特征映射执行平均池化操作。第二步使用核为3的一维卷积来计算结果。第三步,应用上述结果,利用Sigmoid激活函数获得每个通道的权值。第四步,将权值与原始输入特征图的对应元素相乘,得到最终的输出特征图。最后,将结果输出到后续的基卷积块或单独输出。

ECA注意力模块

该部分实现代码如下

# ECANet
class ECA(nn.Module):
    def __init__(self, channel, b=1, gamma=2, kernel_size=3):
        super(ECA, self).__init__()
        # https://github.com/BangguWu/ECANet/issues/24#issuecomment-664926242
        # 自适应内核不容易实现,所以它是固定的“kernel_size=3”
        # 由于输入通道的原因,无法正确确认内核大小,可能会导致错误
        # kernel_size = int(abs((math.log(channel, 2) + b) / gamma))
        # kernel_size = kernel_size if kernel_size % 2 else kernel_size + 1

        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.conv = nn.Conv1d(1, 1, kernel_size=kernel_size, padding=(kernel_size - 1) // 2, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        # 全局空间信息特征
        y = self.avg_pool(x)
        # ECA模块的两个不同分支
        y = self.conv(y.squeeze(-1).transpose(-1, -2)).transpose(-1, -2).unsqueeze(-1)
        # 多尺度信息融合
        y = self.sigmoid(y)
        return x * y.expand_as(x)

1.2.3 基于注意力模块的自适应特征融合

Liu等人( 论文Learning spatial fusion for single-shot object detection)为了解决多尺度特征之间的不一致性问题,提出了自适应空间特征融合的方法。它使网络能够直接学习如何在其他层次上对特征进行空间过滤,以便只保留有用的信息用于组合。如下图所示,本文通过保留三种不同尺度的特征图的ECA模块的最终输出来进行特征提取层。自适应空间特征融合机制对这三个特征图尺度在20×20、40×40和80×80的不同尺度下的特征图信息进行权值和求和,并计算出相应的权值。

基于注意力模块的自适应特征融合

公式(1)中 X i j e c a 1 → l e v e l X^{eca1\rightarrow{level}}_{ij} Xijeca1level X i j e c a 2 → l e v e l X^{eca2\rightarrow{level}}_{ij} Xijeca2level X i j e c a 3 → l e v e l X^{eca3\rightarrow{level}}_{ij} Xijeca3level 分别代表了PANet的三个注意力机制(上图中的ECA-1、ECA-2和ECA-3)的特征信息。本文将上述特征信息与权重参数 α i j l e v e l \alpha^{level}_{ij} αijlevel β i j l e v e l \beta^{level}_{ij} βijlevel γ i j l e v e l \gamma^{level}_{ij} γijlevel (例如: α \alpha α β \beta β γ \gamma γ在位置 ( i , j ) (i, j) (i,j) 的通道中共享的权重参数)调整到相同大小的特征图,然后将它们进行叠加操作形成一个新的融合层。

( 1 )   y i j l e v e l = α i j l e v e l ⋅ X i j e c a 1 → l e v e l + β i j l e v e l ⋅ X i j e c a 2 → l e v e l + γ i j l e v e l ⋅ X i j e c a 3 → l e v e l (1)\ y^{level}_{ij} = \alpha^{level}_{ij} \cdot X^{eca1\rightarrow{level}}_{ij} + \beta^{level}_{ij} \cdot X^{eca2\rightarrow{level}}_{ij} + \gamma^{level}_{ij} \cdot X^{eca3\rightarrow{level}}_{ij} (1) yijlevel=αijlevelXijeca1level+βijlevelXijeca2level+γijlevelXijeca3level

在公式(2)中, α i j l e v e l \alpha^{level}_{ij} αijlevel β i j l e v e l \beta^{level}_{ij} βijlevel γ i j l e v e l \gamma^{level}_{ij} γijlevel 由softmax函数定义为一个和为1的参数并且 公式(3)中它们的范围属于 [ 0 , 1 ] [0, 1] [0,1] 。 公式(4)中是权重 α i j l e v e l \alpha^{level}_{ij} αijlevel β i j l e v e l \beta^{level}_{ij} βijlevel γ i j l e v e l \gamma^{level}_{ij} γijlevel的计算方法,其中 λ α l e v e l \lambda^{level}_{\alpha} λαlevel λ β l e v e l \lambda^{level}_{\beta} λβlevel λ γ l e v e l \lambda^{level}_{\gamma} λγlevel 是通过 X e c a 1 → l e v e l X^{eca1\rightarrow{level}} Xeca1level X e c a 2 → l e v e l X^{eca2\rightarrow{level}} Xeca2level X e c a 3 → l e v e l X^{eca3\rightarrow{level}} Xeca3level 计算所得 , θ \theta θ 是权重参数 α \alpha α, β \beta β and γ \gamma γ 的集合, θ i j l e v e l \theta^{level}_{ij} θijlevel是计算的权重参数名 α i j l e v e l \alpha^{level}_{ij} αijlevel β i j l e v e l \beta^{level}_{ij} βijlevel γ i j l e v e l \gamma^{level}_{ij} γijlevel的统称。

( 2 )   α i j l e v e l + β i j l e v e l + γ i j l e v e l = 1 (2)\ \alpha^{level}_{ij} + \beta^{level}_{ij} + \gamma^{level}_{ij} = 1 (2) αijlevel+βijlevel+γijlevel=1

( 3 )   α i j l e v e l , β i j l e v e l , γ i j l e v e l ∈ [ 0 , 1 ] (3)\ \alpha^{level}_{ij}, \beta^{level}_{ij}, \gamma^{level}_{ij} \in [0, 1] (3) αijlevel,βijlevel,γijlevel[0,1]

( 4 )   θ i j l e v e l = e λ θ i j l e v e l e λ α i j l e v e l + e λ β i j l e v e l + e λ γ i j l e v e l , θ ∈ [ α , β , γ ] (4)\ \theta^{level}_{ij} = \frac{e^{\lambda^{level}_{\theta_{ij}}}}{e^{\lambda^{level}_{\alpha_{ij}}} + e^{\lambda^{level}_{\beta_{ij}}} + e^{\lambda^{level}_{\gamma_{ij}}}}, \theta \in [\alpha, \beta, \gamma] (4) θijlevel=eλαijlevel+eλβijlevel+eλγijleveleλθijlevel,θ[α,β,γ]

该部分实现代码如下

# ASFF实现方式
import torch
import torch.nn as nn
import torch.nn.functional as F


def autopad(k, p=None):  # kernel, padding
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p


class Conv(nn.Module):
    # Standard convolution
    # ch_in, ch_out, kernel, stride, padding, groups
    def __init__(self, in_channels, out_channels, kernel=1, stride=1, padding=None, groups=1, act=True):
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel, stride, autopad(kernel, padding), groups=groups,
                              bias=False)
        self.bn = nn.BatchNorm2d(out_channels)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        return self.act(self.conv(x))


class ASFF(nn.Module):
    def __init__(self, level, multiplier=1, rfb=False, vis=False, act_cfg=True):
        """
        multiplier should be 1, 0.5
        which means, the channel of ASFF can be
        512, 256, 128 -> multiplier=0.5
        1024, 512, 256 -> multiplier=1
        For even smaller, you need change code manually.
        """
        super(ASFF, self).__init__()
        self.level = level
        self.dim = [int(1024 * multiplier), int(512 * multiplier),
                    int(256 * multiplier)]
        # print(self.dim)

        self.inter_dim = self.dim[self.level]
        if level == 0:
            self.stride_level_1 = Conv(int(512 * multiplier), self.inter_dim, 3, 2)

            self.stride_level_2 = Conv(int(256 * multiplier), self.inter_dim, 3, 2)

            self.expand = Conv(self.inter_dim, int(1024 * multiplier), 3, 1)
        elif level == 1:
            self.compress_level_0 = Conv(int(1024 * multiplier), self.inter_dim, 1, 1)
            self.stride_level_2 = Conv(int(256 * multiplier), self.inter_dim, 3, 2)
            self.expand = Conv(self.inter_dim, int(512 * multiplier), 3, 1)
        elif level == 2:
            self.compress_level_0 = Conv(int(1024 * multiplier), self.inter_dim, 1, 1)
            self.compress_level_1 = Conv(int(512 * multiplier), self.inter_dim, 1, 1)
            self.expand = Conv(self.inter_dim, int(256 * multiplier), 3, 1)

        # when adding rfb, we use half number of channels to save memory
        compress_c = 8 if rfb else 16
        self.weight_level_0 = Conv(self.inter_dim, compress_c, 1, 1)
        self.weight_level_1 = Conv(self.inter_dim, compress_c, 1, 1)
        self.weight_level_2 = Conv(self.inter_dim, compress_c, 1, 1)

        self.weight_levels = Conv(compress_c * 3, 3, 1, 1)
        self.vis = vis

    def forward(self, x):  # l,m,s
        """
        #
        256, 512, 1024
        from small -> large
        """
        # max feature
        global level_0_resized, level_1_resized, level_2_resized
        x_level_0 = x[2]
        # mid feature
        x_level_1 = x[1]
        # min feature
        x_level_2 = x[0]

        if self.level == 0:
            level_0_resized = x_level_0
            level_1_resized = self.stride_level_1(x_level_1)
            level_2_downsampled_inter = F.max_pool2d(x_level_2, 3, stride=2, padding=1)
            level_2_resized = self.stride_level_2(level_2_downsampled_inter)
        elif self.level == 1:
            level_0_compressed = self.compress_level_0(x_level_0)
            level_0_resized = F.interpolate(level_0_compressed, scale_factor=2, mode='nearest')
            level_1_resized = x_level_1
            level_2_resized = self.stride_level_2(x_level_2)
        elif self.level == 2:
            level_0_compressed = self.compress_level_0(x_level_0)
            level_0_resized = F.interpolate(level_0_compressed, scale_factor=4, mode='nearest')
            x_level_1_compressed = self.compress_level_1(x_level_1)
            level_1_resized = F.interpolate(x_level_1_compressed, scale_factor=2, mode='nearest')
            level_2_resized = x_level_2

        level_0_weight_v = self.weight_level_0(level_0_resized)
        level_1_weight_v = self.weight_level_1(level_1_resized)
        level_2_weight_v = self.weight_level_2(level_2_resized)

        levels_weight_v = torch.cat((level_0_weight_v, level_1_weight_v, level_2_weight_v), 1)
        levels_weight = self.weight_levels(levels_weight_v)
        levels_weight = F.softmax(levels_weight, dim=1)

        fused_out_reduced = level_0_resized * levels_weight[:, 0:1, :, :] + \
                            level_1_resized * levels_weight[:, 1:2, :, :] + \
                            level_2_resized * levels_weight[:, 2:, :, :]

        out = self.expand(fused_out_reduced)

        if self.vis:
            return out, levels_weight, fused_out_reduced.sum(dim=1)
        else:
            return out
		# 三层从ECA中输出的特征信息
		outputs = (pan_out2, pan_out1, pan_out0)
		# 在特征提取网络中的ASFF使用
        pan_out0 = self.asff_1(outputs)
        pan_out1 = self.asff_2(outputs)
        pan_out2 = self.asff_3(outputs)
		# 三层从ASFF中输出的特征信息
        outputs = (pan_out2, pan_out1, pan_out0)

1.2.4 Bottleneck优化设计

CouvNeXt网络是由Liu等人(论文A convnet for the 2020s)的研究提出的。对大型卷积核采用逆瓶颈结构,并采用较少的归一化和激活函数来提高模型性能。基于这一思想,本文中的模型在每个CSP层中都进行了微观的瓶颈设计,并尝试采用逆瓶颈结构。然而,结果与原始结果相似,且效果没有明显改善,因此本文没有采用逆瓶颈结构。最后,本文基于CSP-Darknet模型,提出了ConvNeXt的瓶颈设计模式。在模型1×1卷积后去掉一个SiLU激活函数,在3×3卷积后删除一个归一化函数,如下图所示。通过对简化的归一化操作和激活函数操作分别进行测试,发现最终结果优于原始结构。

改进的Bottleneck

2 实验效果

2.1 数据集

以下是我们实验中使用的公共数据集: NRSD-MN(重新标记的数据集链接:NRSD-MN-relabel)、PCBNEU-DET。NRSD-MN数据集共有1个类别中的4101张图像,图像大小从400到800像素不等。实验共分为2971个训练集和1130个验证集。PCB数据集共有6个类别中的693张图像,它们的大小都大于1000像素。这6个类别分别是 missing hole、mouse bite、open circuit、short、spur和spurious copper。实验中分为554个训练集和139个验证集。NEU-DET数据集由1800张图像组成,其中6种缺陷被标记为crazing、inclusion、patches、 pitted surface、 rolled in scale和scratches。此外,图像大小为200×200像素。实验中分为1620个训练集和180个验证集。所有的验证集都来自于数据集自己的划分。

2.2 实验环境

本文的实验环境如下: Ubuntu 18.04、Python 3.8、Pytorch 1.8.1、CUDA 11.1,所有模型都使用相同的NVIDIA RTX 3060GPU 12GB。具体的实验参数可以参考文章或源代码。

2.3 实验结果

2.3.1 对比实验

在本研究中,我们设计了一组比较实验和四组消融实验,以mAP@0.5:0.95、mAP@0.5和FPS作为评价指标并以YOLOX为基线。在工业缺陷检测对比实验中,将YOLOX-tiny和YOLOX-s作为基本模型,并与YOLOv3-tiny、YOLOv5-s和YOLOv8-s进行了比较。在实验结果的对比图中,(a)表示图像的真实值,(b)表示基线的预测结果,©表示本文模型的预测结果。详细分析可参考论文实验部分

工业缺陷数据集检测的比较实验结果(mAP@0.5:0.95)

NetworkNRSD(mAP@0.5:0.95)PCB(mAP@0.5:0.95)NEU(mAP@0.5:0.95)
YOLOv3-tiny46.2942.4821.32
YOLOv5-s52.1045.1937.47
YOLOv8-s56.2951.1643.31
YOLOX-tiny56.5045.9141.04
YOLOX-s57.7449.7247.61
AMFF-YOLOX-s61.0651.5849.08

工业缺陷数据集检测的比较实验结果(mAP@0.5)

NetworkNRSD(mAP@0.5)PCB(mAP@0.5)NEU(mAP@0.5)
YOLOv3-tiny78.2690.6955.02
YOLOv5-s80.8590.4072.60
YOLOv8-s80.4893.4275.64
YOLOX-tiny81.6888.0777.98
YOLOX-s80.5089.5178.49
AMFF-YOLOX-s85.0091.0980.48

NRSD对比结果
NRSD对比结果

PCB对比结果
PCB对比结果

NEU-DET对比结果
NEU-DET对比结果

2.3.2 消融实验

为了验证模型各模块的有效性,本文首先在经典的VOC数据集中进行了烧蚀实验。该实验以YOLOX-tiny为基线,将输入大小设置为416×416,其他训练设置与基本设置相同,如下表所示。我们分别将其添加到YOLOX-tiny的FPN和PAN的不同位置的注意机制。在消融实验表中,本实验使用A作为ECA模块,B作为ASFF模块,C作为修改后的Bottleneck模块。详细分析可参考论文实验部分

在VOC2007数据集上的消融实验结果

NetworkVOC(mAP@0.5:0.95)VOC(mAP@0.5)FPS
Baseline35.8559.49340
+ A (FPN)36.6260.45331
+ A (FPN + PAN)36.7360.10328
+ B36.6660.84288
+ A (FPN) + B37.1060.93299
+ A (FPN + PAN) + B37.4161.06298

在NRSD数据集上的消融实验结果

NetworkNRSD(mAP@0.5:0.95)NRSD(mAP@0.5)FPS
Baseline58.2781.89144
+ A59.2583.92142
+ B59.6783.55124
+ C58.7983.29151
+ A + B + C61.0685.00129

在PCB数据集上的消融实验结果

NetworkPCB(mAP@0.5:0.95)PCB(mAP@0.5)FPS
Baseline49.7289.51144
+ A50.4490.02142
+ B51.2790.37119
+ C51.0290.86150
+ A + B + C51.5891.09125

在NEU-DET数据集上的消融实验结果

NetworkNEU(mAP@0.5:0.95)NEU(mAP@0.5)FPS
Baseline47.6178.49153
+ A48.4880.20151
+ B48.1879.53123
+ C48.5379.59151
+ A + B + C49.0880.48131

3 最后

在本文中我们提出了一种改进的工业缺陷检测网络AMFF-YOLOX,该网络结合了注意机制、自适应空间特征融合和改进的瓶颈模块,以在不牺牲太多速度的情况下提高缺陷检测的准确性。通过大量的消融实验和与现有的最先进的方法的比较,验证了该模型的整体有效性和竞争力。

如果有任何关于结构、代码调参、项目部署、数据集等相关问题欢迎访问我的GitHub或给我发邮件(chenyu1998424@gmail.com)。

如果你的我的项目感兴趣,可以访问项目源码网址

如果你对AIGC方向感兴趣,可以来看看我的其它项目:基于分布式的工业缺陷检测扩散模型,文章讲解链接

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChairC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值