File size: 2,953 Bytes
db57546
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import io
import traceback
from typing import Optional
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
from huggingface_hub import hf_hub_download
import insightface
import numpy as np
from PIL import Image
import cv2

app = FastAPI(title="FaceSwap API", version="1.0.0")

# CORS (so that GitHub Pages frontend can call this API)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["*"],
    allow_headers=["*"],
)

MODEL_REPO = "deepinsight/inswapper"
MODEL_FILE = "inswapper_128.onnx"

@app.on_event("startup")
def load_models():
    # Download ONNX model from Hub
    model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE, repo_type="model")
    # Load swap model and face analyzer (CPU providers only on free Spaces)
    app.state.swapper = insightface.model_zoo.get_model(model_path, providers=["CPUExecutionProvider"])
    app.state.analyser = insightface.app.FaceAnalysis(name="buffalo_l", providers=["CPUExecutionProvider"])
    app.state.analyser.prepare(ctx_id=0, det_size=(640, 640))

@app.get("/health")
def health():
    return {"status": "ok"}

@app.get("/", response_class=HTMLResponse)
def index():
    return """
    <h2>FaceSwap API</h2>
    <form action="/swap" enctype="multipart/form-data" method="post">
      Source: <input name="source" type="file" accept="image/*"><br><br>
      Target: <input name="target" type="file" accept="image/*"><br><br>
      <button type="submit">Swap</button>
    </form>
    <p>POST /swap with form-data: source, target</p>
    """

def _read_image(file: UploadFile) -> np.ndarray:
    data = file.file.read()
    pil = Image.open(io.BytesIO(data)).convert("RGB")
    return np.array(pil)

@app.post("/swap")
def swap_faces(source: UploadFile = File(...), target: UploadFile = File(...)):
    try:
        src = _read_image(source)
        tgt = _read_image(target)

        faces_src = app.state.analyser.get(src)
        faces_tgt = app.state.analyser.get(tgt)

        if not faces_src:
            raise HTTPException(status_code=400, detail="No face detected in source image.")
        if not faces_tgt:
            raise HTTPException(status_code=400, detail="No face detected in target image.")

        face_src = faces_src[0]
        face_tgt = faces_tgt[0]

        # InsightFace returns BGR
        result = app.state.swapper.get(tgt, face_tgt, face_src, paste_back=True)
        result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
        buf = io.BytesIO()
        Image.fromarray(result_rgb).save(buf, format="PNG")
        buf.seek(0)
        return StreamingResponse(buf, media_type="image/png")
    except HTTPException:
        raise
    except Exception as e:
        traceback.print_exc()
        return JSONResponse(status_code=500, content={"error": str(e)})