File size: 3,851 Bytes
3881be4
 
 
7e6ea8d
f626ecb
 
88a25be
3881be4
f626ecb
7e6ea8d
f626ecb
 
7e6ea8d
f626ecb
7e6ea8d
f626ecb
7e6ea8d
f626ecb
 
 
 
 
 
7e6ea8d
f626ecb
7e6ea8d
 
f626ecb
 
7e6ea8d
f626ecb
3881be4
f626ecb
7e6ea8d
 
 
3881be4
 
6b06308
f626ecb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e6ea8d
f626ecb
3881be4
 
 
 
 
 
 
 
 
f626ecb
3881be4
 
 
 
f626ecb
3881be4
f626ecb
3881be4
 
 
 
f626ecb
 
 
 
 
 
 
 
 
 
 
 
3881be4
 
40709e4
 
7e6ea8d
 
 
f626ecb
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
import cv2
import insightface
from insightface.app import FaceAnalysis
import onnxruntime as ort
import os
import numpy as np

class FaceSwapper:
    def __init__(self, det_size=(1280, 1280), use_enhancer=False):
        """
        det_size: higher = better detection on A100 (try 1280 or even 1600)
        use_enhancer: enable GFPGAN-style enhancement (requires gfpgan package)
        """
        # Force CUDA first
        available = ort.get_available_providers()
        print(f"[FaceSwapper] Available ONNX providers: {available}")

        providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
        if 'CUDAExecutionProvider' not in available:
            print("[WARNING] CUDAExecutionProvider not found! Falling back to CPU. Check onnxruntime-gpu + CUDA.")
            providers = ['CPUExecutionProvider']

        # Face analysis (detection + landmarks + embedding)
        self.app = FaceAnalysis(
            name='buffalo_l',
            providers=providers
        )
        # ctx_id=0 → first GPU. A100 loves larger det_size
        self.app.prepare(ctx_id=0, det_size=det_size)

        # Inswapper
        self.swapper = insightface.model_zoo.get_model(
            'inswapper_128.onnx',
            download=True,
            download_zip=True,
            providers=providers
        )

        self.use_enhancer = false
        self.enhancer = None
        if use_enhancer:
            try:
                from gfpgan import GFPGANer
                # You need to place GFPGANv1.4.pth in the Space or let it download
                self.enhancer = GFPGANer(
                    model_path='https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth',
                    upscale=1,
                    arch='clean',
                    channel_multiplier=2,
                    bg_upsampler=None
                )
                print("[FaceSwapper] GFPGAN enhancer loaded")
            except Exception as e:
                print(f"[WARNING] Could not load enhancer: {e}")
                self.use_enhancer = False

    def swap_faces(self, source_path, source_face_idx, target_path, target_face_idx, enhance=False):
        source_img = cv2.imread(source_path)
        target_img = cv2.imread(target_path)

        if source_img is None or target_img is None:
            raise ValueError("Could not read one or both images")

        source_faces = self.app.get(source_img)
        target_faces = self.app.get(target_img)

        # Sort left → right for consistent indexing
        source_faces = sorted(source_faces, key=lambda x: x.bbox[0])
        target_faces = sorted(target_faces, key=lambda x: x.bbox[0])

        if len(source_faces) < source_face_idx or source_face_idx < 1:
            raise ValueError(f"Source image contains {len(source_faces)} faces, but requested face {source_face_idx}")
        if len(target_faces) < target_face_idx or target_face_idx < 1:
            raise ValueError(f"Target image contains {len(target_faces)} faces, but requested face {target_face_idx}")

        source_face = source_faces[source_face_idx - 1]
        target_face = target_faces[target_face_idx - 1]

        result = self.swapper.get(target_img, target_face, source_face, paste_back=True)

        # Optional enhancement (big quality jump)
        if (enhance or self.use_enhancer) and self.enhancer is not None:
            _, _, result = self.enhancer.enhance(
                result,
                has_aligned=False,
                only_center_face=False,
                paste_back=True,
                weight=0.8   # 0.5–1.0, higher = stronger restoration
            )

        return result

    def count_faces(self, img_path):
        img = cv2.imread(img_path)
        if img is None:
            return 0
        faces = self.app.get(img)
        return len(faces)