DegMaTsu commited on
Commit
b96ff87
·
verified ·
1 Parent(s): 19b59ba

Upload reactor_swapper.py

Browse files
custom_nodes/comfyui-reactor-node/scripts/reactor_swapper.py CHANGED
@@ -14,14 +14,13 @@ from insightface.app.common import Face
14
  # except:
15
  # cuda = None
16
  import torch
17
- import gc # для очистки кэша
18
 
19
  import folder_paths
20
  import comfy.model_management as model_management
21
  from modules.shared import state
22
 
23
  # 1. Добавьте импорт logging наверху файла, если его там нет:
24
- import logging # закомментирован, вызывал ошибку
25
  from scripts.reactor_logger import logger
26
  from reactor_utils import (
27
  move_path,
@@ -127,21 +126,9 @@ def getFaceSwapModel(model_path: str):
127
  if FS_MODEL is None or CURRENT_FS_MODEL_PATH is None or CURRENT_FS_MODEL_PATH != model_path:
128
  CURRENT_FS_MODEL_PATH = model_path
129
  FS_MODEL = unload_model(FS_MODEL)
130
-
131
- # Извлекаем имя файла модели из пути
132
- model_filename = os.path.basename(model_path)
133
-
134
- # Определяем правильный путь в зависимости от типа модели
135
- if "hyperswap" in model_filename.lower():
136
- # Ищем в директории hyperswap
137
- correct_path = os.path.join(folder_paths.models_dir, "hyperswap", model_filename)
138
- FS_MODEL = ort.InferenceSession(correct_path, providers=providers)
139
- elif "reswapper" in model_filename.lower():
140
- # Ищем в директории reswapper
141
- correct_path = os.path.join(folder_paths.models_dir, "reswapper", model_filename)
142
- FS_MODEL = insightface.model_zoo.get_model(correct_path, providers=providers)
143
  else:
144
- # Для моделей insightface используем оригинальный путь
145
  FS_MODEL = insightface.model_zoo.get_model(model_path, providers=providers)
146
  return FS_MODEL
147
 
@@ -150,10 +137,10 @@ def get_landmarks_5(face):
150
  # face.landmark_5: np.ndarray shape (5,2)
151
  # Если нет, попробуй face.kps или face.landmark
152
  if hasattr(face, 'landmark_5') and face.landmark_5 is not None:
153
- # logger.debug("landmark_5: %s", face.landmark_5)
154
  return face.landmark_5
155
  elif hasattr(face, 'kps') and face.kps is not None:
156
- # logger.debug("kps: %s", face.kps)
157
  return face.kps
158
  elif hasattr(face, 'landmark') and face.landmark is not None:
159
  # 68-точечная разметка, берём нужные индексы
@@ -161,9 +148,9 @@ def get_landmarks_5(face):
161
  # Пример: [36, 45, 30, 48, 54] — левый/правый глаз, нос, левый/правый рот
162
  if face.landmark.shape[0] >= 68:
163
  idxs = [36, 45, 30, 48, 54]
164
- # logger.debug("landmark (68 точек): %s", face.landmark[idxs])
165
  return face.landmark[idxs]
166
- # logger.warning("Нет подходящих точек в объекте Face. Доступные атрибуты: %s", dir(face))
167
  return None
168
 
169
  #### Что проверить:
@@ -191,14 +178,14 @@ def create_gradient_mask(crop_size=256):
191
 
192
  # 3. Рисуем эллипс (заполняем белым цветом, значение=1.0)
193
  cv2.ellipse(
194
- mask, # Массив для рисования
195
- center, # Центр эллипса
196
- axes, # Полуоси (ширина, высота)
197
- angle=0, # Угол поворота
198
- startAngle=0, # Начальный угол дуги
199
- endAngle=360, # Конечный угол дуги (360 = полный эллипс)
200
- color=1.0, # Значение для заполнения (белый = 1.0)
201
- thickness=-1 # -1 = заполнить всю область эллипса
202
  )
203
 
204
  # 4. Применяем размытие для плавных краёв
@@ -225,7 +212,7 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
225
  mask = create_gradient_mask(crop_size)
226
 
227
  # Сохраняем для отладки
228
- # cv2.imwrite("debug_original_mask.png", (mask * 255).astype(np.uint8))
229
 
230
  # Преобразуем в трехканальную маску
231
  mask_3c = np.stack([mask] * 3, axis=2)
@@ -236,7 +223,7 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
236
  # 3. Обратное преобразование (WARP_INVERSE_MAP) для лица И маски
237
  # Для лица (INTER_LANCZOS4 — высококачественная интерполяция)
238
  inv_face = cv2.warpAffine(
239
- swapped_face.astype(np.float32).copy(),
240
  M,
241
  (w, h),
242
  flags=cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP,
@@ -259,8 +246,8 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
259
  inv_mask = cv2.GaussianBlur(inv_mask, (3, 3), 0)
260
 
261
  # Отладка
262
- # logger.debug("Warped face shape: %s | Min: %s | Max: %s", inv_face.shape, inv_face.min(), inv_face.max())
263
- # logger.debug("Warped mask shape: %s | Min: %s | Max: %s", inv_mask.shape, inv_mask.min(), inv_mask.max())
264
 
265
  # Визуализация
266
  # cv2.imshow("Warped Face", inv_face.astype(np.uint8))
@@ -276,7 +263,7 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
276
  result = np.clip(result, 0, 255).astype(np.uint8)
277
 
278
  # Сохраняем результат
279
- # cv2.imwrite("debug_result.png", result)
280
 
281
  return result
282
 
@@ -302,7 +289,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
302
  visualize_points(target_img, target_landmarks_5, (0, 255, 0)) # Зеленые точки
303
 
304
  if target_landmarks_5 is None:
305
- # logger.error("Не удалось получить 5 точек для целевого лица")
306
  # Важно: Если ошибка, возвращаем None и исходную матрицу (или обрабатываем ошибку иначе)
307
  return None, None
308
 
@@ -317,7 +304,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
317
 
318
  # Вычисляем аффинную матрицу
319
  M = get_affine_transform(target_landmarks_5.astype(np.float32), std_landmarks_256)
320
- # logger.debug("Affine Matrix M (used for cropping):\n%s", M)
321
 
322
  #### Что проверить:
323
  # Матрица `M` не должна содержать `NaN` или бесконечности.
@@ -330,7 +317,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
330
  #### Что проверить:
331
  # Окно `"Crop Before Inference"` должно показывать лицо, вырезанное по аффинному преобразованию.
332
  # Если изображение черное — проблема в `M` или `target_landmarks_5`.
333
- # logger.debug("Crop shape: %s | Min: %s | Max: %s", crop.shape, crop.min(), crop.max())
334
  # cv2.imshow("Crop Before Inference", crop)
335
  # cv2.waitKey(1) # Отображает изображение
336
 
@@ -346,9 +333,9 @@ def run_hyperswap(session, source_face, target_face, target_img):
346
  # 5. Инференс
347
  try:
348
  output = session.run(None, {'source': source_embedding, 'target': crop_input})[0][0]
349
- # logger.debug("Model output shape: %s | Min: %s | Max: %s", output.shape, output.min(), output.max())
350
  except Exception as e:
351
- # logger.error("Ошибка выполнения модели: %s", e)
352
  return target_img
353
 
354
  # 6. Обратная нормализация
@@ -366,7 +353,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
366
  #### Что проверить:
367
  # `output` должен быть в диапазоне `[0..255]` и содержать лицо.
368
  # Если `output` черный — проблема в нормализации/денормализации или в самой модели.
369
- # logger.debug("Output after denormalization: Min: %s | Max: %s", output.min(), output.max())
370
  # cv2.imshow("Output After Denormalization", output)
371
  # cv2.waitKey(1)
372
 
@@ -374,13 +361,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
374
  # Мы больше не вызываем warp_and_paste_face отсюда.
375
  # return result
376
 
377
- # --- Безопасное преобразование перед возвратом ---
378
- # Убедимся, что результат всегда float32 в диапазоне [0..255]
379
- output = output.astype(np.float32)
380
- output = np.clip(output, 0, 255)
381
- torch.cuda.empty_cache()
382
- gc.collect()
383
- return output, M # Возвращаем лицо (256x256) и матрицу M
384
 
385
  def sort_by_order(face, order: str):
386
  if order == "left-right":
@@ -475,13 +456,13 @@ def get_face_single(img_data: np.ndarray, face, face_index=0, det_size=(640, 640
475
  try:
476
  faces_sorted = sort_by_order(face, order)
477
  selected_face = faces_sorted[face_index]
478
- # logger.debug("Выбрано лицо: bbox=%s, landmark_5=%s, kps=%s, landmark=%s",
479
- # selected_face.bbox,
480
- # hasattr(selected_face, "landmark_5"),
481
- # hasattr(selected_face, "kps"),
482
- # hasattr(selected_face, "landmark"))
483
  return selected_face, 0
484
- # return faces_sorted[face_index], 0
485
  # return sorted(face, key=lambda x: x.bbox[0])[face_index], 0
486
  except IndexError:
487
  return None, 0
@@ -503,14 +484,14 @@ def swap_face(
503
  codeformer_weight: float = 0.5,
504
  interpolation: str = "Bicubic",
505
  ):
506
- # >>>>> РЕШЕНИЕ: Принудительная установка уровня DEBUG <<<<<
507
- # if logger.getEffectiveLevel() != logging.DEBUG:
508
- # print("\n--- [ReActor Debug] Принудительная установка уровня логирования на DEBUG (10) в swap_face ---")
509
- # logger.setLevel(logging.DEBUG)
510
 
511
- # Проверочное сообщение (теперь оно должно появиться)
512
- # logger.debug("--- ТЕСТ: swap_face запущена, уровень логирования DEBUG активен. ---")
513
- # >>>>> КОНЕЦ РЕШЕНИЯ <<<<<
514
 
515
  global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH
516
  result_image = target_img
@@ -626,19 +607,13 @@ def swap_face(
626
  if len(source_faces_index) > 1 and source_face_idx > 0:
627
  source_face, src_wrong_gender = get_face_single(source_img, source_faces, face_index=source_faces_index[source_face_idx], gender_source=gender_source, order=faces_order[1])
628
  source_face_idx += 1
629
-
630
- # очистка кэша и памяти перед каждым Hyperswap вызовом
631
- torch.cuda.empty_cache()
632
- gc.collect()
633
- SOURCE_FACES = None
634
- TARGET_FACES = None
635
 
636
  if source_face is not None and src_wrong_gender == 0:
637
  target_face, wrong_gender = get_face_single(target_img, target_faces, face_index=face_num, gender_target=gender_target, order=faces_order[0])
638
  if target_face is not None and wrong_gender == 0:
639
  logger.status(f"Swapping...")
640
  if "hyperswap" in model:
641
- # logger.status(f"Swapping with Hyperswap...")
642
  swapped_face_256, M = run_hyperswap(face_swapper, source_face, target_face, result)
643
  if swapped_face_256 is not None:
644
  result = paste_back(result, swapped_face_256, M, crop_size=256)
@@ -824,17 +799,7 @@ def swap_face_many(
824
  logger.status(f'Source Faces must have no entries (default=0), one entry, or same number of entries as target faces.')
825
  elif source_face is not None:
826
  results = target_imgs
827
-
828
- # Определяем путь к модели в зависимости от типа
829
- if "inswapper" in model:
830
- model_path = os.path.join(insightface_path, model)
831
- elif "reswapper" in model:
832
- model_path = os.path.join(reswapper_path, model)
833
- elif "hyperswap" in model:
834
- model_path = os.path.join(hyperswap_path, model)
835
- else:
836
- model_path = os.path.join(insightface_path, model)
837
-
838
  face_swapper = getFaceSwapModel(model_path)
839
 
840
  source_face_idx = 0
@@ -858,23 +823,16 @@ def swap_face_many(
858
  target_face_single, wrong_gender = get_face_single(target_img, target_face, face_index=face_num, gender_target=gender_target, order=faces_order[0])
859
  if target_face_single is not None and wrong_gender == 0:
860
  result = target_img
861
-
862
- # Обработка в зависимости от типа модели
863
- if "hyperswap" in model:
864
- # Для Hyperswap используем специальную функцию
865
- swapped_face_256, M = run_hyperswap(face_swapper, source_face, target_face_single, result)
866
- if swapped_face_256 is not None:
867
- result = paste_back(result, swapped_face_256, M, crop_size=256)
868
- elif face_boost_enabled:
869
  logger.status(f"Face Boost is enabled")
870
  bgr_fake, M = face_swapper.get(target_img, target_face_single, source_face, paste_back=False)
871
  bgr_fake, scale = restorer.get_restored_face(bgr_fake, face_restore_model, face_restore_visibility, codeformer_weight, interpolation)
872
  M *= scale
873
  result = swapper.in_swap(target_img, bgr_fake, M)
874
  else:
875
- # Для остальных моделей используем метод get()
876
  result = face_swapper.get(target_img, target_face_single, source_face)
877
-
878
  results[i] = result
879
  pbar.update(1)
880
  elif wrong_gender == 1:
 
14
  # except:
15
  # cuda = None
16
  import torch
 
17
 
18
  import folder_paths
19
  import comfy.model_management as model_management
20
  from modules.shared import state
21
 
22
  # 1. Добавьте импорт logging наверху файла, если его там нет:
23
+ import logging
24
  from scripts.reactor_logger import logger
25
  from reactor_utils import (
26
  move_path,
 
126
  if FS_MODEL is None or CURRENT_FS_MODEL_PATH is None or CURRENT_FS_MODEL_PATH != model_path:
127
  CURRENT_FS_MODEL_PATH = model_path
128
  FS_MODEL = unload_model(FS_MODEL)
129
+ if "hyperswap" in model_path.lower():
130
+ FS_MODEL = ort.InferenceSession(model_path, providers=providers)
 
 
 
 
 
 
 
 
 
 
 
131
  else:
 
132
  FS_MODEL = insightface.model_zoo.get_model(model_path, providers=providers)
133
  return FS_MODEL
134
 
 
137
  # face.landmark_5: np.ndarray shape (5,2)
138
  # Если нет, попробуй face.kps или face.landmark
139
  if hasattr(face, 'landmark_5') and face.landmark_5 is not None:
140
+ logger.debug("landmark_5: %s", face.landmark_5)
141
  return face.landmark_5
142
  elif hasattr(face, 'kps') and face.kps is not None:
143
+ logger.debug("kps: %s", face.kps)
144
  return face.kps
145
  elif hasattr(face, 'landmark') and face.landmark is not None:
146
  # 68-точечная разметка, берём нужные индексы
 
148
  # Пример: [36, 45, 30, 48, 54] — левый/правый глаз, нос, левый/правый рот
149
  if face.landmark.shape[0] >= 68:
150
  idxs = [36, 45, 30, 48, 54]
151
+ logger.debug("landmark (68 точек): %s", face.landmark[idxs])
152
  return face.landmark[idxs]
153
+ logger.warning("Нет подходящих точек в объекте Face. Доступные атрибуты: %s", dir(face))
154
  return None
155
 
156
  #### Что проверить:
 
178
 
179
  # 3. Рисуем эллипс (заполняем белым цветом, значение=1.0)
180
  cv2.ellipse(
181
+ mask,
182
+ center,
183
+ axes,
184
+ angle=0,
185
+ startAngle=0,
186
+ endAngle=360,
187
+ color=1.0,
188
+ thickness=-1
189
  )
190
 
191
  # 4. Применяем размытие для плавных краёв
 
212
  mask = create_gradient_mask(crop_size)
213
 
214
  # Сохраняем для отладки
215
+ cv2.imwrite("debug_original_mask.png", (mask * 255).astype(np.uint8))
216
 
217
  # Преобразуем в трехканальную маску
218
  mask_3c = np.stack([mask] * 3, axis=2)
 
223
  # 3. Обратное преобразование (WARP_INVERSE_MAP) для лица И маски
224
  # Для лица (INTER_LANCZOS4 — высококачественная интерполяция)
225
  inv_face = cv2.warpAffine(
226
+ swapped_face.astype(np.float32),
227
  M,
228
  (w, h),
229
  flags=cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP,
 
246
  inv_mask = cv2.GaussianBlur(inv_mask, (3, 3), 0)
247
 
248
  # Отладка
249
+ logger.debug("Warped face shape: %s | Min: %s | Max: %s", inv_face.shape, inv_face.min(), inv_face.max())
250
+ logger.debug("Warped mask shape: %s | Min: %s | Max: %s", inv_mask.shape, inv_mask.min(), inv_mask.max())
251
 
252
  # Визуализация
253
  # cv2.imshow("Warped Face", inv_face.astype(np.uint8))
 
263
  result = np.clip(result, 0, 255).astype(np.uint8)
264
 
265
  # Сохраняем результат
266
+ cv2.imwrite("debug_result.png", result)
267
 
268
  return result
269
 
 
289
  visualize_points(target_img, target_landmarks_5, (0, 255, 0)) # Зеленые точки
290
 
291
  if target_landmarks_5 is None:
292
+ logger.error("Не удалось получить 5 точек для целевого лица")
293
  # Важно: Если ошибка, возвращаем None и исходную матрицу (или обрабатываем ошибку иначе)
294
  return None, None
295
 
 
304
 
305
  # Вычисляем аффинную матрицу
306
  M = get_affine_transform(target_landmarks_5.astype(np.float32), std_landmarks_256)
307
+ logger.debug("Affine Matrix M (used for cropping):\n%s", M)
308
 
309
  #### Что проверить:
310
  # Матрица `M` не должна содержать `NaN` или бесконечности.
 
317
  #### Что проверить:
318
  # Окно `"Crop Before Inference"` должно показывать лицо, вырезанное по аффинному преобразованию.
319
  # Если изображение черное — проблема в `M` или `target_landmarks_5`.
320
+ logger.debug("Crop shape: %s | Min: %s | Max: %s", crop.shape, crop.min(), crop.max())
321
  # cv2.imshow("Crop Before Inference", crop)
322
  # cv2.waitKey(1) # Отображает изображение
323
 
 
333
  # 5. Инференс
334
  try:
335
  output = session.run(None, {'source': source_embedding, 'target': crop_input})[0][0]
336
+ logger.debug("Model output shape: %s | Min: %s | Max: %s", output.shape, output.min(), output.max())
337
  except Exception as e:
338
+ logger.error("Ошибка выполнения модели: %s", e)
339
  return target_img
340
 
341
  # 6. Обратная нормализация
 
353
  #### Что проверить:
354
  # `output` должен быть в диапазоне `[0..255]` и содержать лицо.
355
  # Если `output` черный — проблема в нормализации/денормализации или в самой модели.
356
+ logger.debug("Output after denormalization: Min: %s | Max: %s", output.min(), output.max())
357
  # cv2.imshow("Output After Denormalization", output)
358
  # cv2.waitKey(1)
359
 
 
361
  # Мы больше не вызываем warp_and_paste_face отсюда.
362
  # return result
363
 
364
+ return output, M # Возвращаем лицо (256x256) и матрицу M
 
 
 
 
 
 
365
 
366
  def sort_by_order(face, order: str):
367
  if order == "left-right":
 
456
  try:
457
  faces_sorted = sort_by_order(face, order)
458
  selected_face = faces_sorted[face_index]
459
+ logger.debug("Выбрано лицо: bbox=%s, landmark_5=%s, kps=%s, landmark=%s",
460
+ selected_face.bbox,
461
+ hasattr(selected_face, "landmark_5"),
462
+ hasattr(selected_face, "kps"),
463
+ hasattr(selected_face, "landmark"))
464
  return selected_face, 0
465
+ return faces_sorted[face_index], 0
466
  # return sorted(face, key=lambda x: x.bbox[0])[face_index], 0
467
  except IndexError:
468
  return None, 0
 
484
  codeformer_weight: float = 0.5,
485
  interpolation: str = "Bicubic",
486
  ):
487
+ # >>>>> РЕШЕНИЕ: Принудительная установка уровня DEBUG <<<<<
488
+ if logger.getEffectiveLevel() != logging.DEBUG:
489
+ print("\n--- [ReActor Debug] Принудительная установка уровня логирования на DEBUG (10) в swap_face ---")
490
+ logger.setLevel(logging.DEBUG)
491
 
492
+ # Проверочное сообщение (теперь оно должно появиться)
493
+ logger.debug("--- ТЕСТ: swap_face запущена, уровень логирования DEBUG активен. ---")
494
+ # >>>>> КОНЕЦ РЕШЕНИЯ <<<<<
495
 
496
  global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH
497
  result_image = target_img
 
607
  if len(source_faces_index) > 1 and source_face_idx > 0:
608
  source_face, src_wrong_gender = get_face_single(source_img, source_faces, face_index=source_faces_index[source_face_idx], gender_source=gender_source, order=faces_order[1])
609
  source_face_idx += 1
 
 
 
 
 
 
610
 
611
  if source_face is not None and src_wrong_gender == 0:
612
  target_face, wrong_gender = get_face_single(target_img, target_faces, face_index=face_num, gender_target=gender_target, order=faces_order[0])
613
  if target_face is not None and wrong_gender == 0:
614
  logger.status(f"Swapping...")
615
  if "hyperswap" in model:
616
+ logger.status(f"Swapping with Hyperswap...")
617
  swapped_face_256, M = run_hyperswap(face_swapper, source_face, target_face, result)
618
  if swapped_face_256 is not None:
619
  result = paste_back(result, swapped_face_256, M, crop_size=256)
 
799
  logger.status(f'Source Faces must have no entries (default=0), one entry, or same number of entries as target faces.')
800
  elif source_face is not None:
801
  results = target_imgs
802
+ model_path = model_path = os.path.join(insightface_path, model)
 
 
 
 
 
 
 
 
 
 
803
  face_swapper = getFaceSwapModel(model_path)
804
 
805
  source_face_idx = 0
 
823
  target_face_single, wrong_gender = get_face_single(target_img, target_face, face_index=face_num, gender_target=gender_target, order=faces_order[0])
824
  if target_face_single is not None and wrong_gender == 0:
825
  result = target_img
826
+ # logger.status(f"Swapping {i}...")
827
+ if face_boost_enabled:
 
 
 
 
 
 
828
  logger.status(f"Face Boost is enabled")
829
  bgr_fake, M = face_swapper.get(target_img, target_face_single, source_face, paste_back=False)
830
  bgr_fake, scale = restorer.get_restored_face(bgr_fake, face_restore_model, face_restore_visibility, codeformer_weight, interpolation)
831
  M *= scale
832
  result = swapper.in_swap(target_img, bgr_fake, M)
833
  else:
834
+ # logger.status(f"Swapping as-is")
835
  result = face_swapper.get(target_img, target_face_single, source_face)
 
836
  results[i] = result
837
  pbar.update(1)
838
  elif wrong_gender == 1: