dappai commited on
Commit
4a9bdfe
·
verified ·
1 Parent(s): 20e4394

Upload folder using huggingface_hub

Browse files
Files changed (10) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +19 -0
  3. app.py +81 -0
  4. best_model.pth +3 -0
  5. cam.py +70 -0
  6. face_crop.py +32 -0
  7. inference.py +216 -0
  8. model.py +67 -0
  9. requirements.txt +12 -0
  10. video_frames.py +36 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ WORKDIR /app
9
+
10
+ COPY --chown=user requirements.txt .
11
+
12
+ RUN pip install --upgrade pip && \
13
+ pip install --no-cache-dir -r requirements.txt
14
+
15
+ COPY --chown=user . .
16
+
17
+ EXPOSE 7860
18
+
19
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import shutil
4
+ import uuid
5
+ import os
6
+
7
+ from inference import run_inference, generate_heatmap
8
+
9
+ app = FastAPI()
10
+
11
+ @app.get("/")
12
+ def root():
13
+ return {"status": "Deepfake API running"}
14
+
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_credentials=True,
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ @app.post("/predict")
24
+ async def predict(video: UploadFile = File(...)):
25
+
26
+ file_id = f"{uuid.uuid4()}.mp4"
27
+ path = f"/tmp/{file_id}"
28
+
29
+ try:
30
+
31
+ with open(path, "wb") as buffer:
32
+ shutil.copyfileobj(video.file, buffer)
33
+
34
+ result = run_inference(path)
35
+
36
+ return result
37
+
38
+ except Exception as e:
39
+
40
+ raise HTTPException(
41
+ status_code=500,
42
+ detail=str(e)
43
+ )
44
+
45
+ finally:
46
+
47
+ if os.path.exists(path):
48
+ os.remove(path)
49
+
50
+ @app.post("/heatmap")
51
+ async def heatmap(data: dict):
52
+
53
+ try:
54
+
55
+ frame_index = int(data.get("frame_index", -1))
56
+
57
+ if frame_index < 0:
58
+ raise HTTPException(
59
+ status_code=400,
60
+ detail="Invalid frame index"
61
+ )
62
+
63
+ heatmap, regions = generate_heatmap(frame_index)
64
+
65
+ if heatmap is None:
66
+ raise HTTPException(
67
+ status_code=404,
68
+ detail="Frame not found"
69
+ )
70
+
71
+ return {
72
+ "heatmap": heatmap,
73
+ "regions": regions
74
+ }
75
+
76
+ except Exception as e:
77
+
78
+ raise HTTPException(
79
+ status_code=500,
80
+ detail=str(e)
81
+ )
best_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:00f6cc7b027ddbcda2f320a792149dd12b9a35734b49e978b55131c7b70d6ed0
3
+ size 30952435
cam.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import cv2
3
+ import numpy as np
4
+ import base64
5
+
6
+
7
+ class GradCAM:
8
+
9
+ def __init__(self, model, target_layer):
10
+
11
+ self.model = model
12
+ self.target_layer = target_layer
13
+
14
+ self.gradients = None
15
+ self.activations = None
16
+
17
+ self.target_layer.register_forward_hook(self.forward_hook)
18
+ self.target_layer.register_full_backward_hook(self.backward_hook)
19
+
20
+ def forward_hook(self, module, input, output):
21
+
22
+ self.activations = output.detach()
23
+
24
+ def backward_hook(self, module, grad_input, grad_output):
25
+
26
+ self.gradients = grad_output[0].detach()
27
+
28
+ def generate(self, input_tensor):
29
+
30
+ output = self.model(input_tensor)
31
+
32
+ pred_class = output.argmax()
33
+
34
+ self.model.zero_grad()
35
+
36
+ output[:, pred_class].backward()
37
+
38
+ gradients = self.gradients[0].cpu().numpy()
39
+ activations = self.activations[0].cpu().numpy()
40
+
41
+ weights = np.mean(gradients, axis=(1, 2))
42
+
43
+ cam = np.zeros(activations.shape[1:], dtype=np.float32)
44
+
45
+ for i, w in enumerate(weights):
46
+ cam += w * activations[i]
47
+
48
+ cam = np.maximum(cam, 0)
49
+
50
+ cam = cam / (cam.max() + 1e-8)
51
+
52
+ cam = cv2.resize(cam, (240, 240))
53
+
54
+ return cam
55
+
56
+
57
+ def overlay_heatmap(frame, cam):
58
+
59
+ heatmap = cv2.applyColorMap(
60
+ np.uint8(255 * cam),
61
+ cv2.COLORMAP_JET
62
+ )
63
+
64
+ overlay = heatmap * 0.4 + frame * 0.6
65
+
66
+ overlay = overlay.astype(np.uint8)
67
+
68
+ _, buffer = cv2.imencode(".jpg", overlay)
69
+
70
+ return base64.b64encode(buffer).decode("utf-8")
face_crop.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+
3
+ face_detector = cv2.CascadeClassifier(
4
+ cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
5
+ )
6
+
7
+ def crop_face(frame):
8
+
9
+ if face_detector.empty():
10
+ return frame
11
+
12
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
13
+
14
+ faces = face_detector.detectMultiScale(
15
+ gray,
16
+ scaleFactor=1.3,
17
+ minNeighbors=5
18
+ )
19
+
20
+ if len(faces) == 0:
21
+
22
+ h, w, _ = frame.shape
23
+ return frame[h//4:3*h//4, w//4:3*w//4]
24
+
25
+ x, y, w, h = faces[0]
26
+
27
+ x = max(0, x)
28
+ y = max(0, y)
29
+
30
+ face = frame[y:y+h, x:x+w]
31
+
32
+ return face
inference.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import cv2
3
+ import numpy as np
4
+ import torchvision.transforms as T
5
+ from collections import OrderedDict
6
+ import base64
7
+
8
+ from model import DeepfakeEffNetTransformer
9
+ from cam import GradCAM, overlay_heatmap
10
+
11
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
12
+
13
+ # LOAD MODEL
14
+
15
+ model = DeepfakeEffNetTransformer()
16
+
17
+ state_dict = torch.load(
18
+ "best_model.pth",
19
+ map_location="cpu"
20
+ )
21
+
22
+ new_state = OrderedDict()
23
+
24
+ for k, v in state_dict.items():
25
+ name = k.replace("module.", "")
26
+ new_state[name] = v
27
+
28
+ model.load_state_dict(new_state)
29
+
30
+ model = model.to(device)
31
+ model.eval()
32
+
33
+ print("Model loaded")
34
+
35
+ # GRADCAM TARGET LAYER
36
+
37
+ target_layer = model.cnn.blocks[-1]
38
+ grad_cam = GradCAM(model, target_layer)
39
+
40
+ # FACE DETECTOR
41
+
42
+ face_detector = cv2.CascadeClassifier(
43
+ cv2.data.haarcascades +
44
+ "haarcascade_frontalface_default.xml"
45
+ )
46
+
47
+ # FRAME CACHE
48
+
49
+ LAST_FRAMES = []
50
+
51
+ # FRAME EXTRACTION
52
+
53
+ def extract_and_crop(video_path, num_frames=10):
54
+
55
+ cap = cv2.VideoCapture(video_path)
56
+
57
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
58
+
59
+ idx = np.linspace(0, total_frames - 1, num_frames).astype(int)
60
+
61
+ frames = []
62
+
63
+ for i in idx:
64
+
65
+ cap.set(cv2.CAP_PROP_POS_FRAMES, i)
66
+
67
+ ret, frame = cap.read()
68
+
69
+ if not ret:
70
+ continue
71
+
72
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
73
+
74
+ faces = face_detector.detectMultiScale(
75
+ gray,
76
+ scaleFactor=1.3,
77
+ minNeighbors=5
78
+ )
79
+
80
+ if len(faces) > 0:
81
+
82
+ x, y, w, h = faces[0]
83
+
84
+ face = frame[y:y+h, x:x+w]
85
+
86
+ else:
87
+
88
+ face = frame
89
+
90
+ face = cv2.resize(face, (240,240))
91
+ face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
92
+
93
+ frames.append(face)
94
+
95
+ cap.release()
96
+
97
+ return frames
98
+
99
+ # TRANSFORM
100
+
101
+ transform = T.Compose([
102
+ T.ToPILImage(),
103
+ T.Resize((240,240)),
104
+ T.ToTensor(),
105
+ T.Normalize([0.5]*3,[0.5]*3)
106
+ ])
107
+
108
+ # INFERENCE
109
+
110
+ def run_inference(video_path):
111
+
112
+ global LAST_FRAMES
113
+
114
+ frames = extract_and_crop(video_path)
115
+
116
+ LAST_FRAMES = frames
117
+
118
+ if len(frames) == 0:
119
+
120
+ return {
121
+ "label": "Video tidak terbaca",
122
+ "confidence": 0,
123
+ "frames": []
124
+ }
125
+
126
+ imgs = []
127
+
128
+ for f in frames:
129
+
130
+ img = transform(f)
131
+ imgs.append(img)
132
+
133
+ imgs = torch.stack(imgs).unsqueeze(0).to(device)
134
+
135
+ with torch.no_grad():
136
+
137
+ outputs = model(imgs)
138
+
139
+ probs = torch.softmax(outputs, dim=1)[0]
140
+
141
+ pred = torch.argmax(probs).item()
142
+
143
+ confidence = probs[pred].item() * 100
144
+
145
+ label = "Real" if pred == 0 else "Fake"
146
+
147
+ encoded_frames = []
148
+
149
+ for f in frames:
150
+
151
+ _, buffer = cv2.imencode(
152
+ ".jpg",
153
+ cv2.cvtColor(f, cv2.COLOR_RGB2BGR)
154
+ )
155
+
156
+ encoded_frames.append(
157
+ base64.b64encode(buffer).decode("utf-8")
158
+ )
159
+
160
+ return {
161
+ "label": label,
162
+ "confidence": confidence,
163
+ "frames": encoded_frames
164
+ }
165
+
166
+ # REGION IMPORTANCE
167
+
168
+ def compute_regions(cam):
169
+
170
+ regions = {}
171
+
172
+ regions["Forehead"] = cam[0:60, :].mean()
173
+ regions["Eyes"] = cam[60:110, :].mean()
174
+ regions["Cheeks"] = cam[110:170, :].mean()
175
+ regions["Mouth"] = cam[170:220, :].mean()
176
+ regions["Chin"] = cam[220:240, :].mean()
177
+
178
+ total = sum(regions.values()) + 1e-8
179
+
180
+ result = []
181
+
182
+ for k,v in regions.items():
183
+
184
+ result.append({
185
+ "name": k,
186
+ "value": float(v / total)
187
+ })
188
+
189
+ return result
190
+
191
+ # HEATMAP GENERATION
192
+
193
+ def generate_heatmap(frame_index):
194
+
195
+ global LAST_FRAMES
196
+
197
+ if frame_index >= len(LAST_FRAMES):
198
+ return None, None
199
+
200
+ frame = LAST_FRAMES[frame_index]
201
+
202
+ img = transform(frame)
203
+
204
+ seq = torch.stack([img] * 10)
205
+ seq = seq.unsqueeze(0).to(device)
206
+
207
+ cam = grad_cam.generate(seq)
208
+
209
+ regions = compute_regions(cam)
210
+
211
+ heatmap = overlay_heatmap(
212
+ cv2.cvtColor(frame, cv2.COLOR_RGB2BGR),
213
+ cam
214
+ )
215
+
216
+ return heatmap, regions
model.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import timm
4
+
5
+
6
+ class DeepfakeEffNetTransformer(nn.Module):
7
+
8
+ def __init__(self):
9
+ super().__init__()
10
+
11
+ # CNN BACKBONE
12
+ self.cnn = timm.create_model(
13
+ "tf_efficientnetv2_b1",
14
+ pretrained=False,
15
+ num_classes=0,
16
+ global_pool=""
17
+ )
18
+
19
+ self.pool = nn.AdaptiveAvgPool2d(1)
20
+ feat_dim = self.cnn.num_features
21
+
22
+ # PROJECTION
23
+ self.proj = nn.Linear(feat_dim, 512)
24
+
25
+ # POSITIONAL EMBEDDING
26
+ self.pos_embed = nn.Parameter(
27
+ torch.randn(1, 10, 512)
28
+ )
29
+
30
+ # TRANSFORMER
31
+ encoder_layer = nn.TransformerEncoderLayer(
32
+ d_model=512,
33
+ nhead=8,
34
+ dim_feedforward=1024,
35
+ batch_first=True
36
+ )
37
+
38
+ self.transformer = nn.TransformerEncoder(
39
+ encoder_layer,
40
+ num_layers=2
41
+ )
42
+
43
+ # CLASSIFIER
44
+ self.classifier = nn.Sequential(
45
+ nn.Linear(512, 128),
46
+ nn.ReLU(),
47
+ nn.Dropout(0.3),
48
+ nn.Linear(128, 2)
49
+ )
50
+
51
+ def forward(self, x):
52
+ B, T, C, H, W = x.shape
53
+ x = x.view(B * T, C, H, W)
54
+
55
+ # CNN FEATURES
56
+ feats = self.cnn(x)
57
+ feats = self.pool(feats)
58
+ feats = feats.view(B, T, -1)
59
+
60
+ # PROJECTION
61
+ feats = self.proj(feats)
62
+ feats = feats + self.pos_embed
63
+
64
+ # TEMPORAL TRANSFORMER
65
+ out = self.transformer(feats)
66
+ out = out.mean(dim=1)
67
+ return self.classifier(out)
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-multipart
4
+
5
+ numpy
6
+ pillow
7
+
8
+ torch
9
+ torchvision
10
+ timm
11
+
12
+ opencv-python-headless
video_frames.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+
3
+
4
+ def extract_frames(video_path, num_frames=10):
5
+
6
+ cap = cv2.VideoCapture(video_path)
7
+
8
+ if not cap.isOpened():
9
+ raise RuntimeError("Cannot open video")
10
+
11
+ total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
12
+
13
+ step = max(total // num_frames, 1)
14
+
15
+ frames = []
16
+
17
+ for i in range(num_frames):
18
+
19
+ cap.set(cv2.CAP_PROP_POS_FRAMES, i * step)
20
+
21
+ ret, frame = cap.read()
22
+
23
+ if not ret:
24
+ break
25
+
26
+ frames.append(frame)
27
+
28
+ cap.release()
29
+
30
+ if len(frames) == 0:
31
+ raise RuntimeError("No frames extracted")
32
+
33
+ while len(frames) < num_frames:
34
+ frames.append(frames[-1])
35
+
36
+ return frames