|
|
|
|
|
|
|
|
import gradio as gr |
|
|
import cv2 |
|
|
import insightface |
|
|
from insightface.app import FaceAnalysis |
|
|
from insightface.model_zoo import get_model |
|
|
import numpy as np |
|
|
import os |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FaceAnalysis(name="buffalo_l") |
|
|
|
|
|
|
|
|
try: |
|
|
app.prepare(ctx_id=0, det_size=(640, 640)) |
|
|
print("✅ FaceAnalysis chargé avec GPU") |
|
|
except Exception: |
|
|
app.prepare(ctx_id=-1, det_size=(640, 640)) |
|
|
print("⚠️ GPU indisponible → fallback CPU") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MODEL_PATH = "inswapper_128.onnx" |
|
|
HF_URL = ( |
|
|
"https://huggingface.co/ezioruan/inswapper_128.onnx/" |
|
|
"resolve/main/inswapper_128.onnx" |
|
|
) |
|
|
MIN_BYTES = 200 * 1024 * 1024 |
|
|
|
|
|
def ensure_inswapper(path: str): |
|
|
|
|
|
if os.path.exists(path) and os.path.getsize(path) < MIN_BYTES: |
|
|
print(f"⚠️ {path} corrompu → suppression") |
|
|
os.remove(path) |
|
|
|
|
|
|
|
|
if not os.path.exists(path): |
|
|
print("⬇️ Téléchargement de inswapper_128.onnx depuis Hugging Face...") |
|
|
cmd = ( |
|
|
"wget -L --retry-connrefused --waitretry=1 " |
|
|
"--tries=5 --timeout=30 " |
|
|
f"-O {path} '{HF_URL}'" |
|
|
) |
|
|
code = os.system(cmd) |
|
|
if code != 0: |
|
|
raise RuntimeError("❌ Échec du téléchargement de inswapper_128.onnx") |
|
|
|
|
|
|
|
|
size = os.path.getsize(path) |
|
|
if size < MIN_BYTES: |
|
|
raise RuntimeError( |
|
|
f"❌ inswapper_128.onnx invalide (taille {size} bytes)" |
|
|
) |
|
|
|
|
|
print(f"✅ inswapper_128.onnx prêt ({size // (1024*1024)} MB)") |
|
|
|
|
|
ensure_inswapper(MODEL_PATH) |
|
|
|
|
|
|
|
|
swapper = get_model(MODEL_PATH, download=False, download_zip=False) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def swap_face(source_img, target_img): |
|
|
""" |
|
|
Swap le visage de source_img vers target_img |
|
|
""" |
|
|
try: |
|
|
if source_img is None or target_img is None: |
|
|
return None, "❌ Merci de charger deux images" |
|
|
|
|
|
|
|
|
source = cv2.cvtColor(np.array(source_img), cv2.COLOR_RGB2BGR) |
|
|
target = cv2.cvtColor(np.array(target_img), cv2.COLOR_RGB2BGR) |
|
|
|
|
|
|
|
|
source_faces = app.get(source) |
|
|
target_faces = app.get(target) |
|
|
|
|
|
if len(source_faces) == 0: |
|
|
return None, "❌ Aucun visage détecté dans l'image source" |
|
|
|
|
|
if len(target_faces) == 0: |
|
|
return None, "❌ Aucun visage détecté dans l'image cible" |
|
|
|
|
|
source_face = source_faces[0] |
|
|
result = target.copy() |
|
|
|
|
|
|
|
|
for face in target_faces: |
|
|
result = swapper.get( |
|
|
result, |
|
|
face, |
|
|
source_face, |
|
|
paste_back=True |
|
|
) |
|
|
|
|
|
|
|
|
result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB) |
|
|
|
|
|
return result_rgb, "✅ Face swap réussi" |
|
|
|
|
|
except Exception as e: |
|
|
return None, f"❌ Erreur : {str(e)}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Face Swapper Pro", theme=gr.themes.Soft()) as demo: |
|
|
gr.Markdown(""" |
|
|
# 🎭 Face Swapper Pro |
|
|
### Échangez les visages entre deux images |
|
|
|
|
|
**Instructions :** |
|
|
1. Image source = visage à utiliser |
|
|
2. Image cible = visage(s) à remplacer |
|
|
3. Cliquez sur **Swap Faces** |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
source_image = gr.Image( |
|
|
label="📸 Image source", |
|
|
type="pil", |
|
|
height=400 |
|
|
) |
|
|
target_image = gr.Image( |
|
|
label="🎯 Image cible", |
|
|
type="pil", |
|
|
height=400 |
|
|
) |
|
|
|
|
|
swap_btn = gr.Button("🔄 Swap Faces", variant="primary") |
|
|
status_text = gr.Textbox(label="Statut", interactive=False) |
|
|
result_image = gr.Image(label="✨ Résultat", height=500) |
|
|
|
|
|
swap_btn.click( |
|
|
fn=swap_face, |
|
|
inputs=[source_image, target_image], |
|
|
outputs=[result_image, status_text] |
|
|
) |
|
|
|
|
|
gr.Markdown(""" |
|
|
--- |
|
|
💡 **Conseils** |
|
|
- Visages bien éclairés |
|
|
- Angle similaire = meilleur rendu |
|
|
- Fonctionne avec plusieurs visages sur la cible |
|
|
""") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |
|
|
|
|
|
|