1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
| import torch import torch.nn as nn
class BiFuseNet(nn.Module): """ BiFuseNet: 3D 时空多模态 BAC 估计网络 架构: 1. 双路 3D ResNet 主干 (RGB + IR) 2. Bi-directional Hierarchical Fusion (BiHF) - Early fusion: 浅层特征交叉注意力 - Middle fusion: 中层特征融合 - Late fusion: 深层语义融合 3. 3D 时空卷积捕获时序动态 4. 分级头: sober / moderate / severe 输入: (B, C_rgb, T, H, W) + (B, C_ir, T, H, W) 输出: (B, 3) 三级分类 + (B, 1) BAC 回归 """ def __init__(self, num_classes=3, dropout=0.3): super().__init__() self.rgb_branch = ResNet3D( in_channels=3, layers=[2, 2, 2, 2], base_ch=32 ) self.ir_branch = ResNet3D( in_channels=1, layers=[2, 2, 2, 2], base_ch=32 ) self.bihf = BiHierarchicalFusion( channels_list=[32, 64, 128, 256], num_heads=4 ) self.temporal_attn = TemporalAttention(256, num_heads=4) self.classifier = nn.Sequential( nn.AdaptiveAvgPool3d(1), nn.Flatten(), nn.Linear(256, 128), nn.ReLU(), nn.Dropout(dropout), nn.Linear(128, num_classes) ) self.regressor = nn.Sequential( nn.AdaptiveAvgPool3d(1), nn.Flatten(), nn.Linear(256, 64), nn.ReLU(), nn.Dropout(dropout), nn.Linear(64, 1), nn.Sigmoid() ) def forward(self, rgb_video, ir_video): """ Args: rgb_video: (B, 3, T, H, W) RGB 面部视频 ir_video: (B, 1, T, H, W) 红外面部视频 Returns: logits: (B, 3) 三级分类 bac: (B, 1) BAC 估计值 """ rgb_feats = self.rgb_branch(rgb_video) ir_feats = self.ir_branch(ir_video) fused = self.bihf(rgb_feats, ir_feats) fused = self.temporal_attn(fused) logits = self.classifier(fused) bac = self.regressor(fused) return logits, bac
class ResNet3D(nn.Module): """简化版 3D ResNet""" def __init__(self, in_channels=3, layers=[2,2,2,2], base_ch=32): super().__init__() self.stem = nn.Sequential( nn.Conv3d(in_channels, base_ch, kernel_size=(3,7,7), stride=(1,2,2), padding=(1,3,3)), nn.BatchNorm3d(base_ch), nn.ReLU(), nn.MaxPool3d(kernel_size=(1,3,3), stride=(1,2,2)) ) self.blocks = nn.ModuleList() ch = base_ch for i, num_blocks in enumerate(layers): out_ch = ch * 2 if i > 0 else ch block = self._make_layer(ch, out_ch, num_blocks, stride=(2,2,2) if i > 0 else (1,2,2)) self.blocks.append(block) ch = out_ch def _make_layer(self, in_ch, out_ch, num_blocks, stride): layers = [ResBlock3D(in_ch, out_ch, stride)] for _ in range(1, num_blocks): layers.append(ResBlock3D(out_ch, out_ch)) return nn.Sequential(*layers) def forward(self, x): x = self.stem(x) feats = [] for block in self.blocks: x = block(x) feats.append(x) return feats
class ResBlock3D(nn.Module): def __init__(self, in_ch, out_ch, stride=1): super().__init__() self.conv1 = nn.Conv3d(in_ch, out_ch, 3, stride, 1, bias=False) self.bn1 = nn.BatchNorm3d(out_ch) self.conv2 = nn.Conv3d(out_ch, out_ch, 3, 1, 1, bias=False) self.bn2 = nn.BatchNorm3d(out_ch) self.relu = nn.ReLU() self.downsample = None if stride != 1 or in_ch != out_ch: self.downsample = nn.Sequential( nn.Conv3d(in_ch, out_ch, 1, stride, bias=False), nn.BatchNorm3d(out_ch) ) def forward(self, x): identity = x out = self.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) if self.downsample: identity = self.downsample(x) return self.relu(out + identity)
if __name__ == "__main__": model = BiFuseNet(num_classes=3) rgb = torch.randn(2, 3, 16, 224, 224) ir = torch.randn(2, 1, 16, 224, 224) logits, bac = model(rgb, ir) print(f"Input RGB: {rgb.shape}") print(f"Input IR: {ir.shape}") print(f"Classification: {logits.shape}") print(f"BAC estimate: {bac.shape}") print(f"Params: {sum(p.numel() for p in model.parameters()):,}")
|