
博主简介

AI小怪兽 | 计算机视觉布道者 | 视觉检测领域创新者
深耕计算机视觉与深度学习领域,专注于目标检测前沿技术的探索与突破。长期致力于YOLO系列算法的结构性创新、性能极限优化与工业级落地实践,旨在打通从学术研究到产业应用的最后一公里。
🚀 核心专长与技术创新
🏆 行业影响力与商业实践
💡 未来方向与使命
秉持 “让每一行代码都有温度” 的技术理念,未来将持续聚焦于实时检测、语义分割及工业缺陷检测的商业化闭环等核心方向。愿与业界同仁协同创新,共同推动技术边界,以坚实的技术能力赋能实体经济与行业变革。

论文:CF-YOLO: Context-Aware Feature Refinement for Camouflaged Industrial Micro-Defect Detection
摘要:工业部件(如铜管)表面微缺陷的自动检测对于质量保证至关重要,但由于异常尺度微小且与复杂背景视觉融合,其检测仍然具有挑战性。这些因素导致特征表示微弱,以及高误报率和漏检率。为解决这些问题,我们提出了一种新颖的实时检测框架,专为高效上下文感知和特征细化而设计。我们的方法集成了上下文感知聚合模块,该模块协同大核感知以获取宏观纹理上下文和小核聚合以进行清晰边界描绘,有效打破了背景伪装。此外,特征加法细化模块采用线性复杂度的加法令牌混合器,对细粒度异常的表示进行全局验证和细化,抑制噪声引起的误差。为了支持该领域的研究,我们引入了铜管缺陷数据集,这是一个手动标注的基准,包含来自铜管检测场景的1,847张图像和4,898个边界框缺陷实例。大量实验表明,我们的检测器在CTDD上取得了强劲且一致的性能,在保持实时推理速度的同时,在mAP@50和精确率上分别优于代表性基线检测器(包括YOLOv11)2.2%和3.9%。这项工作为高精度工业检测提供了一种鲁棒且高效的解决方案,弥合了上下文理解与详细特征分析之间的差距。
关键词:目标检测;工业缺陷检测;特征细化;微缺陷检测;上下文感知特征聚合;铜管缺陷数据集
伪代码:
"""
CF-YOLO: Context-Aware Feature Refinement for Camouflaged Industrial Micro-Defect Detection
PyTorch-style Pseudocode Implementation
This pseudocode outlines the core architectural components and training/inference flow
as described in the paper. For brevity, complex operations are represented with placeholder
functions that convey the intended logic rather than full CUDA-optimized implementations.
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
import numpy as np
# ============================================================================
# 1. Configuration
# ============================================================================
class Config:
# Dataset
num_classes = 1 # single-class defect detection
img_size = 640
batch_size = 16
epochs = 200
# Model architecture
backbone_channels = [64, 128, 256, 512] # typical YOLO stages
cpam_kernel_large = 11
cpam_kernel_small = 7
farm_hidden_dim = 256
# Loss weights
lambda_box = 7.5
lambda_cls = 0.5
lambda_dfl = 1.5
# Training
lr = 0.01
momentum = 0.937
weight_decay = 0.0005
nms_threshold = 0.45
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# ============================================================================
# 2. Context-Perception Aggregation Module (CPAM)
# ============================================================================
class CPAM(nn.Module):
"""
CPAM decouples feature processing into large-kernel perception (LKP)
and small-kernel aggregation (SKA) to capture macro-texture and refine local details.
"""
def __init__(self, in_channels, out_channels, kernel_large=11, kernel_small=7, groups=4):
super().__init__()
# LKP branch
self.pw1 = nn.Conv2d(in_channels, in_channels//2, 1)
self.dw_large = nn.Conv2d(in_channels//2, in_channels//2, kernel_large, padding=kernel_large//2, groups=in_channels//2)
self.pw2 = nn.Conv2d(in_channels//2, out_channels, 1)
# SKA branch: group-wise dynamic convolution will be applied during forward
self.groups = groups
self.kernel_small = kernel_small
self.out_channels = out_channels
def forward(self, x):
# Input: x [B, C, H, W]
B, C, H, W = x.shape
# ---- LKP stage ----
# Pointwise reduce channels
x_reduced = self.pw1(x) # [B, C/2, H, W]
# Large-kernel depthwise conv to perceive macro-texture
x_context = self.dw_large(x_reduced) # [B, C/2, H, W]
# Generate dynamic weight tensor W_agg
W_agg = self.pw2(x_context) # [B, out_channels, H, W]
# ---- SKA stage ----
# Reshape W_agg into dynamic kernels of size (kernel_small x kernel_small)
# For simplicity, we simulate group-wise dynamic convolution using unfold + matmul
# In practice, this would be a custom CUDA kernel for efficiency.
# Here we provide a high-level logical implementation.
# Pad input to maintain spatial dimensions
pad = self.kernel_small // 2
x_pad = F.pad(x, (pad, pad, pad, pad)) # [B, C, H+pad*2, W+pad*2]
# Extract local patches (unfold)
patches = F.unfold(x_pad, kernel_size=self.kernel_small, stride=1) # [B, C*K*K, H*W]
patches = patches.view(B, -1, self.kernel_small*self.kernel_small, H*W) # [B, C, K*K, N]
# Group-wise split
group_size = C // self.groups
patches_groups = patches.chunk(self.groups, dim=1) # list of [B, group_size, K*K, N]
# W_agg also needs to be grouped: W_agg [B, out_channels, H, W] -> we use it as group-wise kernels
# We approximate: use W_agg to weight the patches
# For each group, compute weighted sum over spatial positions
# Simplified: element-wise multiply patches with appropriate weights (omitted complex reshape)
# Pseudocode:
out = []
for g in range(self.groups):
patch_g = patches_groups[g] # [B, gC, K*K, N]
# Generate dynamic weights for this group
# W_agg slice: take corresponding output channels (out_channels/g)
w_g = W_agg[:, g*self.out_channels//self.groups : (g+1)*self.out_channels//self.groups, :, :] # [B, out_g, H, W]
# Reshape w_g to [B, out_g, 1, N] (N=H*W)
w_g_flat = w_g.view(B, -1, 1, H*W) # [B, out_g, 1, N]
# Weighted aggregation: sum over kernel positions (weighted by w_g)
# This is a simplified representation: actual implementation uses depthwise conv with dynamic kernels
aggregated = torch.sum(patch_g * w_g_flat, dim=2, keepdim=True) # [B, out_g, 1, N]
out.append(aggregated)
# Concatenate groups and reshape back
out = torch.cat(out, dim=1) # [B, out_channels, 1, H*W]
out = out.view(B, self.out_channels, H, W) # [B, out_channels, H, W]
# Residual connection (if in_channels == out_channels, else no residual)
if in_channels == self.out_channels:
out = out + x
return out
# ============================================================================
# 3. Feature Additive Refinement Module (FARM)
# ============================================================================
class FARM(nn.Module):
"""
FARM uses a linear-complexity additive token mixer to globally verify and refine features.
It consists of spatial and channel interaction branches.
"""
def __init__(self, dim, hidden_dim=None):
super().__init__()
self.dim = dim
self.hidden_dim = hidden_dim or dim
# Query, Key, Value projections
self.q_proj = nn.Linear(dim, dim)
self.k_proj = nn.Linear(dim, dim)
self.v_proj = nn.Linear(dim, dim)
# Spatial branch: 3x3 depthwise conv + sigmoid
self.dw_spatial = nn.Conv2d(dim, dim, 3, padding=1, groups=dim)
# Channel branch: global avg pool + pointwise conv + sigmoid
self.pw_channel = nn.Conv2d(dim, dim, 1)
# Output projection
self.out_proj = nn.Linear(dim, dim)
def forward(self, x):
# x: [B, C, H, W] -> flatten to [B, N, C] where N=H*W
B, C, H, W = x.shape
N = H * W
x_flat = x.flatten(2).transpose(1, 2) # [B, N, C]
# Project to Q, K, V
Q = self.q_proj(x_flat) # [B, N, C]
K = self.k_proj(x_flat)
V = self.v_proj(x_flat)
# ---- Context mapping Phi(T) ----
# Spatial interaction: 3x3 depthwise conv on spatial grid (use 2D conv)
# Reshape Q and K to 2D for spatial conv
Q_spatial = Q.transpose(1,2).view(B, C, H, W) # [B, C, H, W]
K_spatial = K.transpose(1,2).view(B, C, H, W)
# Spatial branch: depthwise conv + sigmoid
Q_spat = torch.sigmoid(self.dw_spatial(Q_spatial)) # spatial attention from Q
K_spat = torch.sigmoid(self.dw_spatial(K_spatial))
# Combine spatial terms (addition)
spat_term = Q_spat + K_spat
# Channel branch: global avg pool + pointwise conv + sigmoid, then multiply with original
Q_chan = torch.sigmoid(self.pw_channel(Q_spatial.mean(dim=(2,3), keepdim=True))) * Q_spatial
K_chan = torch.sigmoid(self.pw_channel(K_spatial.mean(dim=(2,3), keepdim=True))) * K_spatial
# Combine channel terms
chan_term = Q_chan + K_chan
# Overall Phi(Q) + Phi(K) = spat_term + chan_term
phi_sum = spat_term + chan_term # [B, C, H, W]
# Flatten back to [B, N, C]
phi_sum_flat = phi_sum.flatten(2).transpose(1,2) # [B, N, C]
# Modulate Value branch: element-wise multiply
refined = phi_sum_flat * V # [B, N, C]
# Output projection
refined = self.out_proj(refined) # [B, N, C]
# Reshape back to [B, C, H, W]
refined = refined.transpose(1,2).view(B, C, H, W)
# Residual connection
return x + refined
# ============================================================================
# 4. CF-YOLO Backbone with CPAM integrated
# ============================================================================
class CFYOLOBackbone(nn.Module):
"""
Backbone alternates C3k2 blocks (standard YOLO) and CPAM modules.
"""
def __init__(self, in_channels=3, out_channels_list=[64, 128, 256, 512]):
super().__init__()
# This is a placeholder; actual YOLO backbone would be more complex.
# We simulate stages.
self.stage1 = nn.Sequential(
nn.Conv2d(in_channels, 64, 3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.SiLU(),
)
self.stage2 = self._make_stage(64, 128, 2) # C3k2-like
self.stage3 = self._make_stage(128, 256, 2)
self.stage4 = self._make_stage(256, 512, 2)
# CPAM modules inserted after certain stages (according to paper)
self.cpam3 = CPAM(128, 128) # after stage2
self.cpam4 = CPAM(256, 256) # after stage3
self.cpam5 = CPAM(512, 512) # after stage4
def _make_stage(self, in_ch, out_ch, num_blocks):
# Simplified C3k2 (just a few conv layers)
layers = []
layers.append(nn.Conv2d(in_ch, out_ch, 3, stride=2, padding=1))
layers.append(nn.BatchNorm2d(out_ch))
layers.append(nn.SiLU())
for _ in range(num_blocks):
layers.append(nn.Conv2d(out_ch, out_ch, 3, padding=1))
layers.append(nn.BatchNorm2d(out_ch))
layers.append(nn.SiLU())
return nn.Sequential(*layers)
def forward(self, x):
# Feature pyramid outputs: F3, F4, F5
x1 = self.stage1(x) # stride 2
x2 = self.stage2(x1) # stride 4
F3 = self.cpam3(x2) # stride 8? (depending on design)
x3 = self.stage3(F3)
F4 = self.cpam4(x3) # stride 16
x4 = self.stage4(F4)
F5 = self.cpam5(x4) # stride 32
return F3, F4, F5
# ============================================================================
# 5. FARM applied on F5 before neck
# ============================================================================
class CFYOLONeck(nn.Module):
"""
Neck performs cross-scale feature fusion. FARM is applied to F5 before fusion.
"""
def __init__(self, channels=[128, 256, 512]):
super().__init__()
self.farm = FARM(channels[-1]) # apply to highest-level feature (F5)
# Then standard neck (e.g., PANet) would be here.
# For brevity, we skip detailed implementation.
def forward(self, F3, F4, F5):
# Apply FARM on F5
F5_refined = self.farm(F5)
# Return refined pyramid (for simplicity, we just return them)
return F3, F4, F5_refined
# ============================================================================
# 6. Decoupled Head (Classification + Regression)
# ============================================================================
class DecoupledHead(nn.Module):
"""
Decoupled detection head with separate branches for classification and regression.
"""
def __init__(self, in_channels, num_classes, num_anchors=1, reg_max=16):
super().__init__()
self.num_classes = num_classes
self.reg_max = reg_max
# Classification branch: DWConv + 1x1
self.cls_conv = nn.Sequential(
nn.Conv2d(in_channels, in_channels, 3, padding=1, groups=in_channels),
nn.Conv2d(in_channels, num_classes * num_anchors, 1)
)
# Regression branch: Conv3x3 + 1x1
self.reg_conv = nn.Sequential(
nn.Conv2d(in_channels, in_channels, 3, padding=1),
nn.Conv2d(in_channels, 4 * reg_max * num_anchors, 1)
)
def forward(self, x):
cls_out = self.cls_conv(x) # [B, num_classes, H, W]
reg_out = self.reg_conv(x) # [B, 4*reg_max, H, W]
return cls_out, reg_out
# ============================================================================
# 7. Full CF-YOLO Model
# ============================================================================
class CFYOLO(nn.Module):
def __init__(self, num_classes=1, config=Config):
super().__init__()
self.backbone = CFYOLOBackbone()
self.neck = CFYOLONeck()
# Detection heads for each scale
self.heads = nn.ModuleList([
DecoupledHead(128, num_classes),
DecoupledHead(256, num_classes),
DecoupledHead(512, num_classes)
])
self.nms_thresh = config.nms_threshold
def forward(self, x):
# Extract features
F3, F4, F5 = self.backbone(x)
# Neck with FARM
F3, F4, F5_refined = self.neck(F3, F4, F5)
# Predictions
cls3, reg3 = self.heads[0](F3)
cls4, reg4 = self.heads[1](F4)
cls5, reg5 = self.heads[2](F5_refined)
# In training, return losses; in inference, decode and NMS
if self.training:
return cls3, reg3, cls4, reg4, cls5, reg5
else:
# Decode and NMS (pseudo-code)
detections = self._decode_and_nms(cls3, reg3, cls4, reg4, cls5, reg5)
return detections
def _decode_and_nms(self, cls3, reg3, cls4, reg4, cls5, reg5):
# Combine predictions from all scales, decode boxes, apply NMS
# This is a placeholder for actual decoding logic.
boxes = []
scores = []
# ... (omitted for brevity)
return boxes, scores
# ============================================================================
# 8. Loss Function (CIoU + BCE + DFL)
# ============================================================================
def compute_loss(cls_pred, reg_pred, targets, config):
"""
Compute total loss: L_CIoU + L_BCE + L_DFL
"""
# Placeholder: actual implementation would match predictions to targets.
loss_box = torch.tensor(0.0)
loss_cls = torch.tensor(0.0)
loss_dfl = torch.tensor(0.0)
# ... (detailed matching and loss computation)
total_loss = config.lambda_box * loss_box + config.lambda_cls * loss_cls + config.lambda_dfl * loss_dfl
return total_loss
# ============================================================================
# 9. Training Loop (Pseudocode)
# ============================================================================
def train():
config = Config()
model = CFYOLO(num_classes=config.num_classes, config=config).to(config.device)
optimizer = torch.optim.SGD(model.parameters(), lr=config.lr, momentum=config.momentum, weight_decay=config.weight_decay)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=config.epochs)
# Dummy dataloader
train_loader = DataLoader(...)
for epoch in range(config.epochs):
model.train()
for batch_idx, (images, targets) in enumerate(train_loader):
images = images.to(config.device)
# targets: list of dicts with boxes, labels
# Forward
cls3, reg3, cls4, reg4, cls5, reg5 = model(images)
# Compute loss
loss = compute_loss((cls3, reg3, cls4, reg4, cls5, reg5), targets, config)
# Backward
optimizer.zero_grad()
loss.backward()
optimizer.step()
scheduler.step()
print(f"Epoch {epoch+1}/{config.epochs}, Loss: {loss.item():.4f}")
# ============================================================================
# 10. Inference (Pseudocode)
# ============================================================================
def inference(model, image):
model.eval()
with torch.no_grad():
detections = model(image.unsqueeze(0).to(Config.device))
# detections contain boxes and scores after NMS
return detections
# ============================================================================
# Entry point
# ============================================================================
if __name__ == '__main__':
# This is just a placeholder to show the structure.
print("CF-YOLO Pseudocode Structure")
# train() would be called if dataset is available.在本文中,我们提出了CF-YOLO,其全称为上下文感知与特征细化YOLO。此外,缩写“CF”还隐含地体现了集成到我们架构中的“从粗到细”检测理念,该理念协同发挥了大核卷积和加法注意力的优势。首先,为解决“背景伪装”问题,我们引入了上下文感知聚合模块。通过将特征处理解耦为大核上下文感知和小核动态聚合,CPAM有效地建模了铜表面的宏观纹理,使网络能够将异常与背景区分开。其次,为解决“微尺度”问题,我们在空间注意力阶段之后设计了特征加法细化模块。FARM采用线性复杂度的加法令牌混合器,将空间细节与通道级语义属性集成在一起。这使得能够对候选特征进行全局语义验证,有效地细化表示,以区分真实缺陷与复杂的表面噪声。
除了算法改进之外,缺乏高质量、公开可用的铜管缺陷数据集严重阻碍了该领域的研究进展。为弥合这一差距,我们构建并发布了一个名为铜管缺陷数据集的综合数据集。CTDD包含在工业光照条件下采集的1,847张铜管图像和4,898个单类边界框缺陷标注。近期研究强调了透明数据集和可复现评估在视觉计算研究中的重要性[27, 28]。因此,CTDD为工业缺陷检测提供了一个标准化的基准。CTDD的发布不仅为评估我们提出的方法提供了一个严格的基准,也为工业表面检测领域的数据增强和未来研究提供了宝贵的资源。

本文核心贡献如下:
在本工作中,我们将微缺陷检测任务定义为一个有监督的密集预测问题,旨在定位和分类工业表面上的异常。将数据集表示为 D = {(I_i, Y_i)}_{i=1}^{N},其中 I_i ∈ R^{H×W×3} 表示输入图像,Y_i 表示对应的真实标注集。每个真实目标由一个边界框 b = (c_x, c_y, w, h) 和一个类别标签 c ∈ {1, ..., N} 参数化,其中 N 表示检测类别数。我们的主要目标是构建一个深度神经网络 F,学习从输入图像 I_i 到一组预测分布的映射函数。特征提取过程将输入图像转换为多尺度特征金字塔 P = {P_3, P_4, P_5},其中 P_l ∈ R^{H/2^l × W/2^l × C_l} 表示步长为 2^l 的特征,该步长决定了每个层级空间分辨率的下采样比例。
所提出的 CF-YOLO 架构旨在平衡宏观上下文感知与微观语义细化,以应对工业场景中背景伪装和弱特征表示的具体挑战。如图 2 所示,该网络包含三个层级组件:一个上下文感知骨干网络、一个特征细化颈部和一个解耦检测头。虽然基于 YOLOv11 框架构建,CF-YOLO 通过两项策略性增强重新定义了特征编码流程。给定输入图像 I_i ∈ R^{H×W×C},骨干网络首先利用堆叠的 Conv3×3 和 C3k2 模块提取基础特征表示,记为 F_1 和 F_2。这些特征随后由 CPAM 模块处理,以有效将伪装异常与背景分离开。通过交替使用这些 C3k2 模块和 CPAM 模块,网络构建了一个多尺度特征金字塔 F = {F_3, F_4, F_5}。所选用的步长-8、步长-16 和步长-32 金字塔平衡了微缺陷敏感性和上下文推理:步长-8 特征为小的低对比度缺陷保留了精细空间线索,而步长-16 和步长-32 特征则为抑制氧化、加工纹理和其他类似背景的响应提供了更广泛的上下文。使用更低步长的特征会增加内存和延迟,而去除 F_3 则会在下采样过程中丢失微弱的缺陷证据。在多尺度融合阶段之前,高分辨率特征图 F_5 通过 FARM 模块进行特定细化,以显式捕获微尺度缺陷。在此增强之后,金字塔特征通过跨尺度融合颈部进行交互,生成细化后的集合 P = {P_3, P_4, P_5}。最后,这些特征被馈送到预测头以生成多尺度输出 Y_l = (O_l^{cls}, O_l^{reg}),并通过 NMS 合并以产生最终检测结果 Y_{final}。

受原生 YOLO 架构中感受野有限所导致的特征提取局限性的启发,我们引入了上下文感知聚合模块。设 F_in ∈ R^{H_i × W_i × C_{in}} 表示由 C3k2 模块处理后的特征。CPAM 将特征处理解耦为两个顺序阶段:大核感知和小核聚合。

LKP 阶段旨在以最小的计算开销捕获背景的宏观纹理。我们首先通过逐点卷积将通道维度降低至 C' = C/2 以保持效率。随后,应用核大小为 K_L × K_L 的大核深度卷积来感知广泛的上下文范围 Ω_L。最终的逐点卷积将特征投影以生成动态权重张量 W_{agg} ∈ R^{H×W×D},该张量编码了空间上下文引导。此操作公式化为: W_{agg} = PW_2( DW_{K_L}( PW_1(F_in) ) ) (1)
感知之后,SKA 阶段在 W_{agg} 的引导下执行精确的特征融合。权重张量被重塑以形成大小为 K_S × K_S 的动态核。我们将输入 F_in 划分为 G 个组,以促进分组动态卷积。最终输出 F_cp 通过使用生成的权重组装局部邻域 N_{K_S} 来获得: F_cp = A_{dynamic}(F_in, W_{agg}) = W_{agg} ⊛ F_in (2) 其中 ⊛ 表示分组动态卷积。通过将大范围感知与局部聚合相结合,CPAM 有效地从复杂背景中描绘出缺陷边界。
尽管使用 C3k2 模块和 CPAM 交替进行特征提取在一定程度上部分缓解了“目标伪装”问题并提高了识别精度,但这种架构仍然不足以捕获小目标的特征。为解决这一局限,我们引入了一个特征加法细化模块,以增强网络准确感知和捕获小目标的能力。FARM 通过一种线性复杂度的全局验证机制来解决此问题。设 F_i ∈ R^{W'×H'×C} 为展平后的输入特征。FARM 通过线性变换将 F_i 投影到查询、键和值空间。

与二次复杂度的自注意力机制不同,FARM 采用一种卷积加法令牌混合器。我们定义了一个上下文映射函数 Φ(·),该函数集成了双域交互。空间交互通过 3×3 深度卷积后接 Sigmoid 激活 σ 来建模,而通道交互则通过全局平均池化和逐通道调制来建模。对于通用令牌张量 T ∈ {Q, K},该映射定义为: Φ(T) = σ( DW_{3×3}(T) ) + σ( PW( AvgPool(T) ) ) ⊙ T (3) 关键地,FARM 使用加法相似度函数而非矩阵乘法来计算注意力图。细化后的输出 F_{farm} 通过用聚合后的上下文调制值分支来生成: F_{farm} = Proj( Φ(Q) + Φ(K) ) ⊙ V (4)
在多尺度特征融合之后,细化后的特征金字塔 P = {P_l}_{l=3}^{5} 被馈送到检测头。为解决分类所需的平移不变性与定位所需的平移变性之间的冲突,我们采用了解耦检测头架构。如图 5 所示,对于每个尺度层级 P_l,处理过程被分流到两个并行的流中:设 P = {P_3, P_4, P_5} ∈ R^{H/2^l × W/2^l × C_l} 为尺度 l 的输入特征图。为适应两个任务的不同需求,解耦检测头通过具有专门架构设计的并行流来处理特征。

具体而言,对于分类分支,我们通过采用深度卷积后接 1×1 卷积来优先考虑计算效率,同时保留语义判别能力。相比之下,回归分支侧重于空间精度以实现准确的边界框估计,采用标准的 3×3 卷积后接 1×1 卷积。因此,分类输出张量 O_l^{cls} 和回归输出张量 O_l^{reg} 正式获得为: O_l^{cls} = Conv2D( DWConv(X_l) ) ∈ R^{H_l × W_l × C_{cls}} (5) O_l^{reg} = Conv2D( Conv3×3(X_l) ) ∈ R^{H_l × W_l × 4n} (6) 其中 C_{cls} 表示缺陷类别数,n 表示用于积分表示的离散区间的数量。
检测过程在三个粒度层级上同时运行。所有尺度的输出被解码成一个全局的候选预测集 Y_{raw} = ∪{l=3}^{5} [Y_l],其中 Y_l = (O_l^{cls}, O_l^{reg})。每个预测包含一个边界框、一个类别标签和一个置信度分数。为消除同一缺陷的冗余检测,我们应用非极大值抑制机制。NMS 迭代地选择置信度分数最高的提议,并抑制与所选提议的交并比大于阈值 θ{nms} 的有效框。最终检测结果 Y_{final} 公式化为: Y_{final} = NMS( Y_{raw}, θ_{nms} ) (7) 这确保了每个微缺陷由一个最准确的边界框表示。
为端到端地训练 CF-YOLO 框架,我们采用了一个综合的多任务损失函数 L_total,该函数同时优化确定性和概率性输出。总目标定义为: L_total = λ_{box} L_{CIoU} + λ_{cls} L_{BCE} + λ_{dfl} L_{DFL} (8) 其中 λ_{box}、λ_{cls} 和 λ_{dfl} 是用于平衡每个损失项贡献的超参数。
具体而言,回归损失 L_{CIoU} 使用 Complete-IoU 来惩罚预测框 ˆb 与真实框 b_{gt} 之间的几何错位。分类损失 L_{BCE} 使用二元交叉熵来优化类别预测概率。最后,分布焦点损失 L_{DFL} 针对回归分支中的积分表示,迫使预测分布 P(x) 在目标值周围锐化。这使模型能够专注于细化伪装缺陷典型的模糊边界。
工业铜管缺陷的公开可用数据集极为稀缺,这严重阻碍了数据驱动检测算法的发展。为填补这一空白并促进社区研究,我们构建并发布了铜管缺陷数据集。该数据集共包含1,847张高分辨率图像和4,898个单类缺陷实例,这些图像在多样化的工业光照条件下采集,以模拟真实的制造环境。为确保标注质量,所有图像均由专业质检工程师使用LabelImg工具进行标注,并严格遵守COCO标注格式。该数据集采用单类缺陷标注,所有标注的缺陷框均归属于一个缺陷类别。为进行实验评估,我们按照8:1:1的比例将数据集随机划分为训练集、验证集和测试集。这种严格的划分确保了评估能够反映模型在未见数据上的泛化能力。数据集的一些代表性可视化样本如图6所示。

所有实验均在一台配备NVIDIA GeForce RTX 3090 GPU的工作站上进行。软件环境包括Python 3.10和PyTorch 2.1.0。我们基于Ultralytics框架实现了所提出的CF-YOLO及所有基线模型,以确保公平比较。
在训练期间,输入图像被调整为640×640像素。我们采用带动量0.937和权重衰减0.0005的随机梯度下降优化器。初始学习率设为0.01,并使用余弦退火调度器。所有模型均从头开始训练200个epoch,批量大小为16。训练阶段应用了标准数据增强技术(包括Mosaic和Mixup[46])以增强模型鲁棒性,但在最后10个epoch中禁用这些增强以确保精确收敛。
为全面且严格地评估所提方法的性能,我们采用了一个多维评估协议,包括精确率、F1分数、平均精度[47]和平均交并比[48]。
首先,为评估定位和分类能力,我们采用标准的平均精度均值,具体报告AP50。该指标表示在交并比阈值为0.5时计算的平均精度。对于包含N个缺陷类别的集合,mAP定义为每个类别的平均精度的平均值: mAP = (1/N) Σ_{i=1}^{N} AP_i @ IoU = 0.5 (9) 其中AP_i是第i个类别的精确率-召回率曲线下的面积。AP50被广泛认为是工业缺陷检测中基准测试模型灵敏度的实际标准。此外,AP75和AP95分别表示在IoU阈值为0.75和0.95时评估的AP。
在工业场景中,控制假阳性率与确保高召回率同样关键。因此,我们利用精确率、F1分数和F0.5分数来评估模型的可靠性。精确率衡量所有阳性预测中真正例预测的比例,而F1分数提供精确率和召回率的调和平均值。F0.5给予精确率更高的权重,这在工业检测中非常重要,因为误报可能会中断生产: 精确率 = TP / (TP + FP) (10) F1 = 2 · (精确率 · 召回率) / (精确率 + 召回率) (11) F0.5 = (1 + 0.5²) · (精确率 · 召回率) / (0.5² · 精确率 + 召回率) (12) 其中TP和FP分别表示真正例和假正例。高F1分数表明该方法在最小化漏检和减少误报之间实现了稳健的平衡。
最后,为评估框级定位一致性,我们采用平均交并比。由于CTDD提供的是边界框标注而非分割掩码,mIoU在预测边界框与真实边界框之间进行计算: mIoU = (1/M) Σ_{j=1}^{M} ( |B̂_j ∩ B_j| / |B̂_j ∪ B_j| ) (13) 其中B̂_j和B_j分别表示第j个匹配缺陷实例的预测边界框和真实边界框。该指标评估的是框级空间对齐,而非像素级掩码重叠或缺陷形状一致性。
为在实际工业场景中验证CF-YOLO,我们在相同的CTDD划分下,与已确立的检测器和近期代表性方法进行了对比研究。比较包括:
已确立的基于CNN的基线:我们对一套奠基性检测器进行了基准测试,以作为稳健的基线。这包括高精度两阶段架构和代表性单阶段或无锚框范式。
近期架构:为反映检测方法的最新进展,我们纳入了YOLOv11n、RF-DETR、RT-DETRv2、Conditional DETR和ETDNet。YOLOv11n被用作主要基线,因为CF-YOLO是通过修改YOLOv11检测流程实现的,这能在紧密匹配的检测器系列中隔离CPAM和FARM的效果。
为确保公平和严格的比较,所有基线方法均使用其官方超参数配置进行评估,同时在CTDD训练集和测试集上严格共享相同的数据划分。定量比较结果报告在表1中。

如结果所示,CF-YOLO在评估的CTDD基线中获得了最佳的精确率、AP50、AP75、F1分数、F0.5分数和平均值,而ETDNet获得了最高的AP95,RF-DETR获得了最高的mIoU。与YOLOv11n相比,CF-YOLO将AP50从0.801提升至0.823,F1分数从0.771提升至0.796,精确率从0.843提升至0.882。这些结果表明所提出的上下文感知和特征细化设计在CTDD上是有效的。为更直观地评估所提方法在实际工业场景中的鲁棒性,我们在CTDD测试集上可视化了CF-YOLO以及基线YOLOv11和其他评估模型的检测结果。如图7所示,比较结果揭示了在挑战性条件下的明显性能差距。

可以观察到,基线方法在微尺度缺陷和低对比度目标上表现显著困难。在背景伪装的情况下,主流检测器常因特征提取能力不足而遭受漏检和错误检测。此外,它们在具有复杂表面氧化或光照噪声的区域容易产生假阳性。相比之下,得益于集成的注意力机制和多尺度特征融合,CF-YOLO对细微异常表现出强敏感性。我们的方法在评估的CTDD协议下定位了许多纹理相似的缺陷并抑制了背景干扰。
为提供CTDD之外的外部检查,我们在公开的NEU-DET工业表面缺陷数据集[55]上评估了CF-YOLO。由于本工作被定义为单类边界框缺陷检测,原始的六个NEU-DET缺陷类别被合并为一个单一的缺陷类别。最终设置包含1,800张图像和4,189个缺陷实例,使用种子0划分为1,260张训练图像、270张验证图像和270张测试图像。如表2所示,CF-YOLO在NEU-DET上提升了AP50、F1分数和召回率,而AP50-95和精确率略低于YOLOv11n。因此,我们将此结果解释为初步的外部验证,而非跨所有工业缺陷领域的通用泛化证据。

为严格验证CF-YOLO中每个提出组件的有效性,我们在CTDD测试集上进行了全面的消融研究。我们将原始的YOLOv11作为强基线,并逐步引入上下文感知聚合模块和特征加法细化模块。定量比较,聚焦于精确率、F1分数和AP@50,总结在表3中。此外,验证CPAM和FARM具体贡献的定性可视化结果如图8所示。

CPAM的优势:如表3第二行所示,将CPAM集成到骨干网络中相比基线提高了F1分数和AP@50。这一增益主要归因于“大核感知”机制,该机制扩展了感受野以捕获铜表面的宏观纹理,并有助于保留微弱的缺陷证据。
FARM的优势:第三行展示了单独添加FARM的影响。FARM在此组件设置中提高了F1分数和AP@50,表明加法特征细化有助于抑制由复杂表面噪声引起的假阳性。
协同优势:最后,结合了CPAM和FARM的完整CF-YOLO在报告的组件消融指标中取得了最高性能。它在精确率、F1分数和AP@50上分别比基线提高了3.9%、4.0%和7.4%。
我们进一步分析了CF-YOLO的主要设计选择,包括FARM分支组成、FARM放置位置、CPAM放置位置/数量以及CPAM核配置。目的是验证所采用的设计是否必要,而不仅仅是报告最终的组件组合。
对于FARM分支组成,我们移除了空间分支、移除了通道分支,并将双域混合器替换为标准卷积。如表4所示,完整的FARM在所有指标上取得了最佳性能,而移除任一分支都会降低检测精度。这证明了双域设计在特征细化方面的有效性。

我们进一步评估了三种FARM放置策略:在颈部之前、在颈部内部和在颈部之后。如表5所示,将FARM放置在颈部内部产生了最高的AP50、AP50-95和F1分数。这表明特征细化在多尺度特征融合期间最为有效。

对于CPAM,我们研究了不同的放置深度和模块数量。如表6报告,中骨干配置取得了最佳性能,而减少CPAM模块数量会导致明显的精度下降。这些结果证实了中层上下文感知和充分上下文聚合的重要性。

最后,我们通过改变小核聚合和大核感知的尺寸来评估CPAM的不同双核配置。如表7所示,所采用的设置在评估的所有组合中取得了最佳的整体性能。这表明有效的缺陷检测依赖于大尺度上下文感知与局部特征聚合之间的平衡交互,而非仅使用更大的核。

在本文中,我们提出了CF-YOLO,一种专门用于工业铜管缺陷检测的检测器。通过集成上下文感知聚合模块和特征加法细化模块,我们的方法解决了微缺陷固有的背景伪装和语义模糊的挑战。CPAM扩大了感受野以区分缺陷与基底,而FARM执行全局语义验证以抑制噪声引起的假阳性。此外,我们发布了铜管缺陷数据集以缓解该领域的数据稀缺问题。大量实验表明,CF-YOLO在CTDD上,在评估方法中取得了最佳的AP50、精确率和F1分数,同时在RTX 3090上保持了实时推理速度。CF-YOLO的一个局限性是,虽然针对GPU平台上的精确实时检测进行了优化,但其在资源受限的边缘设备上的部署可能仍会受到上下文聚合和特征细化额外计算开销的影响。未来的工作将探索模型压缩和量化感知优化以提高部署效率。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。