论文解读:On the Detection of Digital Face Manipulation(2020 CVPR)

论文解读:On the Detection of Digital Face Manipulation(2020 CVPR)

一.创新点

  1. 制作了一个综合的fakeface数据集,包括0.8M真实人脸和1.8M由不同方法生成的伪人脸。
  2. 提出一种新颖的基于注意力的层,用于提高分类性能并产生指示被操纵的面部区域的注意力图。
  3. 提出一种新的度量,称为IINC(Inverse Intersection Non-containment ),用于评估注意力图,产生比现有度量更一致的评估。

二.注意力机制

1.特点

  1. 并行计算
  2. 考虑了序列的前后关系
  3. 参数共享

更多参考:综述—图像处理中的注意力机制_xys430381_1的专栏-CSDN博客_图像注意力机制

2.方法

  1. 空间域(在H*W方向上)(本文采用)
  2. 通道域(在Channel方向上)
  3. 混合域

三、论文结构

网络结构

1.主干网络:Xception

Xception
Xception是一种主干网络(Backbone),在许多模型中放置在网络前端,用来提取特征图F。Xception包含三个主要模块:Entry flow, Middle flow 和 Exit flow。

  1. Entry flow:这一模块包括最开始的两层普通卷积和三个可分离卷积块,其中每个可分离卷积块又包括两次可分离卷积,且第一次可分离卷积需要对Channel数调整,第二次可分离卷积后需要残差块处理。
  2. Middle flow:这一模块为一个可分离卷积块重复八次,每个卷积快包括三次可分离卷积。
  3. Exit flow:这一模块包括一个可分离卷积块和后续收尾操作:2*可分离卷积,全局平均池化,全连接等。
  4. 注意力层:论文中提到:“We convert Xception-Net into our model by inserting the attention-based layer between Block 4 and Block 5 of the middle flow, and then fine-tune on DFFD training set.”即注意力层在Middle flow中第4个模块和第5个模块之间。

代码实现:

class SeparableConv2d(nn.Module):		#可分离卷积(整合了depthwise和pointwose卷积)
    def __init__(self, c_in, c_out, ks, stride=1, padding=0, dilation=1, bias=False):
        super(SeparableConv2d, self).__init__()
        self.c = nn.Conv2d(c_in, c_in, ks, stride, padding, dilation, groups=c_in, bias=bias)
        self.pointwise = nn.Conv2d(c_in, c_out, 1, 1, 0, 1, 1, bias=bias)

    def forward(self, x):
        x = self.c(x)
        x = self.pointwise(x)
        return x
    
class Block(nn.Module):				#基本块
    def __init__(self, c_in, c_out, reps, stride=1, start_with_relu=True, grow_first=True):
        """
        c_in:输入层channel数
        c_out:输出层channel数
        reps:一个Block中SeparableConv2d块的数量
        stride:卷积步长
        start_with_relu:是否为网络中的第一个Block
        grow_first:是否为网络中的最后一个Block
        """
        super(Block, self).__init__()

        self.skip = None
        self.skip_bn = None
        if c_out != c_in or stride != 1:	# 是否加入残差模块的条件判断
            self.skip = nn.Conv2d(c_in, c_out, 1, stride=stride, bias=False)
            self.skip_bn = nn.BatchNorm2d(c_out)

        self.relu = nn.ReLU(inplace=True)

        rep = []
        c = c_in
        # 如果是Block中的第一次卷积,就需要调整Channel数
        if grow_first:
            rep.append(self.relu)
            rep.append(SeparableConv2d(c_in, c_out, 3, stride=1, padding=1, bias=False))
            rep.append(nn.BatchNorm2d(c_out))
            c = c_out

        # 中间过程的卷积Channel数不变
        for i in range(reps - 1):
            rep.append(self.relu)
            rep.append(SeparableConv2d(c, c, 3, stride=1, padding=1, bias=False))
            rep.append(nn.BatchNorm2d(c))
		# 只有Exit flow中卷积块的最后一次卷积,Channel数扩充
        if not grow_first:
            rep.append(self.relu)
            rep.append(SeparableConv2d(c_in, c_out, 3, stride=1, padding=1, bias=False))
            rep.append(nn.BatchNorm2d(c_out))

        # 如果是第一个Block中的第一个卷积块,就需要去掉最开始的relu层,因为已经做过了
        if not start_with_relu:
            rep = rep[1:]
        else:
            rep[0] = nn.ReLU(inplace=False)

        if stride != 1:
            rep.append(nn.MaxPool2d(3, stride, 1))
        self.rep = nn.Sequential(*rep)

    def forward(self, inp):
        x = self.rep(inp)

        if self.skip is not None:
            y = self.skip(inp)
            y = self.skip_bn(y)
        else:
            y = inp

        x += y
        return x
    
# 主干网络(注意力层也夹在其中)
class Xception(nn.Module):
    """
  Xception optimized for the ImageNet dataset, as specified in
  https://arxiv.org/pdf/1610.02357.pdf
  """

    def __init__(self, maptype, templates, num_classes=1000):
        super(Xception, self).__init__()
        self.num_classes = num_classes
        
        
		# Entry flow
        self.conv1 = nn.Conv2d(3, 32, 3, 2, 0, bias=False)
        self.bn1 = nn.BatchNorm2d(32)
        self.relu = nn.ReLU(inplace=True)

        self.conv2 = nn.Conv2d(32, 64, 3, bias=False)
        self.bn2 = nn.BatchNorm2d(64)

        self.block1 = Block(64, 128, 2, 2, start_with_relu=False, grow_first=True)
        self.block2 = Block(128, 256, 2, 2, start_with_relu=True, grow_first=True)
        self.block3 = Block(256, 728, 2, 2, start_with_relu=True, grow_first=True)
        
        # Middle flow
        self.block4 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block5 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block6 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block7 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block8 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block9 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block10 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        self.block11 = Block(728, 728, 3, 1, start_with_relu=True, grow_first=True)
        
        # Exit flow
        self.block12 = Block(728, 1024, 2, 2, start_with_relu=True, grow_first=False)

        self.conv3 = SeparableConv2d(1024, 1536, 3, 1, 1)
        self.bn3 = nn.BatchNorm2d(1536)

        self.conv4 = SeparableConv2d(1536, 2048, 3, 1, 1)
        self.bn4 = nn.BatchNorm2d(2048)

        self.last_linear = nn.Linear(2048, num_classes)

        if maptype == 'none':
            self.map = [1, None]
        elif maptype == 'reg':
            self.map = RegressionMap(728)
        elif maptype == 'tmp':
            self.map = TemplateMap(728, templates)
        elif maptype == 'pca_tmp':
            self.map = PCATemplateMap(728)
        else:
            print('Unknown map type: `{0}`'.format(maptype))
            sys.exit()

    def features(self, input):
        # Entry flow
        x = self.conv1(input)
        x = self.bn1(x)
        x = self.relu(x)

        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu(x)

        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        
        # Middle flow
        x = self.block4(x)
        x = self.block5(x)
        x = self.block6(x)
        x = self.block7(x)
        # attention-based layer
        mask, vec = self.map(x)
        x = x * mask
        x = self.block8(x)
        x = self.block9(x)
        x = self.block10(x)
        x = self.block11(x)
        
        # Exit flow
        x = self.block12(x)
        x = self.conv3(x)
        x = self.bn3(x)
        x = self.relu(x)

        x = self.conv4(x)
        x = self.bn4(x)
        return x, mask, vec

    def logits(self, features):
        x = self.relu(features)
        x = F.adaptive_avg_pool2d(x, (1, 1))
        x = x.view(x.size(0), -1)
        x = self.last_linear(x)
        return x

    def forward(self, input):
        x, mask, vec = self.features(input)
        x = self.logits(x)
        return x, mask, vec

2.注意力层

论文结构
论文中给出了两种计算注意力层的方法,用于相互比较:1. MAM Map 2.Reg.Map。

1. MAM Map

全称Manipulation Appearance Model,是作者自己提出的一个结构。该结构包括一个可分离卷积块和一个全连接,以此来估计各个区域的权重 α \alpha α。此时, M a t t = M ‾ + A ⋅ α M_{att}=\overline{M}+A·\alpha Matt=M+Aα,其中 M ‾ \overline{M} M A A A通过主成分分析(PCA)提取,将在后文介绍。

# MAM Map
class TemplateMap(nn.Module):
    def __init__(self, c_in, templates):
        super(TemplateMap, self).__init__()
        self.c = Block(c_in, 364, 2, 2, start_with_relu=True, grow_first=False)
        self.l = nn.Linear(364, 10)
        self.relu = nn.ReLU(inplace=True)

        self.templates = templates

    def forward(self, x):
        # 可分离卷积
        v = self.c(x)
        v = self.relu(v)
        # 适应性平均池化
        v = F.adaptive_avg_pool2d(v, (1, 1))
        v = v.view(v.size(0), -1)
        # 全连接
        v = self.l(v)
        mask = torch.mm(v, self.templates.reshape(10, 361))
        mask = mask.reshape(x.shape[0], 1, 19, 19)

        return mask, v
2.Reg. Map

这里采用Direct regression,通过一个可分离卷积和 S i g m o i d Sigmoid Sigmoid函数可以得到。

# Reg. Map
class RegressionMap(nn.Module):
    def __init__(self, c_in):
        super(RegressionMap, self).__init__()
        self.c = SeparableConv2d(c_in, 1, 3, stride=1, padding=1, bias=False)
        self.s = nn.Sigmoid()

    def forward(self, x):
        # 可分离卷积
        mask = self.c(x)
        # sigmoid
        mask = self.s(mask)
        return mask, None
3.PCA

在上文中, M a t t = M ‾ + A ⋅ α M_{att}=\overline{M}+A·\alpha Matt=M+Aα,其中 M ‾ \overline{M} M A A A通过主成分分析(PCA)提取。其数据来源于FaceApp计算的100个真实的manipulation masks。其提取结果如下(10个主成分):
PCA结果

# PCA
class PCATemplateMap(nn.Module):
    def __init__(self, templates):
        super(PCATemplateMap, self).__init__()
        self.templates = templates

    def forward(self, x):
        fe = x.view(x.shape[0], x.shape[1], x.shape[2] * x.shape[3])
        fe = torch.transpose(fe, 1, 2)
        # 均值
        mu = torch.mean(fe, 2, keepdim=True)
        fea_diff = fe - mu

        cov_fea = torch.bmm(fea_diff, torch.transpose(fea_diff, 1, 2))
        B = self.templates.reshape(1, 10, 361).repeat(x.shape[0], 1, 1)
        D = torch.bmm(torch.bmm(B, cov_fea), torch.transpose(B, 1, 2))
        # 特征值和特征向量
        eigen_value, eigen_vector = D.symeig(eigenvectors=True)
        index = torch.tensor([9]).cuda()
        eigen = torch.index_select(eigen_vector, 2, index)

        v = eigen.squeeze(-1)
        mask = torch.mm(v, self.templates.reshape(10, 361))
        mask = mask.reshape(x.shape[0], 1, 19, 19)
        return mask, v

3.损失函数

样本共分为三种场景:supervised, weakly supervised 和 unsupervised。

但总的损失函数始终为: L = L c l a s s i f i e r + λ ∗ L m a p \frak L=\frak L_{\rm classifier} +\lambda \ast \frak L_{\rm map} L=Lclassifier+λLmap
因此,损失函数也分三种情况讨论。

supervised: L m a p = ∣ ∣ M a t t − M g t ∣ ∣ \quad \frak L_{\rm map}=|| \rm M_{\it att}-\rm M_{\it gt}|| Lmap=MattMgt

M g t \rm M_{\it gt} Mgt是真实图像,用全0矩阵表示。对于完全伪造的图像,则用全1矩阵表示。

weakly supervised: L m a p = { ∣ S i g m o i d ( M a t t ) − 0 ∣ , i f   r e a l ∣ m a x ( S i g m o i d ( M a t t ) ) − 0.75 ∣ i f   f a k e \quad \frak L_{\rm map}=\begin{cases}|\rm Sigmoid(M_{\it att})-0|, & \rm if \, real \\|\rm max(Sigmoid(M_{\it att}))-0.75| &\rm if\, fake\end{cases} Lmap={Sigmoid(Matt)0,max(Sigmoid(Matt))0.75ifrealiffake

对于真实图像,这种损失可以使注意力机制对其不产生激活。对于伪造图像,其损失会保持足够大。

unsupervised: λ m = 0 \quad \lambda_m=0 λm=0

此时总损失仅通过分类损失得到。

四、其他细节

1.数据集

论文构造了一个数据集,包括真实图像和虚假图像。

真实图像主要通过FFHQ和CelebA数据集获得。

虚假图像的来源有以下几种:

  1. 身份交换和表情修改:FaceBook++
  2. 属性操作:通过StarGAN训练FaceApp的图像得到
  3. 全脸合成:通过与预训练的PGGAN和StyleGAN得到

2.新的度量标准IINC

"IINC improves upon other metrics by measuring the non-overlap ratio of both maps, rather than their combined overlap, as in IoU. "即IINC相比于传统的IoU,除了考虑重叠的部分之外,还考虑了非重叠的部分。
I I N C = 1 3 − ∣ U ∣ ∗ { 0 i f    M g t ‾ = 0    a n d    M a t t ‾ = 0 1 i f    M g t ‾ = 0    x o r    M g t ‾ = 0 2 − ∣ I ∣ M a t t − ∣ I ∣ M g t o t h e r w i s e IINC=\frac{1}{3-|\rm U|}\ast \begin{cases} 0 &\rm if \; \overline{M_{\it gt}}=0 \; and \; \overline{M_{\it att}}=0 \\ 1 &\rm if \; \overline{M_{\it gt}}=0 \; xor \; \overline{M_{\it gt}}=0 \\ 2-\frac{|\rm I|}{\rm M_{\it att}}-\frac{|\rm I|}{\rm M_{\it gt}} &otherwise \end{cases} IINC=3U1012MattIMgtIifMgt=0andMatt=0ifMgt=0xorMgt=0otherwise
其中,U和I的含义与IoU中的定义相同,即Union和Intersection。
IINC与其他度量的对比

五、论文链接

pdf

code

dataset

2020 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值