4、早期融合(输入级融合):特征拼接、通道叠加、像素级融合的实现方法与代码示例
在Edge AI的多传感器融合体系中,早期融合(Early Fusion)是最直观、也最符合人类感知直觉的一种策略。它发生在数据进入神经网络主干网络之前,将来自不同模态(如摄像头、毫米波雷达、激光雷达、IMU)的原始数据或浅层特征,在输入空间或特征图的早期阶段进行对齐与合并。这种融合方式的核心逻辑是:让模型在最早阶段就同时“看到”所有模态的信息,从而学习到跨模态的联合特征表示。本章将深入剖析早期融合的三种典型实现形态——特征拼接、通道叠加与像素级融合,并给出可直接运行的代码示例与工程注意事项。
4.1 核心原理:为什么在输入阶段融合?
早期融合的核心动机是利用原始数据的互补性。以自动驾驶场景为例,摄像头提供丰富的纹理和颜色信息,但受光照影响大;毫米波雷达对金属目标敏感且能直接测量速度,但分辨率低;激光雷达提供精确的深度信息,但在雨雪天气性能衰减。如果将这些数据在输入层就拼接在一起,神经网络的第一层卷积就能同时提取跨模态的联合特征,例如“红色的、快速接近的、距离30米的物体”。这种融合方式在理论上具有最强的信息保留能力,因为没有任何中间处理环节丢失原始信息。
从数学角度看,早期融合可以表示为:X_fused = F(X_camera, X_radar, X_lidar),其中 F 是拼接、叠加或加权操作。融合后的张量 X_fused 的维度为 (B, C_total, H, W),其中 C_total 是各模态通道数之和(拼接)或保持原通道数(像素级融合)。值得注意的是,早期融合要求所有输入必须具有相同的空间分辨率 H 和 W,这通常需要预处理阶段进行上采样或下采样对齐。
4.2 三种实现方法详解与代码示例
4.2.1 特征拼接(Feature Concatenation)
特征拼接是最常用的早期融合方式。它将各模态的特征图在通道维度上直接连接,形成一个新的高维特征图。在PyTorch中,使用 torch.cat() 函数实现。假设我们有一个RGB图像 (B, 3, H, W)、一个雷达距离图 (B, 2, H, W) 和一个激光雷达深度图 (B, 1, H, W),拼接后的特征图为 (B, 6, H, W)。
import torch
import torch.nn as nn
class EarlyFusionConcat(nn.Module):
def __init__(self, in_channels_list, out_channels):
"""
in_channels_list: 各模态的通道数列表,如 [3, 2, 1]
out_channels: 融合后经过卷积输出的通道数
"""
super().__init__()
total_channels = sum(in_channels_list)
# 拼接后接一个1x1卷积,用于通道压缩和特征交互
self.conv = nn.Conv2d(total_channels, out_channels, kernel_size=1)
self.bn = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, camera, radar, lidar):
# 假设输入已经完成空间对齐,形状均为 (B, C, H, W)
fused = torch.cat([camera, radar, lidar], dim=1) # (B, 6, H, W)
out = self.conv(fused)
out = self.bn(out)
out = self.relu(out)
return out
# 使用示例
model = EarlyFusionConcat([3, 2, 1], out_channels=32)
camera_input = torch.randn(2, 3, 224, 224)
radar_input = torch.randn(2, 2, 224, 224)
lidar_input = torch.randn(2, 1, 224, 224)
output = model(camera_input, radar_input, lidar_input)
print(f"拼接融合输出形状: {output.shape}") # (2, 32, 224, 224)
拼接方式的优点是实现简单、信息无损,缺点是通道数增加导致计算量上升,且模型需要自行学习各模态的权重关系。在实际工程中,通常会在拼接后紧跟一个1×1卷积或3×3卷积,以实现跨通道的信息混合和降维。
4.2.2 通道叠加(Channel Stacking)
通道叠加与特征拼接类似,但区别在于它新增了一个维度来区分模态,而不是简单地在通道维度上合并。在PyTorch中,使用 torch.stack() 函数。这种方式保留了模态的身份信息,使得后续网络可以显式地知道每个通道来自哪个传感器。
import torch
import torch.nn as nn
class EarlyFusionStack(nn.Module):
def __init__(self, num_modalities, in_channels, out_channels):
"""
num_modalities: 模态数量,如 3
in_channels: 每个模态的通道数(假设各模态相同)
out_channels: 输出通道数
"""
super().__init__()
# 输入形状变为 (B, num_modalities, C, H, W)
# 使用3D卷积或reshape后接2D卷积
self.conv3d = nn.Conv3d(in_channels, out_channels, kernel_size=(1, 3, 3), padding=(0, 1, 1))
self.bn = nn.BatchNorm3d(out_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, modalities):
"""
modalities: 一个张量列表,每个元素形状为 (B, C, H, W)
"""
# 在dim=1处堆叠,形成 (B, num_modalities, C, H, W)
stacked = torch.stack(modalities, dim=1)
# 调整维度顺序为 (B, C, num_modalities, H, W) 以适配Conv3d
# Conv3d 期望输入为 (B, C_in, D, H, W)
stacked = stacked.permute(0, 2, 1, 3, 4)
out = self.conv3d(stacked)
out = self.bn(out)
out = self.relu(out)
# 压缩模态维度,输出 (B, out_channels, H, W)
out = out.squeeze(2)
return out
# 使用示例
model = EarlyFusionStack(num_modalities=3, in_channels=3, out_channels=32)
mods = [torch.randn(2, 3, 224, 224) for _ in range(3)]
output = model(mods)
print(f"通道叠加融合输出形状: {output.shape}") # (2, 32, 224, 224)
通道叠加的优点是显式保留了模态维度,便于模型学习模态间的相对重要性;缺点是引入了3D卷积,计算开销更大,且对内存占用较高。在Edge AI设备上,如果算力有限,建议优先考虑特征拼接。
4.2.3 像素级融合(Pixel-level Fusion)
像素级融合是在空间维度上对每个像素位置进行加权合并。这种方式不增加通道数,而是将多模态数据在像素级别“混合”成一个新的单通道或多通道特征图。常见的操作包括加权平均、最大值融合或基于注意力机制的动态加权。
import torch
import torch.nn as nn
import torch.nn.functional as F
class PixelLevelFusion(nn.Module):
def __init__(self, in_channels, out_channels):
"""
in_channels: 每个模态的通道数
out_channels: 融合后的输出通道数
"""
super().__init__()
# 学习每个模态的权重(可学习的标量或通道级权重)
self.weights = nn.Parameter(torch.ones(3) / 3) # 假设3个模态
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
self.bn = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, camera, radar, lidar):
# 对权重进行softmax归一化,确保和为1
w = F.softmax(self.weights, dim=0)
# 像素级加权求和
fused = w[0] * camera + w[1] * radar + w[2] * lidar
out = self.conv(fused)
out = self.bn(out)
out = self.relu(out)
return out
# 使用示例
model = PixelLevelFusion(in_channels=3, out_channels=32)
camera_input = torch.randn(2, 3, 224, 224)
radar_input = torch.randn(2, 3, 224, 224) # 注意:像素级融合要求各模态通道数一致
lidar_input = torch.randn(2, 3, 224, 224)
output = model(camera_input, radar_input, lidar_input)
print(f"像素级融合输出形状: {output.shape}") # (2, 32, 224, 224)
像素级融合的优点是计算效率高、参数少,适合资源受限的Edge AI设备;缺点是要求各模态数据已经高度对齐,且简单的加权平均可能无法充分挖掘跨模态的复杂关系。更高级的像素级融合会引入注意力机制,根据像素位置动态调整权重。
4.3 案例拆解:智能座舱驾驶员监控系统(DMS)
我们以一个实际的Edge AI项目——驾驶员疲劳检测系统为例,说明早期融合的工程实现。该系统部署在车载边缘计算单元(如NVIDIA Jetson Orin)上,需要实时分析驾驶员状态。
传感器配置与数据规格:
- 红外摄像头:输出灰度图像,分辨率 640×480,通道数1,用于捕捉驾驶员面部表情和眼睛状态。
- 毫米波雷达:输出微动特征图,分辨率 64×64,通道数2(距离变化率和微多普勒能量),用于检测呼吸和心跳引起的微小胸腔运动。
- IMU(惯性测量单元):输出头部姿态角,经过投影后生成 64×64 的2通道特征图(俯仰角和横滚角),用于判断头部是否异常倾斜。
初始现象与目标:单一摄像头在黑暗环境下效果差,单一雷达无法区分具体面部动作。目标是通过早期融合,在输入阶段将三种数据合并,提升疲劳检测的准确率至95%以上,同时保证在Jetson Orin上的推理延迟低于15ms。
关键操作步骤:
- 空间对齐:将雷达和IMU特征图通过双线性插值上采样至 640×480,与摄像头分辨率一致。使用仿射变换将IMU姿态角投影到图像坐标系。
- 时间同步:采用最近邻时间戳匹配,确保三种数据的时间差小于10ms。
- 融合实现:选择特征拼接方式,将1通道红外图像、2通道雷达特征、2通道IMU特征拼接为5通道输入。在拼接后使用一个步长为2的3×3卷积进行降采样,输出16通道特征图,送入MobileNetV3主干网络。
- 模型量化:使用INT8量化将模型从FP32的120MB压缩至15MB,推理延迟从28ms降至11ms。
结果对比与复盘:
| 方案 | 准确率 | 夜间表现 | 推理延迟 | 内存占用 |
|---|---|---|---|---|
| 仅摄像头 | 82% | 差(光照依赖) | 8ms | 8MB |
| 仅雷达 | 71% | 优 | 5ms | 5MB |
| 早期融合(拼接) | 96% | 优 | 11ms | 15MB |
复盘发现,早期融合的收益主要来自雷达对呼吸信号的补充,使得模型能够识别“微睡眠”状态下的呼吸频率变化。但代价是内存占用增加了近一倍,且需要额外的预处理时间。经验是:在算力允许的情况下,早期融合的收益通常大于后期融合,但必须做好数据对齐和同步。