Hi Devpost! We are team blueberry jam and this is our submission to topic #5.

We built Quorum, a multi-model, consensus-based approach to robust hybrid AI image detection.

Briefly: Quorum uses several small specialised models to match and even beat a single large one, while holding on to robustness (performance after degradation), accuracy, and precision when deciding whether an image is AI-generated or real.

Try out the Demo (CPU only so might be slow - please run locally for GPU performance): https://huggingface.co/spaces/adrieljss/quorum-demo-cpu-only

Introduction and How Quorum Works (briefly)

AI-generated images are everywhere now, especially on social media, and the technology behind them keeps advancing. So a detector has to handle several very different kinds of fake at once (fully synthetic, tampered/inpainted, deepfakes), and it has to keep working after the image has been through a platform's compression, resizing and re-encoding.

Quorum was built on the fact that we wanted to try another approach to a long-standing and still-advancing problem: AI image detection and classification. This problem has been around for years and is well tried-and-tested, with no real breakthrough.

Sure, you can always add more datasets and make the model bigger (vertical scaling), but is there another approach?

So we wanted to tackle it from another point of view. What happens if you scale models horizontally, on the architecture side, instead of vertically by adding parameters?

Quorum uses a "voting system", where small specialised models, each trained for one specific type of AI image, can each cast a vote and control the final verdict. Firstly, we want to state the fact that detecting an AI image is actually very complex: it has many types, e.g. fully synthetic, tampered with/inpainted, deepfake. And so, each of them requires a different approach instead of one big model. The current version of Quorum has three voters:

branch input params the question it answers
general 768-d CLIP embedding 769 is this fully synthetic?
tampered 768-d CLIP embedding 769 does it contain an AI-edited region?
face 769-d = CLIP(aligned face crop) + standardised log_2(face px) 772 is any face in it synthetic?

Every image is embedded once by a frozen CLIP ViT-L/14-quickgelu, which we never fine-tune. All three voters are linear probes on that same vector, so the table above is the entire trained model.

The Voting System

$$\mathrm{pred} = \max\Big(\ \sigma\big(\max(z_{\text{general}},\ 1.25\,z_{\text{tampered}}) - \text{SHIFT}\big),\ \ \max_i p_{\text{face},i}\ \Big)$$

  • max is really just an OR, and that is the point. "AI touched this image" means fully synthetic or locally edited, which are opposite problems. Our general probe scores below chance on edited photographs, and it is right to, because an edited photo is still globally authentic.
  • We built the learned combiner first. It lost to a plain max, because it is forced into a single trade-off across two detectors that disagree by design.
  • The SHIFT is not cosmetic. Our threshold used to be 0.5, the sigmoid's default, meaning nobody had actually chosen it — and it flagged far too many ordinary photographs as AI. We now shift the score so 0.5 lands on a threshold cross-validated over generator families, each fold scored on the family it never saw. The shift is monotone, so AUROC does not move, but precision jumps.

Datasets

Ten sources. The line between what we train on and what we score on is the thing we were most careful about — an "unseen generator" claim is only worth something if the generator is really unseen.

dataset role n
Train SID_Set real vs fully synthetic, plus face crops 16,000
SID_Set tampered the tampered probe — AI-inpainted real photos 3,949
WildFake (Midjourney) generator diversity for general 1,500
So-Fake-OOD calib_ood carve calibration + threshold, 4-of-5-family rotation 2,044
Eval So-Fake-OOD test_ood the headline — 10 generator families absent from training 4,198
Organizer validation (COCO val2017 + WildFake DALL·E) the brief's benchmark, quarantined 8,719
SID_Set / So-Fake tampered eval edited photos, same corpus and foreign corpus 4,499
LAION-5B real holdout false accusations on a corpus we never touched 2,000

We read all of it exactly once into a cached set of embeddings, so the whole project fits on one laptop and a new experiment takes minutes instead of a day. Our manifest builder then asserts that no image and no generator family crosses the train/eval line, so a contaminated split fails the build. We split by family rather than by generator on purpose: putting e.g. Ideogram2 and Ideogram3 on opposite sides would let us call a sibling model "unseen".

How we maintain robustness

This is the part we like most: robustness needed no architectural change at all. Same frozen CLIP, same linear probes. It comes entirely from what the probe is shown while being trained.

We built a degradation grid out of the transformations the problem statement asks about — JPEG, blur, downscaling, noise, cropping and colour jitter — seeded on each image's content hash so it reproduces exactly. Every training image contributes its clean version plus a few random degradations, and every eval image is scored on all of them. The probe never gets to assume a clean input, so it never learns a feature that a platform's encoder would have destroyed anyway.

general face spectral tampered
clean 0.9844 0.9520 0.6341 0.9069
blur05 0.9888 0.9541 0.6074 0.9110
blur10 0.9907 0.9401 0.6303 0.8847
blur20 0.9889 0.8887 0.5605 0.7893
crop08 0.9723 0.9341 0.5376 0.8464
jitter02 0.9828 0.9485 0.6230 0.8848
jpeg30 0.9859 0.9283 0.6706 0.9473
jpeg50 0.9896 0.9323 0.6863 0.9448
jpeg70 0.9916 0.9432 0.7311 0.9278
jpeg90 0.9852 0.9557 0.6073 0.9024
noise002 0.9843 0.9366 0.6289 0.8023
noise005 0.9821 0.9424 0.5948 0.7709
noise01 0.9871 0.9254 0.5591 0.7286
resize025 0.9890 0.9109 0.5199 0.9002
resize05 0.9876 0.9372 0.6170 0.8740
worst-case drop 0.0121 0.0633 0.1143 0.1782
  • JPEG does not hurt us at all — heavy compression scores slightly above clean. CLIP's own pretraining data is already full of recompressed web images, so this is simply what the backbone expects to see.
  • Noise is the only transformation that really hurts, and it hurts tampered most. No amount of probe tuning recovers information that has already been destroyed.

We also composed every pair of degradations against each other, in case stacking two of them compounds. It does not.

Results and Performance

Everything we trained comes to 2,310 parameters, 11 KB across three .npz files, plus a 227 KB off-the-shelf face detector. But we want to be upfront about the rest of it: underneath those sits frozen CLIP, 304M parameters and a 1.7 GB download. We did not train it and do not count it as ours, but it is the bulk of what actually runs. So this is not a "sub-1000-parameter detector", and we are not going to claim it is.

The claim that does survive is the one that matters in production. On an RTX 4060 Laptop the backbone runs at roughly 72 images per second and our heads add microseconds on top — so anywhere CLIP embeddings already exist, e.g. search, recommendation or moderation, adding detection costs microseconds and an 11 KB file. Not cheap. Free.

eval set n AUROC ACC PREC RECALL F1
So-Fake-OOD clean (headline — 10 unseen generator families) 4,198 0.9265 0.8380 0.9021 0.7588 0.8243
So-Fake-OOD, all 15 variants 62,970 0.9206 0.8221 0.9076 0.7177 0.8016
Organizer set, clean 8,719 0.9722 0.9185 0.8872 0.9266 0.9065
Organizer set, all 15 variants 130,785 0.9594 0.8989 0.8675 0.9005 0.8837
SID_Set tampered, clean 3,595 0.9120 0.8473 0.8665 0.7492 0.8036
Foreign tampered, clean 5,096 0.7260 0.5608 0.8464 0.3103 0.4541

The organizer row is the friendlier number and the brief says it will not count toward scoring, so our honest headline is the So-Fake-OOD row. The bottom row is our tampered branch tested on somebody else's edits — we report it rather than only the good one.

Against two published detectors. We ran CNNDetection (CVPR'20) and FatFormer (CVPR'24) at their released weights, here, on exactly the same pixels we score, instead of copying numbers out of their papers.

detector clean jpeg30 blur20 resize025 noise005 mean AUROC drop clean ACC
Quorum 0.977 0.983 0.953 0.974 0.947 0.967 0.030 93.4%
FatFormer 0.646 0.883 0.252 0.302 0.515 0.520 0.394 50.3%
CNNDetection (crop) 0.586 0.527 0.505 0.506 0.521 0.529 0.081 49.9%
CNNDetection (resize) 0.390 0.378 0.324 0.314 0.451 0.371 0.076 49.9%

Accuracy is not comparable across detectors sitting at their own operating points, so read the AUROC columns. FatFormer under blur goes inverted — it calls blurred AI images real more often than it calls real photographs real — and its JPEG column is not even monotone. A detector whose ranking reverses under a transformation cannot be given a threshold at all.

And where we still fail, on generators none of the three were trained on:

generator quorum fatformer cnnd (crop) cnnd (resize)
ADM (guided diffusion) 0.483 0.749 0.618 0.763
DF-GAN 0.716 0.995 0.927 0.966
GigaGAN 0.770 0.711 0.679 0.725
VQ-VAE FFHQ 0.224 0.784 0.571 0.810
GPT-Image-2 0.791 0.766 0.501 0.519
Nano Banana 0.800 0.749 0.511 0.501
— averaged —
all six, clean 0.631 0.792 0.635 0.714
all six, mean over the grid 0.695 0.786 0.630 0.713
modern only (GPT-Image-2, Nano Banana) 0.795 0.757 0.506 0.510
legacy only (ADM, DF-GAN, GigaGAN, VQ-VAE) 0.548 0.810 0.699 0.816

The short version: we beat both detectors on the modern generators, even though our own training data comes from considerably older ones. We lose on the legacy GAN families, which are exactly the ones they were built for.

What we have tried and failed

The resulting Quorum model does not come from a single-shot hypothesis, but from rigorous experiments. There are some things that we have tried and we want to say, such as:

  • Spectral branch (detects frequency-domain artifacts). Discontinued — modern generators barely leave that fingerprint any more, so it sat near chance and added false detections. It survives in the demo as a display signal, never in the score.
  • Text branch (detects deformed text). Discontinued — modern generators render near-perfect text now. Our OCR features transferred below chance and mostly flipped sign between datasets. CLIP on the text crops did work, but was worth almost nothing.
  • A nonlinear head (MLP). Did not beat the plain linear one, and doubled our false positives on real photographs. One linear boundary was never the bottleneck; data breadth is.
  • Per-generator specialists, one probe each, combined with max. Lost to a single pooled probe, and lost most on the exact generator it was meant to help.
  • Training on the newest generators, aimed straight at our worst failure. Worse overall, worse on false accusations, and worse the more we added. Giving it its own branch was twice as bad. The lesson: max can only ever raise a score, so a branch with poor precision adds false positives that nothing can overrule.
  • Hard-negative mining. Cut false positives on the corpus we mined, barely moved one we had not. It learned that those watermarks are real, not that watermarks are real.

One pattern we did not expect: six branches have now lost their bid for a seat in max, and every one of them read the same CLIP embedding. The only one that ever won a seat, face, brought a genuinely different feature. That is now how we decide what is even worth trying.

How we leveraged agenting coding to build

Quorum was built in ~7 days, and within that time we needed to perform lots of research, training and testing. And so, we leveraged AI coding agents (e.g. Claude Code, Codex) to get the velocity we wanted to be production-ready, while keeping five people's work parallel and in sync. Here is how we do it:

  • Inside the code repository you will find the docs/ folder, which contains our "AI context database / memory bank". Here we pass context around and update it as needed so that it matches the current state of Quorum after changes.
  • At every step that we take, we write tests — python scripts/selfcheck.py runs every check in the repo under one command and one exit code. This is what makes the AI results reliable and usable, because an agent cannot quietly break the scorer.
  • We also have TODOs, which keep the agentic coding tools in line with the goal.

Lastly, while we rely heavily on these coding tools, we also want to emphasise the fact that the ideas and hypotheses come from us. We use AI to quickly iterate through them, test if they work, and see what additional recommendations it may give. And so, with this system, we hope to maintain codebase quality and idea quality while not hampering our velocity.

Closing Note

Next up is patch-level self-consistency — scoring patches of an image against each other, instead of against a global idea of what real photography looks like. It is the proper fix for our biggest weakness, needs no new parameters, and gives us a heat map, which is the explainability we currently lack. After that: retraining tampered on two corpora so it learns editing rather than one dataset's editing, and validating C2PA signatures properly.

We also want more branches, e.g. for reconstructed and upscaled images, screenshots, documents, or video frames. Three is where we shipped, not a limit, and each new one costs a few hundred parameters.

But the finding we would most like to leave behind is the cheap one. You do not need a bigger model. Three linear probes and a few kilobytes, on a backbone a platform is very likely already running, beat two published detectors on the set that decides this and gave up almost nothing across the whole degradation grid. Horizontal scaling worked, and we would like to see how far it goes.

Thanks for reading!

Built With

+ 3 more
Share this project:

Updates

Submission history