tbdavid2019 commited on
Commit
8af4a0a
·
0 Parent(s):
.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 處理結果檔案(不需要版本控制)
2
+ backend/results/*
3
+ backend/uploads/*
4
+
5
+ # AI 模型檔案(檔案太大,需要另外下載)
6
+ # inswapper_128.onnx 檔案 529MB,超過 GitHub 100MB 限制
7
+ backend/models/inswapper_128.onnx
8
+ backend/models/*.pth
9
+ backend/models/*.pt
10
+
11
+ # Python
12
+ __pycache__/
13
+ *.py[cod]
14
+ *$py.class
15
+ *.so
16
+ .Python
17
+ build/
18
+ develop-eggs/
19
+ dist/
20
+ downloads/
21
+ eggs/
22
+ .eggs/
23
+ lib/
24
+ lib64/
25
+ parts/
26
+ sdist/
27
+ var/
28
+ wheels/
29
+ share/python-wheels/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+ MANIFEST
34
+
35
+ # PyInstaller
36
+ *.manifest
37
+ *.spec
38
+
39
+ # Installer logs
40
+ pip-log.txt
41
+ pip-delete-this-directory.txt
42
+
43
+ # Unit test / coverage reports
44
+ htmlcov/
45
+ .tox/
46
+ .nox/
47
+ .coverage
48
+ .coverage.*
49
+ .cache
50
+ nosetests.xml
51
+ coverage.xml
52
+ *.cover
53
+ *.py,cover
54
+ .hypothesis/
55
+ .pytest_cache/
56
+ cover/
57
+
58
+ # Translations
59
+ *.mo
60
+ *.pot
61
+
62
+ # Django stuff:
63
+ *.log
64
+ local_settings.py
65
+ db.sqlite3
66
+ db.sqlite3-journal
67
+
68
+ # Flask stuff:
69
+ instance/
70
+ .webassets-cache
71
+
72
+ # Scrapy stuff:
73
+ .scrapy
74
+
75
+ # Sphinx documentation
76
+ docs/_build/
77
+
78
+ # PyBuilder
79
+ .pybuilder/
80
+ target/
81
+
82
+ # Jupyter Notebook
83
+ .ipynb_checkpoints
84
+
85
+ # IPython
86
+ profile_default/
87
+ ipython_config.py
88
+
89
+ # pyenv
90
+ .python-version
91
+
92
+ # pipenv
93
+ Pipfile.lock
94
+
95
+ # poetry
96
+ poetry.lock
97
+
98
+ # pdm
99
+ .pdm.toml
100
+
101
+ # PEP 582
102
+ __pypackages__/
103
+
104
+ # Celery stuff
105
+ celerybeat-schedule
106
+ celerybeat.pid
107
+
108
+ # SageMath parsed files
109
+ *.sage.py
110
+
111
+ # Environments
112
+ .env
113
+ .venv
114
+ env/
115
+ venv/
116
+ ENV/
117
+ env.bak/
118
+ venv.bak/
119
+
120
+ # Spyder project settings
121
+ .spyderproject
122
+ .spyproject
123
+
124
+ # Rope project settings
125
+ .ropeproject
126
+
127
+ # mkdocs documentation
128
+ /site
129
+
130
+ # mypy
131
+ .mypy_cache/
132
+ .dmypy.json
133
+ dmypy.json
134
+
135
+ # Pyre type checker
136
+ .pyre/
137
+
138
+ # pytype static type analyzer
139
+ .pytype/
140
+
141
+ # Cython debug symbols
142
+ cython_debug/
143
+
144
+ # PyCharm
145
+ .idea/
146
+
147
+ # VS Code
148
+ .vscode/
149
+ *.code-workspace
150
+
151
+ # macOS
152
+ .DS_Store
153
+ .AppleDouble
154
+ .LSOverride
155
+
156
+ # Windows
157
+ Thumbs.db
158
+ Thumbs.db:encryptable
159
+ ehthumbs.db
160
+ ehthumbs_vista.db
161
+ *.stackdump
162
+ [Dd]esktop.ini
163
+ $RECYCLE.BIN/
164
+ *.cab
165
+ *.msi
166
+ *.msix
167
+ *.msm
168
+ *.msp
169
+ *.lnk
170
+
171
+ # Linux
172
+ *~
173
+
174
+ # Docker
175
+ .dockerignore
176
+
177
+ # Node.js (如果有前端建置工具)
178
+ node_modules/
179
+ npm-debug.log*
180
+ yarn-debug.log*
181
+ yarn-error.log*
182
+ lerna-debug.log*
183
+ .pnpm-debug.log*
184
+
185
+ # Runtime data
186
+ pids
187
+ *.pid
188
+ *.seed
189
+ *.pid.lock
190
+
191
+ # Coverage directory used by tools like istanbul
192
+ coverage/
193
+ *.lcov
194
+
195
+ # nyc test coverage
196
+ .nyc_output
197
+
198
+ # Grunt intermediate storage
199
+ .grunt
200
+
201
+ # Bower dependency directory
202
+ bower_components
203
+
204
+ # node-waf configuration
205
+ .lock-wscript
206
+
207
+ # Compiled binary addons
208
+ build/Release
209
+
210
+ # Dependency directories
211
+ jspm_packages/
212
+
213
+ # TypeScript cache
214
+ *.tsbuildinfo
215
+
216
+ # Optional npm cache directory
217
+ .npm
218
+
219
+ # Optional eslint cache
220
+ .eslintcache
221
+
222
+ # Optional stylelint cache
223
+ .stylelintcache
224
+
225
+ # Microbundle cache
226
+ .rpt2_cache/
227
+ .rts2_cache_cjs/
228
+ .rts2_cache_es/
229
+ .rts2_cache_umd/
230
+
231
+ # Optional REPL history
232
+ .node_repl_history
233
+
234
+ # Output of 'npm pack'
235
+ *.tgz
236
+
237
+ # Yarn Integrity file
238
+ .yarn-integrity
239
+
240
+ # dotenv environment variable files
241
+ .env.development.local
242
+ .env.test.local
243
+ .env.production.local
244
+ .env.local
245
+
246
+ # parcel-bundler cache
247
+ .cache
248
+ .parcel-cache
249
+
250
+ # Next.js build output
251
+ .next
252
+ out
253
+
254
+ # Nuxt.js build / generate output
255
+ .nuxt
256
+ dist
257
+
258
+ # Gatsby files
259
+ .cache/
260
+ public
261
+
262
+ # Storybook build outputs
263
+ .out
264
+ .storybook-out
265
+ storybook-static
266
+
267
+ # Temporary folders
268
+ tmp/
269
+ temp/
270
+
271
+ # Logs
272
+ logs
273
+ *.log
274
+
275
+ # Runtime data
276
+ pids
277
+ *.pid
278
+ *.seed
279
+ *.pid.lock
280
+
281
+ # 專案特定的臨時檔案
282
+ *.tmp
283
+ *.temp
284
+ *.bak
285
+ *.swp
286
+ *.swo
287
+
288
+ # 測試檔案
289
+ test_images/
290
+ test_results/
291
+
292
+
293
+ myenv/
Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Python 3.11 slim 版本作為基礎映像
2
+ FROM python:3.11-slim
3
+
4
+ # 設定工作目錄
5
+ WORKDIR /app
6
+
7
+ # 安裝系統依賴
8
+ RUN apt-get update && apt-get install -y \
9
+ libgl1-mesa-glx \
10
+ libglib2.0-0 \
11
+ libsm6 \
12
+ libxext6 \
13
+ libxrender-dev \
14
+ libgomp1 \
15
+ wget \
16
+ build-essential \
17
+ g++ \
18
+ cmake \
19
+ && rm -rf /var/lib/apt/lists/*
20
+
21
+ # 複製 requirements.txt 並安裝 Python 依賴
22
+ COPY backend/requirements.txt .
23
+ RUN pip install --no-cache-dir -r requirements.txt
24
+
25
+ # 創建模型目錄(模型檔案由 docker-compose.yml 中的 model-downloader 服務下載)
26
+ RUN mkdir -p /app/{models,results,uploads}
27
+
28
+ # 由於使用 volume 掛載,不需要 COPY 後端代碼
29
+ # 後端代碼會通過 docker-compose.yml 中的 volume 掛載
30
+
31
+ # 設定環境變數
32
+ ENV PYTHONPATH=/app
33
+ ENV ENVIRONMENT=development
34
+
35
+ # 暴露端口
36
+ EXPOSE 3001
37
+
38
+ # 啟動命令(會被 docker-compose.yml 中的 command 覆蓋)
39
+ CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "3001", "--reload"]
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SwapFace
3
+ emoji: ⚡
4
+ colorFrom: gray
5
+ colorTo: pink
6
+ sdk: gradio
7
+ sdk_version: 5.34.2
8
+ app_file: app.py
9
+ pinned: false
10
+ short_description: 換臉
11
+ ---
12
+
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
README2.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🎭 AI 頭像工作室 (Gradio 版)
2
+
3
+ 這是一個使用 Gradio 和 InsightFace 技術建立的 AI 換臉應用程式。專案經過簡化,移除了前後端分離的複雜架構,改用單一的 Gradio 應用程式,使其更容易在本地端執行、打包成 Docker 映像檔,或部署到 Hugging Face Spaces。
4
+
5
+ ## ✨ 功能
6
+
7
+ - **互動式介面**: 使用 Gradio 建立,操作直觀,無需前端開發知識。
8
+ - **自訂換臉**: 上傳您的照片和目標風格照片,一鍵生成換臉結果。
9
+ - **預設模板**: 提供多個預設模板,方便快速體驗。
10
+ - **多人臉支援**: 支援對圖片中的多張人臉進行選擇性替換。
11
+ - **自動模型下載**: 首次執行時,應用程式會自動下載所需的 AI 模型。
12
+
13
+ ## 📂 專案結構
14
+
15
+ 專案已簡化為適合 Gradio 應用的結構:
16
+
17
+ ```
18
+ swapFace/
19
+ ├── Dockerfile # 用於建立 Docker 映像檔
20
+ ├── app.py # 主要的 Gradio 應用程式
21
+ ├── requirements.txt # Python 依賴
22
+ ├── README.md # 就是您正在看的這個檔案
23
+ ├── core/ # 核心 AI 處理邏輯
24
+ │ ├── __init__.py
25
+ │ ├── config.py
26
+ │ └── face_processor.py
27
+ └── models/ # 存放 AI 模型和圖片模板
28
+ └── templates/
29
+ ├── step01.jpg
30
+ └── ...
31
+ ```
32
+
33
+ ---
34
+
35
+ ## 🚀 如何執行
36
+
37
+ 您可以選擇兩種方式來執行此應用程式:**1. 在本地端直接執行** 或 **2. 使用 Docker**。
38
+
39
+ ### 方案一:在本地端直接執行 (推薦給開發者)
40
+
41
+ 1. **環境準備**:
42
+ 建議在 Python 虛擬環境中進行操作。
43
+ ```bash
44
+ # 建立虛擬環境
45
+ python3 -m venv venv
46
+
47
+ # 啟用虛擬環境 (macOS/Linux)
48
+ source venv/bin/activate
49
+ # (Windows)
50
+ # venv\Scripts\activate
51
+ ```
52
+
53
+ 2. **安裝依賴**:
54
+ ```bash
55
+ pip install -r requirements.txt
56
+ ```
57
+
58
+ 3. **啟動應用**:
59
+ ```bash
60
+ python app.py
61
+ ```
62
+
63
+ 應用程式啟動後,會顯示一個本地 URL (例如 `http://127.0.0.1:7860`)。在瀏覽器中打開此連結即可開始使用。
64
+
65
+ **⚠️ 注意**: 首次啟動時,程式會自動從網路下載約 530MB 的 AI 模型 (`inswapper_128.onnx`) 到 `models` 目錄下,請耐心等待。這只需要執行一次。
66
+
67
+ ### 方案二:使用 Docker 執行 (推薦用於部署或隔離環境)
68
+
69
+ 確保您的電腦上已經安裝了 [Docker](https://www.docker.com/products/docker-desktop/)。
70
+
71
+ 1. **建立 Docker 映像檔**:
72
+ 在專案根目錄下(與 `Dockerfile` 同層級),執行以下指令:
73
+ ```bash
74
+ # -t 後面是您為映像檔取的名稱,例如 'ai-avatar-studio'
75
+ docker build -t ai-avatar-studio .
76
+ ```
77
+
78
+ 2. **執行 Docker 容器**:
79
+ 映像檔建立成功後,使用以下指令來啟動容器:
80
+ ```bash
81
+ # -p 7860:7860 將您本機的 7860 連接埠映射到容器的 7860 連接埠
82
+ # --rm 會在容器停止後自動刪除,保持系統乾淨
83
+ docker run --rm -p 7860:7860 ai-avatar-studio
84
+ ```
85
+
86
+ 3. **訪問應用**:
87
+ 容器啟動後,同樣會先下載模型(如果映像檔中沒有的話)。完成後,在瀏覽器中打開 `http://localhost:7860` 即可使用。
88
+
89
+ ---
90
+
91
+ ## 🤗 部署到 Hugging Face Spaces
92
+
93
+ 這個專案結構也非常適合直接部署到 Hugging Face Spaces。
94
+
95
+ 1. 在 Hugging Face 上建立一個新的 Space,並選擇 **Gradio** 作為 SDK。
96
+ 2. 將專案中的所有檔案(**除了 `Dockerfile`**)上傳到 Space 的 Git 儲存庫。
97
+ 3. Hugging Face 會自動處理環境安裝和應用啟動。大功告成!
app.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ from PIL import Image
5
+ from pathlib import Path
6
+
7
+ # 導入臉部處理器
8
+ from core.face_processor import FaceProcessor
9
+
10
+ # --- 初始化模型 ---
11
+ face_processor = None
12
+ try:
13
+ print("🚀 正在初始化 AI 模型,請稍候...")
14
+ face_processor = FaceProcessor()
15
+ print("✅ AI 模型載入成功!")
16
+ except Exception as e:
17
+ print(f"❌ 模型載入失敗: {e}")
18
+ print(" 如果看到下載錯誤,請參考 README.md 中的手動下載指南。")
19
+ face_processor = None
20
+
21
+ # --- Gradio 介面 ---
22
+ def create_examples():
23
+ """檢查範例圖片是否存在,如果存在則建立範例列表"""
24
+ example_list = []
25
+ template_dir = Path(__file__).parent / 'models' / 'templates'
26
+
27
+ # 檢查範例圖片 step01.jpg 和 step02.jpg 是否存在
28
+ source_example_path = template_dir / 'step01.jpg'
29
+ target_example_path = template_dir / 'step02.jpg'
30
+
31
+ if source_example_path.exists() and target_example_path.exists():
32
+ print("✅ 找到範例圖片,正在建立 Gradio 範例...")
33
+ example_list.append([
34
+ str(source_example_path),
35
+ str(target_example_path),
36
+ "0", # 臉部索引
37
+ None # 模板
38
+ ])
39
+ else:
40
+ print("⚠️ 未找到範例圖片(例如 models/templates/step01.jpg),Gradio 將不會顯示範例。")
41
+ print(f" - 檢查路徑: {source_example_path}")
42
+ print(f" - 檢查路徑: {target_example_path}")
43
+
44
+ return example_list
45
+
46
+ # 建立範例
47
+ examples = create_examples()
48
+
49
+ # 取得模板圖片列表
50
+ def get_template_files():
51
+ template_dir = Path(__file__).parent / "models" / "templates"
52
+ try:
53
+ templates = [str(p) for p in template_dir.glob("*.jpg")]
54
+ print(f"✅ 成功載入 {len(templates)} 個模板。")
55
+ return templates
56
+ except FileNotFoundError:
57
+ print("⚠️ 找不到模板資料夾 'models/templates',模板選擇功能將無法使用。")
58
+ return []
59
+ except Exception as e:
60
+ print(f"❌ 載入模板時發生錯誤: {e}")
61
+ return []
62
+
63
+ def swap_face_gradio(source_image, target_image, face_index_str, template_choice):
64
+ """Gradio 的主處理函數"""
65
+ if face_processor is None:
66
+ raise gr.Error("AI 模型未能成功載入,無法處理請求。請檢查後台日誌。")
67
+
68
+ print(f"ℹ️ 開始處理... 來源臉孔索引: {face_index_str}")
69
+
70
+ try:
71
+ # --- 1. 參數驗證和準備 ---
72
+ if source_image is None:
73
+ raise gr.Error("請務必上傳來源圖片。")
74
+
75
+ # 如果選擇了模板,目標圖片變成模板
76
+ if template_choice and template_choice != "無":
77
+ print(f" -> 使用模板: {template_choice}")
78
+ target_image = np.array(Image.open(template_choice))
79
+ elif target_image is None:
80
+ raise gr.Error("請上傳目標圖片,或選擇一個模板。")
81
+
82
+ try:
83
+ face_index = int(face_index_str)
84
+ if face_index < 0:
85
+ raise ValueError
86
+ except (ValueError, TypeError):
87
+ raise gr.Error("臉部索引必須是一個大於等於 0 的整數。")
88
+
89
+ # --- 2. 圖片格式轉換 ---
90
+ # Gradio 提供 RGB 格式,insightface 需要 BGR 格式
91
+ source_bgr = cv2.cvtColor(source_image, cv2.COLOR_RGB2BGR)
92
+ target_bgr = cv2.cvtColor(target_image, cv2.COLOR_RGB2BGR)
93
+
94
+ # --- 3. 執行換臉 ---
95
+ result_bgr = face_processor.swap_face(
96
+ source_bgr,
97
+ target_bgr,
98
+ face_index
99
+ )
100
+
101
+ # --- 4. 處理結果 ---
102
+ if result_bgr is None:
103
+ raise gr.Error("換臉失敗,可能是因為無法在圖片中偵測到足夠的臉部。")
104
+
105
+ # 轉回 RGB 格式供 Gradio 顯示
106
+ result_rgb = cv2.cvtColor(result_bgr, cv2.COLOR_BGR2RGB)
107
+ print("✅ 處理完成!")
108
+ return result_rgb
109
+
110
+ except ValueError as ve:
111
+ print(f"❌ 處理時發生錯誤: {ve}")
112
+ raise gr.Error(str(ve))
113
+ except Exception as e:
114
+ import traceback
115
+ error_details = traceback.format_exc()
116
+ print(f"❌ 發生未預期的錯誤: {e}")
117
+ print(f"詳細錯誤堆疊:\n{error_details}")
118
+ raise gr.Error(f"錯誤: {str(e)}")
119
+
120
+ # --- 建立 Gradio 介面 ---
121
+ with gr.Blocks(
122
+ title="換臉 by DAVID888",
123
+ theme=gr.themes.Soft(),
124
+ css=".gradio-container {max-width: 1000px !important}"
125
+ ) as demo:
126
+
127
+ gr.HTML("""
128
+ <div style="text-align: center; font-family: 'Arial', sans-serif; color: #333;">
129
+ <h1 style="color: #2c3e50;">換臉工具</h1>
130
+ <p style="font-size: 1.1em;">上傳一張包含臉部的<b>來源圖片</b>和一張<b>目標圖片</b>,或從下方選擇一個<b>模板</b>作為目標,即可進行換臉。</p>
131
+ <p style="font-size: 0.9em; color: #7f8c8d;">如果來源圖片中有多張臉,請在「來源臉部索引」中���定要替換的臉部(從 0 開始)。</p>
132
+ </div>
133
+ """)
134
+
135
+ with gr.Row():
136
+ with gr.Column(scale=1):
137
+ source_image = gr.Image(
138
+ label="來源圖片 (您的臉部)",
139
+ type="numpy",
140
+ height=300
141
+ )
142
+ face_index_input = gr.Textbox(
143
+ label="來源臉部索引",
144
+ value="0",
145
+ placeholder="如果有多張臉,指定要使用的臉部(從 0 開始)",
146
+ info="0=第一張臉, 1=第二張臉, 以此類推"
147
+ )
148
+
149
+ with gr.Column(scale=1):
150
+ target_image = gr.Image(
151
+ label="目標圖片 (要換到的目標)",
152
+ type="numpy",
153
+ height=300
154
+ )
155
+
156
+ # 模板選擇 (如果有可用模板)
157
+ template_files = get_template_files()
158
+ if template_files:
159
+ template_choice = gr.Dropdown(
160
+ choices=["無"] + template_files,
161
+ value="無",
162
+ label="或選擇一個模板",
163
+ info="選擇模板會覆蓋上方的目標圖片"
164
+ )
165
+ else:
166
+ template_choice = gr.Dropdown(
167
+ choices=["無"],
168
+ value="無",
169
+ label="模板 (無可用模板)",
170
+ interactive=False
171
+ )
172
+
173
+ # 處理按鈕
174
+ swap_button = gr.Button(
175
+ "🚀 開始換臉",
176
+ variant="primary",
177
+ size="lg"
178
+ )
179
+
180
+ # 結果顯示
181
+ result_image = gr.Image(
182
+ label="換臉結果",
183
+ type="numpy",
184
+ height=400
185
+ )
186
+
187
+ # 範例 (如果有的話)
188
+ if examples:
189
+ gr.Examples(
190
+ examples=examples,
191
+ inputs=[source_image, target_image, face_index_input, template_choice],
192
+ label="點擊範例快速開始",
193
+ examples_per_page=3
194
+ )
195
+
196
+ # 綁定處理函數
197
+ swap_button.click(
198
+ fn=swap_face_gradio,
199
+ inputs=[
200
+ source_image,
201
+ target_image,
202
+ face_index_input,
203
+ template_choice
204
+ ],
205
+ outputs=result_image,
206
+ api_name="face_swap"
207
+ )
208
+
209
+ # --- 啟動應用 ---
210
+ if __name__ == "__main__":
211
+ demo.launch(
212
+ server_name="0.0.0.0",
213
+ server_port=7860,
214
+ share=True
215
+ )
core/__init__.py ADDED
File without changes
core/config.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 應用配置檔案
3
+ """
4
+ import os
5
+ from pathlib import Path
6
+
7
+ # 基礎路徑 (現在是專案根目錄)
8
+ BASE_DIR = Path(__file__).parent.parent
9
+ MODELS_DIR = BASE_DIR / "models"
10
+ RESULTS_DIR = BASE_DIR / "results" # Gradio 可能不太需要這個了
11
+
12
+ # AI 模型配置
13
+ MODEL_CONFIG = {
14
+ "FACE_ANALYSIS_MODEL": "buffalo_l",
15
+ "FACE_SWAP_MODEL": "inswapper_128.onnx",
16
+ "DETECTION_SIZE": (640, 640),
17
+ "CTX_ID": 0, # CPU: -1, GPU: 0
18
+ }
19
+
20
+ # 模板配置
21
+ TEMPLATE_CONFIG = {
22
+ "TEMPLATES": {
23
+ "1": {
24
+ "id": "1",
25
+ "name": "模板 1",
26
+ "description": "預設模板",
27
+ "path": "./models/templates/step01.jpg"
28
+ },
29
+ "2": {
30
+ "id": "2",
31
+ "name": "模板 2",
32
+ "description": "預設模板",
33
+ "path": "./models/templates/step02.jpg"
34
+ },
35
+ "3": {
36
+ "id": "3",
37
+ "name": "模板 3",
38
+ "description": "預設模板",
39
+ "path": "./models/templates/step03.jpg"
40
+ },
41
+ "4": {
42
+ "id": "4",
43
+ "name": "模板 4",
44
+ "description": "預設模板",
45
+ "path": "./models/templates/step04.jpg"
46
+ },
47
+ "5": {
48
+ "id": "5",
49
+ "name": "模板 5",
50
+ "description": "預設模板",
51
+ "path": "./models/templates/step05.jpg"
52
+ },
53
+ "6": {
54
+ "id": "6",
55
+ "name": "模板 6",
56
+ "description": "預設模板",
57
+ "path": "./models/templates/step06.jpg"
58
+ }
59
+ }
60
+ }
61
+
62
+ def get_model_path(model_name: str) -> Path:
63
+ """獲取模型檔案路徑"""
64
+ # Hugging Face Spaces 會將模型快取在 /data,但我們的邏輯是下載到 models/
65
+ local_model_path = MODELS_DIR / model_name
66
+ if local_model_path.exists():
67
+ return local_model_path
68
+ # 如果本地沒有,face_processor 會嘗試下載
69
+ return local_model_path
70
+
71
+ def get_template_path(template_id: str) -> Path:
72
+ """獲取模板圖片路徑"""
73
+ template = TEMPLATE_CONFIG["TEMPLATES"].get(template_id)
74
+ if not template:
75
+ raise ValueError(f"Template {template_id} not found")
76
+
77
+ template_path = template["path"]
78
+ # 將相對於專案根目錄的路徑轉換為絕對路徑
79
+ if template_path.startswith("./"):
80
+ return BASE_DIR / template_path[2:]
81
+ return Path(template_path)
82
+
83
+ def ensure_directories():
84
+ """確保必要的目錄存在"""
85
+ # 確保模型和模板目錄存在,以便下載和存取
86
+ (MODELS_DIR / "templates").mkdir(parents=True, exist_ok=True)
87
+
core/face_processor.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 臉部處理核心模組
3
+ """
4
+ import cv2
5
+ import numpy as np
6
+ from PIL import Image
7
+ import insightface
8
+ from insightface.app import FaceAnalysis
9
+ import onnxruntime
10
+ from pathlib import Path
11
+ import urllib.request
12
+ import os
13
+
14
+ class FaceProcessor:
15
+ """臉部處理器,封裝了所有 AI 模型"""
16
+ def __init__(self):
17
+ self.swapper = None
18
+ self.face_analyzer = None
19
+ self._initialize_models()
20
+
21
+ def _ensure_model_downloaded(self, root_dir: Path, model_name: str, url: str):
22
+ """
23
+ 確保指定的模型檔案存在於 models 資料夾中。
24
+ 如果不存在,則從提供的 URL 下載。
25
+ """
26
+ model_dir = root_dir / 'models'
27
+ model_path = model_dir / model_name
28
+
29
+ model_dir.mkdir(parents=True, exist_ok=True)
30
+
31
+ if model_path.exists():
32
+ print(f"INFO:core.face_processor:找到本地模型: {model_path},將跳過下載。")
33
+ return
34
+
35
+ print(f"INFO:core.face_processor:在本地找不到模型,將從以下 URL 下載:")
36
+ print(f" -> URL: {url}")
37
+ print(f" -> 目標路徑: {model_path}")
38
+
39
+ try:
40
+ response = urllib.request.urlopen(url)
41
+ total_length = response.getheader('content-length')
42
+
43
+ with open(model_path, 'wb') as f:
44
+ if total_length is None:
45
+ f.write(response.read())
46
+ print("INFO:core.face_processor:模型下載完成。")
47
+ else:
48
+ dl = 0
49
+ total_length = int(total_length)
50
+ for data in response:
51
+ dl += len(data)
52
+ f.write(data)
53
+ done = int(50 * dl / total_length)
54
+ print(f"\r [ {'=' * done}{' ' * (50-done)} ] {dl * 100 / total_length:.2f}%", end='')
55
+ print("\nINFO:core.face_processor:模型下載完成。")
56
+
57
+ except Exception as e:
58
+ print(f"\nERROR:core.face_processor:下載模型時發生錯誤。")
59
+ if model_path.exists():
60
+ os.remove(model_path)
61
+ print(f" 請檢查您的網路連線,或嘗試手動從以下 URL 下載模型:")
62
+ print(f" {url}")
63
+ print(f" 並將其放置在 '{model_path.parent}' 資料夾中。")
64
+ raise RuntimeError(f"模型下載失敗: {url}") from e
65
+
66
+ def _initialize_models(self):
67
+ """初始化所有需要的模型"""
68
+ try:
69
+ print("INFO:core.face_processor:正在初始化 AI 模型...")
70
+ onnxruntime.set_default_logger_severity(3)
71
+
72
+ root_dir = Path(__file__).parent.parent
73
+ models_dir = root_dir / 'models'
74
+ models_dir.mkdir(exist_ok=True) # 確保 models 資料夾存在
75
+
76
+ providers = onnxruntime.get_available_providers()
77
+ if 'CUDAExecutionProvider' in providers:
78
+ print("INFO:core.face_processor:檢測到 CUDA,將使用 GPU。")
79
+ else:
80
+ print("INFO:core.face_processor:未檢測到 CUDA,將使用 CPU。")
81
+ if 'CoreMLExecutionProvider' in providers:
82
+ providers.insert(0, 'CoreMLExecutionProvider')
83
+
84
+ # --- 載入臉部分析模型 (buffalo_l) ---
85
+ print(f"INFO:core.face_processor:正在檢查臉部分析模型 'buffalo_l'...")
86
+ buffalo_l_path = models_dir / 'buffalo_l'
87
+ print(f" -> 預期路徑: {buffalo_l_path}")
88
+
89
+ if not buffalo_l_path.is_dir() or not any(buffalo_l_path.iterdir()):
90
+ print("WARNING:core.face_processor:未在預期路徑找到 'buffalo_l' 模型資料夾,或該資料夾為空。")
91
+ print(" -> 程式將嘗試從網路下載。若要手動放置,請將解壓縮後的 'buffalo_l' 資料夾完整放入 'models' 目錄中。")
92
+ else:
93
+ print("INFO:core.face_processor:成功找到本地 'buffalo_l' 模型資料夾。")
94
+
95
+ # insightface 會在 root 目錄下尋找 'models' 資料夾,所以 root 應設為專案根目錄
96
+ self.face_analyzer = FaceAnalysis(name='buffalo_l', root=str(root_dir), providers=providers)
97
+ self.face_analyzer.prepare(ctx_id=0, det_size=(640, 640))
98
+
99
+ # --- 載入換臉模型 (inswapper_128.onnx) ---
100
+ model_name = "inswapper_128.onnx"
101
+ model_url = "https://huggingface.co/xingren23/comfyflow-models/resolve/976de8449674de379b02c144d0b3cfa2b61482f2/insightface/inswapper_128.onnx?download=true"
102
+
103
+ self._ensure_model_downloaded(root_dir, model_name, model_url)
104
+
105
+ model_path = root_dir / 'models' / model_name
106
+ print(f"INFO:core.face_processor:準備從本地路徑載入模型: {model_path}")
107
+
108
+ self.swapper = insightface.model_zoo.get_model(
109
+ str(model_path),
110
+ download=False,
111
+ download_zip=False
112
+ )
113
+ print("INFO:core.face_processor:AI 模型載入完成!")
114
+
115
+ except Exception as e:
116
+ print(f"ERROR:core.face_processor:模型初始化失敗:{e}")
117
+ raise RuntimeError(f"無法初始化 AI 模型:{e}") from e
118
+
119
+ def get_faces(self, image):
120
+ """從圖片中偵測所有臉部"""
121
+ try:
122
+ faces = self.face_analyzer.get(image)
123
+ if faces:
124
+ print(f"INFO:core.face_processor:偵測到 {len(faces)} 張臉部")
125
+ else:
126
+ print("WARNING:core.face_processor:在來源圖片中未偵測到任何臉部。")
127
+ return faces
128
+ except Exception as e:
129
+ print(f"ERROR:core.face_processor:臉部偵測失敗:{e}")
130
+ return []
131
+
132
+ def swap_face(self, source_img, target_img, face_index=0):
133
+ """
134
+ 在來源和目標圖片之間交換臉部。
135
+ :param source_img: 來源圖片 (包含要使用的臉部)
136
+ :param target_img: 目標圖片 (要將臉部換到這張圖上)
137
+ :param face_index: 要從來源圖片中使用的臉部索引 (預設為第 0 張)
138
+ :return: 換臉後的圖片
139
+ """
140
+ try:
141
+ source_faces = self.get_faces(source_img)
142
+ if not source_faces:
143
+ raise ValueError("在來源圖片中找不到任何臉部,無法進行換臉。")
144
+
145
+ if face_index >= len(source_faces):
146
+ raise ValueError(f"指定的臉部索引 {face_index} 超出範圍,來源圖片只有 {len(source_faces)} 張臉。")
147
+
148
+ target_faces = self.get_faces(target_img)
149
+ if not target_faces:
150
+ print("WARNING:core.face_processor:在目標圖片中未偵測到臉部,將直接在原圖上操作。")
151
+ # 如果目標沒有臉,某些模型可能允許直接返回,或我們可以選擇返回原圖
152
+ # 在此案例中,我們讓 swapper 決定如何處理
153
+
154
+ source_face = source_faces[face_index]
155
+
156
+ # 執行換臉
157
+ # INSwapper.get() 的正確用法:get(target_img, target_face, source_face, paste_back=True)
158
+ # 如果目標圖片沒有臉部,我們使用 source_face 作為 target_face
159
+ target_face = target_faces[0] if target_faces else source_face
160
+ result_img = self.swapper.get(target_img, target_face, source_face, paste_back=True)
161
+ print("INFO:core.face_processor:換臉處理完成")
162
+ return result_img
163
+
164
+ except ValueError as ve:
165
+ print(f"ERROR:core.face_processor:{ve}")
166
+ raise
167
+ except Exception as e:
168
+ print(f"ERROR:core.face_processor:換臉處理失敗:{e}")
169
+ raise RuntimeError("換臉過程中發生未預期的錯誤") from e
models/templates/step01.jpg ADDED

Git LFS Details

  • SHA256: d92b6a0163bf353c7a4ae1503f68f4dcee1ad4ec7f5eb5f8b67eae9f09c5c9f9
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB
models/templates/step02.jpg ADDED

Git LFS Details

  • SHA256: 344415d5f0421c332d8963d7acf495c2eda5a257a166d43bb4bcb5db62920d66
  • Pointer size: 131 Bytes
  • Size of remote file: 134 kB
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ python-multipart==0.0.6
4
+ insightface==0.7.3
5
+ onnxruntime==1.16.3
6
+ opencv-python==4.8.1.78
7
+ pillow==10.1.0
8
+ numpy==1.24.4
9
+ gradio==4.16.0