Commit
·
2f99bb4
0
Parent(s):
Duplicate from pyesonekyaw/faceforgerydetection
Browse filesCo-authored-by: Pye Sone Kyaw <[email protected]>
- .gitattributes +39 -0
- Examples/Fake/fake1.png +0 -0
- Examples/Fake/fake2.png +0 -0
- Examples/Fake/fake3.png +0 -0
- Examples/Fake/fake4.png +0 -0
- Examples/Fake/fake5.png +0 -0
- Examples/Fake/fake6.png +0 -0
- Examples/Fake/fake7.png +0 -0
- Examples/Fake1.mp4 +3 -0
- Examples/Real/real1.png +0 -0
- Examples/Real/real2.png +0 -0
- Examples/Real/real3.png +0 -0
- Examples/Real/real4.png +0 -0
- Examples/Real/real5.png +0 -0
- Examples/Real/real6.png +0 -0
- Examples/Real1.mp4 +3 -0
- Examples/ReferenceVideos.txt +5 -0
- Examples/multiple.png +0 -0
- README.md +14 -0
- Scripts/DeepFakeMask.py +149 -0
- Scripts/__init__.py +0 -0
- Scripts/ca_generator.py +32 -0
- Scripts/model.py +34 -0
- Scripts/preprocess.py +149 -0
- Scripts/sbi_generator.py +217 -0
- Weights/94_0.9485_val.tar +3 -0
- Weights/FFc23.tar +3 -0
- Weights/README.md +13 -0
- Weights/shape_predictor_81_face_landmarks.dat +3 -0
- app.py +387 -0
- requirements.txt +8 -0
.gitattributes
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
Weights/94_0.9485_val.tar filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
Weights/FFc23.tar filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
Examples/Fake1.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
Examples/Real1.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
*.dat filter=lfs diff=lfs merge=lfs -text
|
Examples/Fake/fake1.png
ADDED
|
Examples/Fake/fake2.png
ADDED
|
Examples/Fake/fake3.png
ADDED
|
Examples/Fake/fake4.png
ADDED
|
Examples/Fake/fake5.png
ADDED
|
Examples/Fake/fake6.png
ADDED
|
Examples/Fake/fake7.png
ADDED
|
Examples/Fake1.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:57f42fe00febf36d3c5afb8a6caa7a7969b6fb6ec68799c5980cd994d611e28d
|
| 3 |
+
size 5237358
|
Examples/Real/real1.png
ADDED
|
Examples/Real/real2.png
ADDED
|
Examples/Real/real3.png
ADDED
|
Examples/Real/real4.png
ADDED
|
Examples/Real/real5.png
ADDED
|
Examples/Real/real6.png
ADDED
|
Examples/Real1.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3aa4657ca2627ed201577c61071d01729d9731c7fdcd8963eee816bb2a3420b4
|
| 3 |
+
size 2090471
|
Examples/ReferenceVideos.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Trying To Create an AI Tom Scott (on a $100 budget) - https://www.youtube.com/watch?v=xCn03u_Jvuo
|
| 2 |
+
It’s Getting Harder to Spot a Deep Fake Video - https://www.youtube.com/watch?v=gLoI9hAX9dw
|
| 3 |
+
Robot Linus Reviews a Keyboard - Deepfake - Cleave Truly Ergonomic - https://www.youtube.com/watch?v=34AmKPJNfCg
|
| 4 |
+
Don't drop your internet! - https://www.youtube.com/watch?v=csnnfJi1j2g
|
| 5 |
+
Bail Reform: Last Week Tonight with John Oliver (HBO) - https://www.youtube.com/watch?v=xQLqIWbc9VM
|
Examples/multiple.png
ADDED
|
README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Generalizable Face Forgery Detection with Self-Blended Consistency Learning
|
| 3 |
+
emoji: 🐰
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 3.9
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
duplicated_from: pyesonekyaw/faceforgerydetection
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
Scripts/DeepFakeMask.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
|
| 6 |
+
|
| 7 |
+
class Mask():
|
| 8 |
+
""" Parent class for masks
|
| 9 |
+
the output mask will be <mask_type>.mask
|
| 10 |
+
channels: 1, 3 or 4:
|
| 11 |
+
1 - Returns a single channel mask
|
| 12 |
+
3 - Returns a 3 channel mask
|
| 13 |
+
4 - Returns the original image with the mask in the alpha channel """
|
| 14 |
+
|
| 15 |
+
def __init__(self, landmarks, face, channels=4):
|
| 16 |
+
logger.info("Initializing %s: (face_shape: %s, channels: %s, landmarks: %s)",
|
| 17 |
+
self.__class__.__name__, face.shape, channels, landmarks)
|
| 18 |
+
self.landmarks = landmarks
|
| 19 |
+
self.face = face
|
| 20 |
+
self.channels = channels
|
| 21 |
+
|
| 22 |
+
mask = self.build_mask()
|
| 23 |
+
self.mask = self.merge_mask(mask)
|
| 24 |
+
logger.info("Initialized %s", self.__class__.__name__)
|
| 25 |
+
|
| 26 |
+
def build_mask(self):
|
| 27 |
+
""" Override to build the mask """
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
def merge_mask(self, mask):
|
| 31 |
+
""" Return the mask in requested shape """
|
| 32 |
+
logger.info("mask_shape: %s", mask.shape)
|
| 33 |
+
assert self.channels in (1, 3, 4), "Channels should be 1, 3 or 4"
|
| 34 |
+
assert mask.shape[2] == 1 and mask.ndim == 3, "Input mask be 3 dimensions with 1 channel"
|
| 35 |
+
|
| 36 |
+
if self.channels == 3:
|
| 37 |
+
retval = np.tile(mask, 3)
|
| 38 |
+
elif self.channels == 4:
|
| 39 |
+
retval = np.concatenate((self.face, mask), -1)
|
| 40 |
+
else:
|
| 41 |
+
retval = mask
|
| 42 |
+
|
| 43 |
+
logger.info("Final mask shape: %s", retval.shape)
|
| 44 |
+
return retval
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class dfl_full(Mask): # pylint: disable=invalid-name
|
| 48 |
+
""" DFL facial mask """
|
| 49 |
+
def build_mask(self):
|
| 50 |
+
mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32)
|
| 51 |
+
|
| 52 |
+
nose_ridge = (self.landmarks[27:31], self.landmarks[33:34])
|
| 53 |
+
jaw = (self.landmarks[0:17],
|
| 54 |
+
self.landmarks[48:68],
|
| 55 |
+
self.landmarks[0:1],
|
| 56 |
+
self.landmarks[8:9],
|
| 57 |
+
self.landmarks[16:17])
|
| 58 |
+
eyes = (self.landmarks[17:27],
|
| 59 |
+
self.landmarks[0:1],
|
| 60 |
+
self.landmarks[27:28],
|
| 61 |
+
self.landmarks[16:17],
|
| 62 |
+
self.landmarks[33:34])
|
| 63 |
+
parts = [jaw, nose_ridge, eyes]
|
| 64 |
+
|
| 65 |
+
for item in parts:
|
| 66 |
+
merged = np.concatenate(item)
|
| 67 |
+
cv2.fillConvexPoly(mask, cv2.convexHull(merged), 255.) # pylint: disable=no-member
|
| 68 |
+
return mask
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class components(Mask): # pylint: disable=invalid-name
|
| 72 |
+
""" Component model mask """
|
| 73 |
+
def build_mask(self):
|
| 74 |
+
mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32)
|
| 75 |
+
|
| 76 |
+
r_jaw = (self.landmarks[0:9], self.landmarks[17:18])
|
| 77 |
+
l_jaw = (self.landmarks[8:17], self.landmarks[26:27])
|
| 78 |
+
r_cheek = (self.landmarks[17:20], self.landmarks[8:9])
|
| 79 |
+
l_cheek = (self.landmarks[24:27], self.landmarks[8:9])
|
| 80 |
+
nose_ridge = (self.landmarks[19:25], self.landmarks[8:9],)
|
| 81 |
+
r_eye = (self.landmarks[17:22],
|
| 82 |
+
self.landmarks[27:28],
|
| 83 |
+
self.landmarks[31:36],
|
| 84 |
+
self.landmarks[8:9])
|
| 85 |
+
l_eye = (self.landmarks[22:27],
|
| 86 |
+
self.landmarks[27:28],
|
| 87 |
+
self.landmarks[31:36],
|
| 88 |
+
self.landmarks[8:9])
|
| 89 |
+
nose = (self.landmarks[27:31], self.landmarks[31:36])
|
| 90 |
+
parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose]
|
| 91 |
+
|
| 92 |
+
for item in parts:
|
| 93 |
+
merged = np.concatenate(item)
|
| 94 |
+
cv2.fillConvexPoly(mask, cv2.convexHull(merged), 255.) # pylint: disable=no-member
|
| 95 |
+
return mask
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class extended(Mask): # pylint: disable=invalid-name
|
| 99 |
+
""" Extended mask
|
| 100 |
+
Based on components mask. Attempts to extend the eyebrow points up the forehead
|
| 101 |
+
"""
|
| 102 |
+
def build_mask(self):
|
| 103 |
+
mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32)
|
| 104 |
+
|
| 105 |
+
landmarks = self.landmarks.copy()
|
| 106 |
+
# mid points between the side of face and eye point
|
| 107 |
+
ml_pnt = (landmarks[36] + landmarks[0]) // 2
|
| 108 |
+
mr_pnt = (landmarks[16] + landmarks[45]) // 2
|
| 109 |
+
|
| 110 |
+
# mid points between the mid points and eye
|
| 111 |
+
ql_pnt = (landmarks[36] + ml_pnt) // 2
|
| 112 |
+
qr_pnt = (landmarks[45] + mr_pnt) // 2
|
| 113 |
+
|
| 114 |
+
# Top of the eye arrays
|
| 115 |
+
bot_l = np.array((ql_pnt, landmarks[36], landmarks[37], landmarks[38], landmarks[39]))
|
| 116 |
+
bot_r = np.array((landmarks[42], landmarks[43], landmarks[44], landmarks[45], qr_pnt))
|
| 117 |
+
|
| 118 |
+
# Eyebrow arrays
|
| 119 |
+
top_l = landmarks[17:22]
|
| 120 |
+
top_r = landmarks[22:27]
|
| 121 |
+
|
| 122 |
+
# Adjust eyebrow arrays
|
| 123 |
+
landmarks[17:22] = top_l + ((top_l - bot_l) // 2)
|
| 124 |
+
landmarks[22:27] = top_r + ((top_r - bot_r) // 2)
|
| 125 |
+
|
| 126 |
+
r_jaw = (landmarks[0:9], landmarks[17:18])
|
| 127 |
+
l_jaw = (landmarks[8:17], landmarks[26:27])
|
| 128 |
+
r_cheek = (landmarks[17:20], landmarks[8:9])
|
| 129 |
+
l_cheek = (landmarks[24:27], landmarks[8:9])
|
| 130 |
+
nose_ridge = (landmarks[19:25], landmarks[8:9],)
|
| 131 |
+
r_eye = (landmarks[17:22], landmarks[27:28], landmarks[31:36], landmarks[8:9])
|
| 132 |
+
l_eye = (landmarks[22:27], landmarks[27:28], landmarks[31:36], landmarks[8:9])
|
| 133 |
+
nose = (landmarks[27:31], landmarks[31:36])
|
| 134 |
+
parts = [r_jaw, l_jaw, r_cheek, l_cheek, nose_ridge, r_eye, l_eye, nose]
|
| 135 |
+
|
| 136 |
+
for item in parts:
|
| 137 |
+
merged = np.concatenate(item)
|
| 138 |
+
cv2.fillConvexPoly(mask, cv2.convexHull(merged), 255.) # pylint: disable=no-member
|
| 139 |
+
return mask
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class facehull(Mask): # pylint: disable=invalid-name
|
| 143 |
+
""" Basic face hull mask """
|
| 144 |
+
def build_mask(self):
|
| 145 |
+
mask = np.zeros(self.face.shape[0:2] + (1, ), dtype=np.float32)
|
| 146 |
+
hull = cv2.convexHull( # pylint: disable=no-member
|
| 147 |
+
np.array(self.landmarks).reshape((-1, 2)))
|
| 148 |
+
cv2.fillConvexPoly(mask, hull, 255.0, lineType=cv2.LINE_AA) # pylint: disable=no-member
|
| 149 |
+
return mask
|
Scripts/__init__.py
ADDED
|
File without changes
|
Scripts/ca_generator.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import albumentations as alb
|
| 2 |
+
from albumentations.pytorch import ToTensorV2
|
| 3 |
+
import cv2
|
| 4 |
+
|
| 5 |
+
def get_augs(name):
|
| 6 |
+
IMG_SIZE = 380
|
| 7 |
+
if name == "REAlbu":
|
| 8 |
+
return alb.Compose([
|
| 9 |
+
alb.HorizontalFlip(),
|
| 10 |
+
alb.CoarseDropout(max_holes = 1, min_height=int(IMG_SIZE*0.02), max_height=int(IMG_SIZE*0.2), min_width=int(IMG_SIZE*0.02), max_width=int(IMG_SIZE*0.2), p=1),
|
| 11 |
+
])
|
| 12 |
+
elif name == "RandCropAlbu":
|
| 13 |
+
return alb.Compose([
|
| 14 |
+
alb.HorizontalFlip(),
|
| 15 |
+
alb.RandomResizedCrop(height = IMG_SIZE, width = IMG_SIZE, scale=(1/1.3, 1.0), ratio=(0.9,1.1)),
|
| 16 |
+
])
|
| 17 |
+
elif name == "DFDCAlbu":
|
| 18 |
+
return alb.Compose([
|
| 19 |
+
alb.ImageCompression(quality_lower=60, quality_upper=100, p=0.5),
|
| 20 |
+
alb.GaussNoise(p=0.1),
|
| 21 |
+
alb.GaussianBlur(blur_limit=3, p=0.05),
|
| 22 |
+
alb.HorizontalFlip(),
|
| 23 |
+
alb.OneOf([
|
| 24 |
+
alb.LongestMaxSize(max_size=IMG_SIZE, interpolation=cv2.INTER_CUBIC),
|
| 25 |
+
alb.LongestMaxSize(max_size=IMG_SIZE, interpolation=cv2.INTER_AREA),
|
| 26 |
+
alb.LongestMaxSize(max_size=IMG_SIZE, interpolation=cv2.INTER_LINEAR)
|
| 27 |
+
], p=1.0),
|
| 28 |
+
alb.PadIfNeeded(min_height=IMG_SIZE, min_width=IMG_SIZE, border_mode=cv2.BORDER_CONSTANT),
|
| 29 |
+
alb.OneOf([alb.RandomBrightnessContrast(), alb.FancyPCA(), alb.HueSaturationValue()], p=0.7),
|
| 30 |
+
alb.ToGray(p=0.2),
|
| 31 |
+
alb.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.2, rotate_limit=10, border_mode=cv2.BORDER_CONSTANT, p=0.5),
|
| 32 |
+
])
|
Scripts/model.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
from efficientnet_pytorch import EfficientNet
|
| 4 |
+
from pytorch_grad_cam import GradCAMElementWise
|
| 5 |
+
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Detector(nn.Module):
|
| 9 |
+
def __init__(self):
|
| 10 |
+
super(Detector, self).__init__()
|
| 11 |
+
self.net=EfficientNet.from_pretrained("efficientnet-b4",advprop=True,num_classes=2)
|
| 12 |
+
|
| 13 |
+
def forward(self,x):
|
| 14 |
+
x=self.net(x)
|
| 15 |
+
return x
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def create_model(path="Weights/94_0.9485_val.tar", device=torch.device('cpu')):
|
| 19 |
+
model=Detector()
|
| 20 |
+
model=model.to(device)
|
| 21 |
+
if device == torch.device('cpu'):
|
| 22 |
+
cnn_sd=torch.load(path, map_location=torch.device('cpu') )["model"]
|
| 23 |
+
else:
|
| 24 |
+
cnn_sd=torch.load(path)["model"]
|
| 25 |
+
model.load_state_dict(cnn_sd)
|
| 26 |
+
model.eval()
|
| 27 |
+
return model
|
| 28 |
+
|
| 29 |
+
def create_cam(model):
|
| 30 |
+
target_layers = [model.net._blocks[-1]]
|
| 31 |
+
targets = [ClassifierOutputTarget(1)]
|
| 32 |
+
cam_algorithm = GradCAMElementWise
|
| 33 |
+
cam = cam_algorithm(model=model,target_layers=target_layers,use_cuda=False)
|
| 34 |
+
return cam
|
Scripts/preprocess.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
from tqdm import tqdm
|
| 4 |
+
|
| 5 |
+
def extract_frames(filename,num_frames,model,image_size=(380,380)):
|
| 6 |
+
cap_org = cv2.VideoCapture(filename)
|
| 7 |
+
|
| 8 |
+
if not cap_org.isOpened():
|
| 9 |
+
print(f'Cannot open: {filename}')
|
| 10 |
+
# sys.exit()
|
| 11 |
+
return []
|
| 12 |
+
|
| 13 |
+
croppedfaces=[]
|
| 14 |
+
idx_list=[]
|
| 15 |
+
frame_count_org = int(cap_org.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 16 |
+
|
| 17 |
+
frame_idxs = np.linspace(0, frame_count_org - 1, num_frames, endpoint=True, dtype=int)
|
| 18 |
+
for cnt_frame in range(frame_count_org):
|
| 19 |
+
ret_org, frame_org = cap_org.read()
|
| 20 |
+
height,width=frame_org.shape[:-1]
|
| 21 |
+
if not ret_org:
|
| 22 |
+
tqdm.write('Frame read {} Error! : {}'.format(cnt_frame,os.path.basename(filename)))
|
| 23 |
+
break
|
| 24 |
+
|
| 25 |
+
if cnt_frame not in frame_idxs:
|
| 26 |
+
continue
|
| 27 |
+
|
| 28 |
+
frame = cv2.cvtColor(frame_org, cv2.COLOR_BGR2RGB)
|
| 29 |
+
|
| 30 |
+
faces = model.predict_jsons(frame)
|
| 31 |
+
try:
|
| 32 |
+
if len(faces)==0:
|
| 33 |
+
tqdm.write('No faces in {}:{}'.format(cnt_frame,os.path.basename(filename)))
|
| 34 |
+
continue
|
| 35 |
+
|
| 36 |
+
size_list=[]
|
| 37 |
+
croppedfaces_temp=[]
|
| 38 |
+
idx_list_temp=[]
|
| 39 |
+
|
| 40 |
+
for face_idx in range(len(faces)):
|
| 41 |
+
x0,y0,x1,y1=faces[face_idx]['bbox']
|
| 42 |
+
bbox=np.array([[x0,y0],[x1,y1]])
|
| 43 |
+
croppedfaces_temp.append(cv2.resize(crop_face(frame,None,bbox,False,crop_by_bbox=True,only_img=True,phase='test'),dsize=image_size).transpose((2,0,1)))
|
| 44 |
+
idx_list_temp.append(cnt_frame)
|
| 45 |
+
size_list.append((x1-x0)*(y1-y0))
|
| 46 |
+
|
| 47 |
+
max_size=max(size_list)
|
| 48 |
+
croppedfaces_temp=[f for face_idx,f in enumerate(croppedfaces_temp) if size_list[face_idx]>=max_size/2]
|
| 49 |
+
idx_list_temp=[f for face_idx,f in enumerate(idx_list_temp) if size_list[face_idx]>=max_size/2]
|
| 50 |
+
croppedfaces+=croppedfaces_temp
|
| 51 |
+
idx_list+=idx_list_temp
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f'error in {cnt_frame}:{filename}')
|
| 54 |
+
print(e)
|
| 55 |
+
continue
|
| 56 |
+
cap_org.release()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
return croppedfaces,idx_list
|
| 61 |
+
|
| 62 |
+
def extract_face(frame,model,image_size=(380,380)):
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
faces = model.predict_jsons(frame)
|
| 66 |
+
|
| 67 |
+
if len(faces[0]['bbox'])==0:
|
| 68 |
+
return []
|
| 69 |
+
croppedfaces=[]
|
| 70 |
+
for face_idx in range(len(faces)):
|
| 71 |
+
x0,y0,x1,y1=faces[face_idx]['bbox']
|
| 72 |
+
bbox=np.array([[x0,y0],[x1,y1]])
|
| 73 |
+
croppedfaces.append(cv2.resize(crop_face(frame,None,bbox,False,crop_by_bbox=True,only_img=True,phase='test'),dsize=image_size).transpose((2,0,1)))
|
| 74 |
+
|
| 75 |
+
return croppedfaces
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def crop_face(img,landmark=None,bbox=None,margin=False,crop_by_bbox=True,abs_coord=False,only_img=False,phase='train'):
|
| 79 |
+
assert phase in ['train','val','test']
|
| 80 |
+
|
| 81 |
+
#crop face------------------------------------------
|
| 82 |
+
H,W=len(img),len(img[0])
|
| 83 |
+
|
| 84 |
+
assert landmark is not None or bbox is not None
|
| 85 |
+
|
| 86 |
+
H,W=len(img),len(img[0])
|
| 87 |
+
|
| 88 |
+
if crop_by_bbox:
|
| 89 |
+
x0,y0=bbox[0]
|
| 90 |
+
x1,y1=bbox[1]
|
| 91 |
+
w=x1-x0
|
| 92 |
+
h=y1-y0
|
| 93 |
+
w0_margin=w/4#0#np.random.rand()*(w/8)
|
| 94 |
+
w1_margin=w/4
|
| 95 |
+
h0_margin=h/4#0#np.random.rand()*(h/5)
|
| 96 |
+
h1_margin=h/4
|
| 97 |
+
else:
|
| 98 |
+
x0,y0=landmark[:68,0].min(),landmark[:68,1].min()
|
| 99 |
+
x1,y1=landmark[:68,0].max(),landmark[:68,1].max()
|
| 100 |
+
w=x1-x0
|
| 101 |
+
h=y1-y0
|
| 102 |
+
w0_margin=w/8#0#np.random.rand()*(w/8)
|
| 103 |
+
w1_margin=w/8
|
| 104 |
+
h0_margin=h/2#0#np.random.rand()*(h/5)
|
| 105 |
+
h1_margin=h/5
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
if margin:
|
| 110 |
+
w0_margin*=4
|
| 111 |
+
w1_margin*=4
|
| 112 |
+
h0_margin*=2
|
| 113 |
+
h1_margin*=2
|
| 114 |
+
elif phase=='train':
|
| 115 |
+
w0_margin*=(np.random.rand()*0.6+0.2)#np.random.rand()
|
| 116 |
+
w1_margin*=(np.random.rand()*0.6+0.2)#np.random.rand()
|
| 117 |
+
h0_margin*=(np.random.rand()*0.6+0.2)#np.random.rand()
|
| 118 |
+
h1_margin*=(np.random.rand()*0.6+0.2)#np.random.rand()
|
| 119 |
+
else:
|
| 120 |
+
w0_margin*=0.5
|
| 121 |
+
w1_margin*=0.5
|
| 122 |
+
h0_margin*=0.5
|
| 123 |
+
h1_margin*=0.5
|
| 124 |
+
|
| 125 |
+
y0_new=max(0,int(y0-h0_margin))
|
| 126 |
+
y1_new=min(H,int(y1+h1_margin)+1)
|
| 127 |
+
x0_new=max(0,int(x0-w0_margin))
|
| 128 |
+
x1_new=min(W,int(x1+w1_margin)+1)
|
| 129 |
+
|
| 130 |
+
img_cropped=img[y0_new:y1_new,x0_new:x1_new]
|
| 131 |
+
if landmark is not None:
|
| 132 |
+
landmark_cropped=np.zeros_like(landmark)
|
| 133 |
+
for i,(p,q) in enumerate(landmark):
|
| 134 |
+
landmark_cropped[i]=[p-x0_new,q-y0_new]
|
| 135 |
+
else:
|
| 136 |
+
landmark_cropped=None
|
| 137 |
+
if bbox is not None:
|
| 138 |
+
bbox_cropped=np.zeros_like(bbox)
|
| 139 |
+
for i,(p,q) in enumerate(bbox):
|
| 140 |
+
bbox_cropped[i]=[p-x0_new,q-y0_new]
|
| 141 |
+
else:
|
| 142 |
+
bbox_cropped=None
|
| 143 |
+
|
| 144 |
+
if only_img:
|
| 145 |
+
return img_cropped
|
| 146 |
+
if abs_coord:
|
| 147 |
+
return img_cropped,landmark_cropped,bbox_cropped,(y0-y0_new,x0-x0_new,y1_new-y1,x1_new-x1),y0_new,y1_new,x0_new,x1_new
|
| 148 |
+
else:
|
| 149 |
+
return img_cropped,landmark_cropped,bbox_cropped,(y0-y0_new,x0-x0_new,y1_new-y1,x1_new-x1)
|
Scripts/sbi_generator.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from imutils import face_utils
|
| 2 |
+
import numpy as np
|
| 3 |
+
import random
|
| 4 |
+
import albumentations as alb
|
| 5 |
+
from .DeepFakeMask import dfl_full, extended, components, facehull
|
| 6 |
+
import cv2
|
| 7 |
+
|
| 8 |
+
def IoUfrom2bboxes(boxA, boxB):
|
| 9 |
+
xA = max(boxA[0], boxB[0])
|
| 10 |
+
yA = max(boxA[1], boxB[1])
|
| 11 |
+
xB = min(boxA[2], boxB[2])
|
| 12 |
+
yB = min(boxA[3], boxB[3])
|
| 13 |
+
interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)
|
| 14 |
+
boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
|
| 15 |
+
boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
|
| 16 |
+
iou = interArea / float(boxAArea + boxBArea - interArea)
|
| 17 |
+
return iou
|
| 18 |
+
|
| 19 |
+
def reorder_landmark(landmark):
|
| 20 |
+
landmark_add = np.zeros((13, 2))
|
| 21 |
+
for idx, idx_l in enumerate([77, 75, 76, 68, 69, 70, 71, 80, 72, 73, 79, 74, 78]):
|
| 22 |
+
landmark_add[idx] = landmark[idx_l]
|
| 23 |
+
landmark[68:] = landmark_add
|
| 24 |
+
return landmark
|
| 25 |
+
|
| 26 |
+
def get_dlib_landmarks(inp, dlib_face_detector, dlib_face_predictor):
|
| 27 |
+
faces = dlib_face_detector(inp, 1)
|
| 28 |
+
if len(faces)==0:
|
| 29 |
+
raise Exception("No faces detected")
|
| 30 |
+
landmarks=[]
|
| 31 |
+
size_list=[]
|
| 32 |
+
for face_idx in range(len(faces)):
|
| 33 |
+
landmark = dlib_face_predictor(inp, faces[face_idx])
|
| 34 |
+
landmark = face_utils.shape_to_np(landmark)
|
| 35 |
+
x0,y0=landmark[:,0].min(),landmark[:,1].min()
|
| 36 |
+
x1,y1=landmark[:,0].max(),landmark[:,1].max()
|
| 37 |
+
face_s=(x1-x0)*(y1-y0)
|
| 38 |
+
size_list.append(face_s)
|
| 39 |
+
landmarks.append(landmark)
|
| 40 |
+
landmarks=np.concatenate(landmarks).reshape((len(size_list),)+landmark.shape)
|
| 41 |
+
landmarks=landmarks[np.argsort(np.array(size_list))[::-1]]
|
| 42 |
+
return landmarks
|
| 43 |
+
|
| 44 |
+
def get_retina_bbox(inp,face_detector):
|
| 45 |
+
faces = face_detector.predict_jsons(inp)
|
| 46 |
+
landmarks=[]
|
| 47 |
+
size_list=[]
|
| 48 |
+
for face_idx in range(len(faces)):
|
| 49 |
+
|
| 50 |
+
x0,y0,x1,y1=faces[face_idx]['bbox']
|
| 51 |
+
landmark=np.array([[x0,y0],[x1,y1]]+faces[face_idx]['landmarks'])
|
| 52 |
+
face_s=(x1-x0)*(y1-y0)
|
| 53 |
+
size_list.append(face_s)
|
| 54 |
+
landmarks.append(landmark)
|
| 55 |
+
landmarks=np.concatenate(landmarks).reshape((len(size_list),)+landmark.shape)
|
| 56 |
+
landmarks=landmarks[np.argsort(np.array(size_list))[::-1]]
|
| 57 |
+
|
| 58 |
+
return landmarks
|
| 59 |
+
|
| 60 |
+
def random_get_hull(landmark,img, face_region):
|
| 61 |
+
face_region = int(face_region)
|
| 62 |
+
if face_region == 1:
|
| 63 |
+
mask = dfl_full(landmarks=landmark.astype('int32'),face=img, channels=3).mask
|
| 64 |
+
elif face_region == 2:
|
| 65 |
+
mask = extended(landmarks=landmark.astype('int32'),face=img, channels=3).mask
|
| 66 |
+
elif face_region == 3:
|
| 67 |
+
mask = components(landmarks=landmark.astype('int32'),face=img, channels=3).mask
|
| 68 |
+
else:
|
| 69 |
+
mask = facehull(landmarks=landmark.astype('int32'),face=img, channels=3).mask
|
| 70 |
+
return mask/255
|
| 71 |
+
|
| 72 |
+
class RandomDownScale(alb.core.transforms_interface.ImageOnlyTransform):
|
| 73 |
+
def apply(self,img,**params):
|
| 74 |
+
return self.randomdownscale(img)
|
| 75 |
+
|
| 76 |
+
def randomdownscale(self,img):
|
| 77 |
+
keep_ratio=True
|
| 78 |
+
keep_input_shape=True
|
| 79 |
+
H,W,C=img.shape
|
| 80 |
+
ratio_list=[2,4]
|
| 81 |
+
r=ratio_list[np.random.randint(len(ratio_list))]
|
| 82 |
+
img_ds=cv2.resize(img,(int(W/r),int(H/r)),interpolation=cv2.INTER_NEAREST)
|
| 83 |
+
if keep_input_shape:
|
| 84 |
+
img_ds=cv2.resize(img_ds,(W,H),interpolation=cv2.INTER_LINEAR)
|
| 85 |
+
|
| 86 |
+
return img_ds
|
| 87 |
+
|
| 88 |
+
def get_source_transforms():
|
| 89 |
+
return alb.Compose([
|
| 90 |
+
alb.Compose([
|
| 91 |
+
alb.RGBShift((-20, 20), (-20, 20), (-20, 20), p=0.3),
|
| 92 |
+
alb.HueSaturationValue(
|
| 93 |
+
hue_shift_limit=(-0.3, 0.3), sat_shift_limit=(-0.3, 0.3), val_shift_limit=(-0.3, 0.3), p=1),
|
| 94 |
+
alb.RandomBrightnessContrast(
|
| 95 |
+
brightness_limit=(-0.1, 0.1), contrast_limit=(-0.1, 0.1), p=1),
|
| 96 |
+
], p=1),
|
| 97 |
+
|
| 98 |
+
alb.OneOf([
|
| 99 |
+
RandomDownScale(p=1),
|
| 100 |
+
alb.Sharpen(alpha=(0.2, 0.5), lightness=(0.5, 1.0), p=1),
|
| 101 |
+
], p=1),
|
| 102 |
+
|
| 103 |
+
], p=1.)
|
| 104 |
+
|
| 105 |
+
def randaffine(img, mask):
|
| 106 |
+
f = alb.Affine(
|
| 107 |
+
translate_percent={'x': (-0.03, 0.03), 'y': (-0.015, 0.015)},
|
| 108 |
+
scale=[0.95, 1/0.95],
|
| 109 |
+
fit_output=False,
|
| 110 |
+
p=1)
|
| 111 |
+
|
| 112 |
+
g = alb.ElasticTransform(
|
| 113 |
+
alpha=50,
|
| 114 |
+
sigma=7,
|
| 115 |
+
alpha_affine=0,
|
| 116 |
+
p=1,
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
transformed = f(image=img, mask=mask)
|
| 120 |
+
img = transformed['image']
|
| 121 |
+
|
| 122 |
+
mask = transformed['mask']
|
| 123 |
+
transformed = g(image=img, mask=mask)
|
| 124 |
+
mask = transformed['mask']
|
| 125 |
+
return img, mask
|
| 126 |
+
|
| 127 |
+
def get_blend_mask(mask):
|
| 128 |
+
H,W=mask.shape
|
| 129 |
+
size_h=np.random.randint(192,257)
|
| 130 |
+
size_w=np.random.randint(192,257)
|
| 131 |
+
mask=cv2.resize(mask,(size_w,size_h))
|
| 132 |
+
kernel_1=random.randrange(5,26,2)
|
| 133 |
+
kernel_1=(kernel_1,kernel_1)
|
| 134 |
+
kernel_2=random.randrange(5,26,2)
|
| 135 |
+
kernel_2=(kernel_2,kernel_2)
|
| 136 |
+
|
| 137 |
+
mask_blured = cv2.GaussianBlur(mask, kernel_1, 0)
|
| 138 |
+
mask_blured = mask_blured/(mask_blured.max())
|
| 139 |
+
mask_blured[mask_blured<1]=0
|
| 140 |
+
|
| 141 |
+
mask_blured = cv2.GaussianBlur(mask_blured, kernel_2, np.random.randint(5,46))
|
| 142 |
+
mask_blured = mask_blured/(mask_blured.max())
|
| 143 |
+
mask_blured = cv2.resize(mask_blured,(W,H))
|
| 144 |
+
return mask_blured.reshape((mask_blured.shape+(1,)))
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def dynamic_blend(source,target,mask,blending_type, mixup_ratio=[0.25,0.5,0.75,1,1,1]):
|
| 148 |
+
"""Performs dynamic blending of source and target, using the mask as the blending region
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
source: source image
|
| 152 |
+
target: target image
|
| 153 |
+
mask: mask image
|
| 154 |
+
|
| 155 |
+
Returns:
|
| 156 |
+
img_blended: blended image
|
| 157 |
+
mask_blurred: augmented mask used for blending
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
mask_blured = get_blend_mask(mask)
|
| 161 |
+
mask_blured_copy = mask_blured.copy()
|
| 162 |
+
|
| 163 |
+
if blending_type == "Poisson":
|
| 164 |
+
# Poisson blending
|
| 165 |
+
b_mask = (mask_blured_copy * 255).astype(np.uint8)
|
| 166 |
+
l, t, w, h = cv2.boundingRect(b_mask)
|
| 167 |
+
center = (int(l + w / 2), int(t + h / 2))
|
| 168 |
+
img_blended = cv2.seamlessClone(source, target, b_mask, center, cv2.NORMAL_CLONE)
|
| 169 |
+
else:
|
| 170 |
+
# Mix up blending
|
| 171 |
+
blend_list=mixup_ratio
|
| 172 |
+
blend_ratio = blend_list[np.random.randint(len(blend_list))]
|
| 173 |
+
|
| 174 |
+
mask_blured_copy = mask_blured.copy()
|
| 175 |
+
mask_blured_copy*=blend_ratio
|
| 176 |
+
|
| 177 |
+
img_blended=(mask_blured_copy * source + (1 - mask_blured_copy) * target)
|
| 178 |
+
|
| 179 |
+
return img_blended,mask_blured
|
| 180 |
+
|
| 181 |
+
def get_transforms():
|
| 182 |
+
return alb.Compose([
|
| 183 |
+
|
| 184 |
+
alb.RGBShift((-20, 20), (-20, 20), (-20, 20), p=0.3),
|
| 185 |
+
alb.HueSaturationValue(
|
| 186 |
+
hue_shift_limit=(-0.3, 0.3), sat_shift_limit=(-0.3, 0.3), val_shift_limit=(-0.3, 0.3), p=0.3),
|
| 187 |
+
alb.RandomBrightnessContrast(
|
| 188 |
+
brightness_limit=(-0.3, 0.3), contrast_limit=(-0.3, 0.3), p=0.3),
|
| 189 |
+
alb.ImageCompression(quality_lower=40, quality_upper=100, p=0.5),
|
| 190 |
+
|
| 191 |
+
],
|
| 192 |
+
additional_targets={f'image1': 'image'},
|
| 193 |
+
p=1.)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def self_blending(img, landmark, blending_type, face_region):
|
| 197 |
+
if np.random.rand() < 0.25:
|
| 198 |
+
landmark = landmark[:68]
|
| 199 |
+
mask = random_get_hull(landmark, img, face_region)
|
| 200 |
+
if mask.shape[-1] == 3:
|
| 201 |
+
mask = mask[:, :, 0]
|
| 202 |
+
|
| 203 |
+
mask_copy = mask
|
| 204 |
+
|
| 205 |
+
source_transforms = get_source_transforms()
|
| 206 |
+
source = img.copy()
|
| 207 |
+
source = source_transforms(image=source.astype(np.uint8))['image']
|
| 208 |
+
|
| 209 |
+
source_before_affine_transforms, mask_before_affine_transforms = source, mask
|
| 210 |
+
source, mask = randaffine(source, mask)
|
| 211 |
+
source_after_affine_transforms, mask_after_affine_transforms = source, mask
|
| 212 |
+
|
| 213 |
+
img_blended, mask = dynamic_blend(source, img, mask, blending_type)
|
| 214 |
+
img_blended = img_blended.astype(np.uint8)
|
| 215 |
+
img = img.astype(np.uint8)
|
| 216 |
+
|
| 217 |
+
return img, img_blended, mask, mask_copy, source_before_affine_transforms, mask_before_affine_transforms, source_after_affine_transforms, mask_after_affine_transforms
|
Weights/94_0.9485_val.tar
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5c100e98694faa776fcf71990377f9c4eca46568417339c06843cdcf2a78d35d
|
| 3 |
+
size 141291061
|
Weights/FFc23.tar
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2b05c2ba36ccb9e9f4f9e1aae9acd443ae2e6400ce725f56104fdb175d3c3267
|
| 3 |
+
size 141290933
|
Weights/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Faceforgerydetection
|
| 3 |
+
emoji: 💩
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 3.9
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
Weights/shape_predictor_81_face_landmarks.dat
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8cae4375589dd915d9a0a881101bed1bbb4e9887e35e63b024388f1ca25ff869
|
| 3 |
+
size 19743860
|
app.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
import cv2
|
| 3 |
+
import dlib
|
| 4 |
+
from pytorch_grad_cam.utils.image import show_cam_on_image
|
| 5 |
+
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
from retinaface.pre_trained_models import get_model
|
| 10 |
+
|
| 11 |
+
from Scripts.model import create_cam, create_model
|
| 12 |
+
from Scripts.preprocess import crop_face, extract_face, extract_frames
|
| 13 |
+
from Scripts.ca_generator import get_augs
|
| 14 |
+
from Scripts.sbi_generator import (IoUfrom2bboxes, get_dlib_landmarks,
|
| 15 |
+
get_retina_bbox, get_transforms,
|
| 16 |
+
reorder_landmark, self_blending)
|
| 17 |
+
|
| 18 |
+
warnings.filterwarnings('ignore')
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# Model Initialization
|
| 22 |
+
device = torch.device('cpu')
|
| 23 |
+
|
| 24 |
+
sbcl = create_model("Weights/94_0.9485_val.tar")
|
| 25 |
+
sbi = create_model("Weights/FFc23.tar")
|
| 26 |
+
|
| 27 |
+
# Face Detector Initialization
|
| 28 |
+
face_detector = get_model("resnet50_2020-07-20", max_size=1024, device=device)
|
| 29 |
+
face_detector.eval()
|
| 30 |
+
|
| 31 |
+
# Grad-CAM Initialization
|
| 32 |
+
cam_sbi = create_cam(sbi)
|
| 33 |
+
cam_sbcl = create_cam(sbcl)
|
| 34 |
+
targets = [ClassifierOutputTarget(1)]
|
| 35 |
+
|
| 36 |
+
# Examples
|
| 37 |
+
examples = ["Examples/Fake/fake1.png", "Examples/Real/real1.png", "Examples/Real/real2.png", "Examples/Fake/fake3.png", "Examples/Real/real3.png",
|
| 38 |
+
"Examples/Fake/fake4.png", "Examples/Real/real4.png", "Examples/Fake/fake5.png", "Examples/Fake/fake6.png", "Examples/Fake/fake7.png", ]
|
| 39 |
+
examples_videos = ['Examples/Fake1.mp4', 'Examples/Real1.mp4']
|
| 40 |
+
examples_sbi = ["Examples/Fake/fake1.png", "Examples/Real/real1.png", "Examples/Real/real2.png", "Examples/Fake/fake3.png", "Examples/Real/real3.png",
|
| 41 |
+
"Examples/Fake/fake4.png", "Examples/Fake/fake5.png", ]
|
| 42 |
+
|
| 43 |
+
# dlib Models
|
| 44 |
+
dlib_face_detector = dlib.get_frontal_face_detector()
|
| 45 |
+
dlib_face_predictor = dlib.shape_predictor(
|
| 46 |
+
'Weights/shape_predictor_81_face_landmarks.dat')
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def generate_sbi(inp, blending_type, face_region):
|
| 50 |
+
"""
|
| 51 |
+
Visualizes the different steps in the self-blended image generation process for both RGB image and mask
|
| 52 |
+
"""
|
| 53 |
+
# Getting face bboxes and landmarks
|
| 54 |
+
landmark = get_dlib_landmarks(
|
| 55 |
+
inp, dlib_face_detector, dlib_face_predictor)[0]
|
| 56 |
+
bbox_lm = np.array([landmark[:, 0].min(), landmark[:, 1].min(),
|
| 57 |
+
landmark[:, 0].max(), landmark[:, 1].max()])
|
| 58 |
+
bboxes = get_retina_bbox(inp, face_detector)[:2]
|
| 59 |
+
|
| 60 |
+
# Reducing bboxes to just one if multiple
|
| 61 |
+
iou_max = -1
|
| 62 |
+
for i in range(len(bboxes)):
|
| 63 |
+
iou = IoUfrom2bboxes(bbox_lm, bboxes[i].flatten())
|
| 64 |
+
if iou_max < iou:
|
| 65 |
+
bbox = bboxes[i]
|
| 66 |
+
iou_max = iou
|
| 67 |
+
|
| 68 |
+
# Input cropping
|
| 69 |
+
landmarks = reorder_landmark(landmark)
|
| 70 |
+
img, landmarks, bbox, __ = crop_face(
|
| 71 |
+
inp, landmarks, bbox, margin=True, crop_by_bbox=False)
|
| 72 |
+
cropped_input_face = img
|
| 73 |
+
|
| 74 |
+
# Blending
|
| 75 |
+
img_r_before_both_transforms, img_f_before_both_transforms, mask, mask_original, source_before_affine_transforms, _, source_after_affine_transforms, mask_after_affine_transforms = self_blending(
|
| 76 |
+
img.copy(), landmark.copy(), blending_type, face_region)
|
| 77 |
+
|
| 78 |
+
# Post-blending transforms
|
| 79 |
+
transformed = get_transforms()(image=img_f_before_both_transforms.astype(
|
| 80 |
+
'uint8'), image1=img_r_before_both_transforms.astype('uint8'))
|
| 81 |
+
img_f_after_both_transforms, img_r_after_both_transforms = transformed[
|
| 82 |
+
'image'], transformed['image1']
|
| 83 |
+
|
| 84 |
+
# Crop and resize the faces
|
| 85 |
+
img_f, _, __, ___, y0_new, y1_new, x0_new, x1_new = crop_face(
|
| 86 |
+
img_f_after_both_transforms, landmark, bbox, margin=False, crop_by_bbox=True, abs_coord=True, phase='train')
|
| 87 |
+
img_r = img_r_after_both_transforms[y0_new:y1_new, x0_new:x1_new]
|
| 88 |
+
img_f, img_r = cv2.resize(img_f, (380, 380), interpolation=cv2.INTER_LINEAR), cv2.resize(
|
| 89 |
+
img_r, (380, 380), interpolation=cv2.INTER_LINEAR)
|
| 90 |
+
|
| 91 |
+
# Mask operations
|
| 92 |
+
mask, mask_original = cv2.cvtColor(mask, cv2.COLOR_GRAY2RGB), cv2.cvtColor(
|
| 93 |
+
mask_original, cv2.COLOR_GRAY2RGB)
|
| 94 |
+
mask_after_affine_transforms = cv2.cvtColor(
|
| 95 |
+
mask_after_affine_transforms, cv2.COLOR_GRAY2RGB)
|
| 96 |
+
return cropped_input_face, img_r_before_both_transforms, img_f_before_both_transforms, img_r_after_both_transforms, img_f_after_both_transforms,\
|
| 97 |
+
img_r, img_f, mask, mask_original, source_before_affine_transforms, source_after_affine_transforms, mask_after_affine_transforms
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def generate_ca(inp):
|
| 101 |
+
"""
|
| 102 |
+
Applies consistency augmentations to the given input face
|
| 103 |
+
"""
|
| 104 |
+
try:
|
| 105 |
+
face = extract_face(inp, face_detector)[0].transpose(1, 2, 0)
|
| 106 |
+
except:
|
| 107 |
+
raise Exception("No faces detected")
|
| 108 |
+
randomErasing, randomCropping, dfdc = get_augs("REAlbu"), get_augs("RandCropAlbu"), get_augs("DFDCAlbu")
|
| 109 |
+
return face, randomErasing(image=face)['image'], randomCropping(image=face)['image'], dfdc(image=face)['image']
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def predict_image(inp, model):
|
| 113 |
+
"""
|
| 114 |
+
Performs inference for a given input image and returns the prediction and CAM image.
|
| 115 |
+
"""
|
| 116 |
+
face_list = extract_face(inp, face_detector)
|
| 117 |
+
|
| 118 |
+
if len(face_list) == 0:
|
| 119 |
+
return {'No face detected!': 1}, None, None
|
| 120 |
+
|
| 121 |
+
with torch.no_grad():
|
| 122 |
+
img = torch.tensor(face_list).to(device).float()/255
|
| 123 |
+
|
| 124 |
+
if model == "Self-Blended Images":
|
| 125 |
+
pred = sbi(img).softmax(1)[:, 1].cpu().data.numpy().tolist()[0]
|
| 126 |
+
else:
|
| 127 |
+
pred = sbcl(img).softmax(1)[:, 1].cpu().data.numpy().tolist()[0]
|
| 128 |
+
|
| 129 |
+
confidences = {'Real': 1-pred, 'Fake': pred}
|
| 130 |
+
|
| 131 |
+
if model == "Self-Blended Images":
|
| 132 |
+
grayscale_cam = cam_sbi(
|
| 133 |
+
input_tensor=img, targets=targets, aug_smooth=True)
|
| 134 |
+
else:
|
| 135 |
+
grayscale_cam = cam_sbcl(
|
| 136 |
+
input_tensor=img, targets=targets, aug_smooth=True)
|
| 137 |
+
grayscale_cam = grayscale_cam[0, :]
|
| 138 |
+
cam_image = show_cam_on_image(face_list[0].transpose(
|
| 139 |
+
1, 2, 0)/255, grayscale_cam, use_rgb=True)
|
| 140 |
+
|
| 141 |
+
return confidences, cam_image
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def predict_video(inp, model):
|
| 145 |
+
"""
|
| 146 |
+
Performs inference for a given input video and returns the prediction and CAM image of the frame with the highest fake probability.
|
| 147 |
+
"""
|
| 148 |
+
face_list, idx_list = extract_frames(inp, 10, face_detector)
|
| 149 |
+
|
| 150 |
+
with torch.no_grad():
|
| 151 |
+
img = torch.tensor(face_list).to(device).float()/255
|
| 152 |
+
if model == "Self-Blended Images":
|
| 153 |
+
pred = sbi(img).softmax(1)[:, 1]
|
| 154 |
+
else:
|
| 155 |
+
pred = sbcl(img).softmax(1)[:, 1]
|
| 156 |
+
|
| 157 |
+
pred_list = []
|
| 158 |
+
idx_img = -1
|
| 159 |
+
for i in range(len(pred)):
|
| 160 |
+
if idx_list[i] != idx_img:
|
| 161 |
+
pred_list.append([])
|
| 162 |
+
idx_img = idx_list[i]
|
| 163 |
+
pred_list[-1].append(pred[i].item())
|
| 164 |
+
pred_res = np.zeros(len(pred_list))
|
| 165 |
+
for i in range(len(pred_res)):
|
| 166 |
+
pred_res[i] = max(pred_list[i])
|
| 167 |
+
pred = pred_res.mean()
|
| 168 |
+
|
| 169 |
+
most_fake = np.argmax(pred_res)
|
| 170 |
+
if model == "Self-Blended Images":
|
| 171 |
+
grayscale_cam = cam_sbi(input_tensor=img[most_fake].unsqueeze(
|
| 172 |
+
0), targets=targets, aug_smooth=True)
|
| 173 |
+
else:
|
| 174 |
+
grayscale_cam = cam_sbcl(input_tensor=img[most_fake].unsqueeze(
|
| 175 |
+
0), targets=targets, aug_smooth=True)
|
| 176 |
+
grayscale_cam = grayscale_cam[0, :]
|
| 177 |
+
cam_image = show_cam_on_image(face_list[most_fake].transpose(
|
| 178 |
+
1, 2, 0)/255, grayscale_cam, use_rgb=True)
|
| 179 |
+
|
| 180 |
+
return {'Real': 1-pred, 'Fake': pred}, cam_image
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
with gr.Blocks(title="Self-Blended Consistency Learning", css="#custom_header {min-height: 3rem} #custom_title {min-height: 3rem; text-align: center}") as demo:
|
| 184 |
+
gr.Markdown("# Face Forgery Detector", elem_id="custom_title")
|
| 185 |
+
gr.Markdown("Gradio Demo for 'Face Forgery Detection with Self-Blended Consistency Learning'. To use it, simply upload your image, or click one of the examples to load them. Paper to be available on ArXiv in the near future.", elem_id="custom_title")
|
| 186 |
+
|
| 187 |
+
with gr.Tab("Image Inference"):
|
| 188 |
+
with gr.Row():
|
| 189 |
+
with gr.Column():
|
| 190 |
+
with gr.Box():
|
| 191 |
+
gr.Markdown("## Inputs", elem_id="custom_header")
|
| 192 |
+
input_image = gr.Image(label="Input Image")
|
| 193 |
+
input_image.style(height=240)
|
| 194 |
+
model_selection = gr.inputs.Radio(['Self-Blended Images', 'Self-Blended Consistency Learning'],
|
| 195 |
+
type="value", default='Self-Blended Consistency Learning', label='Model')
|
| 196 |
+
btn = gr.Button(value="Submit")
|
| 197 |
+
btn.style(full_width=True)
|
| 198 |
+
with gr.Column():
|
| 199 |
+
with gr.Box():
|
| 200 |
+
gr.Markdown("## Outputs", elem_id="custom_header")
|
| 201 |
+
output_image = gr.Image(label="GradCAM Image")
|
| 202 |
+
output_image.style(height=240)
|
| 203 |
+
label_probs = gr.outputs.Label()
|
| 204 |
+
gr.Examples(
|
| 205 |
+
examples=examples,
|
| 206 |
+
inputs=input_image,
|
| 207 |
+
outputs=output_image,
|
| 208 |
+
fn=predict_image,
|
| 209 |
+
cache_examples=False,
|
| 210 |
+
)
|
| 211 |
+
with gr.Tab("Video Inference"):
|
| 212 |
+
with gr.Row():
|
| 213 |
+
with gr.Column():
|
| 214 |
+
with gr.Box():
|
| 215 |
+
gr.Markdown("## Inputs", elem_id="custom_header")
|
| 216 |
+
input_video = gr.Video(label="Input Video")
|
| 217 |
+
input_video.style(height=240)
|
| 218 |
+
model_selection_video = gr.inputs.Radio(
|
| 219 |
+
['Self-Blended Images', 'Self-Blended Consistency Learning'], type="value", default='Self-Blended Consistency Learning', label='Model')
|
| 220 |
+
btn_video = gr.Button(value="Submit")
|
| 221 |
+
btn_video.style(full_width=True)
|
| 222 |
+
|
| 223 |
+
with gr.Column():
|
| 224 |
+
with gr.Box():
|
| 225 |
+
gr.Markdown("## Outputs", elem_id="custom_header")
|
| 226 |
+
output_image_video = gr.Image(label="GradCAM Image")
|
| 227 |
+
output_image_video.style(height=240)
|
| 228 |
+
label_probs_video = gr.outputs.Label()
|
| 229 |
+
gr.Examples(
|
| 230 |
+
examples=examples_videos,
|
| 231 |
+
inputs=input_video,
|
| 232 |
+
outputs=output_image_video,
|
| 233 |
+
fn=predict_video,
|
| 234 |
+
cache_examples=False,
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
with gr.Tab("SBI Generator"):
|
| 238 |
+
gr.Markdown("Input an image with a face to visualize the steps involved in the self-blended image (SBI) generation. Values for augmentations are randomly chosen. Blending type and face region can be varied. \
|
| 239 |
+
This process is a slightly modified version of the process from 'Detecting Deepfakes with Self-Blended Images (CVPR 2022)'", elem_id="custom_header")
|
| 240 |
+
with gr.Row():
|
| 241 |
+
with gr.Column():
|
| 242 |
+
with gr.Box():
|
| 243 |
+
gr.Markdown("## Inputs", elem_id="custom_header")
|
| 244 |
+
input_image_sbi = gr.Image(label="Input Image")
|
| 245 |
+
input_image_sbi.style(height=240)
|
| 246 |
+
btn_sbi = gr.Button(value="Submit")
|
| 247 |
+
btn_sbi.style(full_width=True)
|
| 248 |
+
with gr.Row():
|
| 249 |
+
blending_type = gr.Radio(
|
| 250 |
+
["Poisson", "Mixup"], label="Blending Type", value="Poisson", interactive=True)
|
| 251 |
+
face_region = gr.Radio(
|
| 252 |
+
["1", "2", "3", "4"], label="Face Region", value="1", interactive=True)
|
| 253 |
+
gr.Examples(
|
| 254 |
+
examples=examples_sbi,
|
| 255 |
+
inputs=input_image_sbi,
|
| 256 |
+
fn=generate_sbi,
|
| 257 |
+
cache_examples=False,
|
| 258 |
+
)
|
| 259 |
+
with gr.Row():
|
| 260 |
+
with gr.Box():
|
| 261 |
+
with gr.Column():
|
| 262 |
+
gr.Markdown("# Self-Blended Image Generation",
|
| 263 |
+
elem_id="custom_header")
|
| 264 |
+
|
| 265 |
+
with gr.Box():
|
| 266 |
+
gr.Markdown("## Step 1", elem_id="custom_header")
|
| 267 |
+
gr.Markdown(
|
| 268 |
+
"Using facial landmarks models, obtain face bounding box and facial landmarks to crop face and produce mask.", elem_id="custom_header")
|
| 269 |
+
with gr.Row():
|
| 270 |
+
cropped_input_face = gr.Image(
|
| 271 |
+
label="Input face after cropping")
|
| 272 |
+
cropped_input_face.style(height=240)
|
| 273 |
+
mask_original = gr.Image(label="Original mask")
|
| 274 |
+
mask_original.style(height=240)
|
| 275 |
+
gr.Markdown("The cropped input face is duplicated to become a 'source' face and a 'target' face. Eventually, the source face will be blended onto the target face after augmentations done below.", elem_id="custom_header")
|
| 276 |
+
|
| 277 |
+
with gr.Box():
|
| 278 |
+
gr.Markdown("## Step 2", elem_id="custom_header")
|
| 279 |
+
gr.Markdown("Apply source-target augmentations",
|
| 280 |
+
elem_id="custom_header")
|
| 281 |
+
with gr.Row():
|
| 282 |
+
source_before_affine_transforms = gr.Image(
|
| 283 |
+
label="Source face after source-target augmentations")
|
| 284 |
+
source_before_affine_transforms.style(height=240)
|
| 285 |
+
gr.Markdown("In this case, the source-target augmentations are applied to the source image for straight-forward visualization. In actual training,\
|
| 286 |
+
the augmentations are applied to either source or target face with 1:1 probability. Augmentations applied here \
|
| 287 |
+
include RGBShift, HueSaturationValue, RandomBrightnessContrast, RandomDownScale, Sharpen from Albumentations.")
|
| 288 |
+
|
| 289 |
+
with gr.Box():
|
| 290 |
+
gr.Markdown("## Step 3", elem_id="custom_header")
|
| 291 |
+
gr.Markdown(
|
| 292 |
+
"Apply affine/elastic augmentations to augmented source image/mask", elem_id="custom_header")
|
| 293 |
+
with gr.Row():
|
| 294 |
+
source_after_affine_transforms = gr.Image(
|
| 295 |
+
label="Source face after affine augmentations")
|
| 296 |
+
source_after_affine_transforms.style(height=240)
|
| 297 |
+
|
| 298 |
+
mask_after_affine_transforms = gr.Image(
|
| 299 |
+
label="Mask after elastic augmentations")
|
| 300 |
+
mask_after_affine_transforms.style(height=240)
|
| 301 |
+
|
| 302 |
+
with gr.Box():
|
| 303 |
+
gr.Markdown("## Step 4", elem_id="custom_header")
|
| 304 |
+
gr.Markdown(
|
| 305 |
+
"Apply smoothing augmentations to mask for gentle blending", elem_id="custom_header")
|
| 306 |
+
mask = gr.Image(label="Mask after Gaussian smoothing")
|
| 307 |
+
mask.style(height=240)
|
| 308 |
+
|
| 309 |
+
with gr.Box():
|
| 310 |
+
gr.Markdown("## Step 5", elem_id="custom_header")
|
| 311 |
+
gr.Markdown(
|
| 312 |
+
"Perform blending (based on chosen blending option) to produce self-blended fake", elem_id="custom_header")
|
| 313 |
+
with gr.Row():
|
| 314 |
+
img_r_before_both_transforms = gr.Image(
|
| 315 |
+
label="Real face (for comparison)")
|
| 316 |
+
img_r_before_both_transforms.style(height=240)
|
| 317 |
+
|
| 318 |
+
img_f_before_both_transforms = gr.Image(
|
| 319 |
+
label="Self-blended fake face")
|
| 320 |
+
img_f_before_both_transforms.style(height=240)
|
| 321 |
+
|
| 322 |
+
with gr.Box():
|
| 323 |
+
gr.Markdown("## Step 6", elem_id="custom_header")
|
| 324 |
+
gr.Markdown(
|
| 325 |
+
"Apply the same randomly chosen augmentations to both real and self-blended fake", elem_id="custom_header")
|
| 326 |
+
with gr.Row():
|
| 327 |
+
img_r_after_both_transforms = gr.Image(
|
| 328 |
+
label="Real face after augmentations")
|
| 329 |
+
img_r_after_both_transforms.style(height=240)
|
| 330 |
+
|
| 331 |
+
img_f_after_both_transforms = gr.Image(
|
| 332 |
+
label="Self-blended fake face after augmentations")
|
| 333 |
+
img_f_after_both_transforms.style(height=240)
|
| 334 |
+
gr.Markdown(
|
| 335 |
+
"Augmentations applied here include RGBShift, HueSaturationValue, RandomBrightnessContrast, ImageCompression from Albumentations.")
|
| 336 |
+
|
| 337 |
+
with gr.Box():
|
| 338 |
+
gr.Markdown("## Step 7", elem_id="custom_header")
|
| 339 |
+
gr.Markdown(
|
| 340 |
+
"Crop real and self-blended fake to only have the faces", elem_id="custom_header")
|
| 341 |
+
with gr.Row():
|
| 342 |
+
output_r = gr.Image(label="Final real face")
|
| 343 |
+
output_r.style(height=240)
|
| 344 |
+
|
| 345 |
+
output_f = gr.Image(label="Final SBI face")
|
| 346 |
+
output_f.style(height=240)
|
| 347 |
+
gr.Markdown(
|
| 348 |
+
"These are the images that are eventually fed into the model for training", elem_id="custom_header")
|
| 349 |
+
|
| 350 |
+
with gr.Tab("Consistency Augmentations"):
|
| 351 |
+
gr.Markdown("Input an image with a face to visualize the consistency augmentations. Values for augmentations are randomly chosen.", elem_id="custom_header")
|
| 352 |
+
with gr.Row():
|
| 353 |
+
with gr.Box():
|
| 354 |
+
gr.Markdown("## Input", elem_id="custom_header")
|
| 355 |
+
input_image_ca = gr.Image(label="Input Image")
|
| 356 |
+
input_image_ca.style(height=240)
|
| 357 |
+
btn_ca = gr.Button(value="Submit")
|
| 358 |
+
btn_ca.style(full_width=True)
|
| 359 |
+
gr.Examples(
|
| 360 |
+
examples=examples_sbi,
|
| 361 |
+
inputs=input_image_ca,
|
| 362 |
+
fn=generate_ca,
|
| 363 |
+
cache_examples=False,
|
| 364 |
+
)
|
| 365 |
+
with gr.Row():
|
| 366 |
+
with gr.Box():
|
| 367 |
+
with gr.Row():
|
| 368 |
+
og = gr.Image(label="Cropped Face (No augs)")
|
| 369 |
+
og.style(height=240)
|
| 370 |
+
re = gr.Image(label="Random Erasing")
|
| 371 |
+
re.style(height=240)
|
| 372 |
+
rc = gr.Image(label="Random Cropping")
|
| 373 |
+
rc.style(height=240)
|
| 374 |
+
dfdc = gr.Image(label="DFDC Selimsef")
|
| 375 |
+
dfdc.style(height=240)
|
| 376 |
+
|
| 377 |
+
btn.click(predict_image, inputs=[input_image, model_selection], outputs=[
|
| 378 |
+
label_probs, output_image])
|
| 379 |
+
btn_video.click(predict_video, inputs=[input_video, model_selection_video], outputs=[
|
| 380 |
+
label_probs_video, output_image_video])
|
| 381 |
+
btn_sbi.click(generate_sbi, inputs=[input_image_sbi, blending_type, face_region], outputs=[cropped_input_face, img_r_before_both_transforms, img_f_before_both_transforms,
|
| 382 |
+
img_r_after_both_transforms, img_f_after_both_transforms, output_r, output_f, mask,
|
| 383 |
+
mask_original, source_before_affine_transforms, source_after_affine_transforms, mask_after_affine_transforms])
|
| 384 |
+
btn_ca.click(generate_ca, inputs=[
|
| 385 |
+
input_image_ca], outputs=[og, re, rc, dfdc])
|
| 386 |
+
if __name__ == "__main__":
|
| 387 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dlib
|
| 2 |
+
retinaface_pytorch
|
| 3 |
+
imutils
|
| 4 |
+
numpy
|
| 5 |
+
grad-cam
|
| 6 |
+
gradio
|
| 7 |
+
opencv-python
|
| 8 |
+
efficientnet_pytorch
|