Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DeepGuard — Towards AI-Generated Image Detection

Binary Real (0) vs AI-Generated (1) image detection in PyTorch. This is the code behind the undergraduate thesis "DeepGuard: Towards AI-Generated Image Detection" (Computer Engineering, University of Brasília — UnB), and it doubles as the reproducibility artifact for that work.

Status: archived / thesis complete. All eleven experimental phases are finished. Treat this repository as a settled, reproducible archive: reproduce, evaluate, or extend, but the method decisions are documented in the thesis.

Headline result

The persistent ~83 % accuracy / ~0.92 AUC ceiling reached by every CNN and by full fine-tuning was broken only by LoRA-adapting a DINOv2 ViT-L/14:

Model Preprocess NTIRE test Acc NTIRE test AUC Trainable params Train time
DINOv2 ViT-L/14 + LoRA (r=16, α=32) RGB 90.37 % 0.9705 ~2.4 M / 305 M 16.8 h

Single-stream RGB, trained on a single NVIDIA RTX 2060 Super (8 GB). Reported figures are Clean AUC (undistorted); robustness under the NTIRE degradation pipeline was not evaluated. See all_evaluations.md for the full per-run record across all phases.

Requirements

  • Python 3.12 (pinned to 3.12.12 in .python-version)
  • NVIDIA GPU with CUDA 12.6 for the pinned Torch wheels (the project was developed on an 8 GB RTX 2060 Super; the code is device-agnostic and will fall back to CPU, but training the ViT-L/14 without a GPU is impractical)
  • Dependencies pinned in requirements.txt

requirements.txt pins the CUDA 12.6 build of Torch/torchvision via an --extra-index-url. On a machine with a different CUDA version (or CPU-only), install torch/torchvision first from the appropriate index at https://pytorch.org/get-started/locally/, then install the rest.

Setup

git clone https://github.com/ArthurAntero/project-tcc.git
cd project-tcc

python3.12 -m venv .venv
source .venv/bin/activate

pip install --upgrade pip
pip install -r requirements.txt

timm (DINOv2 backbones) and peft (LoRA) are pinned in requirements.txt and imported dynamically inside src/models.py; no extra steps are needed once the requirements are installed.

Datasets

The project uses two datasets, each laid out as {split}/{REAL,FAKE}/:

Dataset Splits (REAL / FAKE) Total Role
KAGGLE-AI-VS-REAL-IMAGES train 31 980 / 31 980 · test 7 995 / 7 995 79 950 Curated, in-distribution (calibration ceiling)
NTIRE-AI-VS-REAL-IMAGES train 99 765 / 177 220 · test 5 000 / 5 000 · test-hard 1 250 / 1 250 289 485 In-the-wild benchmark (42 generators)

Download links:

After downloading, arrange each dataset on disk as:

<datasets-root>/
├── KAGGLE-AI-VS-REAL-IMAGES/
│   ├── train/{REAL,FAKE}/
│   └── test/{REAL,FAKE}/
└── NTIRE-AI-VS-REAL-IMAGES/
    ├── train/{REAL,FAKE}/
    ├── test/{REAL,FAKE}/
    └── test-hard/{REAL,FAKE}/

Paths are resolved in src/config.py and can be overridden without editing code, via environment variables:

export AI_VS_REAL_DATASETS_ROOT=/path/to/datasets     # folder holding both datasets
export AI_VS_REAL_DATA_DIR=/path/to/datasets/KAGGLE-AI-VS-REAL-IMAGES   # default single dataset

train.py / test.py also accept --datasets-root and --train-dataset / --test-dataset (a folder name under the root, or an absolute path). Run either script with --list-datasets to see what was discovered.

Usage

All three entry points fall back to interactive numbered prompts when required flags are omitted.

Train (single-stream)

python train.py --model dinov2_vitl14 --epochs 10 --batch 8 --lr 1e-4 \
    --train-dataset NTIRE-AI-VS-REAL-IMAGES \
    --preprocess-method none \
    --freeze-backbone lora
  • --model — one of: alexnet, convnext_tiny, densenet121, dinov2_vitb14, dinov2_vitl14, efficientnet_b0, efficientnet_b4, efficientnet_v2_s, regnet_y_3_2gf, resnet50, swin_t, vgg16_bn.
  • --preprocess-methodnone (RGB), lota_nbc (LOTA bit-plane noise), or gradient_full (per-channel directional gradients). All three are 3-channel.
  • --freeze-backbonenone (full fine-tune, default), all (freeze backbone, legacy), or lora (freeze weights, inject LoRA adapters into DINOv2 attention — the winning recipe).

Train (dual-stream "mix", late fusion)

Stream A is always RGB; stream B carries the forensic preprocessing.

python train.py --mix \
    --model-a dinov2_vitl14 --model-b resnet50 \
    --mix-stream-b gradient_full \
    --freeze-backbone-a lora --freeze-backbone-b none \
    --train-dataset NTIRE-AI-VS-REAL-IMAGES

Evaluate a checkpoint

python test.py --checkpoint-experiment dinov2_vitl14_20260511_003834 \
    --test-dataset NTIRE-AI-VS-REAL-IMAGES --test-split test \
    --tta none --class-one-label auto
  • --checkpoint-experiment — experiment id under checkpoints/ (or --checkpoint-path for an explicit best_model.pth).
  • --test-splittest or (NTIRE only) test-hard.
  • --threshold — omit to reuse the threshold saved at training time (grid-searched on validation to maximize balanced accuracy).
  • --ttanone, hflip, 5crop, or 10crop.
  • --class-one-labelauto probes both orientations and picks the best (compatibility with pre-convention checkpoints).

Interactive inference (Gradio)

python app.py

Launches a Gradio UI that scans checkpoints/ for any experiment with a best_model.pth, supports both single and mix checkpoints, and reports P(REAL) and P(FAKE (AI)) explicitly.

Outputs

Every run writes reproducible artifacts (see src/utils.py):

  • Trainingresults/training/{model}/{exp_id}/train-results.json (full config + final metrics + freeze policy + LoRA params + class map + best_threshold), a per-epoch metrics CSV, and a training-curve plot. The checkpoint is saved to checkpoints/{model}/{exp_id}/best_model.pth.
  • Evaluationresults/evaluations/{model}/{exp_id}/{dataset}__{split}/eval-results.json + a confusion matrix.

Every number in the thesis traces to one of these records; the consolidated per-run log is all_evaluations.md.

Key design rules

  • Class mapping is fixed: REAL = 0, FAKE = 1 (explicitly remapped in src/dataset.py; recorded in every train-results.json).
  • Single-logit output (NUM_CLASSES = 1), trained with BCEWithLogitsLoss; inference is sigmoid + threshold.
  • Reproducibility: RANDOM_SEED = 42, stratified 80/20 split.
  • Faithful paper reimplementations. LOTA (Wang et al., 2025) is reimplemented literally in src/preprocess_lota.py — BGNIG (3 LSBs → noise image) + MGPS (single 32×32 max-gradient patch at native resolution).

Reference papers

  • LOTA — Wang et al. (2025), Bit-Planes Guided AI-Generated Image Detection.
  • DINOv2 — Oquab et al. (2024), DINOv2: Learning Robust Visual Features without Supervision.
  • LoRA — Hu et al. (2022), LoRA: Low-Rank Adaptation of Large Language Models.
  • Benchmark — NTIRE 2026 Robust AI-Generated Image Detection in the Wild (42 generators, 36-step degradation pipeline; the public validation splits are used here locally as test / test-hard).

Citation

If you use this code, please cite the thesis:

@thesis{antero2026deepguard,
  author = {Arthur Antero},
  title  = {DeepGuard: Towards AI-Generated Image Detection},
  school = {University of Brasília (UnB)},
  type   = {Undergraduate thesis},
  year   = {2026}
}

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages