首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >LR-ASD:轻量级鲁棒主动说话人检测网络详解

LR-ASD:轻量级鲁棒主动说话人检测网络详解

原创
作者头像
buzzfrog
修改2026-02-26 20:56:08
修改2026-02-26 20:56:08
780
举报
文章被收录于专栏:云上修行云上修行

1. 什么是主动说话人检测

主动说话人检测(Active Speaker Detection, ASD)是一个音视频多模态任务:给定一段包含多人的视频,模型需要逐帧判断每个可见人脸是否正在说话

这项技术在视频会议、影视后期、社交机器人、助听设备等场景中有着广泛应用。其核心挑战在于,仅靠音频无法区分"谁"在说话,仅靠视频又容易被点头、咀嚼等非语言动作干扰,因此必须将音频和视觉信息有效融合。

LR-ASD(Lightweight and Robust Network for Active Speaker Detection)正是为此设计的一个轻量级解决方案。它最初以 Light-ASD 的名字发表于 CVPR 2023,后在 IJCV 2025 发表了扩展版本,在保持极小模型体积(约 0.84M 参数,权重文件仅 3.4MB)的同时,在 AVA-ActiveSpeaker 数据集上达到了 94.45% 的 mAP。

2. 整体架构

LR-ASD 采用经典的双流编码 + 融合检测架构,由四个核心模块组成:

代码语言:txt
复制
                    ┌─────────────────┐
  MFCC (T×4, 13) ──▶│  Audio Encoder  │──▶ 音频嵌入 (T, 128)─┐
                    └─────────────────┘                       │
                                                              ▼
                                                    ┌─────────────────┐     ┌────────────┐
                                                    │     Fusion      │────▶│  Detector   │──▶ (T, 128) ──▶ FC ──▶ 说话/非说话
                                                    └─────────────────┘     └────────────┘
                    ┌─────────────────┐                       ▲
  灰度帧 (T, 112, 112)──▶│ Visual Encoder  │──▶ 视觉嵌入 (T, 128)─┘
                    └─────────────────┘

四个模块分别是:

模块

输入

输出

作用

Audio Encoder

MFCC 特征 (B, 1, 13, T×4)

(B, T, 128)

从音频频谱中提取时序特征

Visual Encoder

灰度人脸帧 (B, 1, T, 112, 112)

(B, T, 128)

从人脸视频中提取时序特征

Fusion

音频嵌入 + 视觉嵌入

(B, T, 256)

注意力机制融合两路特征

Detector

融合特征 (B, T, 256)

(B, T, 128)

双向 GRU 建模时序依赖

最终通过一个全连接层(FC: 128→2)和 softmax 输出每帧属于"说话"类的概率。

3. 模块详解

3.1 Audio Encoder —— 音频编码器

音频编码器接收 MFCC(Mel 频率倒谱系数)特征作为输入。原始音频以 16kHz 采样,通过 python_speech_features.mfcc() 提取 13 维 MFCC 特征,每秒产生约 100 帧(即每个视频帧对应 4 个 MFCC 帧)。

编码器由 3 个 Audio Block + 2 个 MaxPool 层堆叠而成:

代码语言:txt
复制
输入 (B, 1, 13, T×4)
  │
  ├── Audio_Block_1 (1→32)     # MFCC 维度方向卷积 + 时间方向卷积
  ├── MaxPool (时间维度减半)
  ├── Audio_Block_2 (32→64)
  ├── MaxPool (时间维度减半)
  ├── Audio_Block_3 (64→128)
  │
  ├── Mean (在 MFCC 维度取平均)    # 将 13 维压缩为 1
  └── Transpose
  │
输出 (B, T, 128)

每个 Audio Block 的设计体现了空间-时间分离卷积的思想:先用 (k, 1) 大小的卷积核沿 MFCC 频率维度卷积(两层,kernel 大小分别为 5 和 3),再用 (1, k) 大小的卷积核沿时间维度卷积。这种分离设计相比直接使用 2D 卷积,能显著减少参数量,同时分别捕获频率模式和时间动态。

3.2 Visual Encoder —— 视觉编码器

视觉编码器接收灰度人脸帧序列。输入的像素值(0-255)会先进行归一化:

代码语言:python
复制
x = (x / 255 - 0.4161) / 0.1688

其中 mean=0.4161std=0.1688 是在训练集上统计的人脸图像均值和标准差。

编码器结构与音频编码器对称,由 3 个 Visual Block + 2 个 MaxPool3d 层 + AdaptiveMaxPool2d 组成:

代码语言:txt
复制
输入 (B, 1, T, 112, 112)
  │
  ├── Visual_Block_1 (1→32, stride=2)  # 空间分辨率减半 → 56×56
  ├── MaxPool3d (空间减半)              # → 28×28
  ├── Visual_Block_2 (32→64)
  ├── MaxPool3d (空间减半)              # → 14×14
  ├── Visual_Block_3 (64→128)          # 14×14
  │
  ├── Transpose → (B, T, 128, 14, 14)
  ├── Reshape → (B×T, 128, 14, 14)
  ├── AdaptiveMaxPool2d → (B×T, 128, 1, 1)  # 空间维度压缩
  └── View → (B, T, 128)

Visual Block 同样采用空间-时间分离策略:先用 3D 卷积核 (1, k, k) 处理空间信息,再用 (k, 1, 1) 处理时间信息。第一个 Block 在空间维度使用 stride=2 做下采样。

3.3 Fusion —— 注意力融合模块

Fusion 模块负责将音频嵌入 (B, T, 128) 和视觉嵌入 (B, T, 128) 融合为统一表示 (B, T, 256)。

代码语言:python
复制
x = concat(audio_embed, visual_embed, dim=2)   # (B, T, 256)
identity = x.transpose(1, 2)                   # (B, 256, T)
w = sigmoid(BN(Conv1d(identity)))               # 通道注意力权重
x = (identity * w).transpose(1, 2)             # 加权后的融合特征

核心思想是通过一个 1×1 卷积 + BatchNorm + Sigmoid 生成通道注意力权重,让模型自适应地调整音频和视觉通道的贡献程度。例如在嘈杂环境下,模型可以学会降低音频通道的权重,更多依赖视觉信息。

3.4 Detector —— 时序检测器

Detector 模块在融合特征上建模时序依赖关系。它使用手动实现的双向 GRU(而非 PyTorch 内置的 bidirectional GRU),分别用一个正向 GRU 和一个反向 GRU 处理序列:

代码语言:python
复制
x1, _ = gru_forward(dropout(x))       # 正向 GRU: (B, T, 64)
x2, _ = gru_backward(dropout(flip(x)))# 反向 GRU: (B, T, 64)
x2 = flip(x2)                         # 翻转回正序
x = Fusion(x1, x2)                    # 注意力融合: (B, T, 128)

GRU 的隐藏维度为 256 // 4 = 64,正向和反向的输出再通过同一个 Fusion 模块融合,最终得到 (B, T, 128) 的时序感知特征。

3.5 分类头

检测器输出经过 reshape 变为 (B×T, 128),然后通过一个线性层 FC(128→2) 映射到二分类 logits,最后 softmax 取第二个通道("说话"类)的概率作为最终分数。

3.6 损失函数

训练采用多任务学习策略,同时优化两个损失:

  • lossAV:音视频融合分支的 BCELoss,是主要的监督信号
  • lossV:纯视觉分支的 BCELoss,作为辅助损失

总损失为 loss = lossAV + 0.5 × lossV。纯视觉辅助损失迫使视觉编码器单独就能提取出有判别力的特征,从而提升整体的鲁棒性。

4. 数据预处理流程

4.1 音频预处理

代码语言:txt
复制
原始音频 (任意采样率)
  │
  ├── 重采样至 16kHz 单声道
  │
  ├── MFCC 提取 (numcep=13, winlen=0.025s, winstep=0.010s)
  │   → 每秒约 100 帧,每帧 13 维
  │
  ├── 对齐到视频帧数: target_length = num_video_frames × 4
  │   → 不足则 wrap padding,超出则截断
  │
  └── 输出: float32 数组,shape (T×4, 13)

4.2 视频预处理

视频预处理是一个多步骤的 pipeline,分为两个阶段:

阶段一:人脸裁剪(从原始视频到人脸片段)

代码语言:txt
复制
原始视频
  │
  ├── 1. 场景检测 (SceneDetect)
  │   → 将视频按镜头切换分割为多个场景
  │
  ├── 2. 人脸检测 (S3FD)
  │   → 逐帧检测所有人脸的 bounding box
  │   → 检测时将帧缩放至原始尺寸的 0.25 倍以加速
  │
  ├── 3. 人脸跟踪
  │   → 在每个场景内,用 IOU > 0.5 将逐帧检测结果关联为连续轨迹
  │   → 过滤掉长度不足 10 帧的短轨迹
  │   → 使用中值滤波平滑 bounding box 轨迹
  │
  └── 4. 人脸视频裁剪
      → 以人脸框中心为基准,向外扩展 40% (cropScale=0.40)
      → 裁剪并 resize 至 224×224
      → 同步裁剪对应时间段的音频
      → 输出: 每个人脸轨迹一对 .avi + .wav 文件

阶段二:模型输入预处理(从人脸片段到模型输入)

代码语言:txt
复制
人脸视频片段 (224×224, 25fps)
  │
  ├── 转为灰度图
  ├── resize 至 224×224 (如果不是的话)
  ├── 中心裁剪 112×112: face[56:168, 56:168]
  │   → 效果上聚焦于人脸中下部(鼻子、嘴巴、下巴区域)
  │   → 额头和头发被裁掉,嘴部运动占比更大
  │
  └── 输出: float32 数组,shape (T, 112, 112),像素值 0-255
      (归一化在模型 forward 内部完成)

关于中心裁剪这一步值得展开说明:人脸检测时用了 cropScale=0.40 的外扩,使得 224×224 的裁剪画面中人脸居中,但上下留有较多的头发/下巴空间。推理时再取中心 112×112(即原图的 1/4 面积),就自然地把关注区域收窄到了鼻子到下巴的范围。这个设计是合理的——对于说话人检测任务,嘴唇运动是最强的视觉信号。

4.3 训练时的数据增强

训练阶段对音频和视觉各有专门的增强策略:

音频增强:以 50% 的概率随机叠加同 batch 内其他样本的音频作为噪声,SNR 在 -5dB 到 +5dB 之间随机采样,模拟真实环境中的背景干扰。

视觉增强:随机选择以下四种方式之一:

  • orig:保持原样
  • flip:水平翻转
  • crop:随机裁剪 70%~100% 区域并 resize 回原尺寸
  • rotate:随机旋转 -15° 到 +15°

5. 推理流程

5.1 基本推理

对于单个人脸视频片段,推理流程是三步 pipeline:

代码语言:python
复制
# 1. 音频前端:MFCC → 音频嵌入
audio_embed = model.forward_audio_frontend(audio_feature)  # (1, T, 128)

# 2. 视觉前端:灰度帧 → 视觉嵌入
visual_embed = model.forward_visual_frontend(visual_feature)  # (1, T, 128)

# 3. 音视频后端:融合 → 时序检测 → 分类
av_output = model.forward_audio_visual_backend(audio_embed, visual_embed)  # (T, 128)
score = lossAV.forward(av_output, labels=None)  # (T,) 每帧分数

分数 > 0 表示该帧被判定为"正在说话",分数越大越确信。

5.2 多时长滑窗推理

为了获得更稳定的结果,原始代码采用了多时长滑窗推理 + 结果平均的策略:

代码语言:python
复制
duration_set = {1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 6}  # 秒

对同一段视频分别以 1 秒、2 秒、3 秒……6 秒为窗口大小切片推理,每种时长重复多次(1 秒 ×3、2 秒 ×3 等),最后将所有结果在帧维度上取平均。这样做的好处是:

  • 短窗口捕获快速变化,长窗口利用更多上下文
  • 多次推理取平均降低了随机性
  • GRU 在不同长度序列上的行为差异被平滑掉

5.3 完整视频推理 Pipeline

对于一段包含多人的原始视频,完整的推理 pipeline 如下:

代码语言:txt
复制
原始视频.mp4
  │
  ├── Step 1: 场景检测 ──── CPU
  ├── Step 2: 人脸检测 ──── GPU (S3FD)
  ├── Step 3: 人脸跟踪 ──── CPU
  ├── Step 4: 视频裁剪 ──── CPU + ffmpeg
  ├── Step 5: 说话人检测 ── GPU (LR-ASD)
  │   → 对每个人脸轨迹,多时长推理得到逐帧分数
  └── Step 6: 可视化 ────── CPU
      → 绿色框标记说话人,红色框标记非说话人
      → 输出: video_out.avi

6. 训练配置

参数

说明

优化器

AdamW

weight_decay=0.01

初始学习率

0.001

学习率调度

StepLR

每 epoch 衰减为 0.95 倍

最大 epoch

40

Batch size

2000 帧

动态 batch:按总帧数而非样本数

Dropout

0.5

仅在 Detector 的 GRU 输入处

"动态 batch"是一个有意思的设计:由于不同视频片段的长度差异很大(几帧到几百帧),固定样本数的 batch 会导致显存占用波动剧烈。LR-ASD 的做法是将训练集按视频长度排序,然后以"总帧数不超过 2000"为限组织 mini-batch。这样既保证了显存使用的稳定性,又让同一 batch 内的视频长度相近,减少了 padding 的浪费。

7. 性能表现

AVA-ActiveSpeaker 验证集

指标

mAP

94.45%

参数量

0.84M

权重大小

3.4MB

Columbia ASD 数据集

使用 AVA 预训练权重直接测试(zero-shot 迁移):

Name

Bell

Boll

Lieb

Long

Sick

Avg

F1

88.8%

77.9%

90.3%

85.4%

88.3%

86.1%

使用 TalkSet 微调后的权重:

Name

Bell

Boll

Lieb

Long

Sick

Avg

F1

96.9%

89.4%

97.6%

99.0%

99.2%

96.4%

TalkSet 微调带来了超过 10 个百分点的平均 F1 提升,尤其在 Long 和 Sick 两个场景上接近满分。

8. 模型体积分析

LR-ASD 的"轻量"名副其实。以下是各模块的参数分布:

模块

参数数量

占比

Visual Encoder

约 72 个参数张量

~50%

Audio Encoder

约 72 个参数张量

~45%

Fusion

6 个参数张量

~2%

Detector (GRU + Fusion)

14 个参数张量

~3%

分类头 (FC)

2 个参数张量

<1%

整个模型总计约 0.84M 参数(84 万),这得益于:

  • 空间-时间分离卷积大幅减少了卷积层参数
  • 使用 128 维的紧凑嵌入
  • GRU 隐藏层仅 64 维
  • 没有使用预训练的大型 backbone(如 ResNet)

9. 设计亮点总结

  1. 空间-时间分离卷积:音频和视觉编码器都采用了先空间后时间的分离卷积策略,在保持特征表达能力的同时大幅减少参数量。
  2. 通道注意力融合:Fusion 模块通过 1×1 卷积生成通道注意力权重,让模型自适应地平衡音频和视觉通道的贡献,增强了对噪声环境的鲁棒性。
  3. 手动双向 GRU:Detector 使用两个独立的单向 GRU 分别处理正向和反向序列,再通过注意力融合。这比直接使用 bidirectional=True 的 GRU 更灵活,融合方式也更丰富。
  4. 多任务学习:同时优化音视频融合损失和纯视觉损失,迫使视觉编码器单独就具有判别力,提升了在音频缺失或噪声严重情况下的鲁棒性。
  5. 动态 batch:按总帧数组织 batch 而非固定样本数,兼顾了训练效率和显存稳定性。
  6. 多时长推理策略:推理时用多个时间窗口切片并取平均,平衡了短期精确性和长期上下文利用。
  7. 中心裁剪聚焦嘴部区域:推理时从 224×224 的人脸画面中取中心 112×112,让模型更关注嘴部运动这一核心视觉线索。

10. 快速上手

环境依赖

代码语言:txt
复制
torch, torchvision
numpy, opencv-python
scipy, python_speech_features
pandas, tqdm
scenedetect, sklearn

在 AVA 数据集上训练

代码语言:bash
复制
# 下载并预处理 AVA 数据集
python train.py --dataPathAVA /path/to/AVA --download

# 训练
python train.py --dataPathAVA /path/to/AVA

# 使用预训练权重评估
python train.py --dataPathAVA /path/to/AVA --evaluation

在 Columbia 数据集上测试

代码语言:bash
复制
# 使用 AVA 预训练权重
python Columbia_test.py --evalCol --colSavePath /path/to/columbia

# 使用 TalkSet 微调权重
python Columbia_test.py --evalCol --pretrainModel weight/finetuning_TalkSet.model --colSavePath /path/to/columbia

对自己的视频进行推理

代码语言:bash
复制
# 将视频放入 demo 目录
python Columbia_test.py --videoName my_video --videoFolder demo

# 输出: demo/my_video/pyavi/video_out.avi
# 绿色框 = 说话人, 红色框 = 非说话人

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 什么是主动说话人检测
  • 2. 整体架构
  • 3. 模块详解
    • 3.1 Audio Encoder —— 音频编码器
    • 3.2 Visual Encoder —— 视觉编码器
    • 3.3 Fusion —— 注意力融合模块
    • 3.4 Detector —— 时序检测器
    • 3.5 分类头
    • 3.6 损失函数
  • 4. 数据预处理流程
    • 4.1 音频预处理
    • 4.2 视频预处理
    • 4.3 训练时的数据增强
  • 5. 推理流程
    • 5.1 基本推理
    • 5.2 多时长滑窗推理
    • 5.3 完整视频推理 Pipeline
  • 6. 训练配置
  • 7. 性能表现
    • AVA-ActiveSpeaker 验证集
    • Columbia ASD 数据集
  • 8. 模型体积分析
  • 9. 设计亮点总结
  • 10. 快速上手
    • 环境依赖
    • 在 AVA 数据集上训练
    • 在 Columbia 数据集上测试
    • 对自己的视频进行推理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档