Spaces:
Running
Running
Salman Alfarisi commited on
Commit ·
6cc375a
0
Parent(s):
deploy: clean Space build without binary examples
Browse files
.flake8
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[flake8]
|
| 2 |
+
max-line-length = 88
|
| 3 |
+
|
| 4 |
+
# Directories and files to skip
|
| 5 |
+
exclude =
|
| 6 |
+
.venv,
|
| 7 |
+
venv,
|
| 8 |
+
env,
|
| 9 |
+
__pycache__,
|
| 10 |
+
.git,
|
| 11 |
+
.github
|
LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 [Salman Alfarisi]
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the “Software”), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in
|
| 13 |
+
all copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 21 |
+
THE SOFTWARE.
|
| 22 |
+
|
README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
sdk_version: gradio
|
| 3 |
+
app_file: app.py
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# Simple Face-Swap App
|
| 7 |
+
[](https://github.com/salmanalfarisi11/reface-app-simple/actions)
|
| 8 |
+
[](https://huggingface.co/spaces/salman555/reface-app-simple)
|
| 9 |
+
|
| 10 |
+
> A high-performance, 128 px face-swap prototype built with ONNX & GPU acceleration, designed for AI practitioners and developers.
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 📖 Overview
|
| 15 |
+
|
| 16 |
+
This repository provides a streamlined pipeline for swapping faces in still images using a 128×128 px ONNX model and the InsightFace framework. The design prioritizes:
|
| 17 |
+
|
| 18 |
+
- **Accuracy**: Leveraging InsightFace’s state-of-the-art face detector and embedding model.
|
| 19 |
+
- **Performance**: Native ONNXRuntime GPU support for real-time inference.
|
| 20 |
+
- **Usability**: Simple CLI and web UI via Gradio for drag-and-drop interaction.
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 🚀 Features
|
| 25 |
+
|
| 26 |
+
- **CLI Interface**: `swap.py` for scripted batch processing.
|
| 27 |
+
- **Web Demo**: `gradio_app.py` enables drag-and-drop face-swap in your browser.
|
| 28 |
+
- **Cross-Platform**: Pure Python 3.13 compatibility on Linux, macOS, and Windows.
|
| 29 |
+
- **Container-Ready**: Minimal dependencies for Docker or cloud deployment.
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## 🛠️ Tech Stack
|
| 34 |
+
|
| 35 |
+
- **Python 3.13**
|
| 36 |
+
- **InsightFace** (buffalo_l detector + INSwapper ONNX)
|
| 37 |
+
- **ONNXRuntime-GPU** for hardware acceleration
|
| 38 |
+
- **OpenCV** for image I/O and post-processing
|
| 39 |
+
- **Gradio** for interactive web UI
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## 📥 Installation
|
| 44 |
+
|
| 45 |
+
1. **Clone & enter** the project directory
|
| 46 |
+
```bash
|
| 47 |
+
git clone https://github.com/username/reface-app-simple.git
|
| 48 |
+
cd reface-app-simple
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
2. **Create & activate** a virtual environment
|
| 52 |
+
```bash
|
| 53 |
+
python3 -m venv .venv && source .venv/bin/activate
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
3. **Install dependencies**
|
| 57 |
+
```bash
|
| 58 |
+
pip install --upgrade pip
|
| 59 |
+
pip install -r requirements.txt
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
4. **Download ONNX model**
|
| 63 |
+
```bash
|
| 64 |
+
mkdir -p ~/.insightface/models
|
| 65 |
+
wget -O ~/.insightface/models/inswapper_128.onnx \ https://github.com/deepinsight/insightface/releases/download/v0.7/inswapper_128.onnx
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
## ⚙️ Usage
|
| 69 |
+
|
| 70 |
+
### Command-Line
|
| 71 |
+
```bash
|
| 72 |
+
python swap_128.py \
|
| 73 |
+
--src path/to/your_photo.jpg \
|
| 74 |
+
--dst path/to/target.jpg \
|
| 75 |
+
--out result.png \
|
| 76 |
+
--device gpu
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### WEB-UI
|
| 80 |
+
```bash
|
| 81 |
+
python gradio_app.py
|
| 82 |
+
```
|
| 83 |
+
- Open your browser at http://localhost:7860, drag-and-drop two images, and view the swapped result in real time.
|
| 84 |
+
|
| 85 |
+
## Contributing
|
| 86 |
+
|
| 87 |
+
Contributions are welcome! Please:
|
| 88 |
+
|
| 89 |
+
1. Fork the repository.
|
| 90 |
+
2. Create a feature branch (git checkout -b feature/YourFeature).
|
| 91 |
+
3. Commit your changes (git commit -m "feat: your feature").
|
| 92 |
+
4. Open a Pull Request and reference any related issues.
|
| 93 |
+
We follow the Contributor Covenant code of conduct.
|
| 94 |
+
---
|
| 95 |
+
|
| 96 |
+
## License
|
| 97 |
+
|
| 98 |
+
This project is licensed under the **MIT License**. See `LICENSE` for details.
|
app.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
gradio_app.py — Drag-and-drop 128px face-swap web UI with Gradio
|
| 4 |
+
Usage:
|
| 5 |
+
python gradio_app.py
|
| 6 |
+
"""
|
| 7 |
+
import os
|
| 8 |
+
import numpy as np
|
| 9 |
+
import cv2
|
| 10 |
+
import gradio as gr
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def get_models():
|
| 14 |
+
import onnxruntime as ort
|
| 15 |
+
from insightface.app import FaceAnalysis
|
| 16 |
+
from insightface.model_zoo.inswapper import INSwapper
|
| 17 |
+
|
| 18 |
+
providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]
|
| 19 |
+
|
| 20 |
+
app = FaceAnalysis(name="buffalo_l", providers=providers)
|
| 21 |
+
app.prepare(ctx_id=0, det_size=(640, 640))
|
| 22 |
+
|
| 23 |
+
model_path = os.path.expanduser(
|
| 24 |
+
"~/.insightface/models/inswapper_128.onnx"
|
| 25 |
+
)
|
| 26 |
+
if not os.path.isfile(model_path):
|
| 27 |
+
raise FileNotFoundError(f"Model not found: {model_path}")
|
| 28 |
+
|
| 29 |
+
session = ort.InferenceSession(model_path, providers=providers)
|
| 30 |
+
swapper = INSwapper(model_file=model_path, session=session)
|
| 31 |
+
|
| 32 |
+
return app, swapper
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def swap_fn(src, dst):
|
| 36 |
+
app, swapper = get_models()
|
| 37 |
+
|
| 38 |
+
src_img = cv2.cvtColor(np.array(src), cv2.COLOR_RGB2BGR)
|
| 39 |
+
dst_img = cv2.cvtColor(np.array(dst), cv2.COLOR_RGB2BGR)
|
| 40 |
+
|
| 41 |
+
faces_src = app.get(src_img)
|
| 42 |
+
faces_dst = app.get(dst_img)
|
| 43 |
+
if not faces_src or not faces_dst:
|
| 44 |
+
return None, "❌ Face not detected in one of the images."
|
| 45 |
+
|
| 46 |
+
result = swapper.get(dst_img, faces_dst[0], faces_src[0], paste_back=True)
|
| 47 |
+
out_img = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
|
| 48 |
+
|
| 49 |
+
return out_img, "✅ Success"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
iface = gr.Interface(
|
| 53 |
+
fn=swap_fn,
|
| 54 |
+
inputs=[
|
| 55 |
+
gr.Image(type="pil", label="Source (your face)"),
|
| 56 |
+
gr.Image(type="pil", label="Target (celebrity)"),
|
| 57 |
+
],
|
| 58 |
+
outputs=[
|
| 59 |
+
gr.Image(type="numpy", label="Swapped Result"),
|
| 60 |
+
gr.Textbox(label="Status"),
|
| 61 |
+
],
|
| 62 |
+
title="Simple 128px Face-Swap",
|
| 63 |
+
description="Upload two images and get a face-swapped result.",
|
| 64 |
+
allow_flagging="never",
|
| 65 |
+
live=False,
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
if __name__ == "__main__":
|
| 70 |
+
iface.launch(server_name="0.0.0.0", server_port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.2.0
|
| 2 |
+
opencv-python
|
| 3 |
+
insightface==0.7.3
|
| 4 |
+
onnxruntime-gpu
|
| 5 |
+
gradio # <— UI dependency
|
swap.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
swap.py — Simple 128px face-swap using INSwapperONNX + InsightFace
|
| 4 |
+
Usage:
|
| 5 |
+
python swap.py --src <your.jpg> --dst <target.jpg> --out <out.png> --device cpu|gpu
|
| 6 |
+
"""
|
| 7 |
+
import argparse
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import cv2
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def swap_faces(
|
| 14 |
+
src_path: str,
|
| 15 |
+
dst_path: str,
|
| 16 |
+
out_path: str,
|
| 17 |
+
device: str,
|
| 18 |
+
):
|
| 19 |
+
import onnxruntime as ort
|
| 20 |
+
from insightface.app import FaceAnalysis
|
| 21 |
+
from insightface.model_zoo.inswapper import INSwapper
|
| 22 |
+
|
| 23 |
+
providers = ["CUDAExecutionProvider"] if device == "gpu" else [
|
| 24 |
+
"CPUExecutionProvider"
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
app = FaceAnalysis(name="buffalo_l", providers=providers)
|
| 28 |
+
app.prepare(ctx_id=0 if device == "gpu" else -1, det_size=(640, 640))
|
| 29 |
+
|
| 30 |
+
# path to the ONNX model
|
| 31 |
+
model_path = os.path.expanduser(
|
| 32 |
+
"~/.insightface/models/inswapper_128.onnx"
|
| 33 |
+
)
|
| 34 |
+
if not os.path.isfile(model_path):
|
| 35 |
+
sys.exit(f"❌ Model file not found: {model_path}")
|
| 36 |
+
|
| 37 |
+
session = ort.InferenceSession(model_path, providers=providers)
|
| 38 |
+
swapper = INSwapper(model_file=model_path, session=session)
|
| 39 |
+
|
| 40 |
+
src_img = cv2.imread(src_path)
|
| 41 |
+
dst_img = cv2.imread(dst_path)
|
| 42 |
+
if src_img is None or dst_img is None:
|
| 43 |
+
sys.exit("❌ Failed to load one of the images.")
|
| 44 |
+
|
| 45 |
+
faces_src = app.get(src_img)
|
| 46 |
+
faces_dst = app.get(dst_img)
|
| 47 |
+
if not faces_src or not faces_dst:
|
| 48 |
+
sys.exit("❌ Could not detect a face in one of the images.")
|
| 49 |
+
|
| 50 |
+
result = swapper.get(
|
| 51 |
+
dst_img,
|
| 52 |
+
faces_dst[0],
|
| 53 |
+
faces_src[0],
|
| 54 |
+
paste_back=True,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
ext = os.path.splitext(out_path)[1].lower()
|
| 58 |
+
params = [cv2.IMWRITE_JPEG_QUALITY, 100] if ext in (
|
| 59 |
+
".jpg",
|
| 60 |
+
".jpeg",
|
| 61 |
+
) else []
|
| 62 |
+
|
| 63 |
+
cv2.imwrite(out_path, result, params)
|
| 64 |
+
print(f"✅ Swap complete → {out_path}")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def main():
|
| 68 |
+
parser = argparse.ArgumentParser(description="Face-swap 128px CLI")
|
| 69 |
+
|
| 70 |
+
parser.add_argument(
|
| 71 |
+
"--src",
|
| 72 |
+
required=True,
|
| 73 |
+
help="Path to your source image",
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
parser.add_argument(
|
| 77 |
+
"--dst",
|
| 78 |
+
required=True,
|
| 79 |
+
help="Path to target image",
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
parser.add_argument(
|
| 83 |
+
"--out",
|
| 84 |
+
default="result.png",
|
| 85 |
+
help="Output file path",
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
parser.add_argument(
|
| 89 |
+
"--device",
|
| 90 |
+
choices=["cpu", "gpu"],
|
| 91 |
+
default="cpu",
|
| 92 |
+
help="Run on CPU or GPU (needs onnxruntime-gpu)",
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
args = parser.parse_args()
|
| 96 |
+
swap_faces(args.src, args.dst, args.out, args.device)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
if __name__ == "__main__":
|
| 100 |
+
main()
|