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: Матрица аффинного преобразования (
|
|
|
|
| 208 |
# crop_size: Размер кропа (для HyperSwap это 256)
|
| 209 |
|
| 210 |
-
# 1.
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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. Обратное преобразование
|
| 224 |
-
# Используем BORDER_CONSTANT с borderValue=0.5 (серый, чтобы избежать синих/зеленых артефактов)
|
| 225 |
warped_face = cv2.warpAffine(
|
| 226 |
swapped_face_norm,
|
| 227 |
-
M,
|
| 228 |
(w, h),
|
| 229 |
flags=cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP,
|
| 230 |
borderMode=cv2.BORDER_CONSTANT,
|
| 231 |
-
borderValue=0.
|
| 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.
|
| 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 -
|
| 262 |
|
| 263 |
-
#
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 500 |
-
|
| 501 |
-
|
| 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 активен. ---")
|