Spaces:
Paused
Paused
feat(fingerprint): improve CLIP attribution accuracy and add 4th detector
Browse files- Rewrite GENERATOR_PROMPTS to describe visual forensic artifacts instead of
content style — CLIP picks up texture/frequency cues better than generator names
- Add umm-maybe/AI-image-detector (EfficientNet fine-tuned on AI vs real) as 4th
ensemble detector with weight 0.1; rebalance existing weights to [0.4,0.3,0.2,0.1]
- Raise CLIP confidence threshold 0.25 → 0.32 (2.9× above chance for 9 classes)
to reduce incorrect attributions when CLIP is uncertain
src/engines/fingerprint/engine.py
CHANGED
|
@@ -26,18 +26,19 @@ DETECTOR_CANDIDATES = [
|
|
| 26 |
"Organika/sdxl-detector",
|
| 27 |
"haywoodsloan/ai-image-detector-deploy",
|
| 28 |
"dima806/deepfake_vs_real_image_detection",
|
|
|
|
| 29 |
]
|
| 30 |
|
| 31 |
GENERATOR_PROMPTS: dict[str, str] = {
|
| 32 |
-
"real": "
|
| 33 |
-
"sora": "
|
| 34 |
-
"runway": "
|
| 35 |
-
"wav2lip": "
|
| 36 |
-
"stable_diffusion": "
|
| 37 |
-
"sdxl": "
|
| 38 |
-
"midjourney": "
|
| 39 |
-
"dall_e": "
|
| 40 |
-
"unknown_generative": "
|
| 41 |
}
|
| 42 |
|
| 43 |
FAKE_LABEL_KEYWORDS = (
|
|
@@ -185,7 +186,7 @@ class FingerprintEngine:
|
|
| 185 |
if not _detectors:
|
| 186 |
logger.warning("No fingerprint detectors loaded; using neutral fallback score.")
|
| 187 |
|
| 188 |
-
detector_weights = [0.
|
| 189 |
total_w = 0.0
|
| 190 |
weighted_fake = 0.0
|
| 191 |
|
|
@@ -245,8 +246,8 @@ class FingerprintEngine:
|
|
| 245 |
probs = logits.softmax(dim=0).cpu().numpy()
|
| 246 |
max_prob = float(np.max(probs))
|
| 247 |
|
| 248 |
-
# Low confidence attribution → unknown generator
|
| 249 |
-
if max_prob < 0.
|
| 250 |
generator = "unknown_generative"
|
| 251 |
else:
|
| 252 |
generator = list(GENERATOR_PROMPTS.keys())[int(np.argmax(probs))]
|
|
|
|
| 26 |
"Organika/sdxl-detector",
|
| 27 |
"haywoodsloan/ai-image-detector-deploy",
|
| 28 |
"dima806/deepfake_vs_real_image_detection",
|
| 29 |
+
"umm-maybe/AI-image-detector",
|
| 30 |
]
|
| 31 |
|
| 32 |
GENERATOR_PROMPTS: dict[str, str] = {
|
| 33 |
+
"real": "photograph with natural film grain, uneven organic noise, authentic lens distortion, and real-world lighting imperfections",
|
| 34 |
+
"sora": "AI video frame with unnaturally smooth temporal transitions, photorealistic but physically implausible motion, and over-consistent lighting",
|
| 35 |
+
"runway": "AI video frame with painterly color grading artifacts, dreamlike motion blur inconsistencies, and synthetic depth-of-field",
|
| 36 |
+
"wav2lip": "face with sharp unnatural lip boundary artifacts, texture discontinuity around the mouth region, and mismatched skin tone at lip edges",
|
| 37 |
+
"stable_diffusion": "image with soft overly-smooth skin, color bleeding at object edges, dreamlike over-saturation, and repeating background texture patterns",
|
| 38 |
+
"sdxl": "image with hyper-sharp commercial detail, perfect noise-free skin, unnaturally crisp edges, and over-rendered textures lacking real-world imperfection",
|
| 39 |
+
"midjourney": "image with dramatic cinematic vignette, fantasy color palette, exaggerated contrast, hyper-detailed surreal aesthetic, and painterly over-rendering",
|
| 40 |
+
"dall_e": "image with clean flat graphic style, smooth AI-blended gradients, slightly plastic surface quality, and uniformly lit commercial illustration look",
|
| 41 |
+
"unknown_generative": "image with subtle AI artifacts including unnatural smoothness, inconsistent frequency patterns, and synthetic pixel-level regularities absent in real photos",
|
| 42 |
}
|
| 43 |
|
| 44 |
FAKE_LABEL_KEYWORDS = (
|
|
|
|
| 186 |
if not _detectors:
|
| 187 |
logger.warning("No fingerprint detectors loaded; using neutral fallback score.")
|
| 188 |
|
| 189 |
+
detector_weights = [0.4, 0.3, 0.2, 0.1]
|
| 190 |
total_w = 0.0
|
| 191 |
weighted_fake = 0.0
|
| 192 |
|
|
|
|
| 246 |
probs = logits.softmax(dim=0).cpu().numpy()
|
| 247 |
max_prob = float(np.max(probs))
|
| 248 |
|
| 249 |
+
# Low confidence attribution → unknown generator (9 classes: chance=0.11, threshold=2.9×)
|
| 250 |
+
if max_prob < 0.32:
|
| 251 |
generator = "unknown_generative"
|
| 252 |
else:
|
| 253 |
generator = list(GENERATOR_PROMPTS.keys())[int(np.argmax(probs))]
|