DeepfakeGuard V2: face-crop deepfake image classifier

Research and non-commercial use only. This model gives a probability, not proof. It should support human judgement, never replace it.

Try the live demo

This is the image model behind DeepfakeGuard, my BSc (Hons) Computer Science final-year project (University of West London, RAK campus, 2026). It looks at a cropped face and predicts whether it is real or fake (face-swap deepfake).

Model details

Architecture EfficientNetB0 (transfer learning) with a single sigmoid output
Framework TensorFlow 2.19 / Keras 3 (.keras format)
Input One RGB face crop, 224 x 224, raw pixel values 0-255 as float32
Output One number: the probability that the face is real (class 1). Class 0 is fake; see labels_v2.json
Files saved_model_image_classifier_v2.keras (~28 MB) and deepfakeguard_v2.onnx (same model in ONNX format, used by the in-browser demo)
Author Samson Siby

Training data

Trained on frames extracted from Celeb-DF (v2) (Li et al., Celeb-DF: A Large-scale Challenging Dataset for DeepFake Forensics, CVPR 2020). The data was split by video, not by frame (70 / 15 / 15), so frames from the same video never appear in both training and testing. No dataset images are included in this repository.

Results

Version Input Held-out test accuracy Fake recall
V1 Full frame 64% 0.53
V2 (this model) Face crop 80% 0.78

Evaluated on 2,416 held-out test images from Celeb-DF.

An experimental V3 scored 88% offline but labelled more than half of fake images as real when tested live in the app, so it was rejected. In final live testing of the full app (a small hand-picked set), V2 classified 37 of 40 images and 17 of 19 videos correctly. Treat that as a sanity check, not a benchmark.

How to use

import json
import cv2
import numpy as np
from huggingface_hub import hf_hub_download
from tensorflow import keras

repo = "Samson5827/deepfakeguard-v2"
model = keras.models.load_model(hf_hub_download(repo, "saved_model_image_classifier_v2.keras"))
labels = json.load(open(hf_hub_download(repo, "labels_v2.json")))
index_to_label = {int(k): v for k, v in labels["index_to_label"].items()}

face_bgr = cv2.imread("face.jpg")                      # an already-cropped face
face = cv2.cvtColor(cv2.resize(face_bgr, (224, 224), interpolation=cv2.INTER_AREA), cv2.COLOR_BGR2RGB)
p1 = float(model.predict(np.expand_dims(face.astype(np.float32), 0), verbose=0)[0][0])
label = index_to_label[1] if p1 >= 0.5 else index_to_label[0]
print(label, max(p1, 1 - p1))

The model expects a face crop. The DeepfakeGuard app finds the largest face with OpenCV's Haar cascade, adds an 18% margin and then resizes it. Full images without cropping give worse results.

Intended use

  • Research, teaching and experimentation with deepfake detection.
  • A second opinion when reviewing a suspicious face image, alongside human checks.

Out of scope

  • Commercial use.
  • Legal, disciplinary, journalistic or any other decision about a real person based only on this model.
  • Fully AI-generated images or videos (for example text-to-video models). It was trained on face-swap deepfakes only.

Limitations

  • Trained on one dataset. Accuracy on other deepfake methods, compression levels and real-world social media content is likely lower.
  • The pipeline's Haar cascade face detector misses side-on and partly covered faces.
  • About 1 in 5 fake test images were still classified as real.

Licence

Released for research and non-commercial use only, with no warranty. See LICENSE. Any use must also respect the Celeb-DF dataset's terms.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Space using Samson5827/deepfakeguard-v2 1