yolov5里面的骨干组件14:C3模块 (CSP Bottleneck with 3 Convolutions)

C3模块 (CSP Bottleneck with 3 Convolutions)

!abstract 论文信息

  • 论文: YOLOv5开源项目 (无正式论文)
  • 作者: Ultralytics团队
  • 年份: 2020
  • 来源: YOLOv5开源项目
  • 核心贡献: 简化的CSP结构,更适合YOLO的高效模块

引言

在读本篇文章之前,需要先补一下前文的基础,C3模块本质上是CSP模块的官方改进。C3 用更简单的双分支结构实现 CSP:一部分特征经过 Bottleneck 深加工,另一部分走短路径保留,最后拼接融合,从而减少重复计算并兼顾精度

一、核心思想

复制代码
C3的核心: 简化CSP结构,使用3个卷积实现高效特征提取

CSP vs C3:
┌─────────────────────────────────────┐
│  CSP (原始):                         │
│  - 使用Dense Block                  │
│  - 计算量较大                        │
│  - 内存占用高                        │
├─────────────────────────────────────┤
│  C3 (YOLOv5):                       │
│  - 使用Bottleneck堆叠               │
│  - 更轻量化                          │
│  - 推理速度更快                      │
└─────────────────────────────────────┘

直觉理解:
- 保持CSP的跨阶段部分连接思想
- 用更简单的Bottleneck替代Dense Block
- 平衡精度和速度,适合实时检测

二、模块结构

复制代码
C3模块结构:

输入特征图 (C×H×W)
    │
    ▼
┌─────────────────────────────────────┐
│  CV1: 1×1卷积 (通道压缩)             │
│  C×H×W → C/2×H×W                  │
│  (Part2的输入)                       │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Split (分割)                        │
│  直接从输入分出Part1                  │
│  C×H×W → C/2×H×W                  │
│  (旁路,直接传递)                     │
└─────────────────────────────────────┘
    │              │
    ▼              ▼
┌──────────┐  ┌──────────────────────┐
│  Part1   │  │  Part2               │
│  直接传递 │  │  N个Bottleneck堆叠    │
│          │  │  CV1 → Bottleneck×N  │
└──────────┘  └──────────────────────┘
    │              │
    ▼              ▼
┌─────────────────────────────────────┐
│  Concat (拼接)                       │
│  C/2×H×W + C/2×H×W → C×H×W        │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  CV3: 1×1卷积 (通道恢复)             │
│  C×H×W → C×H×W                    │
└─────────────────────────────────────┘
    │
    ▼
输出特征图 (C×H×W)

三、数学公式

复制代码
C3模块的数学表达:

1. 特征提取 (Part2):
   y₁ = CV1(x)  // 1×1卷积压缩通道
   y₂ = Bottleneck_N(...Bottleneck_2(Bottleneck_1(y₁)))
   
   其中:
   - Bottleneck_i: 第i个瓶颈块
   - N: 瓶颈块数量

2. 旁路 (Part1):
   y₃ = x[:, :C/2, :, :]  // 直接取前半通道
   或
   y₃ = CV2(x)  // 用1×1卷积提取

3. Concat (拼接):
   y = Concat(y₃, y₂)

4. 输出 (CV3):
   z = CV3(y)
   
   其中:
   - CV1: 1×1卷积, 压缩通道
   - CV2: 1×1卷积, 提取旁路特征 (可选)
   - CV3: 1×1卷积, 恢复通道

5. Bottleneck内部:
   out = x + Conv2(Conv1(x))  // shortcut连接
   或
   out = Conv2(Conv1(x))       // 无shortcut

四、代码实现

python 复制代码
import torch
import torch.nn as nn

def autopad(k, p=None):
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
    return p

class Conv(nn.Module):
    """基础卷积: Conv + BN + SiLU"""
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act else nn.Identity()
    
    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

class Bottleneck(nn.Module):
    """瓶颈块"""
    def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
        super().__init__()
        c_ = int(c2 * e)  # 隐藏通道数
        self.cv1 = Conv(c1, c_, k[0], 1)
        self.cv2 = Conv(c_, c2, k[1], 1, g=g)
        self.add = shortcut and c1 == c2
    
    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

class C3(nn.Module):
    """C3模块: CSP Bottleneck with 3 Convolutions"""
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        """
        Args:
            c1: 输入通道数
            c2: 输出通道数
            n: Bottleneck数量
            shortcut: 是否使用残差连接
            g: 分组卷积组数
            e: 通道扩展系数
        """
        super().__init__()
        c_ = int(c2 * e)  # 隐藏通道数
        
        # 三个卷积层
        self.cv1 = Conv(c1, c_, 1, 1)  # 压缩通道
        self.cv2 = Conv(c1, c_, 1, 1)  # 旁路特征提取
        self.cv3 = Conv(2 * c_, c2, 1)  # 恢复通道
        
        # Bottleneck堆叠
        self.m = nn.Sequential(
            *[Bottleneck(c_, c_, shortcut, g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n)]
        )
    
    def forward(self, x):
        # Part2: 经过Bottleneck处理
        y1 = self.m(self.cv1(x))
        
        # Part1: 旁路直接传递
        y2 = self.cv2(x)
        
        # Concat + CV3
        return self.cv3(torch.cat([y1, y2], 1))

class C3NoShortcut(nn.Module):
    """无残差连接的C3模块"""
    def __init__(self, c1, c2, n=1, g=1, e=0.5):
        super().__init__()
        c_ = int(c2 * e)
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)
        self.m = nn.Sequential(
            *[Bottleneck(c_, c_, shortcut=False, g=g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n)]
        )
    
    def forward(self, x):
        return self.cv3(torch.cat([self.m(self.cv1(x)), self.cv2(x)], 1))

# 使用示例
if __name__ == "__main__":
    # 创建C3模块
    model = C3(c1=64, c2=64, n=3, shortcut=True)
    x = torch.randn(1, 64, 32, 32)
    
    # 前向传播
    out = model(x)
    
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {out.shape}")
    
    # 计算参数量
    total_params = sum(p.numel() for p in model.parameters())
    print(f"参数量: {total_params:,}")

五、在YOLO中的应用

复制代码
C3模块在YOLOv5中的应用:

YOLOv5 Backbone:
┌─────────────────────────────────────┐
│  Focus → Conv → C3×1 → Conv → C3×3 │
│  → Conv → C3×3 → Conv → C3×1       │
│  → SPPF → Conv                      │
└─────────────────────────────────────┘

YOLOv5 Neck (PAN):
┌─────────────────────────────────────┐
│  上采样 → Concat → C3               │
│  上采样 → Concat → C3               │
│  Conv → Concat → C3                 │
│  Conv → Concat → C3                 │
└─────────────────────────────────────┘

C3模块配置:
┌─────────────────────────────────────┐
│  backbone:                          │
│    - [-1, 1, Conv, [64, 6, 2, 2]]  │
│    - [-1, 1, C3, [64, 3]]          │
│    - [-1, 1, Conv, [128, 3, 2]]    │
│    - [-1, 3, C3, [128]]            │
│    - [-1, 1, Conv, [256, 3, 2]]    │
│    - [-1, 3, C3, [256]]            │
│    ...                              │
└─────────────────────────────────────┘

C3 vs 其他模块对比:
┌────────┬──────────┬──────────┬──────────┐
│ 模块   │ 参数量   │ FLOPs    │ mAP      │
├────────┼──────────┼──────────┼──────────┤
│ C3     │ 基准     │ 基准     │ 基准     │
│ C3x    │ -10%     │ -8%      │ -0.3%   │
│ C3TR   │ +5%      │ +3%      │ +0.5%   │
└────────┴──────────┴──────────┴──────────┘

六、优缺点

优点

复制代码
✅ 结构简单: 三个卷积实现,易于理解和实现
✅ 计算高效: 比原始CSP更轻量
✅ 灵活配置: 可调整Bottleneck数量和通道数
✅ 梯度流畅: CSP结构保证梯度传播
✅ 即插即用: 可替换任何backbone模块

缺点

复制代码
❌ 固定结构: 分割比例固定为1:1
❌ 表达能力: 比DenseNet的特征复用少
❌ 设计空间: Bottleneck类型有限
❌ 缺乏创新: 本质上是CSP的简化版

参考

相关推荐
JAI科研1 天前
YOLO 完全指南(七):YOLO识别工程化 (上)
人工智能·深度学习·神经网络·yolo·目标检测·计算机视觉·transformer
JarmanYuo1 天前
YOLO 涨点研究(十二):具身 CV 进阶篇——Sim2Real 域随机化与真机部署
人工智能·pytorch·python·yolo·计算机视觉
YOLO数据集集合1 天前
无人船视角水面目标检测数据集 |无人船 USV 水面目标检测 自主航行 避障 水域监测 目标检测9076期
深度学习·yolo·目标检测·无人机·无人机视角·无人船·自主航线
YOLO数据集集合1 天前
渔船船只检测数据集 | 船只检测 渔船识别 拖船检测 海事监管 目标检测 YOLO格式 深度学习数据集 计算机视觉9076期
人工智能·深度学习·yolo·目标检测·计算机视觉
AI吃大瓜2 天前
人脸检测和行人检测4:Android实现YOLOv8 YOLO11 YOLO26人脸检测和人体检测(含源码,可实时检测)
android·yolo·人脸检测·人体检测·行人检测·yolo26
飞猫的边缘AI2 天前
边缘AI-13:从YOLOv1到YOLO26:目标检测是怎么进化的
人工智能·yolo·目标检测·ai算法·边缘ai·yolo26
稷下元歌3 天前
工业视觉实战:从标注数据到格式转换到工业质检基础,yolo识别编
yolo
richard_yuu4 天前
AOI 实战第五篇:c_broken 漏检严重,V4 迭代背后的权衡
开发语言·深度学习·yolo
richard_yuu4 天前
AOI 实战第六篇:.onnx 到 C++ 的最后一公里
c++·深度学习·yolo
彭祥.5 天前
基于DeepSeek智能体+YOLO+ResNet的饮食健康分析系统技术实现
yolo