动作迁移的目标很直观:输入一张源人物的静态图像,再给一段驱动视频,模型需要让静态图中的人物按照驱动视频里的姿态和表情动起来。让网络在多个分辨率上同时建立注意力映射,是提升这类任务稳定性的有效方向。

MRAA的核心在于把外观编码器产生的多层特征与运动信息分层对齐。低分辨率特征图包含足够的全局结构,适合处理头部朝向、躯干旋转和肢体整体位置;高分辨率特征图则保留面部纹理、发丝边缘和衣物褶皱。如果在单一尺度上做注意力,感受野很难同时覆盖这两种信息,生成结果常出现要么整体姿态正确但局部模糊,要么局部清晰但肢体结构错乱的问题。
单尺度注意力的局限与多分辨率动机
动作迁移网络通常会从驱动视频中提取关键点热图或稀疏运动场,再通过注意力机制把运动信息注入外观特征。单尺度注意力要求一个注意力图同时完成全局布局和局部细节的映射。比如当驱动人物转头并抬起手臂时,低分辨率注意力图可能只捕捉到头部和手臂的大致区域,却无法精细区分头发、五官和衣领边界;高分辨率注意力图虽然对局部敏感,但由于感受野较小,容易在外观特征与运动特征偏移较大时产生错误的对应关系。
多分辨率注意力并不是简单地把各层特征拼接起来。直接拼接多尺度特征后交给一个卷积层处理,网络可能会偏向某一两个尺度,其他分支的贡献被抑制。MRAA的做法是让每个分辨率分支先独立完成一次交叉注意力计算,得到该尺度下已经对齐过的特征,再进行上采样和融合。这样每个分支的输出都经过显式的注意力筛选,避免了无关背景或错误关键点对后续生成的影响。
这种拆分还有利于训练稳定性。不同分辨率的注意力图关注对象不同,反向传播时梯度不会在一个注意力图上互相冲突。低分辨率分支负责大体姿态,即使预测得不够精细,高分辨率分支仍然可以弥补局部细节;反过来,高分辨率分支如果出现局部错乱,低分辨率分支也能提供结构约束。
MRAA模块的具体结构与代码实现
MRAA模块通常位于外观编码器和解码器之间。外观编码器对源图像提取三个或四个尺度的特征,例如下采样倍率为2、4、8的特征图。运动侧则先把驱动关键点转换成热图或稀疏运动场,再经过若干卷积层将通道数调整到与对应外观特征一致。每个分支的注意力计算采用交叉注意力,其中查询来自运动特征,键和值来自外观特征。这样每个位置上的运动表示会主动检索源外观中最相关的纹理信息。
下面是一个简化版PyTorch实现,包含单尺度交叉注意力和多分辨率聚合两个部分。
import torch
import torch.nn as nn
import torch.nn.functional as F
class ResAttention(nn.Module):
"""单尺度交叉注意力,query来自运动特征,key/value来自外观特征"""
def __init__(self, channels):
super().__init__()
self.q_conv = nn.Conv2d(channels, channels, 1)
self.k_conv = nn.Conv2d(channels, channels, 1)
self.v_conv = nn.Conv2d(channels, channels, 1)
self.out_conv = nn.Conv2d(channels, channels, 1)
self.scale = channels ** -0.5
def forward(self, query, key, value):
B, C, H, W = query.shape
q = self.q_conv(query).view(B, C, -1).permute(0, 2, 1)
k = self.k_conv(key).view(B, C, -1)
v = self.v_conv(value).view(B, C, -1)
attn = torch.bmm(q, k) * self.scale
attn = F.softmax(attn, dim=-1)
y = torch.bmm(attn, v.permute(0, 2, 1))
y = y.permute(0, 2, 1).view(B, C, H, W)
return self.out_conv(y)
class MRAABlock(nn.Module):
"""多分辨率注意力聚合块"""
def __init__(self, channels_list):
super().__init__()
self.branches = nn.ModuleList([
ResAttention(c) for c in channels_list
])
self.fuse = nn.Sequential(
nn.Conv2d(sum(channels_list), max(channels_list), 1),
nn.GroupNorm(8, max(channels_list)),
nn.LeakyReLU(0.2, inplace=True)
)
def forward(self, app_feats, motion_feats):
outs = []
base = app_feats[0]
for i, (a, m) in enumerate(zip(app_feats, motion_feats)):
res = self.branches[i](m, a, a)
if res.shape[-1] != base.shape[-1]:
res = F.interpolate(
res,
size=base.shape[-2:],
mode='bilinear',
align_corners=False
)
outs.append(res)
fused = torch.cat(outs, dim=1)
return self.fuse(fused)
代码中的MRAABlock假设外观特征和运动特征已经在每个分支上完成分辨率对齐。真实实现里,运动特征通常由关键点热图经过几个卷积层得到,而不是直接使用原始热图。融合层使用1x1卷积加组归一化,在小批量训练时比批归一化更稳定。低分辨率分支的注意力图在空间维度上较小,计算量低;高分辨率分支的注意力图较大,但通道数可以设得少一些,从而控制整体显存占用。
另一个实现细节是跳跃连接。如果直接把注意力输出送入解码器,深层网络仍然可能丢失部分原始外观信息。实践中可以在每个分辨率分支的输出上添加来自外观编码器的原始特征,让注意力模块只学习残差增量。这种方式能减少训练早期的纹理丢失,也能降低注意力权重初始化对最终效果的影响。
训练目标与损失函数设计
MRAA动作迁移网络通常无法只依赖像素级L1损失完成训练。L1损失容易让输出偏向模糊,因为网络会倾向于预测所有可能纹理的平均值。更常见的做法是组合多个损失项:L1重建损失负责整体颜色和亮度一致性,感知损失通过VGG网络约束高层语义特征,对抗损失让生成帧具备更真实的纹理。对抗损失一般使用PatchGAN判别器,它只在局部块上判断真假,有助于保持高频细节。
关键点等变损失对动作迁移尤其重要。它要求预测出的关键点在进行随机仿射变换后,与变换后图像重新预测的关键点保持一致。这个约束能让运动编码器更稳定地学习姿态表示,避免注意力分支过度依赖绝对坐标。对于视频数据,还可以加入时间一致性损失,比较相邻生成帧在光流或特征空间中的差异,减少帧间抖动。
损失权重没有固定值,但一个可参考的组合是l_total = 10.0 * l_l1 + 1.0 * l_perc + 0.5 * l_adv + 5.0 * l_equiv。训练早期可以降低对抗损失权重,避免判别器过快压倒生成器。优化器通常选择Adam,学习率设为0.0001到0.0002,并在训练中期逐步衰减。如果使用多个分辨率分支,高分辨率分支的学习率可以设为整体学习率的0.5到0.8倍,防止其在训练初期产生大量噪声梯度。
常见问题、调试方法与效果边界
生成帧模糊是动作迁移中最常见的问题之一。排查时可以先生成低分辨率分支的注意力图,观察是否存在注意力过度扩散的情况。如果低分辨率分支的注意力区域覆盖了背景,说明运动热图的前景约束不足,可以在热图生成时加入人物分割掩码作为额外监督。高分辨率分支权重过低也会导致模糊,可以适当提高感知损失权重或增加高分辨率分支的通道数。
身份漂移表现为生成人物看起来不像源人物,反而更像驱动视频中的人。这通常意味着外观编码器被运动特征过度调制。可以检查每个分支的注意力输出是否偏离了源外观特征的分布,必要时在注意力模块后增加一个较强的残差连接,或者降低交叉注意力中查询对键值的影响强度。注意力图可视化是定位身份漂移的关键手段。
MRAA的局限也很明显。极端遮挡、快速运动、剧烈光照变化和复杂背景仍然会导致注意力失效。多分辨率分支增加了网络参数量和显存占用,移动端或实时场景需要配合剪枝、量化或深度可分离卷积。对于跨域人像,比如源人物来自真实照片、驱动视频来自卡通角色,模型通常需要在目标域上微调,否则注意力对齐会严重偏移。总体上,多分辨率注意力机制显著改善了动作迁移的空间精度与结构稳定性,但它并不是一个可以无脑套用的万能模块,仍需要结合数据特点调整分支数量和损失权重。