MetaQu's picture
Update app.py
ed4c5f6 verified
raw
history blame
8.05 kB
# app.py
import gradio as gr
from transformers import pipeline
from PIL import Image, ExifTags
import numpy as np
# Try import cv2 (opencv-headless). If not available, fallback ke numpy-only functions.
try:
import cv2
HAS_CV2 = True
except Exception:
cv2 = None
HAS_CV2 = False
# ------------------------
# Load HF detector (may require torch installed in requirements.txt)
# ------------------------
try:
hf_detector = pipeline("image-classification", model="umm-maybe/AI-image-detector")
except Exception as e:
hf_detector = None
print("Warning: hf_detector gagal dimuat:", e)
# ------------------------
# Forensic helper functions (works with or without cv2)
# ------------------------
def pil_to_gray_array(img: Image.Image):
return np.array(img.convert("L"), dtype=np.float32)
def estimate_blur(img: Image.Image):
arr = pil_to_gray_array(img)
if HAS_CV2:
return float(cv2.Laplacian(arr.astype(np.uint8), cv2.CV_64F).var())
# fallback: gradient variance
gx, gy = np.gradient(arr)
return float(np.var(gx + gy))
def estimate_noise(img: Image.Image):
arr = pil_to_gray_array(img)
if HAS_CV2:
# remove low-frequency via gaussian blur then std
blurred = cv2.GaussianBlur(arr, (5,5), 0)
noise = arr - blurred
return float(np.std(noise))
# fallback
blurred = np.mean(arr)
noise = arr - blurred
return float(np.std(noise))
def block_highfreq_ratio(img: Image.Image, block=8):
# compute ratio of high-frequency energy per 8x8 block via FFT (fallback for DCT)
arr = pil_to_gray_array(img)
h, w = arr.shape
# pad to multiple of block
ph = ((block - (h % block)) % block)
pw = ((block - (w % block)) % block)
if ph or pw:
arr = np.pad(arr, ((0, ph), (0, pw)), mode='reflect')
H, W = arr.shape
total_energy = 0.0
low_energy = 0.0
# iterate blocks (vectorized)
for i in range(0, H, block):
for j in range(0, W, block):
b = arr[i:i+block, j:j+block]
# 2D FFT
F = np.fft.fft2(b)
mag = np.abs(F)
total_energy += mag.sum()
# low freq: center-ish -> take top-left 2x2 as low freq approx
low_energy += mag[0:2, 0:2].sum()
if total_energy <= 1e-9:
return 0.0
high_ratio = float((total_energy - low_energy) / total_energy) # 0..1
return high_ratio
def edge_std(img: Image.Image):
arr = pil_to_gray_array(img)
if HAS_CV2:
edges = cv2.Canny(arr.astype(np.uint8), 100, 200)
return float(np.std(edges))
gx, gy = np.gradient(arr)
edges = np.hypot(gx, gy)
return float(np.std(edges))
def has_camera_exif(img: Image.Image):
try:
exif = img._getexif()
if not exif:
return False
for tag, val in exif.items():
name = ExifTags.TAGS.get(tag, tag)
if name in ("Make", "Model", "LensModel", "FNumber", "ExposureTime"):
return True
except:
pass
return False
# ------------------------
# Scoring / Ensemble logic
# ------------------------
def final_ai_score_from_components(hf_label, hf_conf, blur, noise, hfreq_ratio, edges, exif_present):
# hf_conf is 0..1
# 1) HF detector contribution
if hf_label is None:
hf_contrib = 0.0
else:
lab = hf_label.lower()
if any(x in lab for x in ("fake","artificial","ai")):
hf_contrib = hf_conf * 100.0
elif "human" in lab or "real" in lab:
# do not trust 'human' fully; translate into moderate ai signal
hf_contrib = (1.0 - hf_conf) * 100.0 * 0.6
else:
hf_contrib = (1.0 - hf_conf) * 100.0 * 0.8
# 2) Forensic contributions -> produce scores 0..100 where larger = more likely AI
# noise: low noise => AI-ish
noise_norm = noise / 100.0 # normalize roughly; adjust if needed
noise_score = max(0.0, min(1.0, 1.0 - noise_norm)) * 100.0
# blur: low variance (very smooth) => AI-ish
blur_norm = blur / 500.0
blur_score = max(0.0, min(1.0, 1.0 - blur_norm)) * 100.0
# high-frequency ratio: very low high-freq => too-smooth => AI-ish (we expect hfreq_ratio small -> AI)
# hfreq_ratio is 0..1, low -> AI
hfreq_score = max(0.0, min(1.0, 0.2 - hfreq_ratio) / 0.2) * 100.0 # thresholding at ~0.2
# edges: low edge std => AI-ish
edges_norm = edges / 30.0
edge_score = max(0.0, min(1.0, 1.0 - edges_norm)) * 100.0
# combine forensic scores (weights can be tuned)
forensic_score = (0.35 * noise_score + 0.30 * blur_score + 0.20 * hfreq_score + 0.15 * edge_score)
# 3) Combine HF + Forensic
combined = 0.6 * hf_contrib + 0.4 * forensic_score # 0..100
# 4) EXIF adjustment: if EXIF present, reduce AI score significantly
if exif_present:
combined = max(0.0, combined - 30.0)
# Clamp
combined = max(0.0, min(100.0, combined))
return combined, {
"hf_contrib": hf_contrib,
"forensic_score": forensic_score,
"noise_score": noise_score,
"blur_score": blur_score,
"hfreq_score": hfreq_score,
"edge_score": edge_score
}
# ------------------------
# Main detect function
# ------------------------
def detect_image(img: Image.Image):
try:
# ensure PIL image
if not isinstance(img, Image.Image):
img = Image.fromarray(np.array(img))
# HF detector inference (if available)
hf_label = None
hf_conf = 0.0
if hf_detector is not None:
try:
res = hf_detector(img, top_k=1)
if isinstance(res, list) and len(res) > 0:
hf_label = res[0].get("label", "")
hf_conf = float(res[0].get("score", 0.0))
except Exception as e:
# fallback: ignore
hf_label = None
hf_conf = 0.0
# Forensic measures
blur = estimate_blur(img)
noise = estimate_noise(img)
hfreq = block_highfreq_ratio(img)
edges = edge_std(img)
exif_ok = has_camera_exif(img)
# Compute final AI score (0..100)
ai_score, comps = final_ai_score_from_components(hf_label, hf_conf, blur, noise, hfreq, edges, exif_ok)
real_score = round(100.0 - ai_score, 2)
ai_score = round(ai_score, 2)
# Interpretations / labels
if ai_score >= 90:
verdict = "🤖 Gambar ini TERLALU MOGOK: Hasil AI (sangat tinggi)"
elif ai_score >= 60:
verdict = "🤖 Gambar ini kemungkinan besar DIHASILKAN AI"
elif ai_score <= 15:
verdict = "✅ Gambar ini tampak ASLI (sangat tinggi)"
elif ai_score <= 40:
verdict = "✅ Gambar ini kemungkinan besar ASLI"
else:
verdict = f"⚖️ Gambar ini {ai_score}% AI / {real_score}% Asli"
# Build output message
out = f"""
### Hasil Deteksi:
{verdict}
**Persentase:** {ai_score}% AI / {real_score}% Asli
**Model Prediksi:** {hf_label if hf_label else 'N/A'} ({hf_conf:.2f})
**Forensik (angka):**
- Blur (var Laplacian / grad-var): {blur:.2f}
- Noise (std highpass): {noise:.2f}
- HighFreq Ratio (block FFT): {hfreq:.3f}
- Edge STD: {edges:.2f}
- EXIF Kamera: {"Ada" if exif_ok else "Tidak"}
**Komponen skor (internal):**
- hf_contrib: {comps['hf_contrib']:.2f}
- forensic_score: {comps['forensic_score']:.2f}
- noise_score: {comps['noise_score']:.2f}
- blur_score: {comps['blur_score']:.2f}
- hfreq_score: {comps['hfreq_score']:.2f}
- edge_score: {comps['edge_score']:.2f}
"""
return out
except Exception as e:
return f"⚠️ Terjadi error saat deteksi: {str(e)}"
# Gradio UI
iface = gr.Interface(
fn=detect_image,
inputs=gr.Image(type="pil"),
outputs="markdown",
title="Improved Hybrid AI vs Real Detector",
description="Gabungan model HF + forensik (noise, blur, DCT/FFT block, edge, EXIF). Tidak ada jaminan 100%."
)
if __name__ == "__main__":
iface.launch()