Artysgor commited on
Commit
a7b1e41
·
verified ·
1 Parent(s): 37923f7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -429
app.py CHANGED
@@ -1,30 +1,26 @@
1
  import os
2
  import cv2
3
- import glob
4
- import time
5
- import torch
6
- import shutil
7
- import argparse
8
- import platform
9
- import datetime
10
- import subprocess
11
  import insightface
12
  import onnxruntime
13
- import numpy as np
14
- import gradio as gr
15
- import threading
16
- import queue
17
- from tqdm import tqdm
18
- import concurrent.futures
19
- from moviepy.editor import VideoFileClip
20
- from PIL import Image
21
- import io
22
 
23
  from face_swapper import Inswapper, paste_to_whole
24
- from face_analyser import detect_conditions, get_analysed_data, swap_options_list
25
- from face_parsing import init_parsing_model, get_parsed_mask, mask_regions, mask_regions_to_list
26
- from face_enhancer import get_available_enhancer_names, load_face_enhancer_model, cv2_interpolations
27
- from utils import trim_video, StreamerThread, ProcessBar, open_directory, split_list_by_lengths, merge_img_sequence_from_ref, create_image_grid
 
 
 
 
 
 
 
 
 
28
 
29
  ## ------------------------------ USER ARGS ------------------------------
30
 
@@ -37,44 +33,6 @@ parser.add_argument(
37
  )
38
  user_args = parser.parse_args()
39
 
40
- ## ------------------------------ DEFAULTS ------------------------------
41
-
42
- USE_COLAB = user_args.colab
43
- USE_CUDA = user_args.cuda
44
- DEF_OUTPUT_PATH = user_args.out_dir
45
- BATCH_SIZE = int(user_args.batch_size)
46
- WORKSPACE = None
47
- OUTPUT_FILE = None
48
- CURRENT_FRAME = None
49
- STREAMER = None
50
- DETECT_CONDITION = "best detection"
51
- DETECT_SIZE = 640
52
- DETECT_THRESH = 0.6
53
- NUM_OF_SRC_SPECIFIC = 10
54
- MASK_INCLUDE = [
55
- "Skin",
56
- "R-Eyebrow",
57
- "L-Eyebrow",
58
- "L-Eye",
59
- "R-Eye",
60
- "Nose",
61
- "Mouth",
62
- "L-Lip",
63
- "U-Lip"
64
- ]
65
- MASK_SOFT_KERNEL = 17
66
- MASK_SOFT_ITERATIONS = 10
67
- MASK_BLUR_AMOUNT = 0.1
68
- MASK_ERODE_AMOUNT = 0.15
69
-
70
- FACE_SWAPPER = None
71
- FACE_ANALYSER = None
72
- FACE_ENHANCER = None
73
- FACE_PARSER = None
74
- FACE_ENHANCER_LIST = ["NONE"]
75
- FACE_ENHANCER_LIST.extend(get_available_enhancer_names())
76
- FACE_ENHANCER_LIST.extend(cv2_interpolations)
77
-
78
  ## ------------------------------ SET EXECUTION PROVIDER ------------------------------
79
  # Note: Non CUDA users may change settings here
80
 
@@ -95,8 +53,8 @@ else:
95
  device = "cuda" if USE_CUDA else "cpu"
96
  EMPTY_CACHE = lambda: torch.cuda.empty_cache() if device == "cuda" else None
97
 
98
- ## ------------------------------ LOAD MODELS ------------------------------
99
 
 
100
  def load_face_analyser_model(name="buffalo_l"):
101
  global FACE_ANALYSER
102
  if FACE_ANALYSER is None:
@@ -105,396 +63,80 @@ def load_face_analyser_model(name="buffalo_l"):
105
  ctx_id=0, det_size=(DETECT_SIZE, DETECT_SIZE), det_thresh=DETECT_THRESH
106
  )
107
 
108
-
109
  def load_face_swapper_model(path="./assets/pretrained_models/inswapper_128.onnx"):
110
  global FACE_SWAPPER
111
  if FACE_SWAPPER is None:
112
- batch = int(BATCH_SIZE) if device == "cuda" else 1
113
- FACE_SWAPPER = Inswapper(model_file=path, batch_size=batch, providers=PROVIDER)
114
-
115
 
116
  def load_face_parser_model(path="./assets/pretrained_models/79999_iter.pth"):
117
  global FACE_PARSER
118
  if FACE_PARSER is None:
119
- FACE_PARSER = init_parsing_model(path, device=device)
120
-
121
 
 
122
  load_face_analyser_model()
123
  load_face_swapper_model()
 
124
 
125
- ## ------------------------------ MAIN PROCESS ------------------------------
126
-
127
-
128
- def process(
129
- input_type,
130
- image_path,
131
- video_path,
132
- directory_path,
133
- source_path,
134
- output_path,
135
- output_name,
136
- keep_output_sequence,
137
- condition,
138
- age,
139
- distance,
140
- face_enhancer_name,
141
- enable_face_parser,
142
- mask_includes,
143
- mask_soft_kernel,
144
- mask_soft_iterations,
145
- blur_amount,
146
- erode_amount,
147
- face_scale,
148
- enable_laplacian_blend,
149
- crop_top,
150
- crop_bott,
151
- crop_left,
152
- crop_right,
153
- *specifics,
154
- ):
155
- global WORKSPACE
156
- global OUTPUT_FILE
157
- global PREVIEW
158
- WORKSPACE, OUTPUT_FILE, PREVIEW = None, None, None
159
-
160
- ## ------------------------------ GUI UPDATE FUNC ------------------------------
161
-
162
- def ui_before():
163
- return (
164
- gr.update(visible=True, value=PREVIEW),
165
- gr.update(interactive=False),
166
- gr.update(interactive=False),
167
- gr.update(visible=False),
168
- )
169
-
170
- def ui_after():
171
- return (
172
- gr.update(visible=True, value=PREVIEW),
173
- gr.update(interactive=True),
174
- gr.update(interactive=True),
175
- gr.update(visible=False),
176
- )
177
-
178
- def ui_after_vid():
179
- return (
180
- gr.update(visible=False),
181
- gr.update(interactive=True),
182
- gr.update(interactive=True),
183
- gr.update(value=OUTPUT_FILE, visible=True),
184
- )
185
-
186
- start_time = time.time()
187
- total_exec_time = lambda start_time: divmod(time.time() - start_time, 60)
188
-
189
- ## ------------------------------ PREPARE INPUTS & LOAD MODELS ------------------------------
190
-
191
- load_face_analyser_model()
192
- load_face_swapper_model()
193
-
194
- if face_enhancer_name != "NONE":
195
- if face_enhancer_name not in cv2_interpolations:
196
- FACE_ENHANCER = load_face_enhancer_model(name=face_enhancer_name, device=device)
197
- else:
198
- FACE_ENHANCER = None
199
-
200
- if enable_face_parser:
201
- load_face_parser_model()
202
-
203
- includes = mask_regions_to_list(mask_includes)
204
- specifics = list(specifics)
205
- half = len(specifics) // 2
206
- sources = specifics[:half]
207
- specifics = specifics[half:]
208
- if crop_top > crop_bott:
209
- crop_top, crop_bott = crop_bott, crop_top
210
- if crop_left > crop_right:
211
- crop_left, crop_right = crop_right, crop_left
212
- crop_mask = (crop_top, 511-crop_bott, crop_left, 511-crop_right)
213
-
214
- def swap_process(image_sequence):
215
- ## ------------------------------ CONTENT CHECK ------------------------------
216
-
217
- if condition != "Specific Face":
218
- source_data = source_path, age
219
- else:
220
- source_data = ((sources, specifics), distance)
221
- analysed_targets, analysed_sources, whole_frame_list, num_faces_per_frame = get_analysed_data(
222
- FACE_ANALYSER,
223
- image_sequence,
224
- source_data,
225
- swap_condition=condition,
226
- detect_condition=DETECT_CONDITION,
227
- scale=face_scale
228
- )
229
-
230
- ## ------------------------------ SWAP FUNC ------------------------------
231
- preds = []
232
- matrs = []
233
- count = 0
234
- for batch_pred, batch_matr in FACE_SWAPPER.batch_forward(whole_frame_list, analysed_targets, analysed_sources):
235
- preds.extend(batch_pred)
236
- matrs.extend(batch_matr)
237
- EMPTY_CACHE()
238
- count += 1
239
-
240
- if USE_CUDA:
241
- image_grid = create_image_grid(batch_pred, size=128)
242
-
243
- ## ------------------------------ FACE ENHANCEMENT ------------------------------
244
-
245
- generated_len = len(preds)
246
- if face_enhancer_name != "NONE":
247
- for idx, pred in tqdm(enumerate(preds), total=generated_len, desc=f"Upscaling with {face_enhancer_name}"):
248
- enhancer_model, enhancer_model_runner = FACE_ENHANCER
249
- pred = enhancer_model_runner(pred, enhancer_model)
250
- preds[idx] = cv2.resize(pred, (512,512))
251
- EMPTY_CACHE()
252
-
253
- ## ------------------------------ FACE PARSING ------------------------------
254
-
255
- if enable_face_parser:
256
- masks = []
257
- count = 0
258
- for batch_mask in get_parsed_mask(FACE_PARSER, preds, classes=includes, device=device, batch_size=BATCH_SIZE, softness=int(mask_soft_iterations)):
259
- masks.append(batch_mask)
260
- EMPTY_CACHE()
261
- count += 1
262
-
263
- if len(batch_mask) > 1:
264
- image_grid = create_image_grid(batch_mask, size=128)
265
- masks = np.concatenate(masks, axis=0) if len(masks) >= 1 else masks
266
- else:
267
- masks = [None] * generated_len
268
-
269
- ## ------------------------------ SPLIT LIST ------------------------------
270
-
271
- split_preds = split_list_by_lengths(preds, num_faces_per_frame)
272
- del preds
273
- split_matrs = split_list_by_lengths(matrs, num_faces_per_frame)
274
- del matrs
275
- split_masks = split_list_by_lengths(masks, num_faces_per_frame)
276
- del masks
277
-
278
- ## ------------------------------ PASTE-BACK ------------------------------
279
-
280
- def post_process(frame_idx, frame_img, split_preds, split_matrs, split_masks, enable_laplacian_blend, crop_mask, blur_amount, erode_amount):
281
- whole_img_path = frame_img
282
- whole_img = cv2.imread(whole_img_path)
283
- blend_method = 'laplacian' if enable_laplacian_blend else 'linear'
284
- for p, m, mask in zip(split_preds[frame_idx], split_matrs[frame_idx], split_masks[frame_idx]):
285
- p = cv2.resize(p, (512,512))
286
- mask = cv2.resize(mask, (512,512)) if mask is not None else None
287
- m /= 0.25
288
- whole_img = paste_to_whole(p, whole_img, m, mask=mask, crop_mask=crop_mask, blend_method=blend_method, blur_amount=blur_amount, erode_amount=erode_amount)
289
- cv2.imwrite(whole_img_path, whole_img)
290
-
291
- def concurrent_post_process(image_sequence, *args):
292
- with concurrent.futures.ThreadPoolExecutor() as executor:
293
- futures = []
294
- for idx, frame_img in enumerate(image_sequence):
295
- future = executor.submit(post_process, idx, frame_img, *args)
296
- futures.append(future)
297
-
298
- for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures), desc="Pasting back"):
299
- result = future.result()
300
-
301
- concurrent_post_process(
302
- image_sequence,
303
- split_preds,
304
- split_matrs,
305
- split_masks,
306
- enable_laplacian_blend,
307
- crop_mask,
308
- blur_amount,
309
- erode_amount
310
- )
311
- ## ------------------------------ Gardio API ------------------------------
312
- iface = gr.Interface(
313
- fn=process_api,
314
- inputs=[
315
- gr.Textbox(label="Source Image (base64)"),
316
- gr.Textbox(label="Target Image (base64)")
317
- ],
318
- outputs=gr.Textbox(label="Result Image (base64)"),
319
- title="Face Swap API",
320
- description="Submit two base64 encoded images to swap faces."
321
- )
322
- ## ------------------------------ IMAGE ------------------------------
323
-
324
- if input_type == "Image":
325
- target = cv2.imread(image_path)
326
- output_file = os.path.join(output_path, output_name + ".png")
327
- cv2.imwrite(output_file, target)
328
-
329
- for info_update in swap_process([output_file]):
330
- yield info_update
331
-
332
- OUTPUT_FILE = output_file
333
- WORKSPACE = output_path
334
- PREVIEW = cv2.imread(output_file)[:, :, ::-1]
335
-
336
- yield get_finsh_text(start_time), *ui_after()
337
-
338
- ## ------------------------------ VIDEO ------------------------------
339
-
340
- elif input_type == "Video":
341
- temp_path = os.path.join(output_path, output_name, "sequence")
342
- os.makedirs(temp_path, exist_ok=True)
343
-
344
- yield "### \n 💽 Extracting video frames...", *ui_before()
345
- image_sequence = []
346
- cap = cv2.VideoCapture(video_path)
347
- curr_idx = 0
348
- while True:
349
- ret, frame = cap.read()
350
- if not ret:break
351
- frame_path = os.path.join(temp_path, f"frame_{curr_idx}.jpg")
352
- cv2.imwrite(frame_path, frame)
353
- image_sequence.append(frame_path)
354
- curr_idx += 1
355
- cap.release()
356
- cv2.destroyAllWindows()
357
-
358
- for info_update in swap_process(image_sequence):
359
- yield info_update
360
-
361
- yield "### \n 🔗 Merging sequence...", *ui_before()
362
- output_video_path = os.path.join(output_path, output_name + ".mp4")
363
- merge_img_sequence_from_ref(video_path, image_sequence, output_video_path)
364
-
365
- if os.path.exists(temp_path) and not keep_output_sequence:
366
- yield "### \n 🚽 Removing temporary files...", *ui_before()
367
- shutil.rmtree(temp_path)
368
-
369
- WORKSPACE = output_path
370
- OUTPUT_FILE = output_video_path
371
-
372
- yield get_finsh_text(start_time), *ui_after_vid()
373
-
374
- ## ------------------------------ DIRECTORY ------------------------------
375
-
376
- elif input_type == "Directory":
377
- extensions = ["jpg", "jpeg", "png", "bmp", "tiff", "ico", "webp"]
378
- temp_path = os.path.join(output_path, output_name)
379
- if os.path.exists(temp_path):
380
- shutil.rmtree(temp_path)
381
- os.mkdir(temp_path)
382
-
383
- file_paths =[]
384
- for file_path in glob.glob(os.path.join(directory_path, "*")):
385
- if any(file_path.lower().endswith(ext) for ext in extensions):
386
- img = cv2.imread(file_path)
387
- new_file_path = os.path.join(temp_path, os.path.basename(file_path))
388
- cv2.imwrite(new_file_path, img)
389
- file_paths.append(new_file_path)
390
-
391
- for info_update in swap_process(file_paths):
392
- yield info_update
393
-
394
- WORKSPACE = temp_path
395
- OUTPUT_FILE = file_paths[-1]
396
-
397
- ## ------------------------------ STREAM ------------------------------
398
-
399
- elif input_type == "Stream":
400
- pass
401
 
 
 
 
 
 
 
402
 
403
- ## ------------------------------ GRADIO FUNC ------------------------------
 
404
 
405
- def analyse_settings_changed(detect_condition, detection_size, detection_threshold):
406
- global FACE_ANALYSER
407
- global DETECT_CONDITION
408
- DETECT_CONDITION = detect_condition
409
- FACE_ANALYSER = insightface.app.FaceAnalysis(name="buffalo_l", providers=PROVIDER)
410
- FACE_ANALYSER.prepare(
411
- ctx_id=0,
412
- det_size=(int(detection_size), int(detection_size)),
413
- det_thresh=float(detection_threshold),
414
- )
415
 
416
- def decode_base64_image(base64_string):
417
- img_data = base64.b64decode(base64_string)
418
- img = Image.open(io.BytesIO(img_data))
419
- return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
420
 
421
- def process_api(source_base64, target_base64):
422
- source_image = decode_base64_image(source_base64)
423
- target_image = decode_base64_image(target_base64)
424
-
425
- temp_source_path = "temp_source.jpg"
426
- temp_target_path = "temp_target.jpg"
427
- cv2.imwrite(temp_source_path, source_image)
428
- cv2.imwrite(temp_target_path, target_image)
429
-
430
- result = process(
431
- input_type="Image",
432
- image_path=temp_target_path,
433
- video_path=None,
434
- directory_path=None,
435
- source_path=temp_source_path,
436
- output_path="output",
437
- output_name="result",
438
- keep_output_sequence=False,
439
- condition="First found face",
440
- age=None,
441
- distance=None,
442
- face_enhancer_name="NONE",
443
- enable_face_parser=False,
444
- mask_includes=MASK_INCLUDE,
445
- mask_soft_kernel=MASK_SOFT_KERNEL,
446
- mask_soft_iterations=MASK_SOFT_ITERATIONS,
447
- blur_amount=MASK_BLUR_AMOUNT,
448
- erode_amount=MASK_ERODE_AMOUNT,
449
- face_scale=1.0,
450
- enable_laplacian_blend=True,
451
- crop_top,
452
- crop_bott,
453
- crop_left,
454
- crop_right,
455
- )
456
-
457
- os.remove(temp_source_path)
458
- os.remove(temp_target_path)
459
-
460
- result_image = cv2.imread("output/result.png")
461
- _, buffer = cv2.imencode('.jpg', result_image)
462
- result_base64 = base64.b64encode(buffer).decode('utf-8')
463
-
464
  return result_base64
465
 
466
- def stop_running():
467
- global STREAMER
468
- if hasattr(STREAMER, "stop"):
469
- STREAMER.stop()
470
- STREAMER = None
471
- return "Cancelled"
472
-
473
-
474
- def slider_changed(show_frame, video_path, frame_index):
475
- if not show_frame:
476
- return None, None
477
- if video_path is None:
478
- return None, None
479
- clip = VideoFileClip(video_path)
480
- frame = clip.get_frame(frame_index / clip.fps)
481
- frame_array = np.array(frame)
482
- clip.close()
483
- return gr.Image.update(value=frame_array, visible=True), gr.Video.update(
484
- visible=False
485
- )
486
 
 
 
 
 
487
 
488
- def trim_and_reload(video_path, output_path, output_name, start_frame, stop_frame):
 
489
  try:
490
- output_path = os.path.join(output_path, output_name)
491
- trimmed_video = trim_video(video_path, output_path, start_frame, stop_frame)
492
  except Exception as e:
493
- print(e)
494
-
495
 
 
496
  if __name__ == "__main__":
497
- if USE_COLAB:
498
- print("Running in colab mode")
499
-
500
- iface.queue(concurrency_count=2, max_size=20).launch(share=USE_COLAB)
 
1
  import os
2
  import cv2
3
+ import base64
4
+ import numpy as np
 
 
 
 
 
 
5
  import insightface
6
  import onnxruntime
7
+ from fastapi import FastAPI, HTTPException
8
+ from pydantic import BaseModel
 
 
 
 
 
 
 
9
 
10
  from face_swapper import Inswapper, paste_to_whole
11
+ from face_analyser import get_analysed_data
12
+ from face_parsing import init_parsing_model, get_parsed_mask
13
+
14
+
15
+ # Глобальные константы и переменные
16
+ USE_COLAB = user_args.colab
17
+ USE_CUDA = user_args.cuda
18
+ PROVIDER = ["CPUExecutionProvider"]
19
+ DETECT_SIZE = 640
20
+ DETECT_THRESH = 0.6
21
+ FACE_ANALYSER = None
22
+ FACE_SWAPPER = None
23
+ FACE_PARSER = None
24
 
25
  ## ------------------------------ USER ARGS ------------------------------
26
 
 
33
  )
34
  user_args = parser.parse_args()
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  ## ------------------------------ SET EXECUTION PROVIDER ------------------------------
37
  # Note: Non CUDA users may change settings here
38
 
 
53
  device = "cuda" if USE_CUDA else "cpu"
54
  EMPTY_CACHE = lambda: torch.cuda.empty_cache() if device == "cuda" else None
55
 
 
56
 
57
+ # Функции загрузки моделей
58
  def load_face_analyser_model(name="buffalo_l"):
59
  global FACE_ANALYSER
60
  if FACE_ANALYSER is None:
 
63
  ctx_id=0, det_size=(DETECT_SIZE, DETECT_SIZE), det_thresh=DETECT_THRESH
64
  )
65
 
 
66
  def load_face_swapper_model(path="./assets/pretrained_models/inswapper_128.onnx"):
67
  global FACE_SWAPPER
68
  if FACE_SWAPPER is None:
69
+ FACE_SWAPPER = Inswapper(model_file=path, batch_size=1, providers=PROVIDER)
 
 
70
 
71
  def load_face_parser_model(path="./assets/pretrained_models/79999_iter.pth"):
72
  global FACE_PARSER
73
  if FACE_PARSER is None:
74
+ FACE_PARSER = init_parsing_model(path, device='cpu')
 
75
 
76
+ # Загрузка всех моделей
77
  load_face_analyser_model()
78
  load_face_swapper_model()
79
+ load_face_parser_model()
80
 
81
+ def base64_to_image(base64_string):
82
+ img_data = base64.b64decode(base64_string)
83
+ nparr = np.frombuffer(img_data, np.uint8)
84
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
85
+ return img
86
+
87
+ def image_to_base64(image):
88
+ _, buffer = cv2.imencode('.png', image)
89
+ return base64.b64encode(buffer).decode('utf-8')
90
+
91
+ def process_images(source_img_base64, target_img_base64):
92
+ # Декодирование base64 в изображения
93
+ source_img = base64_to_image(source_img_base64)
94
+ target_img = base64_to_image(target_img_base64)
95
+
96
+ # Анализ лиц
97
+ analysed_targets, analysed_sources, _, _ = get_analysed_data(
98
+ FACE_ANALYSER,
99
+ [target_img],
100
+ source_img,
101
+ swap_condition="First",
102
+ detect_condition="best detection"
103
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ # Замена лица
106
+ preds = []
107
+ matrs = []
108
+ for batch_pred, batch_matr in FACE_SWAPPER.batch_forward([target_img], analysed_targets, analysed_sources):
109
+ preds.extend(batch_pred)
110
+ matrs.extend(batch_matr)
111
 
112
+ # Парсинг лица и создание маски
113
+ masks = get_parsed_mask(FACE_PARSER, preds, classes=["skin", "l_brow", "r_brow", "l_eye", "r_eye", "nose", "u_lip", "l_lip", "mouth"], device='cpu')
114
 
115
+ # Наложение результата обратно на изображение
116
+ result_img = paste_to_whole(preds[0], target_img, matrs[0], mask=masks[0])
 
 
 
 
 
 
 
 
117
 
118
+ # Кодирование результата в base64
119
+ result_base64 = image_to_base64(result_img)
 
 
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  return result_base64
122
 
123
+ # Создание FastAPI приложения
124
+ app = FastAPI(title="Faceswap API", description="API для замены лица. Отправьте два изображения в формате base64.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
+ # Определение модели запроса
127
+ class SwapRequest(BaseModel):
128
+ source_img: str
129
+ target_img: str
130
 
131
+ @app.post("/swap_face")
132
+ async def swap_face(request: SwapRequest):
133
  try:
134
+ result = process_images(request.source_img, request.target_img)
135
+ return {"result": result}
136
  except Exception as e:
137
+ raise HTTPException(status_code=400, detail=str(e))
 
138
 
139
+ # Запуск сервера
140
  if __name__ == "__main__":
141
+ import uvicorn
142
+ uvicorn.run(app, host="0.0.0.0", port=8000)