Spaces:
Running
Running
starting from the scratch
Browse files- README.md +20 -5
- app.py +86 -0
- face_analysis.py +58 -0
- meanshape_68.pkl +3 -0
- playground.py +24 -0
- requirements.txt +5 -0
README.md
CHANGED
|
@@ -1,14 +1,29 @@
|
|
| 1 |
---
|
| 2 |
title: FaceFusion
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: mit
|
| 11 |
-
short_description:
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: FaceFusion
|
| 3 |
+
emoji: 😻
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: mit
|
| 11 |
+
short_description: Swap faces in images
|
| 12 |
---
|
| 13 |
|
| 14 |
+
## Unofficial FaceFusion Implementation
|
| 15 |
+
|
| 16 |
+
FaceFusion has evolved significantly, increasing in complexity over time.
|
| 17 |
+
This project was therefore developed from scratch to provide a simplified and more accessible implementation while retaining the essential functionality.
|
| 18 |
+
|
| 19 |
+
## Acknowledgments
|
| 20 |
+
|
| 21 |
+
This work preserves key functionality from the original authors:
|
| 22 |
+
- [DeepInsight](https://github.com/deepinsight/insightface)
|
| 23 |
+
- [FaceFusion](https://github.com/facefusion/facefusion)
|
| 24 |
+
|
| 25 |
+
## Contact
|
| 26 |
+
|
| 27 |
+
For questions, comments, or feedback, please contact:
|
| 28 |
+
📧 **[email protected]**
|
| 29 |
+
|
app.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#######################################################################################
|
| 2 |
+
#
|
| 3 |
+
# MIT License
|
| 4 |
+
#
|
| 5 |
+
# Copyright (c) [2025] [[email protected]]
|
| 6 |
+
#
|
| 7 |
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 8 |
+
# of this software and associated documentation files (the "Software"), to deal
|
| 9 |
+
# in the Software without restriction, including without limitation the rights
|
| 10 |
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 11 |
+
# copies of the Software, and to permit persons to whom the Software is
|
| 12 |
+
# furnished to do so, subject to the following conditions:
|
| 13 |
+
#
|
| 14 |
+
# The above copyright notice and this permission notice shall be included in all
|
| 15 |
+
# copies or substantial portions of the Software.
|
| 16 |
+
#
|
| 17 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 18 |
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 19 |
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 20 |
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 21 |
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 22 |
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 23 |
+
# SOFTWARE.
|
| 24 |
+
#
|
| 25 |
+
#######################################################################################
|
| 26 |
+
#
|
| 27 |
+
# Source code is based on or inspired by several projects.
|
| 28 |
+
# For more details and proper attribution, please refer to the following resources:
|
| 29 |
+
#
|
| 30 |
+
# - [Deepinsight] - [https://github.com/deepinsight/insightface]
|
| 31 |
+
# - [FaceFusion] [https://github.com/facefusion/facefusion]
|
| 32 |
+
#
|
| 33 |
+
|
| 34 |
+
import gradio as gr
|
| 35 |
+
from huggingface_hub import hf_hub_download
|
| 36 |
+
from itertools import islice
|
| 37 |
+
from face_analysis import FaceAnalysis
|
| 38 |
+
from models.inswapper import INSwapper
|
| 39 |
+
|
| 40 |
+
REPO_ID = "leonelhs/insightface"
|
| 41 |
+
model_inswapper_path = hf_hub_download(repo_id=REPO_ID, filename="inswapper_128.onnx")
|
| 42 |
+
|
| 43 |
+
face_analyser = FaceAnalysis()
|
| 44 |
+
swapper = INSwapper(model_inswapper_path)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def predict(src_img, dst_img):
|
| 48 |
+
|
| 49 |
+
# Get faces
|
| 50 |
+
src_faces = face_analyser.get(src_img)
|
| 51 |
+
dst_faces = face_analyser.get(dst_img)
|
| 52 |
+
|
| 53 |
+
# Swap the first face found
|
| 54 |
+
if len(src_faces) > 0 and len(dst_faces) > 0:
|
| 55 |
+
return dst_img, swapper.get(dst_img, dst_faces[0], src_faces[0], paste_back=True)
|
| 56 |
+
else:
|
| 57 |
+
raise gr.Error("No faces were found!")
|
| 58 |
+
|
| 59 |
+
with gr.Blocks(title="FaceFusion") as app:
|
| 60 |
+
navbar = gr.Navbar(visible=True, main_page_name="Workspace")
|
| 61 |
+
gr.Markdown("## FaceFusion Lite")
|
| 62 |
+
with gr.Row():
|
| 63 |
+
with gr.Column(scale=1):
|
| 64 |
+
with gr.Row():
|
| 65 |
+
source_image = gr.Image(type="numpy", label="Face image")
|
| 66 |
+
target_image = gr.Image(type="numpy", label="Body image")
|
| 67 |
+
image_btn = gr.Button("Swap face")
|
| 68 |
+
with gr.Column(scale=1):
|
| 69 |
+
with gr.Row():
|
| 70 |
+
output_image = gr.ImageSlider(label="Swapped image", type="pil")
|
| 71 |
+
image_btn.click(
|
| 72 |
+
fn=predict,
|
| 73 |
+
inputs=[source_image, target_image],
|
| 74 |
+
outputs=output_image,
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
with app.route("Readme", "/readme"):
|
| 78 |
+
with open("README.md") as f:
|
| 79 |
+
for line in islice(f, 12, None):
|
| 80 |
+
gr.Markdown(line.strip())
|
| 81 |
+
|
| 82 |
+
app.launch(share=False, debug=True, show_error=True, mcp_server=True, pwa=True)
|
| 83 |
+
app.queue()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
|
face_analysis.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# @Organization : insightface.ai
|
| 3 |
+
# @Author : Jia Guo
|
| 4 |
+
# @Time : 2021-05-04
|
| 5 |
+
# @Function :
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
from __future__ import division
|
| 9 |
+
|
| 10 |
+
import onnxruntime
|
| 11 |
+
|
| 12 |
+
__all__ = ['FaceAnalysis']
|
| 13 |
+
|
| 14 |
+
from utils.common import Face
|
| 15 |
+
from models.arcface_onnx import ArcFaceONNX
|
| 16 |
+
from models.attribute import Attribute
|
| 17 |
+
from models.landmark import Landmark
|
| 18 |
+
from models.retinaface import RetinaFace
|
| 19 |
+
from huggingface_hub import hf_hub_download
|
| 20 |
+
|
| 21 |
+
REPO_ID = "leonelhs/insightface"
|
| 22 |
+
|
| 23 |
+
model_detector_path = hf_hub_download(repo_id=REPO_ID, filename="det_10g.onnx")
|
| 24 |
+
model_landmark_3d_68_path = hf_hub_download(repo_id=REPO_ID, filename="1k3d68.onnx")
|
| 25 |
+
model_landmark_2d_106_path = hf_hub_download(repo_id=REPO_ID, filename="2d106det.onnx")
|
| 26 |
+
model_genderage_path = hf_hub_download(repo_id=REPO_ID, filename="genderage.onnx")
|
| 27 |
+
model_recognition_path = hf_hub_download(repo_id=REPO_ID, filename="w600k_r50.onnx")
|
| 28 |
+
|
| 29 |
+
class FaceAnalysis:
|
| 30 |
+
def __init__(self):
|
| 31 |
+
onnxruntime.set_default_logger_severity(3)
|
| 32 |
+
|
| 33 |
+
self.detector = RetinaFace(model_file=model_detector_path, input_size=(640, 640), det_thresh=0.5)
|
| 34 |
+
self.landmark_3d_68 = Landmark(model_file=model_landmark_3d_68_path)
|
| 35 |
+
self.landmark_2d_106 = Landmark(model_file=model_landmark_2d_106_path)
|
| 36 |
+
self.genderage = Attribute(model_file=model_genderage_path)
|
| 37 |
+
self.recognition = ArcFaceONNX(model_file=model_recognition_path)
|
| 38 |
+
|
| 39 |
+
def get(self, img, max_num=0):
|
| 40 |
+
bboxes, kpss = self.detector.detect(img,
|
| 41 |
+
max_num=max_num,
|
| 42 |
+
metric='default')
|
| 43 |
+
if bboxes.shape[0] == 0:
|
| 44 |
+
return []
|
| 45 |
+
ret = []
|
| 46 |
+
for i in range(bboxes.shape[0]):
|
| 47 |
+
bbox = bboxes[i, 0:4]
|
| 48 |
+
det_score = bboxes[i, 4]
|
| 49 |
+
kps = None
|
| 50 |
+
if kpss is not None:
|
| 51 |
+
kps = kpss[i]
|
| 52 |
+
face = Face(bbox=bbox, kps=kps, det_score=det_score)
|
| 53 |
+
self.landmark_3d_68.get(img, face)
|
| 54 |
+
self.landmark_2d_106.get(img, face)
|
| 55 |
+
self.genderage.get(img, face)
|
| 56 |
+
self.recognition.get(img, face)
|
| 57 |
+
ret.append(face)
|
| 58 |
+
return ret
|
meanshape_68.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:39ffecf84ba73f0d0d7e49380833ba88713c9fcdec51df4f7ac45a48b8f4cc51
|
| 3 |
+
size 974
|
playground.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
|
| 3 |
+
from face_analysis import FaceAnalysis
|
| 4 |
+
from models.inswapper import INSwapper
|
| 5 |
+
|
| 6 |
+
app = FaceAnalysis()
|
| 7 |
+
|
| 8 |
+
swapper = INSwapper('./inswapper_128.onnx')
|
| 9 |
+
|
| 10 |
+
source_img = "/home/leonel/Pictures/bun01.jpg"
|
| 11 |
+
target_img = "/home/leonel/Pictures/ac6a1e147711139.62c72d282c159.png"
|
| 12 |
+
|
| 13 |
+
# Load source and target images
|
| 14 |
+
src_img = cv2.imread(source_img)
|
| 15 |
+
dst_img = cv2.imread(target_img)
|
| 16 |
+
|
| 17 |
+
# Get faces
|
| 18 |
+
src_faces = app.get(src_img)
|
| 19 |
+
dst_faces = app.get(dst_img)
|
| 20 |
+
|
| 21 |
+
# Swap the first face found
|
| 22 |
+
if len(src_faces) > 0 and len(dst_faces) > 0:
|
| 23 |
+
result = swapper.get(dst_img, dst_faces[0], src_faces[0], paste_back=True)
|
| 24 |
+
cv2.imwrite("swapped2.jpg", result)
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy~=2.2.6
|
| 2 |
+
opencv-python~=4.12.0.88
|
| 3 |
+
scikit-image~=0.25.2
|
| 4 |
+
onnx~=1.19.0
|
| 5 |
+
onnxruntime~=1.22.1
|