| import torch |
| import cv2 |
| import numpy as np |
| import torchvision.transforms as T |
| from collections import OrderedDict |
| import base64 |
|
|
| from model import DeepfakeModel |
| from cam import GradCAM, overlay_heatmap |
|
|
| |
|
|
| IMG_SIZE = 240 |
| NUM_FRAMES = 10 |
|
|
| device = torch.device( |
| "cuda" if torch.cuda.is_available() else "cpu" |
| ) |
|
|
| |
|
|
| model = DeepfakeModel() |
|
|
| state_dict = torch.load( |
| "best_model.pth", |
| map_location="cpu" |
| ) |
|
|
| new_state = OrderedDict() |
|
|
| for k, v in state_dict.items(): |
|
|
| name = k.replace( |
| "module.", |
| "" |
| ) |
|
|
| new_state[name] = v |
|
|
| model.load_state_dict( |
| new_state |
| ) |
|
|
| model = model.to(device) |
|
|
| model.eval() |
|
|
| print("Model loaded successfully") |
|
|
| |
|
|
| target_layer = model.cnn.blocks[-1] |
|
|
| grad_cam = GradCAM( |
| model, |
| target_layer |
| ) |
|
|
| |
|
|
| face_detector = cv2.CascadeClassifier( |
| cv2.data.haarcascades + |
| "haarcascade_frontalface_default.xml" |
| ) |
|
|
| |
|
|
| LAST_FRAMES = [] |
|
|
| |
|
|
| transform = T.Compose([ |
|
|
| T.ToPILImage(), |
|
|
| T.Resize( |
| (IMG_SIZE, IMG_SIZE) |
| ), |
|
|
| T.ToTensor(), |
|
|
| T.Normalize( |
| mean=[0.5, 0.5, 0.5], |
| std=[0.5, 0.5, 0.5] |
| ) |
| ]) |
|
|
| |
|
|
| def extract_and_crop( |
| video_path, |
| num_frames=NUM_FRAMES |
| ): |
|
|
| cap = cv2.VideoCapture( |
| video_path |
| ) |
|
|
| total_frames = int( |
| cap.get( |
| cv2.CAP_PROP_FRAME_COUNT |
| ) |
| ) |
|
|
| if total_frames <= 0: |
|
|
| cap.release() |
|
|
| return [] |
|
|
| idx = np.linspace( |
| 0, |
| total_frames - 1, |
| num_frames, |
| dtype=int |
| ) |
|
|
| frames = [] |
|
|
| for i in idx: |
|
|
| cap.set( |
| cv2.CAP_PROP_POS_FRAMES, |
| int(i) |
| ) |
|
|
| ret, frame = cap.read() |
|
|
| if not ret: |
| continue |
|
|
| gray = cv2.cvtColor( |
| frame, |
| cv2.COLOR_BGR2GRAY |
| ) |
|
|
| faces = face_detector.detectMultiScale( |
| gray, |
| scaleFactor=1.1, |
| minNeighbors=5, |
| minSize=(80, 80) |
| ) |
|
|
| if len(faces) > 0: |
|
|
| x, y, w, h = max( |
| faces, |
| key=lambda f: f[2] * f[3] |
| ) |
|
|
| pad = int( |
| 0.15 * max(w, h) |
| ) |
|
|
| x1 = max(0, x - pad) |
| y1 = max(0, y - pad) |
|
|
| x2 = min( |
| frame.shape[1], |
| x + w + pad |
| ) |
|
|
| y2 = min( |
| frame.shape[0], |
| y + h + pad |
| ) |
|
|
| face = frame[ |
| y1:y2, |
| x1:x2 |
| ] |
|
|
| else: |
|
|
| face = frame |
|
|
| face = cv2.resize( |
| face, |
| (IMG_SIZE, IMG_SIZE) |
| ) |
|
|
| face = cv2.cvtColor( |
| face, |
| cv2.COLOR_BGR2RGB |
| ) |
|
|
| frames.append( |
| face |
| ) |
|
|
| cap.release() |
|
|
| if len(frames) == 0: |
|
|
| return [] |
|
|
| while len(frames) < num_frames: |
|
|
| frames.append( |
| frames[-1] |
| ) |
|
|
| return frames |
|
|
| |
|
|
| def run_inference(video_path): |
|
|
| global LAST_FRAMES |
|
|
| frames = extract_and_crop( |
| video_path |
| ) |
|
|
| LAST_FRAMES = frames |
|
|
| if len(frames) == 0: |
|
|
| return { |
| "label": "Video tidak terbaca", |
| "confidence": 0, |
| "frames": [] |
| } |
|
|
| imgs = [] |
|
|
| for frame in frames: |
|
|
| imgs.append( |
| transform(frame) |
| ) |
|
|
| imgs = torch.stack( |
| imgs |
| ) |
|
|
| imgs = imgs.unsqueeze(0) |
|
|
| imgs = imgs.to(device) |
|
|
| with torch.no_grad(): |
|
|
| outputs = model( |
| imgs |
| ) |
|
|
| probs = torch.softmax( |
| outputs, |
| dim=1 |
| )[0] |
|
|
| pred = torch.argmax( |
| probs |
| ).item() |
|
|
| confidence = float( |
| probs[pred].item() * 100 |
| ) |
|
|
| label = ( |
| "Real" |
| if pred == 0 |
| else "Fake" |
| ) |
|
|
| encoded_frames = [] |
|
|
| for frame in frames: |
|
|
| _, buffer = cv2.imencode( |
| ".jpg", |
| cv2.cvtColor( |
| frame, |
| cv2.COLOR_RGB2BGR |
| ) |
| ) |
|
|
| encoded_frames.append( |
| base64.b64encode( |
| buffer |
| ).decode("utf-8") |
| ) |
|
|
| return { |
| "label": label, |
| "confidence": confidence, |
| "frames": encoded_frames |
| } |
|
|
| |
|
|
| def compute_regions(cam): |
|
|
| regions = { |
| "Forehead": cam[0:60, :].mean(), |
| "Eyes": cam[60:110, :].mean(), |
| "Cheeks": cam[110:170, :].mean(), |
| "Mouth": cam[170:220, :].mean(), |
| "Chin": cam[220:240, :].mean() |
| } |
|
|
| total = ( |
| sum(regions.values()) + |
| 1e-8 |
| ) |
|
|
| result = [] |
|
|
| for k, v in regions.items(): |
|
|
| result.append({ |
| "name": k, |
| "value": float(v / total) |
| }) |
|
|
| return result |
|
|
| |
|
|
| def generate_heatmap(frame_index): |
|
|
| global LAST_FRAMES |
|
|
| if frame_index >= len(LAST_FRAMES): |
|
|
| return None, None |
|
|
| frame = LAST_FRAMES[ |
| frame_index |
| ] |
|
|
| img = transform( |
| frame |
| ) |
|
|
| seq = torch.stack( |
| [img] * NUM_FRAMES |
| ) |
|
|
| seq = seq.unsqueeze(0) |
|
|
| seq = seq.to(device) |
|
|
| cam = grad_cam.generate( |
| seq |
| ) |
|
|
| regions = compute_regions( |
| cam |
| ) |
|
|
| heatmap = overlay_heatmap( |
| cv2.cvtColor( |
| frame, |
| cv2.COLOR_RGB2BGR |
| ), |
| cam |
| ) |
|
|
| return heatmap, regions |