File size: 7,376 Bytes
8af4a0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import gradio as gr
import cv2
import numpy as np
from PIL import Image
from pathlib import Path

# 導入臉部處理器
from core.face_processor import FaceProcessor

# --- 初始化模型 ---
face_processor = None
try:
    print("🚀 正在初始化 AI 模型,請稍候...")
    face_processor = FaceProcessor()
    print("✅ AI 模型載入成功!")
except Exception as e:
    print(f"❌ 模型載入失敗: {e}")
    print("   如果看到下載錯誤,請參考 README.md 中的手動下載指南。")
    face_processor = None

# --- Gradio 介面 ---
def create_examples():
    """檢查範例圖片是否存在,如果存在則建立範例列表"""
    example_list = []
    template_dir = Path(__file__).parent / 'models' / 'templates'
    
    # 檢查範例圖片 step01.jpg 和 step02.jpg 是否存在
    source_example_path = template_dir / 'step01.jpg'
    target_example_path = template_dir / 'step02.jpg'

    if source_example_path.exists() and target_example_path.exists():
        print("✅ 找到範例圖片,正在建立 Gradio 範例...")
        example_list.append([
            str(source_example_path),
            str(target_example_path),
            "0", # 臉部索引
            None # 模板
        ])
    else:
        print("⚠️  未找到範例圖片(例如 models/templates/step01.jpg),Gradio 將不會顯示範例。")
        print(f"   - 檢查路徑: {source_example_path}")
        print(f"   - 檢查路徑: {target_example_path}")

    return example_list

# 建立範例
examples = create_examples()

# 取得模板圖片列表
def get_template_files():
    template_dir = Path(__file__).parent / "models" / "templates"
    try:
        templates = [str(p) for p in template_dir.glob("*.jpg")]
        print(f"✅ 成功載入 {len(templates)} 個模板。")
        return templates
    except FileNotFoundError:
        print("⚠️  找不到模板資料夾 'models/templates',模板選擇功能將無法使用。")
        return []
    except Exception as e:
        print(f"❌ 載入模板時發生錯誤: {e}")
        return []

def swap_face_gradio(source_image, target_image, face_index_str, template_choice):
    """Gradio 的主處理函數"""
    if face_processor is None:
        raise gr.Error("AI 模型未能成功載入,無法處理請求。請檢查後台日誌。")

    print(f"ℹ️ 開始處理... 來源臉孔索引: {face_index_str}")

    try:
        # --- 1. 參數驗證和準備 ---
        if source_image is None:
            raise gr.Error("請務必上傳來源圖片。")

        # 如果選擇了模板,目標圖片變成模板
        if template_choice and template_choice != "無":
            print(f"   -> 使用模板: {template_choice}")
            target_image = np.array(Image.open(template_choice))
        elif target_image is None:
            raise gr.Error("請上傳目標圖片,或選擇一個模板。")

        try:
            face_index = int(face_index_str)
            if face_index < 0:
                raise ValueError
        except (ValueError, TypeError):
            raise gr.Error("臉部索引必須是一個大於等於 0 的整數。")

        # --- 2. 圖片格式轉換 ---
        # Gradio 提供 RGB 格式,insightface 需要 BGR 格式
        source_bgr = cv2.cvtColor(source_image, cv2.COLOR_RGB2BGR)
        target_bgr = cv2.cvtColor(target_image, cv2.COLOR_RGB2BGR)

        # --- 3. 執行換臉 --- 
        result_bgr = face_processor.swap_face(
            source_bgr,
            target_bgr,
            face_index
        )

        # --- 4. 處理結果 ---
        if result_bgr is None:
            raise gr.Error("換臉失敗,可能是因為無法在圖片中偵測到足夠的臉部。")

        # 轉回 RGB 格式供 Gradio 顯示
        result_rgb = cv2.cvtColor(result_bgr, cv2.COLOR_BGR2RGB)
        print("✅ 處理完成!")
        return result_rgb

    except ValueError as ve:
        print(f"❌ 處理時發生錯誤: {ve}")
        raise gr.Error(str(ve))
    except Exception as e:
        import traceback
        error_details = traceback.format_exc()
        print(f"❌ 發生未預期的錯誤: {e}")
        print(f"詳細錯誤堆疊:\n{error_details}")
        raise gr.Error(f"錯誤: {str(e)}")

# --- 建立 Gradio 介面 ---
with gr.Blocks(
    title="換臉 by DAVID888",
    theme=gr.themes.Soft(),
    css=".gradio-container {max-width: 1000px !important}"
) as demo:
    
    gr.HTML("""
        <div style="text-align: center; font-family: 'Arial', sans-serif; color: #333;">
            <h1 style="color: #2c3e50;">換臉工具</h1>
            <p style="font-size: 1.1em;">上傳一張包含臉部的<b>來源圖片</b>和一張<b>目標圖片</b>,或從下方選擇一個<b>模板</b>作為目標,即可進行換臉。</p>
            <p style="font-size: 0.9em; color: #7f8c8d;">如果來源圖片中有多張臉,請在「來源臉部索引」中指定要替換的臉部(從 0 開始)。</p>
        </div>
    """)

    with gr.Row():
        with gr.Column(scale=1):
            source_image = gr.Image(
                label="來源圖片 (您的臉部)",
                type="numpy",
                height=300
            )
            face_index_input = gr.Textbox(
                label="來源臉部索引",
                value="0",
                placeholder="如果有多張臉,指定要使用的臉部(從 0 開始)",
                info="0=第一張臉, 1=第二張臉, 以此類推"
            )

        with gr.Column(scale=1):
            target_image = gr.Image(
                label="目標圖片 (要換到的目標)",
                type="numpy", 
                height=300
            )
            
            # 模板選擇 (如果有可用模板)
            template_files = get_template_files()
            if template_files:
                template_choice = gr.Dropdown(
                    choices=["無"] + template_files,
                    value="無",
                    label="或選擇一個模板",
                    info="選擇模板會覆蓋上方的目標圖片"
                )
            else:
                template_choice = gr.Dropdown(
                    choices=["無"],
                    value="無",
                    label="模板 (無可用模板)",
                    interactive=False
                )

    # 處理按鈕
    swap_button = gr.Button(
        "🚀 開始換臉",
        variant="primary",
        size="lg"
    )

    # 結果顯示
    result_image = gr.Image(
        label="換臉結果",
        type="numpy",
        height=400
    )

    # 範例 (如果有的話)
    if examples:
        gr.Examples(
            examples=examples,
            inputs=[source_image, target_image, face_index_input, template_choice],
            label="點擊範例快速開始",
            examples_per_page=3
        )

    # 綁定處理函數
    swap_button.click(
        fn=swap_face_gradio,
        inputs=[
            source_image,
            target_image, 
            face_index_input,
            template_choice
        ],
        outputs=result_image,
        api_name="face_swap"
    )

# --- 啟動應用 ---
if __name__ == "__main__":
    demo.launch(
        server_name="0.0.0.0", 
        server_port=7860, 
        share=True
    )