├── .gitignore ├── example.png ├── restuls └── More_results.pdf ├── models ├── __init__.py ├── FakeFaceNet.py ├── AlexNet.py ├── VGG.py ├── DiscNet.py └── MobileNet.py ├── attacks ├── run_attack.sh ├── utils.py ├── eval.py ├── attack.py └── attack_ens.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | checkPoint/ 3 | models/__pycache__/ 4 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enkiwang/Imperceptible-fake-face-antiforensic/HEAD/example.png -------------------------------------------------------------------------------- /restuls/More_results.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enkiwang/Imperceptible-fake-face-antiforensic/HEAD/restuls/More_results.pdf -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | from .AlexNet import AlexNet 2 | from .FakeFaceNet import FakeNet 3 | from .VGG import VGGNet 4 | from .DiscNet import DiscNet 5 | from .MobileNet import MobileNetV2 6 | 7 | -------------------------------------------------------------------------------- /attacks/run_attack.sh: -------------------------------------------------------------------------------- 1 | 2 | #Experimental Setting 3 | INPUT_DIR=../data/test/clean/fake/ 4 | OUTPUT_DIR=../data/test/advData/fake/ 5 | MODEL_NUM=1 6 | MAX_EPS1=2 7 | MAX_EPS2=6 8 | MAX_EPS3=6 9 | NUM_ITER=10 10 | GPU_ID=0 11 | 12 | python attack.py --input_dir=${INPUT_DIR} --output_dir=${OUTPUT_DIR} \ 13 | --max_epsilon1=${MAX_EPS1} --max_epsilon2=${MAX_EPS2} --max_epsilon3=${MAX_EPS3} \ 14 | --model_num=${MODEL_NUM} --num_iter=${NUM_ITER} \ 15 | --gpu_id=${GPU_ID} 16 | 17 | #python eval.py --imgs_dir=${OUTPUT_DIR} \ 18 | # --max_epsilon1=${MAX_EPS1} --max_epsilon2=${MAX_EPS2} --max_epsilon3=${MAX_EPS3} \ 19 | # --model_num=${MODEL_NUM} --num_iter=${NUM_ITER} \ 20 | # --gpu_id=${GPU_ID} 21 | -------------------------------------------------------------------------------- /models/FakeFaceNet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 6 | 7 | class FakeNet(nn.Module): 8 | 9 | def highPassFilter(self, x): 10 | HPF = torch.Tensor([[0,0,0],[-1,1,0],[0,0,0]]) 11 | HPF = HPF.reshape(1,1,HPF.shape[0],HPF.shape[1]) 12 | HPF = HPF.to(device) 13 | outputs = torch.zeros(x.size()).to(device) 14 | for i in range(3): 15 | ChanFilt = F.conv2d(x[:,i,:,:].unsqueeze(1),HPF, padding=1) 16 | outputs[:,i,:,:] = ChanFilt.squeeze(1) 17 | 18 | return outputs 19 | 20 | def __init__(self): 21 | super(FakeNet, self).__init__() 22 | self.model_name = 'FakeNet' 23 | 24 | self.group1 = nn.Sequential( 25 | nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1), 26 | nn.LeakyReLU(), 27 | nn.MaxPool2d(kernel_size=2, stride=2) 28 | ) 29 | 30 | self.group2 = nn.Sequential( 31 | nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1), 32 | nn.LeakyReLU(), 33 | nn.MaxPool2d(kernel_size=2, stride=2) 34 | ) 35 | 36 | self.group3 = nn.Sequential( 37 | nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1), 38 | nn.LeakyReLU(), 39 | nn.MaxPool2d(kernel_size=2, stride=2) 40 | ) 41 | 42 | self.classifier = nn.Sequential( 43 | nn.Linear(128 * 16 * 16, 1024), 44 | nn.LeakyReLU(), 45 | 46 | nn.Linear(1024, 512), 47 | nn.LeakyReLU(), 48 | 49 | nn.Linear(512, 2) 50 | ) 51 | 52 | def forward(self, x): 53 | x = self.highPassFilter(x) 54 | x = self.group1(x) 55 | x = self.group2(x) 56 | x = self.group3(x) 57 | x = x.view(x.size(0), 128 * 16 * 16) 58 | x = self.classifier(x) 59 | 60 | return x 61 | 62 | -------------------------------------------------------------------------------- /models/AlexNet.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch 3 | import torch.nn.functional as F 4 | 5 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 6 | 7 | class AlexNet(nn.Module): 8 | def highPassFilter(self, x): 9 | HPF = torch.Tensor([[0,0,0],[-1,1,0],[0,0,0]]) 10 | HPF = HPF.reshape(1,1,HPF.shape[0],HPF.shape[1]) 11 | HPF = HPF.to(device) 12 | outputs = torch.zeros(x.size()).to(device) 13 | for i in range(3): 14 | ChanFilt = F.conv2d(x[:,i,:,:].unsqueeze(1),HPF, padding=1) 15 | outputs[:,i,:,:] = ChanFilt.squeeze(1) 16 | 17 | return outputs 18 | 19 | def __init__(self): 20 | super(AlexNet, self).__init__() 21 | 22 | self.model_name = 'AlexNet' 23 | self.features = nn.Sequential( 24 | nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), 25 | nn.ReLU(inplace=True), 26 | nn.MaxPool2d(kernel_size=3, stride=2), 27 | nn.Conv2d(64, 192, kernel_size=5, padding=2), 28 | nn.ReLU(inplace=True), 29 | nn.MaxPool2d(kernel_size=3, stride=2), 30 | nn.Conv2d(192, 384, kernel_size=3, padding=1), 31 | nn.ReLU(inplace=True), 32 | nn.Conv2d(384, 256, kernel_size=3, padding=1), 33 | nn.ReLU(inplace=True), 34 | nn.Conv2d(256, 256, kernel_size=3, padding=1), 35 | nn.ReLU(inplace=True), 36 | nn.MaxPool2d(kernel_size=3, stride=2), 37 | ) 38 | self.classifier = nn.Sequential( 39 | nn.Dropout(0.5), 40 | nn.Linear(256 * 3 * 3, 4096), 41 | nn.ReLU(), 42 | 43 | nn.Dropout(0.5), 44 | nn.Linear(4096, 4096), 45 | nn.ReLU(), 46 | nn.Linear(4096, 2) 47 | ) 48 | 49 | def forward(self, x): 50 | x = self.highPassFilter(x) 51 | x = self.features(x) 52 | x = x.view(x.size(0), 256 * 3 * 3) 53 | x = self.classifier(x) 54 | return x 55 | -------------------------------------------------------------------------------- /models/VGG.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch 3 | import torch.nn.functional as F 4 | 5 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 6 | 7 | class VGGNet(nn.Module): 8 | def highPassFilter(self, x): 9 | HPF = torch.Tensor([[0,0,0],[-1,1,0],[0,0,0]]) 10 | HPF = HPF.reshape(1,1,HPF.shape[0],HPF.shape[1]) 11 | HPF = HPF.to(device) 12 | outputs = torch.zeros(x.size()).to(device) 13 | for i in range(3): 14 | ChanFilt = F.conv2d(x[:,i,:,:].unsqueeze(1),HPF, padding=1) 15 | outputs[:,i,:,:] = ChanFilt.squeeze(1) 16 | 17 | return outputs 18 | 19 | def __init__(self): 20 | super(VGGNet, self).__init__() 21 | 22 | self.model_name = 'VGG' 23 | 24 | self.features = nn.Sequential( 25 | nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1), 26 | nn.ReLU(), 27 | nn.MaxPool2d(kernel_size=2, stride=2), 28 | nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1), 29 | nn.ReLU(), 30 | nn.MaxPool2d(kernel_size=2, stride=2), 31 | nn.Conv2d(64, 128, kernel_size=3, padding=1), 32 | nn.ReLU(), 33 | nn.MaxPool2d(kernel_size=2, stride=2), 34 | nn.Conv2d(128, 256, kernel_size=3, padding=1), 35 | nn.ReLU(), 36 | nn.MaxPool2d(kernel_size=2, stride=2), 37 | nn.Conv2d(256, 512, kernel_size=3, padding=1), 38 | nn.ReLU(), 39 | nn.MaxPool2d(kernel_size=2, stride=2), 40 | ) 41 | 42 | self.classifier = nn.Sequential( 43 | nn.Dropout(0.5), 44 | nn.Linear(512 * 4 * 4, 4096), 45 | nn.ReLU(), 46 | 47 | nn.Dropout(0.5), 48 | nn.Linear(4096, 4096), 49 | nn.LeakyReLU(negative_slope=0.01), 50 | nn.Linear(4096, 2) 51 | ) 52 | 53 | def forward(self, x): 54 | x = self.highPassFilter(x) 55 | x = self.features(x) 56 | x = x.view(x.size(0), 512 * 4 * 4) 57 | x = self.classifier(x) 58 | return x 59 | -------------------------------------------------------------------------------- /models/DiscNet.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch 3 | import torch.nn.functional as F 4 | 5 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 6 | 7 | class DiscNet(nn.Module): 8 | def highPassFilter(self, x): 9 | HPF = torch.Tensor([[0,0,0],[-1,1,0],[0,0,0]]) 10 | HPF = HPF.reshape(1,1,HPF.shape[0],HPF.shape[1]) 11 | HPF = HPF.to(device) 12 | outputs = torch.zeros(x.size()).to(device) 13 | for i in range(3): 14 | ChanFilt = F.conv2d(x[:,i,:,:].unsqueeze(1),HPF, padding=1) 15 | outputs[:,i,:,:] = ChanFilt.squeeze(1) 16 | 17 | return outputs 18 | 19 | def __init__(self): 20 | super(DiscNet, self).__init__() 21 | self.model_name = 'DiscNet' 22 | 23 | self.features = nn.Sequential( 24 | nn.Conv2d(3, 32, kernel_size=4, stride=2, padding=1), 25 | nn.LeakyReLU(negative_slope=0.2), 26 | nn.MaxPool2d(kernel_size=2, stride=2), 27 | nn.Conv2d(32, 64, kernel_size=4, stride=2, padding=1), 28 | nn.LeakyReLU(negative_slope=0.2), 29 | nn.MaxPool2d(kernel_size=2, stride=2), 30 | nn.Conv2d(64, 128, kernel_size=3, padding=1), 31 | nn.LeakyReLU(negative_slope=0.01), 32 | nn.Conv2d(128, 256, kernel_size=3, padding=1), 33 | nn.LeakyReLU(negative_slope=0.2), 34 | nn.Conv2d(256, 512, kernel_size=3, padding=1), 35 | nn.LeakyReLU(negative_slope=0.2), 36 | nn.MaxPool2d(kernel_size=2, stride=2), 37 | ) 38 | 39 | self.classifier = nn.Sequential( 40 | nn.Dropout(0.5), 41 | nn.Linear(512 * 4 * 4, 4096), 42 | nn.LeakyReLU(negative_slope=0.2), 43 | nn.Dropout(0.5), 44 | nn.Linear(4096, 4096), 45 | nn.LeakyReLU(negative_slope=0.2), 46 | nn.Linear(4096, 2), 47 | ) 48 | 49 | def forward(self, x): 50 | x = self.highPassFilter(x) 51 | x = self.features(x) 52 | x = x.view(x.size(0), 512 * 4 * 4) 53 | x = self.classifier(x) 54 | return x 55 | -------------------------------------------------------------------------------- /attacks/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import numpy as np 4 | from PIL import Image 5 | 6 | import torch 7 | import torch.nn as nn 8 | 9 | 10 | def get_file_list(dataset_dir): 11 | file_list = glob.glob(os.path.join(dataset_dir, '*.png')) + glob.glob(os.path.join(dataset_dir, '*.jpg')) 12 | file_list.sort() 13 | return file_list 14 | 15 | def get_pair_list(imgs_path_org, imgs_path_distort, img_format='.png'): 16 | img_lists_distort = get_file_list(imgs_path_distort) 17 | img_lists_org = [] 18 | for i in range(len(img_lists_distort)): 19 | img_name = img_lists_distort[i].split('/')[-1] 20 | img_name_no_format = img_name.split('.')[0] 21 | img_name_new = img_name_no_format + img_format 22 | img_lists_org.append(os.path.join(imgs_path_org, img_name_new)) 23 | return img_lists_org, img_lists_distort 24 | 25 | def read_image_np(img_path): 26 | """ 27 | read image, and normalize images within [0,1]. 28 | """ 29 | img = Image.open(img_path) 30 | return np.array(img) / 255. 31 | 32 | def read_image(img_path): 33 | img = Image.open(img_path) 34 | img_tensor = preprocess(img) 35 | input_tensor = img_tensor.unsqueeze(0) 36 | return input_tensor 37 | 38 | def read_image_pil(img_path): 39 | """ 40 | read image, and normalize images within [0,1]. 41 | """ 42 | img = Image.open(img_path) 43 | return img 44 | 45 | def save_image_pil(img_pil, img_name, save_dir, format='.png'): 46 | # img_pil: Pillow; img_name: 0; save_dir:faces/; format:.jpg 47 | img_save_path = save_dir + img_name + format 48 | img_pil.save(img_save_path, "PNG") 49 | 50 | def read_sz_save(img_dir_input, img_dir_save): 51 | img_lists = get_file_list(img_dir_input) 52 | for img_idx, img_path in enumerate(img_lists): 53 | print('process %d img' %(img_idx+1)) 54 | img = read_image_pil(img_path) 55 | img = img.resize((args.img_sz,args.img_sz),Image.LANCZOS) 56 | save_image_pil(img, str(img_idx+1), img_dir_save) 57 | 58 | def save_image_tensor(img_save_path, data): 59 | img = data.clone().clamp(0, 255).numpy() 60 | img = img.transpose(1, 2, 0).astype("uint8") 61 | img = Image.fromarray(img) 62 | img.save(img_save_path) 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## An Imperceptible Anti-forensic Method, Models and Dataset 2 | 3 | **Perception matters: exploring imperceptible and transferable anti-forensics for GAN-generated fake face imagery detection** 4 | 5 | 8 | 9 | 10 | This repository contains the code, models and dataset for the project "Perception matters: exploring imperceptible and transferable anti-forensics for GAN-generated fake face imagery detection". 11 | 12 | ## Implementation 13 | This code has been tested on Ubuntu 16.04 system, with following pre-requisites. 14 | 15 | ### Pre-requisites 16 | 17 | 1. python >=3.6.10 18 | 2. PyTorch >=0.4.1 19 | 3. torchvision >=0.2.1 20 | 4. Pillow >=5.4.1 21 | 22 | 23 | ### Dataset 24 | The face dataset we used is an image subset dataset downloaded from [here](https://github.com/NVlabs/stylegan). 25 | 26 | If you agree with the license in [here](https://github.com/NVlabs/stylegan/blob/master/LICENSE.txt), you might be permitted to download the downsampled image subset from [here](https://drive.google.com/file/d/1tudf3eFtlPtn5eX7BWW1IxjXtfIgreyH/view?usp=sharing). 27 | 28 | This face image subset consists of 40,000 real face images and 40,000 fake face images with image resolution as 128x128. For real or fake face images, the dataset splits are: 30,000 images are used for model training; 5,000 images for validation; and the rest 5,000 for test. 29 | 30 | After downloading the dataset, please unzip and put them in the data directory. 31 | 32 | 33 | 34 | ### Models 35 | The pretrained deep learning-based fake face forensic models can be downloaded [here](https://drive.google.com/file/d/1Me6i2xKHbMdh7I-JS7ZJ1QN-6e-SBeov/view?usp=sharing). After downloading pretrained models, please uncompress and put them in the checkPoint directory. 36 | 37 | 38 | ### Run attacks 39 | ```bash 40 | cd attacks 41 | bash ./run_attack.sh 42 | ``` 43 | 44 | If you find our code useful in your research, please consider citing our work: 45 | 46 | ```bib 47 | @article{wang2021perception, 48 | title={Perception matters: Exploring imperceptible and transferable anti-forensics for GAN-generated fake face imagery detection}, 49 | author={Wang, Yongwei and Ding, Xin and Yang, Yixin and Ding, Li and Ward, Rabab and Wang, Z Jane}, 50 | journal={Pattern Recognition Letters}, 51 | volume={146}, 52 | pages={15--22}, 53 | year={2021}, 54 | publisher={Elsevier} 55 | } 56 | ``` 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /models/MobileNet.py: -------------------------------------------------------------------------------- 1 | """ 2 | https://github.com/tonylins/pytorch-mobilenet-v2/blob/master/MobileNetV2.py 3 | """ 4 | 5 | import torch.nn as nn 6 | import math 7 | 8 | 9 | def conv_bn(inp, oup, stride): 10 | return nn.Sequential( 11 | nn.Conv2d(inp, oup, 3, stride, 1, bias=False), 12 | nn.BatchNorm2d(oup), 13 | nn.ReLU6(inplace=True) 14 | ) 15 | 16 | 17 | def conv_1x1_bn(inp, oup): 18 | return nn.Sequential( 19 | nn.Conv2d(inp, oup, 1, 1, 0, bias=False), 20 | nn.BatchNorm2d(oup), 21 | nn.ReLU6(inplace=True) 22 | ) 23 | 24 | 25 | class InvertedResidual(nn.Module): 26 | def __init__(self, inp, oup, stride, expand_ratio): 27 | super(InvertedResidual, self).__init__() 28 | self.stride = stride 29 | assert stride in [1, 2] 30 | 31 | hidden_dim = round(inp * expand_ratio) 32 | self.use_res_connect = self.stride == 1 and inp == oup 33 | 34 | if expand_ratio == 1: 35 | self.conv = nn.Sequential( 36 | # dw 37 | nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), 38 | nn.BatchNorm2d(hidden_dim), 39 | nn.ReLU6(inplace=True), 40 | # pw-linear 41 | nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), 42 | nn.BatchNorm2d(oup), 43 | ) 44 | else: 45 | self.conv = nn.Sequential( 46 | # pw 47 | nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False), 48 | nn.BatchNorm2d(hidden_dim), 49 | nn.ReLU6(inplace=True), 50 | # dw 51 | nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), 52 | nn.BatchNorm2d(hidden_dim), 53 | nn.ReLU6(inplace=True), 54 | # pw-linear 55 | nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), 56 | nn.BatchNorm2d(oup), 57 | ) 58 | 59 | def forward(self, x): 60 | if self.use_res_connect: 61 | return x + self.conv(x) 62 | else: 63 | return self.conv(x) 64 | 65 | 66 | class MobileNetV2(nn.Module): 67 | def __init__(self, n_class=2, input_size=128, width_mult=1.): 68 | super(MobileNetV2, self).__init__() 69 | self.model_name = 'MobileNetV2' 70 | block = InvertedResidual 71 | input_channel = 32 72 | last_channel = 1280 73 | interverted_residual_setting = [ 74 | # t, c, n, s 75 | [1, 16, 1, 1], 76 | [6, 24, 2, 2], 77 | [6, 32, 3, 2], 78 | [6, 64, 4, 2], 79 | [6, 96, 3, 1], 80 | [6, 160, 3, 2], 81 | [6, 320, 1, 1], 82 | ] 83 | 84 | # building first layer 85 | assert input_size % 32 == 0 86 | input_channel = int(input_channel * width_mult) 87 | self.last_channel = int(last_channel * width_mult) if width_mult > 1.0 else last_channel 88 | self.features = [conv_bn(3, input_channel, 2)] 89 | # building inverted residual blocks 90 | for t, c, n, s in interverted_residual_setting: 91 | output_channel = int(c * width_mult) 92 | for i in range(n): 93 | if i == 0: 94 | self.features.append(block(input_channel, output_channel, s, expand_ratio=t)) 95 | else: 96 | self.features.append(block(input_channel, output_channel, 1, expand_ratio=t)) 97 | input_channel = output_channel 98 | # building last several layers 99 | self.features.append(conv_1x1_bn(input_channel, self.last_channel)) 100 | # make it nn.Sequential 101 | self.features = nn.Sequential(*self.features) 102 | 103 | # building classifier 104 | self.classifier = nn.Sequential( 105 | nn.Dropout(0.2), 106 | nn.Linear(self.last_channel, n_class), 107 | ) 108 | 109 | self._initialize_weights() 110 | 111 | def forward(self, x): 112 | x = self.features(x) 113 | x = x.mean(3).mean(2) 114 | x = self.classifier(x) 115 | return x 116 | 117 | def _initialize_weights(self): 118 | for m in self.modules(): 119 | if isinstance(m, nn.Conv2d): 120 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 121 | m.weight.data.normal_(0, math.sqrt(2. / n)) 122 | if m.bias is not None: 123 | m.bias.data.zero_() 124 | elif isinstance(m, nn.BatchNorm2d): 125 | m.weight.data.fill_(1) 126 | m.bias.data.zero_() 127 | elif isinstance(m, nn.Linear): 128 | n = m.weight.size(1) 129 | m.weight.data.normal_(0, 0.01) 130 | m.bias.data.zero_() 131 | -------------------------------------------------------------------------------- /attacks/eval.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | sys.path.append('../') 5 | import glob 6 | from PIL import Image 7 | import torch 8 | import torch.nn as nn 9 | from torchvision import transforms 10 | from models import FakeNet,VGGNet,DiscNet,AlexNet,MobileNetV2 11 | import argparse 12 | 13 | parser = argparse.ArgumentParser(description='Anti-forensic evaluation') 14 | parser.add_argument('--imgs_dir', type=str, default='', help='input image directory.') 15 | parser.add_argument('--gpu_id', type=str, default='0', help='GPU ID') 16 | parser.add_argument('--model_num', type=int, default=1, help='model to attack.') 17 | parser.add_argument('--ckpt_fakenet', type=str, default="../checkPoint/FakeNet/model.pth", help='path to FakeNet model.') 18 | parser.add_argument('--ckpt_vggnet', type=str, default="../checkPoint/VGGNet/model.pth", help='path to VGGNet model.') 19 | parser.add_argument('--ckpt_discnet', type=str, default="../checkPoint/DiscNet/model.pth", help='path to DiscNet model.') 20 | parser.add_argument('--ckpt_alexnet', type=str, default="../checkPoint/AlexNet/model.pth", help='path to AlexNet model.') 21 | parser.add_argument('--ckpt_mobilenet', type=str, default="../checkPoint/MobNet/model.pth", help='path to MobileNetV2 model.') 22 | parser.add_argument('--max_epsilon1', type=int, default=2, help='maximum perturbation at Y.') 23 | parser.add_argument('--max_epsilon2', type=int, default=6, help='maximum perturbation at Cb.') 24 | parser.add_argument('--max_epsilon3', type=int, default=6, help='maximum perturbation at Cr.') 25 | parser.add_argument('--num_iter', type=int, default=10, help='number of iterations.') 26 | parser.add_argument('--image_format', type=str, default='png', help='image format.') 27 | parser.add_argument('--image_width', type=int, default=128, help='width of image.') 28 | parser.add_argument('--image_height', type=int, default=128, help='height of image.') 29 | 30 | args = parser.parse_args() 31 | 32 | os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu_id 33 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 34 | 35 | imgs_dir = args.imgs_dir + 'src_m' + str(args.model_num) + '_eps_' + str(args.max_epsilon1) + \ 36 | str(args.max_epsilon2) + str(args.max_epsilon3) + '/' 37 | 38 | record_dir = args.imgs_dir + 'record/' 39 | 40 | if not os.path.isdir(record_dir): 41 | os.mkdir(record_dir) 42 | 43 | 44 | preprocess = transforms.ToTensor() 45 | 46 | 47 | def get_file_list(dataset_dir): 48 | file_list = glob.glob(os.path.join(dataset_dir, '*.png')) + glob.glob(os.path.join(dataset_dir, '*.jpg')) 49 | file_list.sort() 50 | return file_list 51 | 52 | 53 | def read_image(img_path): 54 | img = Image.open(img_path) 55 | img_tensor = preprocess(img) 56 | input_tensor = img_tensor.unsqueeze(0) 57 | return input_tensor 58 | 59 | 60 | def save_records(acc, model_name='1', save_dir=record_dir): 61 | acc_record_path = save_dir + 'src_m' + str(args.model_num) + '_eps_' + str(args.max_epsilon1) + \ 62 | str(args.max_epsilon2) + str(args.max_epsilon3) + '_eval_m' + model_name + '.txt' 63 | print('Evaluated on model: %s, imgs_dir: %s, acc= %.3f' % (model_name, args.imgs_dir, acc)) 64 | f = open(acc_record_path, 'w') 65 | print('Evaluated on model: %s, imgs_dir: %s, acc= %.3f' % (model_name, args.imgs_dir, acc), file=f) 66 | 67 | # load model for attack 68 | def load_model(model_path, net): 69 | "load pretrained model to network" 70 | model = net().to(device) 71 | model.load_state_dict(torch.load(model_path, map_location='cpu')) 72 | return model 73 | 74 | def get_acc(model_path, net, input_dir, gt_label): 75 | model = load_model(model_path, net) 76 | model.eval() 77 | 78 | img_lists = get_file_list(input_dir) 79 | cnt = 0 80 | for img_idx, img_path in enumerate(img_lists): 81 | img = read_image(img_path) 82 | img = img.to(device) 83 | output = model(img) 84 | _, pred = torch.max(output, 1) 85 | if pred.data[0].cpu().numpy() == gt_label: 86 | cnt += 1 87 | acc = cnt / len(img_lists) 88 | return acc 89 | 90 | 91 | # create label tensor 92 | folder_name = args.imgs_dir 93 | imgs_label = folder_name.split('/')[-2] 94 | 95 | if imgs_label.split('_')[-1] == 'real': 96 | img_label = 1 97 | elif imgs_label.split('_')[-1] == 'fake': 98 | img_label = 0 99 | else: 100 | raise ValueError('check folder name') 101 | 102 | 103 | print('\n============================================================================') 104 | print('Evaluating models on attacked face images from directory: %s'%(imgs_dir)) 105 | print('============================================================================\n') 106 | #evaluate FakeNet 107 | print('\n============================================================================') 108 | print('Evaluating on model 1 ...') 109 | acc = get_acc(args.ckpt_fakenet, FakeNet, imgs_dir, img_label) 110 | save_records(acc, '1', record_dir) 111 | print('============================================================================\n') 112 | 113 | 114 | #evaluate VGGNet 115 | print('\n============================================================================') 116 | print('Evaluating on model 2 ...') 117 | acc = get_acc(args.ckpt_vggnet, VGGNet, imgs_dir, img_label) 118 | save_records(acc, '2', record_dir) 119 | print('============================================================================\n') 120 | 121 | 122 | #evaluate DiscNet 123 | print('\n============================================================================') 124 | print('Evaluating on model 3 ...') 125 | acc = get_acc(args.ckpt_discnet, DiscNet, imgs_dir, img_label) 126 | save_records(acc, '3', record_dir) 127 | print('============================================================================\n') 128 | 129 | 130 | #evaluate AlexNet 131 | print('\n============================================================================') 132 | print('Evaluating on model 4 ...') 133 | acc = get_acc(args.ckpt_alexnet, AlexNet, imgs_dir, img_label) 134 | save_records(acc, '4', record_dir) 135 | print('============================================================================\n') 136 | 137 | 138 | #evaluate MobileNetV2 139 | print('\n============================================================================') 140 | print('Evaluating on model 5 ...') 141 | acc = get_acc(args.ckpt_mobilenet, MobileNetV2, imgs_dir, img_label) 142 | save_records(acc, '5', record_dir) 143 | print('============================================================================\n') 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /attacks/attack.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | sys.path.append('../') 5 | import glob 6 | from PIL import Image 7 | import random 8 | import copy 9 | import torch 10 | import torch.nn as nn 11 | from torchvision import transforms 12 | import torch.nn.functional as F 13 | from models import FakeNet,VGGNet,DiscNet,AlexNet,MobileNetV2 14 | import argparse 15 | 16 | parser = argparse.ArgumentParser(description='Imperceptible fake face anti-forensics') 17 | parser.add_argument('--input_dir', type=str, default='', help='input directory.') 18 | parser.add_argument('--output_dir', type=str, default='', help='output directory.') 19 | parser.add_argument('--gpu_id', type=str, default='0', help='GPU ID') 20 | parser.add_argument('--model_num', type=int, default=1, help='model to attack.') 21 | parser.add_argument('--ckpt_fakenet', type=str, default="../checkPoint/FakeNet/model.pth", help='path to FakeNet model.') 22 | parser.add_argument('--ckpt_vggnet', type=str, default="../checkPoint/VGGNet/model.pth", help='path to VGGNet model.') 23 | parser.add_argument('--ckpt_discnet', type=str, default="../checkPoint/DiscNet/model.pth", help='path to DiscNet model.') 24 | parser.add_argument('--ckpt_alexnet', type=str, default="../checkPoint/AlexNet/model.pth", help='path to AlexNet model.') 25 | parser.add_argument('--ckpt_mobilenet', type=str, default="../checkPoint/MobNet/model.pth", help='path to MobileNetV2 model.') 26 | parser.add_argument('--max_epsilon', type=int, default=4, help='maximum perturbation.') 27 | parser.add_argument('--num_iter', type=int, default=10, help='number of iterations.') 28 | parser.add_argument('--image_format', type=str, default='png', help='image format.') 29 | parser.add_argument('--image_width', type=int, default=128, help='width of image.') 30 | parser.add_argument('--image_height', type=int, default=128, help='height of image.') 31 | parser.add_argument('--momentum', type=float, default=1.0, help='momentum.') 32 | parser.add_argument('--max_epsilon1', type=int, default=2, help='maximum perturbation at Y.') 33 | parser.add_argument('--max_epsilon2', type=int, default=6, help='maximum perturbation at Cb.') 34 | parser.add_argument('--max_epsilon3', type=int, default=6, help='maximum perturbation at Cr.') 35 | args = parser.parse_args() 36 | 37 | os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu_id 38 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 39 | 40 | output_dir = args.output_dir + 'src_m' + str(args.model_num) + '_eps_' + str(args.max_epsilon1) + str(args.max_epsilon2) + str(args.max_epsilon3) + '/' 41 | os.makedirs(output_dir, exist_ok=True) 42 | 43 | preprocess = transforms.ToTensor() 44 | 45 | 46 | def get_file_list(dataset_dir): 47 | file_list = glob.glob(os.path.join(dataset_dir, '*.png')) + glob.glob(os.path.join(dataset_dir, '*.jpg')) 48 | file_list.sort() 49 | return file_list 50 | 51 | 52 | def read_image(img_path): 53 | img = Image.open(img_path) 54 | img_tensor = preprocess(img) 55 | input_tensor = img_tensor.unsqueeze(0) 56 | return input_tensor 57 | 58 | 59 | def ycbcr2rgb_np(img): 60 | invA = np.array([[1.1644, 0.0, 1.5960], [1.1644, -0.3918, -0.8130], [1.1644, 2.0172, 0.0] ]) 61 | img = img.astype(np.float) 62 | img[:,:,[1,2]] -= 128 63 | img[:,:,0] -= 16 64 | rgb = img.dot(invA.T) 65 | np.putmask(rgb, rgb > 255, 255) 66 | np.putmask(rgb, rgb < 0, 0) 67 | return np.around(rgb) 68 | 69 | 70 | def rgb2ycbcr_np(img): 71 | #image as np.float, within range [0,255] 72 | A = np.array([[0.2568, 0.5041, 0.0979], [-0.1482, -0.2910, 0.4392], [0.4392, -0.3678, -0.0714]]) 73 | ycbcr = img.dot(A.T) 74 | ycbcr[:,:,[1,2]] += 128 75 | ycbcr[:,:,0] += 16 76 | return ycbcr 77 | 78 | 79 | def read_image_ycbcr(img_path): 80 | img = Image.open(img_path) 81 | img = np.array(img).astype('float') 82 | img_ycbcr = rgb2ycbcr_np(img) 83 | return img_ycbcr 84 | 85 | def ycbcr_to_tensor(img_ycc): 86 | img_ycc = img_ycc.transpose(2,0,1) / 255. 87 | img_ycc_tensor = torch.Tensor(img_ycc) 88 | return img_ycc_tensor.unsqueeze(0) 89 | 90 | 91 | def ycbcr_to_rgb(img_ycc): 92 | img_ycc = img_ycc.squeeze(0) 93 | img_ycc = img_ycc.permute(1,2,0).view(-1,3).float() 94 | invA = torch.tensor([[1.164, 1.164, 1.164], 95 | [0, -0.392, 2.0172], 96 | [1.5960, -0.8130, 0]]) 97 | 98 | invb = torch.tensor([-16.0/255.0, -128.0/255.0, -128.0/255.0]) 99 | invA, invb = invA.to(device), invb.to(device) 100 | img_ycc = (img_ycc + invb).mm(invA) 101 | img_ycc = img_ycc.view(args.image_height, args.image_width, 3) 102 | img_ycc = img_ycc.permute(2,0,1) 103 | img_ycc = img_ycc.unsqueeze(0) 104 | img_ycc = torch.clamp(img_ycc, min=0., max=1.) 105 | return img_ycc 106 | 107 | 108 | def save_image(img_tensor, img_name, save_path): 109 | img = img_tensor.squeeze(0) 110 | img = img.detach().cpu().numpy() 111 | img = img.transpose(1,2,0) 112 | img = np.clip(img * 255, 0, 255) 113 | img = img.astype("uint8") 114 | img = Image.fromarray(img) 115 | img_save_path = save_path + img_name + '.png' 116 | img.save(img_save_path, "PNG") 117 | 118 | 119 | # load model for attack 120 | def load_model(model_path, net): 121 | "load pretrained model to network" 122 | model = net().to(device) 123 | model.load_state_dict(torch.load(model_path, map_location='cpu')) 124 | return model 125 | 126 | if args.model_num == 1: 127 | model = load_model(args.ckpt_fakenet, FakeNet) 128 | if args.model_num == 2: 129 | model = load_model(args.ckpt_vggnet, VGGNet) 130 | if args.model_num == 3: 131 | model = load_model(args.ckpt_discnet, DiscNet) 132 | if args.model_num == 4: 133 | model = load_model(args.ckpt_alexnet, AlexNet) 134 | if args.model_num == 5: 135 | model = load_model(args.ckpt_mobilenet, MobileNetV2) 136 | 137 | model.eval() 138 | 139 | 140 | img_lists = get_file_list(args.input_dir) 141 | # create label tensor 142 | folder_name = args.input_dir 143 | imgs_label = folder_name.split('/')[-2] 144 | 145 | if imgs_label.split('_')[-1] == 'real': 146 | img_label_tensor = torch.Tensor([1]).long() 147 | elif imgs_label.split('_')[-1] == 'fake': 148 | img_label_tensor = torch.Tensor([0]).long() 149 | else: 150 | raise ValueError('check folder name') 151 | 152 | 153 | def get_file_name(img_path, img_type=args.image_format): 154 | if img_type == 'png': 155 | img_name = img_path.split('.png')[-2].split('/')[-1] 156 | elif img_type == 'jpg': 157 | img_name = img_path.split('.jpg')[-2].split('/')[-1] 158 | else: 159 | raise ValueError('check image format.') 160 | return img_name 161 | 162 | 163 | def attack(img_ycc, label, model, eps, mu=1.0, iterMax=args.num_iter): 164 | g_old = torch.zeros_like(img_ycc) 165 | img_ycc_org = copy.deepcopy(img_ycc.detach()) 166 | img_ycc_grad = torch.zeros_like(img_ycc) 167 | 168 | for k in range(iterMax): 169 | img_rgb_k = ycbcr_to_rgb(img_ycc) 170 | img_rgb_k = torch.clamp(img_rgb_k, min=min_val, max=max_val) 171 | output = model(img_rgb_k) 172 | loss = criterion(output, label) 173 | loss.backward() 174 | normalized_grad = img_ycc.grad.data / torch.sum(torch.abs(img_ycc.grad.data)) 175 | g_new = mu * g_old + normalized_grad 176 | for chan in range(num_chan): 177 | img_ycc.data[0,chan,...] = img_ycc.data[0,chan,...] + (eps[chan] / iterMax) * g_new[0,chan,...].sign() 178 | img_ycc.data[0,chan,...] = img_ycc_org[0,chan,...] + \ 179 | torch.clamp(img_ycc.data[0,chan,...] - \ 180 | img_ycc_org[0,chan,...], min=-eps[chan], max=eps[chan]) 181 | 182 | g_old = g_new 183 | img_ycc.grad.data = torch.zeros_like(img_ycc) 184 | 185 | img_rgb = ycbcr_to_rgb(img_ycc) 186 | img_rgb = torch.clamp(img_rgb, min=min_val, max=max_val) 187 | return img_rgb 188 | 189 | 190 | 191 | # perform imperceptible attack 192 | max_val = 1. 193 | min_val = 0. 194 | num_chan = 3 195 | epsilon = [args.max_epsilon1/ 255., args.max_epsilon2/ 255., args.max_epsilon3 / 255. ] 196 | criterion = nn.CrossEntropyLoss(reduction="sum").to(device) 197 | 198 | print('\n============================================================================') 199 | print(args) 200 | print('\n Attacking face images from directory: %s'%(args.input_dir)) 201 | print('============================================================================\n') 202 | 203 | for img_idx, img_path in enumerate(img_lists): 204 | img_ycc = read_image_ycbcr(img_path) 205 | 206 | img_ycc = ycbcr_to_tensor(img_ycc) 207 | img_ycc = img_ycc.to(device) 208 | img_ycc.requires_grad = True 209 | 210 | img_name = get_file_name(img_path, img_type=args.image_format) 211 | img_label = img_label_tensor.to(device) 212 | print('attacking image: %s' %(img_name+'.png')) 213 | img_adv = attack(img_ycc, img_label, model, epsilon) 214 | save_image(img_adv, img_name, output_dir) 215 | 216 | print('\n============================================================================') 217 | print('attacked face images have been saved in %s \n'%(output_dir)) 218 | 219 | 220 | -------------------------------------------------------------------------------- /attacks/attack_ens.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import sys 4 | sys.path.append('../') 5 | import glob 6 | from PIL import Image 7 | import random 8 | import copy 9 | import torch 10 | import torch.nn as nn 11 | from torchvision import transforms 12 | import torch.nn.functional as F 13 | from models import FakeNet,VGGNet,DiscNet,AlexNet,MobileNetV2 14 | import argparse 15 | 16 | parser = argparse.ArgumentParser(description='Imperceptible fake face anti-forensics') 17 | parser.add_argument('--input_dir', type=str, default='', help='input directory.') 18 | parser.add_argument('--output_dir', type=str, default='', help='output directory.') 19 | parser.add_argument('--gpu_id', type=str, default='0', help='GPU ID') 20 | parser.add_argument('--model_num', type=int, default=1, help='model to attack.') 21 | parser.add_argument('--ckpt_fakenet', type=str, default="../checkPoint/FakeNet/model.pth", help='path to FakeNet model.') 22 | parser.add_argument('--ckpt_vggnet', type=str, default="../checkPoint/VGGNet/model.pth", help='path to VGGNet model.') 23 | parser.add_argument('--ckpt_discnet', type=str, default="../checkPoint/DiscNet/model.pth", help='path to DiscNet model.') 24 | parser.add_argument('--ckpt_alexnet', type=str, default="../checkPoint/AlexNet/model.pth", help='path to AlexNet model.') 25 | parser.add_argument('--ckpt_mobilenet', type=str, default="../checkPoint/MobNet/model.pth", help='path to MobileNetV2 model.') 26 | parser.add_argument('--max_epsilon', type=int, default=4, help='maximum perturbation.') 27 | parser.add_argument('--num_iter', type=int, default=10, help='number of iterations.') 28 | parser.add_argument('--image_format', type=str, default='png', help='image format.') 29 | parser.add_argument('--image_width', type=int, default=128, help='width of image.') 30 | parser.add_argument('--image_height', type=int, default=128, help='height of image.') 31 | parser.add_argument('--momentum', type=float, default=1.0, help='momentum.') 32 | parser.add_argument('--max_epsilon1', type=float, default=2.5, help='maximum perturbation at Y.') 33 | parser.add_argument('--max_epsilon2', type=float, default=6, help='maximum perturbation at Cb.') 34 | parser.add_argument('--max_epsilon3', type=float, default=6, help='maximum perturbation at Cr.') 35 | args = parser.parse_args() 36 | 37 | os.environ['CUDA_VISIBLE_DEVICES'] = args.gpu_id 38 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 39 | 40 | output_dir = args.output_dir + 'src_m' + str(args.model_num) + '_eps_' + str(args.max_epsilon1) + str(args.max_epsilon2) + str(args.max_epsilon3) + '_ens_123/' #an example of models to ensemble 41 | os.makedirs(output_dir, exist_ok=True) 42 | 43 | preprocess = transforms.ToTensor() 44 | 45 | 46 | def get_file_list(dataset_dir): 47 | file_list = glob.glob(os.path.join(dataset_dir, '*.png')) + glob.glob(os.path.join(dataset_dir, '*.jpg')) 48 | file_list.sort() 49 | return file_list 50 | 51 | 52 | def read_image(img_path): 53 | img = Image.open(img_path) 54 | img_tensor = preprocess(img) 55 | input_tensor = img_tensor.unsqueeze(0) 56 | return input_tensor 57 | 58 | 59 | def ycbcr2rgb_np(img): 60 | invA = np.array([[1.1644, 0.0, 1.5960], [1.1644, -0.3918, -0.8130], [1.1644, 2.0172, 0.0] ]) 61 | img = img.astype(np.float) 62 | img[:,:,[1,2]] -= 128 63 | img[:,:,0] -= 16 64 | rgb = img.dot(invA.T) 65 | np.putmask(rgb, rgb > 255, 255) 66 | np.putmask(rgb, rgb < 0, 0) 67 | return np.around(rgb) 68 | 69 | 70 | def rgb2ycbcr_np(img): 71 | #image as np.float, within range [0,255] 72 | A = np.array([[0.2568, 0.5041, 0.0979], [-0.1482, -0.2910, 0.4392], [0.4392, -0.3678, -0.0714]]) 73 | ycbcr = img.dot(A.T) 74 | ycbcr[:,:,[1,2]] += 128 75 | ycbcr[:,:,0] += 16 76 | return ycbcr 77 | 78 | 79 | def read_image_ycbcr(img_path): 80 | img = Image.open(img_path) 81 | img = np.array(img).astype('float') 82 | img_ycbcr = rgb2ycbcr_np(img) 83 | return img_ycbcr 84 | 85 | def ycbcr_to_tensor(img_ycc): 86 | img_ycc = img_ycc.transpose(2,0,1) / 255. 87 | img_ycc_tensor = torch.Tensor(img_ycc) 88 | return img_ycc_tensor.unsqueeze(0) 89 | 90 | 91 | def ycbcr_to_rgb(img_ycc): 92 | img_ycc = img_ycc.squeeze(0) 93 | img_ycc = img_ycc.permute(1,2,0).view(-1,3).float() 94 | invA = torch.tensor([[1.164, 1.164, 1.164], 95 | [0, -0.392, 2.0172], 96 | [1.5960, -0.8130, 0]]) 97 | 98 | invb = torch.tensor([-16.0/255.0, -128.0/255.0, -128.0/255.0]) 99 | invA, invb = invA.to(device), invb.to(device) 100 | img_ycc = (img_ycc + invb).mm(invA) 101 | img_ycc = img_ycc.view(args.image_height, args.image_width, 3) 102 | img_ycc = img_ycc.permute(2,0,1) 103 | img_ycc = img_ycc.unsqueeze(0) 104 | img_ycc = torch.clamp(img_ycc, min=0., max=1.) 105 | return img_ycc 106 | 107 | 108 | def save_image(img_tensor, img_name, save_path): 109 | img = img_tensor.squeeze(0) 110 | img = img.detach().cpu().numpy() 111 | img = img.transpose(1,2,0) 112 | img = np.clip(img * 255, 0, 255) 113 | img = img.astype("uint8") 114 | img = Image.fromarray(img) 115 | img_save_path = save_path + img_name + '.png' 116 | img.save(img_save_path, "PNG") 117 | 118 | 119 | # load model for attack 120 | def load_model(model_path, net): 121 | "load pretrained model to network" 122 | model = net().to(device) 123 | model.load_state_dict(torch.load(model_path, map_location='cpu')) 124 | return model 125 | 126 | model1 = load_model(args.ckpt_fakenet, FakeNet) 127 | model2 = load_model(args.ckpt_vggnet, VGGNet) 128 | model3 = load_model(args.ckpt_discnet, DiscNet) 129 | 130 | model1.eval() 131 | model2.eval() 132 | model3.eval() 133 | 134 | models = {'model1':model1, 'model2':model2, 'model3':model3} 135 | 136 | img_lists = get_file_list(args.input_dir) 137 | # create label tensor 138 | folder_name = args.input_dir 139 | imgs_label = folder_name.split('/')[-2] 140 | 141 | if imgs_label.split('_')[-1] == 'real': 142 | img_label_tensor = torch.Tensor([1]).long() 143 | elif imgs_label.split('_')[-1] == 'fake': 144 | img_label_tensor = torch.Tensor([0]).long() 145 | else: 146 | raise ValueError('check folder name') 147 | 148 | 149 | def get_file_name(img_path, img_type=args.image_format): 150 | if img_type == 'png': 151 | img_name = img_path.split('.png')[-2].split('/')[-1] 152 | elif img_type == 'jpg': 153 | img_name = img_path.split('.jpg')[-2].split('/')[-1] 154 | else: 155 | raise ValueError('check image format.') 156 | return img_name 157 | 158 | 159 | def attack_ens(img_ycc, label, model, eps, mu=1.0, iterMax=args.num_iter): 160 | g_old = torch.zeros_like(img_ycc) 161 | img_ycc_org = copy.deepcopy(img_ycc.detach()) 162 | img_ycc_grad = torch.zeros_like(img_ycc) 163 | 164 | for k in range(iterMax): 165 | img_rgb_k = ycbcr_to_rgb(img_ycc) 166 | img_rgb_k = torch.clamp(img_rgb_k, min=min_val, max=max_val) 167 | output = (models['model1'](img_rgb_k) + models['model2'](img_rgb_k) + 168 | models['model3'](img_rgb_k) ) / 3.0 # an example of models to ensemble 169 | loss = criterion(output, label) 170 | loss.backward() 171 | normalized_grad = img_ycc.grad.data / torch.sum(torch.abs(img_ycc.grad.data)) 172 | g_new = mu * g_old + normalized_grad 173 | for chan in range(num_chan): 174 | img_ycc.data[0,chan,...] = img_ycc.data[0,chan,...] + (eps[chan] / iterMax) * g_new[0,chan,...].sign() 175 | img_ycc.data[0,chan,...] = img_ycc_org[0,chan,...] + \ 176 | torch.clamp(img_ycc.data[0,chan,...] - \ 177 | img_ycc_org[0,chan,...], min=-eps[chan], max=eps[chan]) 178 | 179 | g_old = g_new 180 | img_ycc.grad.data = torch.zeros_like(img_ycc) 181 | 182 | img_rgb = ycbcr_to_rgb(img_ycc) 183 | img_rgb = torch.clamp(img_rgb, min=min_val, max=max_val) 184 | return img_rgb 185 | 186 | 187 | 188 | # perform imperceptible attack 189 | max_val = 1. 190 | min_val = 0. 191 | num_chan = 3 192 | epsilon = [args.max_epsilon1/ 255., args.max_epsilon2/ 255., args.max_epsilon3 / 255. ] 193 | criterion = nn.CrossEntropyLoss(reduction="sum").to(device) 194 | 195 | print('\n============================================================================') 196 | print(args) 197 | print('\n Attacking face images from directory: %s'%(args.input_dir)) 198 | print('============================================================================\n') 199 | 200 | for img_idx, img_path in enumerate(img_lists): 201 | img_ycc = read_image_ycbcr(img_path) 202 | 203 | img_ycc = ycbcr_to_tensor(img_ycc) 204 | img_ycc = img_ycc.to(device) 205 | img_ycc.requires_grad = True 206 | 207 | img_name = get_file_name(img_path, img_type=args.image_format) 208 | img_label = img_label_tensor.to(device) 209 | print('attacking image: %s' %(img_name+'.png')) 210 | img_adv = attack_ens(img_ycc, img_label, model, epsilon) 211 | save_image(img_adv, img_name, output_dir) 212 | 213 | print('\n============================================================================') 214 | print('attacked face images have been saved in %s \n'%(output_dir)) 215 | 216 | 217 | --------------------------------------------------------------------------------