File size: 6,137 Bytes
757340c | 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 143 144 145 146 147 148 149 150 151 152 153 | import spaces
import gradio as gr
import torch
from transformers import CLIPModel, CLIPProcessor
from PIL import Image
# ─── Model Loading (module-level for ZeroGPU) ───────────────────────────
MODEL_ID = "kwanY/styleid"
device = "cuda" if torch.cuda.is_available() else "cpu"
model = CLIPModel.from_pretrained(MODEL_ID).to(device)
processor = CLIPProcessor.from_pretrained(MODEL_ID)
model.eval()
# ─── Inference ───────────────────────────────────────────────────────────
@spaces.GPU(duration=30)
def compare_faces(img1: Image.Image, img2: Image.Image):
"""Compare face identity between two images using StyleID."""
if img1 is None or img2 is None:
return "⚠️ Please upload both images.", ""
def get_embedding(img):
inputs = processor(images=img.convert("RGB"), return_tensors="pt").to(device)
with torch.no_grad():
emb = model.get_image_features(**inputs)
emb = emb / emb.norm(dim=-1, keepdim=True)
return emb
e1 = get_embedding(img1)
e2 = get_embedding(img2)
score = (e1 * e2).sum(dim=-1).item()
# Interpretation based on paper thresholds
if score >= 0.55:
verdict = "✅ Confidently Same Person"
color = "#22c55e"
elif score >= 0.45:
verdict = "✅ Very Likely Same Person"
color = "#4ade80"
elif score >= 0.35:
verdict = "🟡 Probably Same Person"
color = "#facc15"
elif score >= 0.25:
verdict = "🟠 Uncertain — Borderline"
color = "#fb923c"
else:
verdict = "❌ Likely Different People"
color = "#ef4444"
# Create a visual gauge
score_pct = max(0, min(100, int(score * 100)))
gauge_html = f"""
<div style="text-align:center; padding: 20px;">
<div style="font-size: 48px; font-weight: bold; color: {color};">{score:.3f}</div>
<div style="font-size: 20px; margin-top: 8px; color: {color}; font-weight: 600;">{verdict}</div>
<div style="margin-top: 16px; background: #1f2937; border-radius: 12px; height: 24px; overflow: hidden; position: relative;">
<div style="background: linear-gradient(90deg, #ef4444, #fb923c, #facc15, #4ade80, #22c55e); height: 100%; width: {score_pct}%; border-radius: 12px; transition: width 0.5s;"></div>
</div>
<div style="display: flex; justify-content: space-between; font-size: 11px; color: #9ca3af; margin-top: 4px;">
<span>Different</span>
<span>Uncertain</span>
<span>Same</span>
</div>
</div>
"""
details = f"""### How to Read the Score
| Range | Meaning |
|-------|---------|
| **> 0.55** | ✅ Confidently same person |
| **0.45 – 0.55** | ✅ Very likely same person |
| **0.35 – 0.45** | 🟡 Probably same person |
| **0.25 – 0.35** | 🟠 Uncertain / borderline |
| **< 0.25** | ❌ Likely different people |
**Your Score: {score:.4f}**
> **Note:** StyleID is optimized for comparing a real photo against its stylized version (anime, cartoon, painting, sketch, etc.). Works best with a single prominent face per image.
"""
return gauge_html, details
# ─── UI ──────────────────────────────────────────────────────────────────
DESCRIPTION = """
# 🎭 StyleID — Does AI Preserve Your Face?
Upload a **real photo** and a **stylized portrait** (anime, cartoon, painting, sketch, etc.)
→ StyleID checks if the identity is preserved across art styles.
**Powered by [StyleID](https://huggingface.co/kwanY/styleid)** — a perception-aware face identity model trained to match human judgments across stylizations.
### 🔬 How it works
StyleID uses a fine-tuned CLIP ViT-L/14 encoder calibrated against human psychometric experiments.
Unlike standard face recognition (ArcFace, AdaFace), StyleID is specifically designed for **cross-style** identity matching —
real photo ↔ anime, cartoon, painting, caricature, 3D render, etc.
### ⚡ Use Cases
- Check if your AI-stylized portrait still looks like you
- Evaluate face-swap / face-transfer quality
- Compare identity preservation across different AI art tools
- Research: benchmark stylization models for ID preservation
"""
TIPS = """
### 💡 Tips for Best Results
1. **One face per image** — StyleID doesn't do face detection
2. **Face should be prominent** — at least 25% of the image
3. **Frontal or 3/4 view works best** — extreme profiles may reduce scores
4. **Works across styles** — anime, cartoon, oil painting, watercolor, pencil sketch, 3D render, caricature
5. **NOT a security tool** — for research/evaluation only
"""
with gr.Blocks(
title="StyleID — Face Identity Across Art Styles",
theme=gr.themes.Soft(
primary_hue="violet",
secondary_hue="blue",
),
css="footer { display: none !important; }"
) as demo:
gr.Markdown(DESCRIPTION)
with gr.Row():
with gr.Column():
img1 = gr.Image(type="pil", label="📸 Original Photo (Real Face)", height=400)
with gr.Column():
img2 = gr.Image(type="pil", label="🎨 Stylized Portrait (Anime, Cartoon, etc.)", height=400)
btn = gr.Button("🔍 Compare Identity", variant="primary", size="lg")
with gr.Row():
with gr.Column():
gauge = gr.HTML(label="Identity Score")
with gr.Column():
details = gr.Markdown(label="Details")
btn.click(
fn=compare_faces,
inputs=[img1, img2],
outputs=[gauge, details],
)
with gr.Accordion("💡 Tips & Info", open=False):
gr.Markdown(TIPS)
gr.Markdown("""
---
**Model:** [kwanY/styleid](https://huggingface.co/kwanY/styleid) | **Paper:** [StyleID (arXiv 2604.21689)](https://arxiv.org/abs/2604.21689) | **License:** Research use only — not for biometric authentication.
""")
demo.launch()
|