Upload reactor_swapper.py
Browse files
custom_nodes/comfyui-reactor-node/scripts/reactor_swapper.py
CHANGED
|
@@ -200,71 +200,71 @@ def create_gradient_mask(crop_size=256):
|
|
| 200 |
#### 1. Используйте `cv2.BORDER_TRANSPARENT` (OpenCV ≥ 4.5)
|
| 201 |
# Этот флаг позволяет **не заполнять** области за пределами маски никаким цветом (пиксели остаются `0` или "прозрачные").
|
| 202 |
def paste_back(target_img, swapped_face, M, crop_size=256):
|
|
|
|
| 203 |
|
| 204 |
-
#
|
| 205 |
-
#
|
| 206 |
-
#
|
| 207 |
-
#
|
| 208 |
-
# M: Матрица аффинного преобразования (Target -> Crop)
|
| 209 |
-
# crop_size: Размер кропа (для HyperSwap это 256)
|
| 210 |
|
| 211 |
# 1. Создание мягкой маски (Эрозия + Размытие)
|
| 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)
|
| 219 |
-
|
| 220 |
# 2. Получаем размеры целевого изображения
|
| 221 |
h, w = target_img.shape[:2]
|
| 222 |
-
|
| 223 |
-
# 3.
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
(w, h),
|
| 229 |
flags=cv2.INTER_LANCZOS4 | cv2.WARP_INVERSE_MAP,
|
| 230 |
-
borderMode=cv2.
|
|
|
|
| 231 |
)
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
M,
|
| 237 |
(w, h),
|
| 238 |
flags=cv2.INTER_CUBIC | cv2.WARP_INVERSE_MAP,
|
| 239 |
-
borderMode=cv2.
|
|
|
|
| 240 |
)
|
| 241 |
-
|
| 242 |
-
#
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
#
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
#
|
| 263 |
-
result =
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
return result
|
| 269 |
|
| 270 |
#### Что проверить:
|
|
@@ -338,7 +338,6 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 338 |
|
| 339 |
# --- CPU FLOAT NORMALIZATION FIX ---
|
| 340 |
# предотвращает появление "синей кожи" и "шума" при работе на CPU
|
| 341 |
-
# (адаптировано из патча patch_cpu_fix.diff)
|
| 342 |
if isinstance(output, np.ndarray):
|
| 343 |
# устранение NaN и бесконечностей
|
| 344 |
output = np.nan_to_num(output, nan=0.0, posinf=255.0, neginf=0.0)
|
|
@@ -360,7 +359,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 360 |
# (ваш код без изменений, но без старой денормализации)
|
| 361 |
output = output.transpose(1, 2, 0) # CHW -> HWC
|
| 362 |
output = output[:, :, ::-1] # RGB -> BGR (Убедитесь, что это BGR, если вход был BGR)
|
| 363 |
-
logger.debug("Output after denormalization: Min: %s | Max: %s", output.min(), output.max())
|
| 364 |
|
| 365 |
# Визуализация после денормализации
|
| 366 |
#### Что проверить:
|
|
|
|
| 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
|
| 269 |
|
| 270 |
#### Что проверить:
|
|
|
|
| 338 |
|
| 339 |
# --- CPU FLOAT NORMALIZATION FIX ---
|
| 340 |
# предотвращает появление "синей кожи" и "шума" при работе на CPU
|
|
|
|
| 341 |
if isinstance(output, np.ndarray):
|
| 342 |
# устранение NaN и бесконечностей
|
| 343 |
output = np.nan_to_num(output, nan=0.0, posinf=255.0, neginf=0.0)
|
|
|
|
| 359 |
# (ваш код без изменений, но без старой денормализации)
|
| 360 |
output = output.transpose(1, 2, 0) # CHW -> HWC
|
| 361 |
output = output[:, :, ::-1] # RGB -> BGR (Убедитесь, что это BGR, если вход был BGR)
|
| 362 |
+
logger.debug("Output after denormalization: Min: %s | Max: %s | NaN count: %s", output.min(), output.max(), np.isnan(output).sum())
|
| 363 |
|
| 364 |
# Визуализация после денормализации
|
| 365 |
#### Что проверить:
|