Spaces:
Running on Zero
Running on Zero
Download app.py from Rubli/deepfake-detector-api: direct link, hf CLI and curl.
- Browser
- Download file 5.17 kB
-
https://huggingface.co/spaces/Rubli/deepfake-detector-api/resolve/main/app.py
- Command line
-
hf download hf://spaces/Rubli/deepfake-detector-api/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Rubli/deepfake-detector-api/resolve/main/app.py
5.17 kB
| import os | |
| import sys | |
| import gc | |
| import gradio as gr | |
| import torch | |
| import numpy as np | |
| import cv2 | |
| import onnxruntime | |
| import spaces | |
| from PIL import Image | |
| from transformers import CLIPImageProcessor | |
| from huggingface_hub import hf_hub_download | |
| # DUMMY GPU FUNCTION (Required for ZeroGPU startup) | |
| def _dummy_gpu_check(): | |
| """ | |
| This function is required to satisfy Hugging Face's ZeroGPU startup check. | |
| Our actual models run on CPU to save resources, so this is never used. | |
| """ | |
| return {"status": "ok"} | |
| # CONFIGURATION & SETUP | |
| _DEVICE = "cpu" | |
| FUSION_MODEL_ID = "knmrfr/deepfake-detector" | |
| FUSION_PROCESSOR_ID = "openai/clip-vit-large-patch14" | |
| DETECTRA_REPO = "TensorFold/detectra-v1" | |
| DETECTRA_THRESHOLD = 0.65 | |
| FUSION_THRESHOLD = 0.85 | |
| _face_cascade = None | |
| def _extract_face(image): | |
| global _face_cascade | |
| if _face_cascade is None: | |
| cascade_path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml" | |
| _face_cascade = cv2.CascadeClassifier(cascade_path) | |
| if image.mode != "RGB": image = image.convert("RGB") | |
| try: | |
| img_bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) | |
| gray = cv2.equalizeHist(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)) | |
| faces = _face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30)) | |
| if len(faces) == 0: return None | |
| x, y, w, h = faces[0] | |
| return image.crop((x, y, x + w, y + h)).resize((224, 224), Image.BICUBIC) | |
| except Exception: | |
| return None | |
| def _run_detectra(image): | |
| session = None | |
| try: | |
| detectra_path = hf_hub_download(repo_id=DETECTRA_REPO, filename="model.onnx") | |
| session = onnxruntime.InferenceSession(detectra_path, providers=['CPUExecutionProvider']) | |
| input_name = session.get_inputs()[0].name | |
| w, h = image.size | |
| scale = 440 / min(w, h) | |
| new_w, new_h = int(w * scale), int(h * scale) | |
| img_resized = image.resize((new_w, new_h), Image.BILINEAR) | |
| left, top = (new_w - 384) // 2, (new_h - 384) // 2 | |
| img_cropped = img_resized.crop((left, top, left + 384, top + 384)) | |
| img_array = np.array(img_cropped).astype(np.float32) / 255.0 | |
| mean = np.array([0.485, 0.456, 0.406], dtype=np.float32) | |
| std = np.array([0.229, 0.224, 0.225], dtype=np.float32) | |
| img_array = (img_array - mean) / std | |
| img_array = np.transpose(img_array, (2, 0, 1)) | |
| img_array = np.expand_dims(img_array, axis=0) | |
| logit = session.run(None, {input_name: img_array})[0] | |
| return float(1 / (1 + np.exp(-logit[0][0]))) | |
| except Exception as e: | |
| print(f"Detectra error: {e}") | |
| return 0.0 | |
| finally: | |
| if session is not None: del session | |
| gc.collect() | |
| def _run_fusion(image): | |
| model = None | |
| processor = None | |
| try: | |
| model_code_path = hf_hub_download(repo_id=FUSION_MODEL_ID, filename="model.py") | |
| sys.path.insert(0, os.path.dirname(model_code_path)) | |
| from model import DeepfakeDetector | |
| model = DeepfakeDetector.from_pretrained(FUSION_MODEL_ID).eval().to(_DEVICE) | |
| processor = CLIPImageProcessor.from_pretrained(FUSION_PROCESSOR_ID) | |
| face = _extract_face(image) | |
| if face is None: return 0.0 | |
| inputs = processor(images=face, return_tensors="pt").to(_DEVICE) | |
| with torch.no_grad(): | |
| outputs = model(**inputs) | |
| logits = outputs.logits if hasattr(outputs, "logits") else outputs["logits"] | |
| probs = torch.softmax(logits, dim=1)[0] | |
| fake_idx = 1 | |
| for i, label in model.id2label.items(): | |
| if "fake" in str(label).lower(): fake_idx = int(i); break | |
| return probs[fake_idx].item() | |
| except Exception as e: | |
| print(f"Fusion error: {e}") | |
| return 0.0 | |
| finally: | |
| if model is not None: del model | |
| if processor is not None: del processor | |
| gc.collect() | |
| def detect_image(image): | |
| if image is None: | |
| return "No image provided", 0.0, {} | |
| image = image.convert("RGB") | |
| ai_prob = _run_detectra(image) | |
| fusion_prob = _run_fusion(image) | |
| if ai_prob > DETECTRA_THRESHOLD: | |
| verdict = "AI-Generated" | |
| confidence = ai_prob * 100 | |
| elif fusion_prob > FUSION_THRESHOLD: | |
| verdict = "Deepfake (Manipulated)" | |
| confidence = fusion_prob * 100 | |
| else: | |
| verdict = "Real" | |
| confidence = ((1 - ai_prob) + (1 - fusion_prob)) / 2 * 100 | |
| scores = { | |
| "AI_Score": round(ai_prob * 100, 2), | |
| "Deepfake_Score": round(fusion_prob * 100, 2) | |
| } | |
| return verdict, round(confidence, 2), scores | |
| # GRADIO INTERFACE & LAUNCH | |
| demo = gr.Interface( | |
| fn=detect_image, | |
| inputs=gr.Image(type="pil", label="Upload Image"), | |
| outputs=[ | |
| gr.Textbox(label="Verdict"), | |
| gr.Number(label="Confidence (%)"), | |
| gr.JSON(label="Detailed Scores") | |
| ], | |
| title="Deepfake & AI Image Detector", | |
| description="Upload an image to check if it's Real, AI-Generated, or Manipulated." | |
| ) | |
| # Launch at module level so Hugging Face detects the app correctly | |
| if __name__ == "__main__": | |
| demo.launch() |