DegMaTsu commited on
Commit
cfe7f41
·
verified ·
1 Parent(s): aaa7bc5

Upload reactor_swapper.py

Browse files
custom_nodes/comfyui-reactor-node/scripts/reactor_swapper.py CHANGED
@@ -199,70 +199,60 @@ def create_gradient_mask(crop_size=256):
199
 
200
  #### 1. Используйте `cv2.BORDER_TRANSPARENT` (OpenCV ≥ 4.5)
201
  # Этот флаг позволяет **не заполнять** области за пределами маски никаким цветом (пиксели остаются `0` или "прозрачные").
202
- def paste_back(target_img, swapped_face, M, crop_size=256):
203
- # Улучшенная функция paste_back с идеальной овальной маской и исправлениями артефактов
204
 
205
  # target_img: Исходное изображение (BGR, numpy, uint8)
206
  # swapped_face: Результат работы модели (256x256, BGR, uint8)
207
- # M: Матрица аффинного преобразования (Target -> Crop), но здесь используется M_inv из run_hyperswap
 
208
  # crop_size: Размер кропа (для HyperSwap это 256)
209
 
210
- # 1. Создание мягкой маски (Эрозия + Размытие)
211
- mask = create_gradient_mask(crop_size)
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  # Преобразуем в трехканальную маску
214
  mask_3c = np.stack([mask] * 3, axis=2)
215
 
216
- # 2. Получаем размеры целевого изображения
217
- h, w = target_img.shape[:2]
218
-
219
  # 3. Нормализация swapped_face к float32 [0,1] для warp
220
  swapped_face_norm = swapped_face.astype(np.float32) / 255.0
221
- mask_norm = mask_3c.astype(np.float32) # Маска уже [0,1]
222
 
223
- # 4. Обратное преобразование (WARP_INVERSE_MAP) для лица И маски
224
- # Используем BORDER_CONSTANT с borderValue=0.5 (серый, чтобы избежать синих/зеленых артефактов)
225
  warped_face = cv2.warpAffine(
226
  swapped_face_norm,
227
- M, # Это M_inv из run_hyperswap
228
  (w, h),
229
  flags=cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP,
230
  borderMode=cv2.BORDER_CONSTANT,
231
- borderValue=0.5 # Серый фон вместо черного/белого
232
- )
233
-
234
- warped_mask = cv2.warpAffine(
235
- mask_norm,
236
- M, # Это M_inv из run_hyperswap
237
- (w, h),
238
- flags=cv2.INTER_CUBIC | cv2.WARP_INVERSE_MAP,
239
- borderMode=cv2.BORDER_CONSTANT,
240
- borderValue=0.0 # Маска: 0 за пределами
241
  )
242
 
243
  # 5. Обработка после warp: Clip, NaN fix
244
- warped_face = np.clip(warped_face, 0, 1) # Убираем отрицательные
245
- warped_face = np.nan_to_num(warped_face, nan=0.5) # NaN -> серый
246
-
247
- warped_mask = np.clip(warped_mask, 0, 1)
248
- warped_mask = np.nan_to_num(warped_mask, nan=0.0)
249
 
250
- # 6. Дополнительное размытие для устранения артефактов (опционально, но помогает)
251
- warped_mask = cv2.GaussianBlur(warped_mask, (3, 3), 0)
252
-
253
- # Отладочные логи (добавьте после warp)
254
- logger.debug("Warped face shape: %s | Min: %s | Max: %s | NaN count: %s",
255
- warped_face.shape, warped_face.min(), warped_face.max(), np.isnan(warped_face).sum())
256
- logger.debug("Warped mask shape: %s | Min: %s | Max: %s | NaN count: %s",
257
- warped_mask.shape, warped_mask.min(), warped_mask.max(), np.isnan(warped_mask).sum())
258
-
259
- # 7. Плавное наложение в float32
260
  target_float = target_img.astype(np.float32) / 255.0
261
- result_float = target_float * (1.0 - warped_mask) + warped_face * warped_mask
262
 
263
- # 8. Обратная нормализация к uint8
264
  result = (result_float * 255).clip(0, 255).astype(np.uint8)
265
 
 
 
 
 
266
  logger.debug("Final result: shape %s | Min: %s | Max: %s", result.shape, result.min(), result.max())
267
 
268
  return result
@@ -304,12 +294,10 @@ def run_hyperswap(session, source_face, target_face, target_img):
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` или бесконечности.
311
- # Если матрица нулевая или искаженная — проблема в точках `target_landmarks_5`.
312
-
313
  # Применяем аффинное преобразование с новой матрицей M
314
  crop = cv2.warpAffine(target_img, M, (256, 256), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
315
 
@@ -340,7 +328,6 @@ def run_hyperswap(session, source_face, target_face, target_img):
340
 
341
  # --- CPU FLOAT NORMALIZATION FIX ---
342
  # предотвращает появление "синей кожи" и "шума" при работе на CPU
343
- # (адаптировано из патча patch_cpu_fix.diff)
344
  if isinstance(output, np.ndarray):
345
  # устранение NaN и бесконечностей
346
  output = np.nan_to_num(output, nan=0.0, posinf=255.0, neginf=0.0)
@@ -362,7 +349,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
362
  # (ваш код без изменений, но без старой денормализации)
363
  output = output.transpose(1, 2, 0) # CHW -> HWC
364
  output = output[:, :, ::-1] # RGB -> BGR (Убедитесь, что это BGR, если вход был BGR)
365
- logger.debug("Output after denormalization: Min: %s | Max: %s", output.min(), output.max())
366
 
367
  # Визуализация после денормализации
368
  #### Что проверить:
@@ -371,8 +358,9 @@ def run_hyperswap(session, source_face, target_face, target_img):
371
  logger.debug("Output after denormalization: Min: %s | Max: %s", output.min(), output.max())
372
  # cv2.imshow("Output After Denormalization", output)
373
  # cv2.waitKey(1)
374
-
375
- return output, M # Возвращаем лицо (256x256) и матрицу M
 
376
 
377
  def sort_by_order(face, order: str):
378
  if order == "left-right":
@@ -496,9 +484,9 @@ def swap_face(
496
  interpolation: str = "Bicubic",
497
  ):
498
  # >>>>> РЕШЕНИЕ: Принудительная установка уровня DEBUG <<<<<
499
- if logger.getEffectiveLevel() != logging.DEBUG:
500
- print("\n--- [ReActor Debug] Принудительная установка уровня логирования на DEBUG (10) в swap_face ---")
501
- logger.setLevel(logging.DEBUG)
502
 
503
  # Проверочное сообщение (теперь оно должно появиться)
504
  logger.debug("--- ТЕСТ: swap_face запущена, уровень логирования DEBUG активен. ---")
 
199
 
200
  #### 1. Используйте `cv2.BORDER_TRANSPARENT` (OpenCV ≥ 4.5)
201
  # Этот флаг позволяет **не заполнять** области за пределами маски никаким цветом (пиксели остаются `0` или "прозрачные").
202
+ def paste_back(target_img, swapped_face, M, bbox, crop_size=256):
203
+ # Улучшенная функция paste_back с овальной маской на основе bbox (без warp маски)
204
 
205
  # target_img: Исходное изображение (BGR, numpy, uint8)
206
  # swapped_face: Результат работы модели (256x256, BGR, uint8)
207
+ # M: Матрица аффинного преобразования (M_inv из run_hyperswap)
208
+ # bbox: Bounding box целевого лица [x1, y1, x2, y2]
209
  # crop_size: Размер кропа (для HyperSwap это 256)
210
 
211
+ # 1. Получаем размеры целевого изображения
212
+ h, w = target_img.shape[:2]
213
+
214
+ # 2. Создаем овальную маску в пространстве оригинального изображения на основе bbox
215
+ mask = np.zeros((h, w), dtype=np.float32)
216
+ center = ((bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2) # Центр bbox
217
+ axes = ((bbox[2] - bbox[0]) / 2 * 0.8, (bbox[3] - bbox[1]) / 2 * 0.8) # Оси эллипса (с запасом 0.8)
218
+ cv2.ellipse(mask, (int(center[0]), int(center[1])), (int(axes[0]), int(axes[1])),
219
+ angle=0, startAngle=0, endAngle=360, color=1.0, thickness=-1)
220
+
221
+ # Применяем размытие для плавных краев
222
+ mask = cv2.GaussianBlur(mask, (15, 15), 0)
223
+ mask = np.clip(mask, 0, 1)
224
 
225
  # Преобразуем в трехканальную маску
226
  mask_3c = np.stack([mask] * 3, axis=2)
227
 
 
 
 
228
  # 3. Нормализация swapped_face к float32 [0,1] для warp
229
  swapped_face_norm = swapped_face.astype(np.float32) / 255.0
 
230
 
231
+ # 4. Обратное преобразование для лица
 
232
  warped_face = cv2.warpAffine(
233
  swapped_face_norm,
234
+ M,
235
  (w, h),
236
  flags=cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP,
237
  borderMode=cv2.BORDER_CONSTANT,
238
+ borderValue=0.0 # Черный фон (маска отдельно)
 
 
 
 
 
 
 
 
 
239
  )
240
 
241
  # 5. Обработка после warp: Clip, NaN fix
242
+ warped_face = np.clip(warped_face, 0, 1)
243
+ warped_face = np.nan_to_num(warped_face, nan=0.0)
 
 
 
244
 
245
+ # 6. Плавн��е наложение
 
 
 
 
 
 
 
 
 
246
  target_float = target_img.astype(np.float32) / 255.0
247
+ result_float = target_float * (1.0 - mask_3c) + warped_face * mask_3c
248
 
249
+ # 7. Обратная нормализация к uint8
250
  result = (result_float * 255).clip(0, 255).astype(np.uint8)
251
 
252
+ logger.debug("Warped face shape: %s | Min: %s | Max: %s | NaN count: %s",
253
+ warped_face.shape, warped_face.min(), warped_face.max(), np.isnan(warped_face).sum())
254
+ logger.debug("Mask shape: %s | Min: %s | Max: %s",
255
+ mask_3c.shape, mask_3c.min(), mask_3c.max())
256
  logger.debug("Final result: shape %s | Min: %s | Max: %s", result.shape, result.min(), result.max())
257
 
258
  return result
 
294
 
295
  # Вычисляем аффинную матрицу
296
  M = get_affine_transform(target_landmarks_5.astype(np.float32), std_landmarks_256)
297
+ M_inv = cv2.invertAffineTransform(M) # Обратная для paste
298
  logger.debug("Affine Matrix M (used for cropping):\n%s", M)
299
+ logger.debug("Inverse Affine Matrix M_inv (used for paste):\n%s", M_inv)
300
 
 
 
 
 
301
  # Применяем аффинное преобразование с новой матрицей M
302
  crop = cv2.warpAffine(target_img, M, (256, 256), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
303
 
 
328
 
329
  # --- CPU FLOAT NORMALIZATION FIX ---
330
  # предотвращает появление "синей кожи" и "шума" при работе на CPU
 
331
  if isinstance(output, np.ndarray):
332
  # устранение NaN и бесконечностей
333
  output = np.nan_to_num(output, nan=0.0, posinf=255.0, neginf=0.0)
 
349
  # (ваш код без изменений, но без старой денормализации)
350
  output = output.transpose(1, 2, 0) # CHW -> HWC
351
  output = output[:, :, ::-1] # RGB -> BGR (Убедитесь, что это BGR, если вход был BGR)
352
+ logger.debug("Output after denormalization: Min: %s | Max: %s | NaN count: %s", output.min(), output.max(), np.isnan(output).sum())
353
 
354
  # Визуализация после денормализации
355
  #### Что проверить:
 
358
  logger.debug("Output after denormalization: Min: %s | Max: %s", output.min(), output.max())
359
  # cv2.imshow("Output After Denormalization", output)
360
  # cv2.waitKey(1)
361
+
362
+ # 7. Возвращаем результат 256x256 И обратную матрицу M_inv
363
+ return output, M_inv
364
 
365
  def sort_by_order(face, order: str):
366
  if order == "left-right":
 
484
  interpolation: str = "Bicubic",
485
  ):
486
  # >>>>> РЕШЕНИЕ: Принудительная установка уровня DEBUG <<<<<
487
+ # if logger.getEffectiveLevel() != logging.DEBUG:
488
+ # print("\n--- [ReActor Debug] Принудительная установка уровня логирования на DEBUG (10) в swap_face ---")
489
+ # logger.setLevel(logging.DEBUG)
490
 
491
  # Проверочное сообщение (теперь оно должно появиться)
492
  logger.debug("--- ТЕСТ: swap_face запущена, уровень логирования DEBUG активен. ---")