4. 视频特征提取(SlowFast)

任务说明

本案例使用PyTorch官方TorchHub提供的预训练SlowFast模型(slowfast_r50)提取视频片段的特征表示,并与I3D模型(i3d_r50)在同一组视频输入上提取的特征进行对比。通过对比两类模型输出的特征向量维度、数值分布及语义区分度,理解双路径帧率架构与三维卷积架构在视频表征上的差异。

环境依赖

torch>=1.9
torchvision
pytorchvideo
av  # 视频解码后端

核心实现

import torch
import torch.nn.functional as F
from torchvision.transforms import Compose, Resize, CenterCrop
from pytorchvideo.data.encoded_video import EncodedVideo
from pytorchvideo.transforms import ApplyTransformToKey, ShortSideScale, UniformTemporalSubsample
from torchvision.transforms._transforms_video import NormalizeVideo

# ---------- 配置 ----------
SLOWFAST_ALPHA = 4  # SlowFast的alpha参数(slow路径帧率 = fast路径帧率 / alpha)
NUM_CLASSES = 400    # 预训练类别数(Kinetics-400)
CHECKPOINT_PATH = "slowfast_r50.pyth"  # 若本地无缓存会自动下载

# ---------- 视频预处理 ----------
def build_transform(crop_size, num_frames, mode="slowfast"):
    """构建SlowFast或I3D所需的视频预处理流水线"""
    if mode == "slowfast":
        # SlowFast需要两个不同帧率的输入流
        transform = Compose([
            ApplyTransformToKey(
                key="video",
                transform=Compose([
                    UniformTemporalSubsample(num_frames * SLOWFAST_ALPHA),  # 先采样fast路径帧数
                    ShortSideScale(size=256),
                ]),
            ),
        ])
    else:  # I3D
        transform = Compose([
            ApplyTransformToKey(
                key="video",
                transform=Compose([
                    UniformTemporalSubsample(num_frames),
                    ShortSideScale(size=256),
                ]),
            ),
        ])
    return transform

def load_video(video_path, num_frames, mode):
    """加载视频并返回预处理后的张量"""
    video = EncodedVideo.from_path(video_path, decoder="pyav")
    transform = build_transform(crop_size=256, num_frames=num_frames, mode=mode)
    clip = video.get_clip(start_sec=0, end_sec=min(8, video.duration))
    processed = transform(clip)
    return processed["video"]  # shape: [C, T, H, W]

# ---------- 模型加载 ----------
def load_slowfast():
    """加载预训练SlowFast R50"""
    model = torch.hub.load("facebookresearch/pytorchvideo", "slowfast_r50", pretrained=True)
    model.eval()
    return model

def load_i3d():
    """加载预训练I3D R50"""
    model = torch.hub.load("facebookresearch/pytorchvideo", "i3d_r50", pretrained=True)
    model.eval()
    return model

# ---------- 特征提取 ----------
def extract_slowfast_features(model, video_tensor):
    """输入video_tensor: [C, T*alpha, H, W],输出SlowFast特征"""
    # SlowFast需要将输入拆分为slow/fast两个路径
    fast_pathway = video_tensor
    # slow路径按alpha间隔采样帧
    slow_pathway = video_tensor[:, ::SLOWFAST_ALPHA, :, :]
    # 标准化(ImageNet统计)
    mean = torch.tensor([0.45, 0.45, 0.45]).view(3, 1, 1)
    std = torch.tensor([0.225, 0.225, 0.225]).view(3, 1, 1)
    slow_pathway = (slow_pathway - mean) / std
    fast_pathway = (fast_pathway - mean) / std
    # 输入模型
    with torch.no_grad():
        features = model([slow_pathway, fast_pathway])  # 返回预分类特征
    return features

def extract_i3d_features(model, video_tensor):
    """输入video_tensor: [C, T, H, W],输出I3D特征"""
    mean = torch.tensor([0.45, 0.45, 0.45]).view(3, 1, 1)
    std = torch.tensor([0.225, 0.225, 0.225]).view(3, 1, 1)
    video_tensor = (video_tensor - mean) / std
    with torch.no_grad():
        features = model(video_tensor)
    return features

# ---------- 主流程 ----------
def compare_features(video_path):
    """提取并对比SlowFast与I3D特征"""
    # 1. 加载模型
    sf_model = load_slowfast()
    i3d_model = load_i3d()

    # 2. 加载视频(SlowFast需要更多帧)
    sf_frames = 32  # fast路径帧数
    i3d_frames = 32
    sf_video = load_video(video_path, sf_frames, mode="slowfast")
    i3d_video = load_video(video_path, i3d_frames, mode="i3d")

    # 3. 提取特征
    sf_feat = extract_slowfast_features(sf_model, sf_video)   # [1, 2304]
    i3d_feat = extract_i3d_features(i3d_model, i3d_video)     # [1, 2048]

    # 4. 对比分析
    print(f"SlowFast特征维度: {sf_feat.shape[1]}")
    print(f"I3D特征维度: {i3d_feat.shape[1]}")
    print(f"SlowFast特征均值: {sf_feat.mean().item():.4f}, 标准差: {sf_feat.std().item():.4f}")
    print(f"I3D特征均值: {i3d_feat.mean().item():.4f}, 标准差: {i3d_feat.std().item():.4f}")

    # 5. 余弦相似度(验证语义一致性)
    cos_sim = F.cosine_similarity(sf_feat, i3d_feat, dim=1)
    print(f"SlowFast与I3D特征余弦相似度: {cos_sim.item():.4f}")

    return sf_feat, i3d_feat

if __name__ == "__main__":
    # 使用示例视频(可替换为本地视频路径)
    video_path = "sample_video.mp4"
    sf_feat, i3d_feat = compare_features(video_path)

运行与验证

  1. 准备视频:将任意短视频(建议5~10秒,MP4格式)命名为sample_video.mp4放在脚本同目录。
  2. 执行脚本:python compare_slowfast_i3d.py(首次运行会自动下载预训练权重,约200MB)。
  3. 通过条件:
    • 程序无报错完成特征提取;
    • SlowFast输出维度为2304(含slow+fast路径拼接),I3D输出维度为2048;
    • 两类特征均值在0附近(因已做归一化),标准差在0.5~1.5之间;
    • 余弦相似度通常为0.3~0.7(不同视频有差异),表明两者捕捉到部分共享语义但表征方式不同。

关键说明

  • SlowFast的alpha=4意味着fast路径以全帧率采样,slow路径每4帧取1帧,模型内部通过侧向连接融合两条路径信息,最终输出特征维度为2304(slow路径2048维 + fast路径256维拼接)。
  • I3D使用统一的3D卷积核处理全部帧,输出单一2048维特征。
  • 本实现使用pytorchvideo的预训练权重,输入需为[C, T, H, W]格式且像素值在[0,1]范围,内部会做归一化处理。
  • 若需提取中间层特征(而非最终分类前特征),可修改模型返回点,但本案例聚焦于标准特征对比。
SlowFast vs I3D 特征提取流程对比 输入视频 SlowFast 双路径采样 alpha=4, 帧率不同 Slow路径 Fast路径 侧向 特征拼接 SlowFast特征 维度: 2304 I3D 统一3D卷积 所有帧相同处理 3D Conv 特征提取 时空卷积核 I3D特征 维度: 2048 对比分析 维度差异 · 数值分布 · 余弦相似度