From fb1a64c7b12cb53eabba2b30f361acb468268ad8 Mon Sep 17 00:00:00 2001 From: huanghaoyuan Date: Tue, 21 Feb 2023 20:05:21 +0800 Subject: [PATCH] Initial commit --- .gitattributes | 29 ++ README.md | 53 +++ __init__.py | 13 + __pycache__/classifiers.cpython-39.pyc | Bin 0 -> 8303 bytes __pycache__/kernel_utils.cpython-39.pyc | Bin 0 -> 12146 bytes classifiers.py | 317 ++++++++++++++ deepfake.png | Bin 0 -> 54132 bytes deepfake.py | 70 ++++ kernel_utils.py | 390 ++++++++++++++++++ requirements.txt | 9 + ...pFakeClassifier_tf_efficientnet_b7_ns_0_31 | 3 + ...pFakeClassifier_tf_efficientnet_b7_ns_0_23 | 3 + 12 files changed, 887 insertions(+) create mode 100644 .gitattributes create mode 100644 README.md create mode 100644 __init__.py create mode 100644 __pycache__/classifiers.cpython-39.pyc create mode 100644 __pycache__/kernel_utils.cpython-39.pyc create mode 100644 classifiers.py create mode 100644 deepfake.png create mode 100644 deepfake.py create mode 100644 kernel_utils.py create mode 100644 requirements.txt create mode 100644 weights/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31 create mode 100644 weights/final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..544318d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,29 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bin.* filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zstandard filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text +weights filter=lfs diff=lfs merge=lfs -text +weights/** filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md new file mode 100644 index 0000000..f10f45e --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Deepfake + +*author: Zhuoran Yu* + +
+ +## Description + +Deepfake techniques, which present realistic AI-generated videos of people doing and saying fictional things, have the potential to have a significant impact on how people determine the legitimacy of information presented online. + +This operator predicts the probability of a fake video for a given video.This is an adaptation from [DeepfakeDetection](https://github.com/smu-ivpl/DeepfakeDetection). + +
+ +## Code Example + +Load videos from path '/home/test_video' +and use deepfake operator to predict the probabilities of fake videos. + + +```python +import towhee +( + towhee.glob['path']('/home/test_video') + .deepfake['path', 'scores']() + .select['path', 'scores']() + .show() +) +``` + + + +```shell +[0.9893, 0.9097] +``` + +
+ +## Interface + +A deepfake operator takes videos' paths as input. +It predicts the probabilities of fake videos.The higher the score, the higher the probability of it being a fake video.(It can be considered to be a fake video with score higher than 0.5) + +**Parameters:** + +***filepath:*** *str* + +Absolute address of the test videos. + + +**Returns:** *list* + +The probabilities of videos being fake ones. \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..37f5bd7 --- /dev/null +++ b/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 Zilliz. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/__pycache__/classifiers.cpython-39.pyc b/__pycache__/classifiers.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..912cf3988f84be55e2ef75fa2520089edb173dad GIT binary patch literal 8303 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 literal 0 HcmV?d00001 diff --git a/classifiers.py b/classifiers.py new file mode 100644 index 0000000..8c4faed --- /dev/null +++ b/classifiers.py @@ -0,0 +1,317 @@ +from functools import partial + +import numpy as np +import torch +from timm.models.efficientnet import tf_efficientnet_b7_ns +from torch import nn +from torch.nn.modules.dropout import Dropout +from torch.nn.modules.linear import Linear +from torch.nn.modules.pooling import AdaptiveAvgPool2d +#from facebook_deit import deit_base_patch16_224, deit_distill_large_patch16_384, deit_distill_large_patch32_384 +#from taming_transformer import Decoder, VUNet, ActNorm +import functools +#from vit_pytorch.distill import DistillableViT, DistillWrapper, DistillableEfficientViT +import re + +encoder_params = { + "tf_efficientnet_b7_ns": { + "features": 2560, + "init_op": partial(tf_efficientnet_b7_ns, pretrained=True, drop_path_rate=0.2) + } +} + +class GlobalWeightedAvgPool2d(nn.Module): + """ + Global Weighted Average Pooling from paper "Global Weighted Average + Pooling Bridges Pixel-level Localization and Image-level Classification" + """ + + def __init__(self, features: int, flatten=False): + super().__init__() + self.conv = nn.Conv2d(features, 1, kernel_size=1, bias=True) + self.flatten = flatten + + def fscore(self, x): + m = self.conv(x) + m = m.sigmoid().exp() + return m + + def norm(self, x: torch.Tensor): + return x / x.sum(dim=[2, 3], keepdim=True) + + def forward(self, x): + input_x = x + x = self.fscore(x) + x = self.norm(x) + x = x * input_x + x = x.sum(dim=[2, 3], keepdim=not self.flatten) + return x + + +class DeepFakeClassifier(nn.Module): + def __init__(self, encoder, dropout_rate=0.0) -> None: + super().__init__() + self.encoder = encoder_params[encoder]["init_op"]() + self.avg_pool = AdaptiveAvgPool2d((1, 1)) + self.dropout = Dropout(dropout_rate) + self.fc = Linear(encoder_params[encoder]["features"], 1) + + def forward(self, x): + x = self.encoder.forward_features(x) + x = self.avg_pool(x).flatten(1) + x = self.dropout(x) + x = self.fc(x) + return x + +class NLayerDiscriminator(nn.Module): + """Defines a PatchGAN discriminator as in Pix2Pix + --> see https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/master/models/networks.py + """ + def __init__(self, input_nc=3, ndf=64, n_layers=3, use_actnorm=False): + """Construct a PatchGAN discriminator + Parameters: + input_nc (int) -- the number of channels in input images + ndf (int) -- the number of filters in the last conv layer + n_layers (int) -- the number of conv layers in the discriminator + norm_layer -- normalization layer + """ + super(NLayerDiscriminator, self).__init__() + if not use_actnorm: + norm_layer = nn.BatchNorm2d + else: + norm_layer = ActNorm + if type(norm_layer) == functools.partial: # no need to use bias as BatchNorm2d has affine parameters + use_bias = norm_layer.func != nn.BatchNorm2d + else: + use_bias = norm_layer != nn.BatchNorm2d + + kw = 4 + padw = 1 + sequence = [nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)] + nf_mult = 1 + nf_mult_prev = 1 + for n in range(1, n_layers): # gradually increase the number of filters + nf_mult_prev = nf_mult + nf_mult = min(2 ** n, 8) + sequence += [ + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=2, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + + nf_mult_prev = nf_mult + nf_mult = min(2 ** n_layers, 8) + sequence += [ + nn.Conv2d(ndf * nf_mult_prev, ndf * nf_mult, kernel_size=kw, stride=1, padding=padw, bias=use_bias), + norm_layer(ndf * nf_mult), + nn.LeakyReLU(0.2, True) + ] + + sequence += [ + nn.Conv2d(ndf * nf_mult, 1, kernel_size=kw, stride=1, padding=padw)] # output 1 channel prediction map + self.main = nn.Sequential(*sequence) + + def forward(self, input): + """Standard forward.""" + return self.main(input) + +class Discriminator(nn.Module): + def __init__(self, channel = 3, n_strided=6): + super(Discriminator, self).__init__() + self.main = nn.Sequential( + nn.Conv2d(channel, 64, 4, 2, 1, bias=False), #384 -> 192 + nn.LeakyReLU(0.2, inplace=True), + nn.Conv2d(64, 128, 4, 2, 1, bias=False), #192->96 + nn.BatchNorm2d(128), + nn.LeakyReLU(0.2, inplace=True), + nn.Conv2d(128, 256, 4, 2, 1, bias=False), # 96->48 + nn.BatchNorm2d(256), + nn.LeakyReLU(0.2, inplace=True), + nn.Conv2d(256, 512, 4, 2, 1, bias=False), #48->24 + nn.BatchNorm2d(512), + nn.LeakyReLU(0.2, inplace=True), + nn.Conv2d(512, 1024, 4, 2, 1, bias=False), #24->12 + nn.BatchNorm2d(1024), + nn.LeakyReLU(0.2, inplace=True), + nn.Conv2d(1024, 1, 4, 2, 1, bias=False), #12->6 + ) + self.last = nn.Sequential( + #(B, 6*6) + nn.Linear(6*6, 1), + #nn.Sigmoid() + ) + + def discriminator_block(in_filters, out_filters): + layers = [nn.Conv2d(in_filters, out_filters, 4, stride=2, padding=1), nn.LeakyReLU(0.01)] + return layers + + layers = discriminator_block(channel, 32) + curr_dim = 32 + for _ in range(n_strided-1): + layers.extend(discriminator_block(curr_dim, curr_dim*2)) + curr_dim *= 2 + layers.extend(discriminator_block(curr_dim,curr_dim)) + self.model = nn.Sequential(*layers) + self.out1 = nn.Conv2d(curr_dim, 1, 3, stride=1, padding=0, bias=False) + def forward(self, x): + #x = self.main(x).view(-1,6*6) + feature_repr = self.model(x) + x = self.out1(feature_repr) + return x.view(-1, 1)#self.last(x) + +############################## +# RESNET +############################## + + +class ResidualBlock(nn.Module): + def __init__(self, in_features): + super(ResidualBlock, self).__init__() + + conv_block = [ + nn.Conv2d(in_features, in_features, 3, stride=1, padding=1, bias=False), + nn.InstanceNorm2d(in_features, affine=True, track_running_stats=True), + nn.ReLU(inplace=True), + nn.Conv2d(in_features, in_features, 3, stride=1, padding=1, bias=False), + nn.InstanceNorm2d(in_features, affine=True, track_running_stats=True), + ] + + self.conv_block = nn.Sequential(*conv_block) + + def forward(self, x): + return x + self.conv_block(x) + +class Pre_training(nn.Module): + def __init__(self, encoder, channel=3, res_blocks=5, dropout_rate=0.0, patch_size=16) -> None: + super().__init__() + self.encoder = encoder_params[encoder]["init_op"]() + self.emb_ch = encoder_params[encoder]["features"] + + ''' + self.teacher = DeepFakeClassifier(encoder="tf_efficientnet_b7_ns").to("cuda") + checkpoint = torch.load('weights/final_111_DeepFakeClassifier_tf_efficientnet_b7_ns_0_36', map_location='cpu') + state_dict = checkpoint.get("state_dict", checkpoint) + self.teacher.load_state_dict({re.sub("^module.", "", k): v for k, v in state_dict.items()}, strict=False) + ''' + ''' + self.deconv = nn.Sequential( + nn.Conv2d(self.emb_ch, self.emb_ch//2, kernel_size=3, stride=1, padding=1), + nn.BatchNorm2d(self.emb_ch // 2), + nn.ReLU(True), + nn.Conv2d(self.emb_ch//2, self.emb_ch //4, kernel_size=3, stride=1, padding=1), + nn.BatchNorm2d(self.emb_ch //4), + nn.ReLU(True), + ) + ''' + ''' + self.deconv = nn.Sequential( + nn.ConvTranspose2d(self.emb_ch, self.emb_ch//2 , kernel_size=4, stride=2, padding=1, bias=False), + nn.BatchNorm2d(self.emb_ch//2), + nn.ReLU(True), + nn.ConvTranspose2d(self.emb_ch//2, self.emb_ch // 4, kernel_size=4, stride=2, padding=1, bias=False), + nn.BatchNorm2d(self.emb_ch // 4), + nn.ReLU(True), + nn.ConvTranspose2d(self.emb_ch//4, self.emb_ch // 8, kernel_size=4, stride=2, padding=1, bias=False), + nn.BatchNorm2d(self.emb_ch // 8), + nn.ReLU(True), + nn.ConvTranspose2d(self.emb_ch//8, channel, kernel_size=4, stride=2, padding=1, bias=False), + nn.Tanh() + ) + ''' + #self.deconv = nn.ConvTranspose2d(self.emb_ch, 3, kernel_size=16, stride=16) + #self.decoder = Decoder(double_z = False, z_channels = 1024, resolution= 384, in_channels=3, out_ch=3, ch=64 + # , ch_mult=[1,1,2,2], num_res_blocks = 0, attn_resolutions=[16], dropout=0.0) + #nn.ConvTranspose2d(encoder_params[encoder]["features"], channel, kernel_size=patch_size, stride=patch_size) + channels = self.emb_ch + model = [ + nn.ConvTranspose2d(channels, channels, 7, stride=1, padding=3, bias=False), + nn.InstanceNorm2d(channels, affine=True, track_running_stats=True), + nn.ReLU(inplace=True), + ] + curr_dim = channels + + for _ in range(2): + model+=[ + nn.ConvTranspose2d(curr_dim, curr_dim//2, 4, stride=2, padding=1, bias=False), + nn.InstanceNorm2d(curr_dim//2, affine=True, track_running_stats=True), + nn.ReLU(inplace=True), + ] + curr_dim //= 2 + + #Residual blocks + for _ in range(res_blocks): + model += [ResidualBlock(curr_dim)] + #Upsampling + for _ in range(2): + model += [ + nn.ConvTranspose2d(curr_dim, curr_dim//2, 4, stride=2, padding=1, bias=False), + nn.InstanceNorm2d(curr_dim//2, affine=True, track_running_stats=True), + nn.ReLU(inplace=True), + ] + curr_dim = curr_dim //2 + #output layer + model += [nn.Conv2d(curr_dim, channel, 7, stride=1, padding=3), nn.Tanh()] + self.model = nn.Sequential(*model) + self.fc = Linear(encoder_params[encoder]["features"], 1) + self.dropout = Dropout(dropout_rate) + ''' + def generator(self, x, freeze): + if freeze: + with torch.no_grad(): + _, z = self.encoder.pre_training(x) + for param in self.encoder.parameters(): + param.requires_grad = False + else: + #with torch.enable_grad(): + for param in self.encoder.parameters(): + param.requires_grad = True + _, z = self.encoder.pre_training(x) + x = self.model(z) + return x + def discriminator(self, x ,freeze): + if freeze: + with torch.no_grad(): + cls_token, _ = self.encoder.pre_training(x) + for param in self.encoder.parameters(): + param.requires_grad = False + else: + #with torch.enable_grad(): + for param in self.encoder.parameters(): + param.requires_grad = True + cls_token, _ = self.encoder.pre_training(x) + x = self.dropout(cls_token) + cls = self.fc(x) + return cls + ''' + def get_class(self,x): + for param in self.teacher.parameters(): + param.requires_grad = False + teacher_logits = self.teacher(x) + return teacher_logits + + def forward(self, x): + cls_token, z = self.encoder.pre_training(x) + #with torch.no_grad(): + # teacher_logits = self.teacher(x) + #x = self.deconv(x) + #x = self.decoder(x) + #cls = self.dropout(cls_token) + #cls_token = self.fc(cls) + + x = self.model(z) + return x#, cls_token, teacher_logits#, labels + +class DeepFakeClassifierGWAP(nn.Module): + def __init__(self, encoder, dropout_rate=0.5) -> None: + super().__init__() + self.encoder = encoder_params[encoder]["init_op"]() + self.avg_pool = GlobalWeightedAvgPool2d(encoder_params[encoder]["features"]) + self.dropout = Dropout(dropout_rate) + self.fc = Linear(encoder_params[encoder]["features"], 1) + + def forward(self, x): + x = self.encoder.forward_features(x) + x = self.avg_pool(x).flatten(1) + x = self.dropout(x) + x = self.fc(x) + return x diff --git a/deepfake.png b/deepfake.png new file mode 100644 index 0000000000000000000000000000000000000000..fecf931011304105966fd31fad30b4bc232efca5 GIT binary patch literal 54132 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 literal 0 HcmV?d00001 diff --git a/deepfake.py b/deepfake.py new file mode 100644 index 0000000..f93a1fb --- /dev/null +++ b/deepfake.py @@ -0,0 +1,70 @@ +import argparse +import os +import re +import string +import time +import sys +from pathlib import Path +import torch +import pandas as pd + +import towhee +from towhee.operator.base import NNOperator, OperatorFlag +from towhee import register +import warnings +warnings.filterwarnings('ignore') +import logging +log = logging.getLogger() + +@register(output_schema=["scorelist"], + flag=OperatorFlag.STATELESS | OperatorFlag.REUSEABLE) + +class Deepfake(NNOperator): + ''' + Deepfake + ''' + def __init__(self): + super().__init__() + sys.path.append(str(Path(__file__).parent)) + weights_dir = os.path.join(str(Path(__file__).parent),"weights/") + self.model_paths = [os.path.join(weights_dir,model) for model in os.listdir(weights_dir)] + self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + def __call__(self, filepath: string) -> list: + from kernel_utils import VideoReader, FaceExtractor, confident_strategy, predict_on_video + from classifiers import DeepFakeClassifier + models = [] + for path in self.model_paths: + model = DeepFakeClassifier(encoder="tf_efficientnet_b7_ns").to(self.device) + print("loading state dict {}".format(path)) + checkpoint = torch.load(path, map_location="cpu") + state_dict = checkpoint.get("state_dict", checkpoint) + model.load_state_dict({re.sub("^module.", "", k): v for k, v in state_dict.items()}, strict=False) + model.eval() + del checkpoint + models.append(model.half()) + frames_per_video = 32 + video_reader = VideoReader() + video_read_fn = lambda x: video_reader.read_frames(x, num_frames=frames_per_video) + face_extractor = FaceExtractor(video_read_fn) + input_size = 384 + strategy = confident_strategy + #stime = time.time() + prediction = predict_on_video(False, face_extractor=face_extractor, video_path=filepath, + input_size=input_size, batch_size=frames_per_video, models=models, + strategy=strategy, apply_compression=False) + ''' + test_videos = sorted([x for x in os.listdir(filepath) if x[-4:] == ".mp4"]) + print("Predicting {} videos".format(len(test_videos))) + predictions = predict_on_video_set(False, face_extractor=face_extractor, input_size=input_size, models=models, + strategy=strategy, frames_per_video=frames_per_video, videos=test_videos, + num_workers=2, test_dir=filepath) + ''' + return prediction +''' +if __name__ == "__main__": + filepath = "/Users/zilliz/Desktop/deepfake_video/test/aagfhgtpmv.mp4" + op = Deepfake() + pred = op(filepath=filepath) + print(pred) +''' diff --git a/kernel_utils.py b/kernel_utils.py new file mode 100644 index 0000000..5356852 --- /dev/null +++ b/kernel_utils.py @@ -0,0 +1,390 @@ +import os +import math +import cv2 +import numpy as np +import torch +from PIL import Image +from albumentations.augmentations.functional import image_compression +from facenet_pytorch.models.mtcnn import MTCNN +from concurrent.futures import ThreadPoolExecutor +import matplotlib.pyplot as plt +from torchvision.transforms import Normalize +import logging + +log = logging.getLogger() +mean = [0.485, 0.456, 0.406] +std = [0.229, 0.224, 0.225] +normalize_transform = Normalize(mean, std) +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +class VideoReader: + """Helper class for reading one or more frames from a video file.""" + + def __init__(self, verbose=True, insets=(0, 0)): + """Creates a new VideoReader. + + Arguments: + verbose: whether to print warnings and error messages + insets: amount to inset the image by, as a percentage of + (width, height). This lets you "zoom in" to an image + to remove unimportant content around the borders. + Useful for face detection, which may not work if the + faces are too small. + """ + self.verbose = verbose + self.insets = insets + + def read_frames(self, path, num_frames, jitter=0, seed=None): + """Reads frames that are always evenly spaced throughout the video. + + Arguments: + path: the video file + num_frames: how many frames to read, -1 means the entire video + (warning: this will take up a lot of memory!) + jitter: if not 0, adds small random offsets to the frame indices; + this is useful so we don't always land on even or odd frames + seed: random seed for jittering; if you set this to a fixed value, + you probably want to set it only on the first video + """ + assert num_frames > 0 + + capture = cv2.VideoCapture(path) + frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT)) + if frame_count <= 0: return None + + frame_idxs = np.linspace(0, frame_count - 1, num_frames, endpoint=True, dtype=np.int) + if jitter > 0: + np.random.seed(seed) + jitter_offsets = np.random.randint(-jitter, jitter, len(frame_idxs)) + frame_idxs = np.clip(frame_idxs + jitter_offsets, 0, frame_count - 1) + + result = self._read_frames_at_indices(path, capture, frame_idxs) + capture.release() + return result + + def read_random_frames(self, path, num_frames, seed=None): + """Picks the frame indices at random. + + Arguments: + path: the video file + num_frames: how many frames to read, -1 means the entire video + (warning: this will take up a lot of memory!) + """ + assert num_frames > 0 + np.random.seed(seed) + + capture = cv2.VideoCapture(path) + frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT)) + if frame_count <= 0: return None + + frame_idxs = sorted(np.random.choice(np.arange(0, frame_count), num_frames)) + result = self._read_frames_at_indices(path, capture, frame_idxs) + + capture.release() + return result + + def read_frames_at_indices(self, path, frame_idxs): + """Reads frames from a video and puts them into a NumPy array. + + Arguments: + path: the video file + frame_idxs: a list of frame indices. Important: should be + sorted from low-to-high! If an index appears multiple + times, the frame is still read only once. + + Returns: + - a NumPy array of shape (num_frames, height, width, 3) + - a list of the frame indices that were read + + Reading stops if loading a frame fails, in which case the first + dimension returned may actually be less than num_frames. + + Returns None if an exception is thrown for any reason, or if no + frames were read. + """ + assert len(frame_idxs) > 0 + capture = cv2.VideoCapture(path) + result = self._read_frames_at_indices(path, capture, frame_idxs) + capture.release() + return result + + def _read_frames_at_indices(self, path, capture, frame_idxs): + try: + frames = [] + idxs_read = [] + for frame_idx in range(frame_idxs[0], frame_idxs[-1] + 1): + # Get the next frame, but don't decode if we're not using it. + ret = capture.grab() + if not ret: + if self.verbose: + log.error("Error grabbing frame %d from movie %s" % (frame_idx, path)) + break + + # Need to look at this frame? + current = len(idxs_read) + if frame_idx == frame_idxs[current]: + ret, frame = capture.retrieve() + if not ret or frame is None: + if self.verbose: + log.error("Error retrieving frame %d from movie %s" % (frame_idx, path)) + break + + frame = self._postprocess_frame(frame) + frames.append(frame) + idxs_read.append(frame_idx) + + if len(frames) > 0: + return np.stack(frames), idxs_read + if self.verbose: + log.error("No frames read from movie %s" % path) + return None + except: + if self.verbose: + log.error("Exception while reading movie %s" % path) + return None + + def read_middle_frame(self, path): + """Reads the frame from the middle of the video.""" + capture = cv2.VideoCapture(path) + frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT)) + result = self._read_frame_at_index(path, capture, frame_count // 2) + capture.release() + return result + + def read_frame_at_index(self, path, frame_idx): + """Reads a single frame from a video. + + If you just want to read a single frame from the video, this is more + efficient than scanning through the video to find the frame. However, + for reading multiple frames it's not efficient. + + My guess is that a "streaming" approach is more efficient than a + "random access" approach because, unless you happen to grab a keyframe, + the decoder still needs to read all the previous frames in order to + reconstruct the one you're asking for. + + Returns a NumPy array of shape (1, H, W, 3) and the index of the frame, + or None if reading failed. + """ + capture = cv2.VideoCapture(path) + result = self._read_frame_at_index(path, capture, frame_idx) + capture.release() + return result + + def _read_frame_at_index(self, path, capture, frame_idx): + capture.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) + ret, frame = capture.read() + if not ret or frame is None: + if self.verbose: + log.error("Error retrieving frame %d from movie %s" % (frame_idx, path)) + return None + else: + frame = self._postprocess_frame(frame) + return np.expand_dims(frame, axis=0), [frame_idx] + + def _postprocess_frame(self, frame): + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + if self.insets[0] > 0: + W = frame.shape[1] + p = int(W * self.insets[0]) + frame = frame[:, p:-p, :] + + if self.insets[1] > 0: + H = frame.shape[1] + q = int(H * self.insets[1]) + frame = frame[q:-q, :, :] + + return frame + + +class FaceExtractor: + def __init__(self, video_read_fn): + self.video_read_fn = video_read_fn + self.detector = MTCNN(margin=0, thresholds=[0.7, 0.8, 0.8], device=device) + + def process_videos(self, input_dir, filenames, video_idxs): + videos_read = [] + frames_read = [] + frames = [] + results = [] + for video_idx in video_idxs: + # Read the full-size frames from this video. + filename = filenames[video_idx] + video_path = os.path.join(input_dir, filename) + result = self.video_read_fn(video_path) + # Error? Then skip this video. + if result is None: continue + + videos_read.append(video_idx) + + # Keep track of the original frames (need them later). + my_frames, my_idxs = result + + frames.append(my_frames) + frames_read.append(my_idxs) + for i, frame in enumerate(my_frames): + h, w = frame.shape[:2] + img = Image.fromarray(frame.astype(np.uint8)) + img = img.resize(size=[s // 2 for s in img.size]) + + batch_boxes, probs = self.detector.detect(img, landmarks=False) + + faces = [] + scores = [] + if batch_boxes is None: + continue + for bbox, score in zip(batch_boxes, probs): + if bbox is not None: + xmin, ymin, xmax, ymax = [int(b * 2) for b in bbox] + w = xmax - xmin + h = ymax - ymin + p_h = h // 3 + p_w = w // 3 + crop = frame[max(ymin - p_h, 0):ymax + p_h, max(xmin - p_w, 0):xmax + p_w] + faces.append(crop) + scores.append(score) + + frame_dict = {"video_idx": video_idx, + "frame_idx": my_idxs[i], + "frame_w": w, + "frame_h": h, + "faces": faces, + "scores": scores} + results.append(frame_dict) + + return results + + def process_video(self, video_path): + """Convenience method for doing face extraction on a single video.""" + input_dir = os.path.dirname(video_path) + filenames = [os.path.basename(video_path)] + return self.process_videos(input_dir, filenames, [0]) + + + +def confident_strategy(pred, t=0.8): + pred = np.array(pred) + sz = len(pred) + fakes = np.count_nonzero(pred > t) + # 11 frames are detected as fakes with high probability + if fakes > sz // 2.5 and fakes > 11: + return np.mean(pred[pred > t]) + elif np.count_nonzero(pred < 0.2) > 0.9 * sz: + return np.mean(pred[pred < 0.2]) + else: + return np.mean(pred) + +strategy = confident_strategy + + +def put_to_center(img, input_size): + img = img[:input_size, :input_size] + image = np.zeros((input_size, input_size, 3), dtype=np.uint8) + start_w = (input_size - img.shape[1]) // 2 + start_h = (input_size - img.shape[0]) // 2 + image[start_h:start_h + img.shape[0], start_w: start_w + img.shape[1], :] = img + return image + + +def isotropically_resize_image(img, size, interpolation_down=cv2.INTER_AREA, interpolation_up=cv2.INTER_CUBIC): + h, w = img.shape[:2] + if max(w, h) == size: + return img + if w > h: + scale = size / w + h = h * scale + w = size + else: + scale = size / h + w = w * scale + h = size + interpolation = interpolation_up if scale > 1 else interpolation_down + resized = cv2.resize(img, (int(w), int(h)), interpolation=interpolation) + return resized + +def dist(p1, p2): + return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) + +detector = MTCNN(margin=0, thresholds=(0.7, 0.8, 0.8), device=device) +def predict_on_video(distill, face_extractor, video_path, batch_size, input_size, models, strategy=np.mean, + apply_compression=False): + batch_size *= 4 + try: + faces = face_extractor.process_video(video_path) + if len(faces) > 0: + x = np.zeros((batch_size, input_size, input_size, 3), dtype=np.uint8) + #e = np.zeros((batch_size, 32, 32, 3), dtype=np.uint8) #eye + n = 0 + for frame_data in faces: + for face in frame_data["faces"]: + #print(face) + # _,_,landmark = detector.detect(face, landmarks=True) + '''# eye 0524 + try: + landmark = np.around(landmark[0]).astype(np.int16) + (x1, y1), (x2, y2) = landmark[:2] + w = dist((x1, y1), (x2, y2)) + dilation = int(w // 4) + eye_image = face[y2 - dilation:y1 + dilation, x1 - dilation:x2 + dilation] + eye_image = cv2.resize(eye_image, dsize=(32, 32), interpolation=cv2.INTER_CUBIC) + except Exception as ex: + eye_image = cv2.resize(face, dsize=(32, 32), interpolation=cv2.INTER_CUBIC) + '''# + resized_face = isotropically_resize_image(face, input_size) + resized_face = put_to_center(resized_face, input_size) + + if apply_compression: + resized_face = image_compression(resized_face, quality=90, image_type=".jpg") + #eye_image = image_compression(eye_image, quality=90, image_type=".jpg")#eye + if n + 1 < batch_size: + x[n] = resized_face + #e[n] = eye_image#eye + n += 1 + else: + pass + if n > 0: + x = torch.tensor(x, device=device).float() + #e = torch.tensor(e, device="cuda").float() #eye + # Preprocess the images. + x = x.permute((0, 3, 1, 2)) + #e = e.permute((0, 3, 1, 2))#eye + for i in range(len(x)): + x[i] = normalize_transform(x[i] / 255.) + #e[i] = normalize_transform(e[i] / 255.) #eye + # Make a prediction, then take the average. + with torch.no_grad(): + preds = [] + for model in models: + if distill: + _, y_pred, _ = model(x[:n]) #eye , e[:n].half() + else: + y_pred = model(x[:n]) + y_pred = torch.sigmoid(y_pred.squeeze()) + bpred = y_pred[:n].cpu().numpy() + preds.append(strategy(bpred)) + return np.mean(preds) + except Exception as e: + log.error("Prediction error on video %s: %s" % (video_path, str(e))) + + return 0.5 + + +def predict_on_video_set(distill, face_extractor, videos, input_size, num_workers, test_dir, frames_per_video, models, + strategy=np.mean, + apply_compression=False): + def process_file(i): + filename = videos[i] + y_pred = predict_on_video(distill, face_extractor=face_extractor, video_path=os.path.join(test_dir, filename), + input_size=input_size, + batch_size=frames_per_video, + models=models, strategy=strategy, apply_compression=apply_compression) + return y_pred + + with ThreadPoolExecutor(max_workers=num_workers) as ex: + predictions = ex.map(process_file, range(len(videos))) + #predictions = [] + #for i in range(len(videos)): + # predictions.append(process_file(i)) + return list(predictions) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4560ab8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +dlib +facenet-pytorch +albumentations +timm +pytorch_toolbelt +tensorboardx +matplotlib +tqdm +pandas \ No newline at end of file diff --git a/weights/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31 b/weights/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31 new file mode 100644 index 0000000..5751074 --- /dev/null +++ b/weights/final_777_DeepFakeClassifier_tf_efficientnet_b7_ns_0_31 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00dd6cd9466cddfd7da7c333d8fecae81593307827093deaf4d7cdf704bc8bfa +size 266910617 diff --git a/weights/final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23 b/weights/final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23 new file mode 100644 index 0000000..b41c1b6 --- /dev/null +++ b/weights/final_999_DeepFakeClassifier_tf_efficientnet_b7_ns_0_23 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:849036dc211387420412feb68c7451e98a948072b94e213e1104e2f2bf7791ad +size 266910615