Commit
·
8af4a0a
0
Parent(s):
- .gitattributes +1 -0
- .gitignore +293 -0
- Dockerfile +39 -0
- README.md +13 -0
- README2.md +97 -0
- app.py +215 -0
- core/__init__.py +0 -0
- core/config.py +87 -0
- core/face_processor.py +169 -0
- models/templates/step01.jpg +3 -0
- models/templates/step02.jpg +3 -0
- requirements.txt +9 -0
.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
|
models/templates/step02.jpg
ADDED
![]() |
Git LFS Details
|
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
|