File size: 5,383 Bytes
b1f86ad
 
 
 
 
 
 
 
 
3d0e83d
b1f86ad
3d0e83d
b1f86ad
 
 
3d0e83d
b1f86ad
 
 
 
3d0e83d
b1f86ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d0e83d
 
 
 
 
 
 
 
 
b1f86ad
3d0e83d
 
07bc192
b1f86ad
 
 
 
 
 
3d0e83d
b1f86ad
 
 
 
 
 
 
3d0e83d
 
 
 
07bc192
 
 
 
 
 
 
b1f86ad
 
3d0e83d
b1f86ad
 
 
 
 
 
 
 
3d0e83d
b1f86ad
 
 
 
 
 
 
3d0e83d
b1f86ad
 
 
 
 
 
 
3d0e83d
b1f86ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d0e83d
 
 
07bc192
 
3d0e83d
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import io
import cv2
import numpy as np
import torch
from PIL import Image
import spaces
from transformers import AutoImageProcessor, AutoModelForImageClassification
import gradio as gr

# 1. Inisialisasi Model & Processor
RESNET_MODEL_ID = "prithivMLmods/Deep-Fake-Detector-Model"
print(f"[*] Inisialisasi arsitektur forensik: {RESNET_MODEL_ID}")
processor = AutoImageProcessor.from_pretrained(RESNET_MODEL_ID)
resnet_model = AutoModelForImageClassification.from_pretrained(RESNET_MODEL_ID).eval()

# 2. Analisis Spektral 2D FFT (CPU)
def analyze_phase_spectrum(img_pil: Image.Image) -> float:
    gray_img = img_pil.convert("L")
    w, h = gray_img.size
    crop_size = 256

    if w >= crop_size and h >= crop_size:
        left = (w - crop_size) // 2
        top = (h - crop_size) // 2
        gray_crop = gray_img.crop((left, top, left + crop_size, top + crop_size))
        gray = np.array(gray_crop, dtype=np.float32) / 255.0
    else:
        gray = np.array(gray_img.resize((256, 256)), dtype=np.float32) / 255.0

    f = np.fft.fft2(gray)
    fshift = np.fft.fftshift(f)
    mag = np.abs(fshift)

    cy, cx = 128, 128
    y, x = np.ogrid[:256, :256]
    low_freq_mask = ((x - cx)**2 + (y - cy)**2) <= (32**2)

    low_energy = float(np.mean(mag[low_freq_mask]) + 1e-5)
    high_energy = float(np.mean(mag[~low_freq_mask]) + 1e-5)

    spectral_ratio = high_energy / low_energy
    score = 1.0 / (1.0 + np.exp(-40.0 * (spectral_ratio - 0.045)))
    return float(np.clip(score, 0.05, 0.95))

# 3. Pipeline Forensik Lengkap (Terdaftar Langsung di ZeroGPU)
@spaces.GPU
def predict_forensic(input_image):
    if input_image is None:
        return {
            "verdict": "INCONCLUSIVE",
            "calibrated_confidence": 0.0,
            "reason": "Payload gambar kosong."
        }

    pil_img = Image.fromarray(input_image).convert("RGB")
    
    # Profiler Degradasi
    gray_np = np.array(pil_img.convert("L"))
    laplacian_var = float(cv2.Laplacian(gray_np, cv2.CV_64F).var())
    w, h = pil_img.size
    is_downscaled = bool((w * h) < (720 * 1280))
    quality_score = float(np.clip(laplacian_var / 100.0, 0.05, 1.0))

    if laplacian_var < 5.0 or (laplacian_var < 35.0 and is_downscaled):
        return {
            "verdict": "INCONCLUSIVE",
            "calibrated_confidence": 0.50,
            "reason": "Severe physical degradation outside safe forensic envelope.",
            "metrics": {"sharpness": round(laplacian_var, 2), "dimensions": f"{w}x{h}", "quality": quality_score}
        }

    # Inferensi Akselerasi ZeroGPU (A100)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    resnet_model.to(device)

    inputs = processor(images=pil_img, return_tensors="pt").to(device)
    with torch.inference_mode():
        outputs = resnet_model(**inputs)
        probs = torch.softmax(outputs.logits, dim=-1)[0]

    prob_real = float(probs[0].item())
    score_visual = float(np.clip(1.0 - prob_real, 0.01, 0.99))
    score_phase = analyze_phase_spectrum(pil_img)

    # Kalibrasi Fusi Bukti
    w_visual = max(0.4, 0.8 * quality_score)
    w_phase = 0.2
    combined_score = (score_visual * w_visual + score_phase * w_phase) / (w_visual + w_phase)
    disagreement = abs(score_visual - score_phase)

    if combined_score <= 0.35 and score_visual < 0.15:
        verdict = "LIKELY NATURAL CAPTURE"
        confidence = round(1.0 - combined_score, 3)
        reason = "Optical sensor texture and visual residuals indicate natural capture."
    elif disagreement > 0.55 and quality_score < 0.25 and combined_score > 0.35:
        verdict = "INCONCLUSIVE"
        confidence = 0.50
        reason = "Visual channel and spectral phase exhibit acute divergence under heavy compression."
    elif combined_score >= 0.55:
        verdict = "LIKELY AI-GENERATED"
        confidence = round(min(combined_score, 0.70 + (0.28 * quality_score)), 3)
        reason = "Forensic visual patterns and high-frequency spectral phase reflect synthetic artifacts."
    elif combined_score <= 0.45:
        verdict = "LIKELY NATURAL CAPTURE"
        confidence = round(1.0 - combined_score, 3)
        reason = "Visual features and phase distributions remain consistent with standard optical capture."
    else:
        verdict = "INCONCLUSIVE"
        confidence = 0.50
        reason = "Technical ambiguity margin (0.45 - 0.55)."

    return {
        "verdict": verdict,
        "calibrated_confidence": round(confidence, 3),
        "reason": reason,
        "metrics": {
            "dimensions": f"{w}x{h}",
            "sharpness": round(laplacian_var, 2),
            "quality_index": round(quality_score, 2),
            "score_visual": round(score_visual, 3),
            "score_phase": round(score_phase, 3),
            "combined_score": round(combined_score, 3)
        }
    }

# 4. Antarmuka Gradio Resmi (Memenuhi Spesifikasi ZeroGPU HF)
with gr.Blocks(title="SecurePixel Engine") as demo:
    gr.Markdown("# 🛡️ SecurePixel Forensics Engine (ZeroGPU)")
    
    with gr.Row():
        image_input = gr.Image(type="numpy", label="Input Frame")
        result_output = gr.JSON(label="Forensic Result")
    
    btn_predict = gr.Button("Analyze Frame", variant="primary")
    btn_predict.click(
        fn=predict_forensic,
        inputs=image_input,
        outputs=result_output,
        api_name="predict"
    )

demo.launch()