vadim71 commited on
Commit
5c67354
·
verified ·
1 Parent(s): 72eda63

Upload 4 files

Browse files
Files changed (4) hide show
  1. .gitattributes +13 -0
  2. app.py +270 -0
  3. packages.txt +2 -0
  4. requirements.txt +11 -0
.gitattributes ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.onnx filter=lfs diff=lfs merge=lfs -text
2
+ *.pth filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
5
+ *.pt filter=lfs diff=lfs merge=lfs -text
6
+ *.h5 filter=lfs diff=lfs merge=lfs -text
7
+ *.tflite filter=lfs diff=lfs merge=lfs -text
8
+ *.tgz filter=lfs diff=lfs merge=lfs -text
9
+ *.wasm filter=lfs diff=lfs merge=lfs -text
10
+ *.xz filter=lfs diff=lfs merge=lfs -text
11
+ *.zip filter=lfs diff=lfs merge=lfs -text
12
+ *.zst filter=lfs diff=lfs merge=lfs -text
13
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ from PIL import Image
5
+ import urllib.request
6
+ import gradio as gr
7
+ import insightface
8
+ from insightface.app import FaceAnalysis
9
+ from gfpgan import GFPGANer
10
+
11
+ # --- Загрузка моделей при первом старте ---
12
+ def download_file(url: str, filename: str):
13
+ if not os.path.exists(filename):
14
+ print(f"Скачиваю {filename} из {url}...")
15
+ urllib.request.urlretrieve(url, filename)
16
+ print(f"{filename} скачан.")
17
+
18
+ # Пути к моделям
19
+ INSWAPPER_URL = "https://huggingface.co/ezioruan/inswapper_128.onnx/resolve/main/inswapper_128.onnx"
20
+ INSWAPPER_PATH = "inswapper_128.onnx"
21
+ GFPGAN_URL = "https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth"
22
+ GFPGAN_PATH = "GFPGANv1.3.pth"
23
+
24
+ download_file(INSWAPPER_URL, INSWAPPER_PATH)
25
+ download_file(GFPGAN_URL, GFPGAN_PATH)
26
+
27
+ assert os.path.exists(INSWAPPER_PATH), "inswapper_128.onnx не найден."
28
+ assert os.path.exists(GFPGAN_PATH), "GFPGANv1.3.pth не найден."
29
+
30
+ # --- Инициализация моделей ---
31
+ try:
32
+ import torch
33
+ has_gpu = torch.cuda.is_available()
34
+ except Exception:
35
+ has_gpu = False
36
+
37
+ ctx_id = 0 if has_gpu else -1 # 0 = GPU, -1 = CPU
38
+ print("GPU доступен" if has_gpu else "Работаем на CPU")
39
+
40
+ providers = ['CPUExecutionProvider'] # Только CPU для бесплатного тарифа
41
+
42
+ app_face = FaceAnalysis(name="buffalo_l", providers=providers)
43
+ app_face.prepare(ctx_id=ctx_id, det_size=(640, 640))
44
+
45
+ swapper = insightface.model_zoo.get_model(INSWAPPER_PATH, providers=providers)
46
+ face_enhancer = GFPGANer(
47
+ model_path=GFPGAN_PATH,
48
+ upscale=1,
49
+ arch="clean",
50
+ channel_multiplier=2,
51
+ bg_upsampler=None,
52
+ )
53
+
54
+ MAX_PREVIEWS = 8 # Максимум маленьких превью
55
+
56
+ # --- Тексты для локализации ---
57
+ TEXTS = {
58
+ "ru": {
59
+ "lang_radio_label": "Язык / Language",
60
+ "title_md": "# FaceSwap Pro\nСлева — фото **донора**, справа — фото, где нужно заменить лица.",
61
+ "step1_title": "### 1. Фото донора",
62
+ "step1_input_label": "Загрузите фото донора (может быть несколько лиц)",
63
+ "step1_donor_choice_label": "Выберите лицо-донора",
64
+ "step2_title": "### 2. Фото, которое меняем",
65
+ "step2_input_label": "Загрузите изображение для замены лиц",
66
+ "step2_target_choices_label": "Выберите лица для замены (если ничего не выбрано — меняем все)",
67
+ "step3_title": "### 3. Настройки и экспорт",
68
+ "use_enh_label": "Улучшить качество результата (GFPGAN)",
69
+ "eta_initial": "Оценка времени появится после обнаружения лиц.",
70
+ "fmt_label": "Формат файла для скачивания",
71
+ "run_btn": "Запустить замену",
72
+ "download_btn": "Скачать результат",
73
+ "before_after_label": "До / После",
74
+ "eta_fmt_sec": "Оценочное время обработки: ~{sec} сек.",
75
+ "eta_fmt_min": "Оценочное время обработки: ~{min} мин {sec} сек.",
76
+ "msg_need_images": "Загрузите оба изображения, чтобы начать обработку.",
77
+ "msg_no_donor_faces": "На фото донора не найдено ни одного лица.",
78
+ "msg_no_target_faces": "На целевом фото не найдено ни одного лица.",
79
+ "msg_prep": "Подготовка к обработке...",
80
+ "msg_swap_step": "Замена лица {i} из {n}",
81
+ "msg_enh": "Улучшение качества (GFPGAN)...",
82
+ "msg_done": "Готово. Обработано лиц: {n}.",
83
+ "progress_done": "Готово!",
84
+ "donor_option": "Донор {i}",
85
+ "target_option": "Лицо {i}",
86
+ },
87
+ "en": {
88
+ "lang_radio_label": "Language / Язык",
89
+ "title_md": "# FaceSwap Pro\nLeft — **donor photo**, right — photo where faces will be replaced.",
90
+ "step1_title": "### 1. Donor photo",
91
+ "step1_input_label": "Upload donor photo (can contain multiple faces)",
92
+ "step1_donor_choice_label": "Choose donor face",
93
+ "step2_title": "### 2. Target photo",
94
+ "step2_input_label": "Upload image where faces will be replaced",
95
+ "step2_target_choices_label": "Choose faces to replace (if none selected — replace all)",
96
+ "step3_title": "### 3. Settings & Export",
97
+ "use_enh_label": "Enhance result quality (GFPGAN)",
98
+ "eta_initial": "Time estimate will appear after detecting faces.",
99
+ "fmt_label": "Download file format",
100
+ "run_btn": "Start swapping",
101
+ "download_btn": "Download result",
102
+ "before_after_label": "Before / After",
103
+ "eta_fmt_sec": "Estimated processing time: ~{sec} sec.",
104
+ "eta_fmt_min": "Estimated processing time: ~{min} min {sec} sec.",
105
+ "msg_need_images": "Please upload both images to start.",
106
+ "msg_no_donor_faces": "No faces detected on donor image.",
107
+ "msg_no_target_faces": "No faces detected on target image.",
108
+ "msg_prep": "Preparing for processing...",
109
+ "msg_swap_step": "Replacing face {i} of {n}",
110
+ "msg_enh": "Enhancing quality (GFPGAN)...",
111
+ "msg_done": "Done. Faces processed: {n}.",
112
+ "progress_done": "Done!",
113
+ "donor_option": "Donor {i}",
114
+ "target_option": "Face {i}",
115
+ },
116
+ }
117
+
118
+ def T(lang: str, key: str, **kwargs) -> str:
119
+ s = TEXTS[lang][key]
120
+ return s.format(**kwargs)
121
+
122
+ # --- Основная логика обработки ---
123
+ def detect_faces_generic(img):
124
+ if img is None:
125
+ return [], []
126
+ bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
127
+ faces = app_face.get(bgr)
128
+ faces = sorted(faces, key=lambda f: f.bbox[0])
129
+ previews = []
130
+ for f in faces:
131
+ x1, y1, x2, y2 = map(int, f.bbox)
132
+ h, w = bgr.shape[:2]
133
+ x1 = max(0, x1); y1 = max(0, y1)
134
+ x2 = min(w, x2); y2 = min(h, y2)
135
+ crop = bgr[y1:y2, x1:x2]
136
+ previews.append(cv2.cvtColor(crop, cv2.COLOR_BGR2RGB))
137
+ return previews, faces
138
+
139
+ def update_donor_faces(img, lang: str):
140
+ previews, faces = detect_faces_generic(img)
141
+ updates = []
142
+ for i in range(MAX_PREVIEWS):
143
+ if i < len(previews):
144
+ updates.append(gr.update(value=previews[i], visible=True))
145
+ else:
146
+ updates.append(gr.update(value=None, visible=False))
147
+ labels = [T(lang, "donor_option", i=i + 1) for i in range(len(previews))]
148
+ default = labels[0] if labels else None
149
+ radio_update = gr.update(choices=labels, value=default)
150
+ return updates + [radio_update, faces]
151
+
152
+ def update_target_faces(img, use_enhancer: bool, lang: str):
153
+ previews, faces = detect_faces_generic(img)
154
+ updates = []
155
+ for i in range(MAX_PREVIEWS):
156
+ if i < len(previews):
157
+ updates.append(gr.update(value=previews[i], visible=True))
158
+ else:
159
+ updates.append(gr.update(value=None, visible=False))
160
+ labels = [T(lang, "target_option", i=i + 1) for i in range(len(previews))]
161
+ default = labels if labels else []
162
+ checkbox_update = gr.update(choices=labels, value=default)
163
+ eta_text = estimate_time(lang, len(faces), use_enhancer)
164
+ return updates + [checkbox_update, faces, eta_text]
165
+
166
+ def estimate_time(lang: str, num_faces: int, use_enhancer: bool) -> str:
167
+ if num_faces <= 0:
168
+ return TEXTS[lang]["eta_initial"]
169
+ base_per_face = 4 if has_gpu else 10
170
+ extra_per_face = 6 if use_enhancer else 0
171
+ total = num_faces * (base_per_face + extra_per_face)
172
+ if total < 60:
173
+ return T(lang, "eta_fmt_sec", sec=int(total))
174
+ m = int(total // 60)
175
+ s = int(total % 60)
176
+ return T(lang, "eta_fmt_min", min=m, sec=s)
177
+
178
+ # --- Интерфейс Gradio ---
179
+ custom_css = """
180
+ .gradio-container { max-width: 1200px; margin: 0 auto; }
181
+ .step-card { background: radial-gradient(circle at top left, #0b1220, #020617); border-radius: 18px; padding: 18px 20px; }
182
+ .face-thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: 8px; }
183
+ .progress-ring { width: 120px; height: 120px; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
184
+ .progress-ring-text { font-size: 14px; color: #e5e7eb; text-align: center; }
185
+ """
186
+
187
+ premium_theme = gr.themes.Default(
188
+ primary_hue="violet",
189
+ neutral_hue="slate",
190
+ ).set(
191
+ body_text_size="16px",
192
+ button_large_padding="12px 22px",
193
+ )
194
+
195
+ with gr.Blocks(title="FaceSwap Pro", css=custom_css) as demo:
196
+ lang_state = gr.State("ru")
197
+ lang_radio = gr.Radio(choices=["RU", "EN"], value="RU", label="Язык / Language")
198
+ title_md = gr.Markdown(TEXTS["ru"]["title_md"])
199
+
200
+ with gr.Row():
201
+ with gr.Column():
202
+ donor_img = gr.Image(label="Фото донора", type="numpy")
203
+ donor_previews = [gr.Image(visible=False, interactive=False, width=64, height=64) for _ in range(MAX_PREVIEWS)]
204
+ donor_choice = gr.Radio(label="Выберите лицо-донора", choices=[])
205
+ with gr.Column():
206
+ target_img = gr.Image(label="Целевое фото", type="numpy")
207
+ target_previews = [gr.Image(visible=False, interactive=False, width=64, height=64) for _ in range(MAX_PREVIEWS)]
208
+ target_choices = gr.CheckboxGroup(label="Выберите лица для замены", choices=[])
209
+
210
+ with gr.Row():
211
+ use_enh = gr.Checkbox(label="Улучшить качество результата (GFPGAN)", value=True)
212
+ fmt = gr.Dropdown(label="Формат файла для скачивания", choices=["png", "jpeg", "webp"], value="png")
213
+ run_btn = gr.Button("Запустить замену", variant="primary")
214
+ download_btn = gr.DownloadButton("Скачать результат")
215
+
216
+ result_img = gr.Image(label="Результат", interactive=False)
217
+ status_md = gr.Markdown("")
218
+ before_after = gr.Gallery(label="До / После", columns=2)
219
+
220
+ def switch_language(choice):
221
+ lang = "ru" if choice == "RU" else "en"
222
+ t = TEXTS[lang]
223
+ return (
224
+ lang,
225
+ gr.update(value=t["title_md"]),
226
+ gr.update(label=t["step1_input_label"]),
227
+ gr.update(label=t["step1_donor_choice_label"]),
228
+ gr.update(label=t["step2_input_label"]),
229
+ gr.update(label=t["step2_target_choices_label"]),
230
+ gr.update(label=t["use_enh_label"]),
231
+ gr.update(label=t["fmt_label"]),
232
+ gr.update(value=t["run_btn"]),
233
+ gr.update(value=t["download_btn"]),
234
+ )
235
+
236
+ lang_radio.change(
237
+ fn=switch_language,
238
+ inputs=lang_radio,
239
+ outputs=[
240
+ lang_state,
241
+ title_md,
242
+ donor_img,
243
+ donor_choice,
244
+ target_img,
245
+ target_choices,
246
+ use_enh,
247
+ fmt,
248
+ run_btn,
249
+ download_btn,
250
+ ],
251
+ )
252
+
253
+ donor_img.change(
254
+ fn=update_donor_faces,
255
+ inputs=[donor_img, lang_state],
256
+ outputs=donor_previews + [donor_choice],
257
+ )
258
+
259
+ target_img.change(
260
+ fn=update_target_faces,
261
+ inputs=[target_img, use_enh, lang_state],
262
+ outputs=target_previews + [target_choices],
263
+ )
264
+
265
+ demo.launch(
266
+ share=False,
267
+ ssr_mode=False,
268
+ theme=premium_theme,
269
+ css=custom_css
270
+ )
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ libglib2.0-0
2
+ libgl1
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch==2.4.1
2
+ torchvision==0.19.1
3
+ gradio==4.36.1
4
+ insightface==0.7.3
5
+ onnxruntime==1.16.3
6
+ opencv-python-headless==4.8.1.78
7
+ gfpgan==1.3.8
8
+ basicsr==1.3.4.2
9
+ facexlib==0.3.0
10
+ Pillow==10.3.0
11
+ huggingface_hub==0.23.0