Upload 2 files
Browse files
custom_nodes/comfyui-reactor-node/scripts/reactor_logger.py
CHANGED
|
@@ -1,47 +1,54 @@
|
|
| 1 |
-
import logging
|
| 2 |
-
import copy
|
| 3 |
-
import sys
|
| 4 |
-
|
| 5 |
-
from modules import shared
|
| 6 |
-
from reactor_utils import addLoggingLevel
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
class ColoredFormatter(logging.Formatter):
|
| 10 |
-
COLORS = {
|
| 11 |
-
"DEBUG": "\033[0;36m", # CYAN
|
| 12 |
-
"STATUS": "\033[38;5;173m", # Calm ORANGE
|
| 13 |
-
"INFO": "\033[0;32m", # GREEN
|
| 14 |
-
"WARNING": "\033[0;33m", # YELLOW
|
| 15 |
-
"ERROR": "\033[0;31m", # RED
|
| 16 |
-
"CRITICAL": "\033[0;37;41m", # WHITE ON RED
|
| 17 |
-
"RESET": "\033[0m", # RESET COLOR
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
def format(self, record):
|
| 21 |
-
colored_record = copy.copy(record)
|
| 22 |
-
levelname = colored_record.levelname
|
| 23 |
-
seq = self.COLORS.get(levelname, self.COLORS["RESET"])
|
| 24 |
-
colored_record.levelname = f"{seq}{levelname}{self.COLORS['RESET']}"
|
| 25 |
-
return super().format(colored_record)
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
# Create a new logger
|
| 29 |
-
logger = logging.getLogger("ReActor")
|
| 30 |
-
logger.propagate = False
|
| 31 |
-
|
| 32 |
-
# Add Custom Level
|
| 33 |
-
# logging.addLevelName(logging.INFO, "STATUS")
|
| 34 |
-
addLoggingLevel("STATUS", logging.INFO + 5)
|
| 35 |
-
|
| 36 |
-
# Add handler if we don't have one.
|
| 37 |
-
if not logger.handlers:
|
| 38 |
-
handler = logging.StreamHandler(sys.stdout)
|
| 39 |
-
handler.setFormatter(
|
| 40 |
-
ColoredFormatter("[%(name)s] %(asctime)s - %(levelname)s - %(message)s",datefmt="%H:%M:%S")
|
| 41 |
-
)
|
| 42 |
-
logger.addHandler(handler)
|
| 43 |
-
|
| 44 |
-
# Configure logger
|
| 45 |
-
loglevel_string = getattr(shared.cmd_opts, "reactor_loglevel", "INFO")
|
| 46 |
-
loglevel = getattr(logging, loglevel_string.upper(), "info")
|
| 47 |
-
logger.setLevel(loglevel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import copy
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
from modules import shared
|
| 6 |
+
from reactor_utils import addLoggingLevel
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class ColoredFormatter(logging.Formatter):
|
| 10 |
+
COLORS = {
|
| 11 |
+
"DEBUG": "\033[0;36m", # CYAN
|
| 12 |
+
"STATUS": "\033[38;5;173m", # Calm ORANGE
|
| 13 |
+
"INFO": "\033[0;32m", # GREEN
|
| 14 |
+
"WARNING": "\033[0;33m", # YELLOW
|
| 15 |
+
"ERROR": "\033[0;31m", # RED
|
| 16 |
+
"CRITICAL": "\033[0;37;41m", # WHITE ON RED
|
| 17 |
+
"RESET": "\033[0m", # RESET COLOR
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
def format(self, record):
|
| 21 |
+
colored_record = copy.copy(record)
|
| 22 |
+
levelname = colored_record.levelname
|
| 23 |
+
seq = self.COLORS.get(levelname, self.COLORS["RESET"])
|
| 24 |
+
colored_record.levelname = f"{seq}{levelname}{self.COLORS['RESET']}"
|
| 25 |
+
return super().format(colored_record)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# Create a new logger
|
| 29 |
+
logger = logging.getLogger("ReActor")
|
| 30 |
+
logger.propagate = False
|
| 31 |
+
|
| 32 |
+
# Add Custom Level
|
| 33 |
+
# logging.addLevelName(logging.INFO, "STATUS")
|
| 34 |
+
addLoggingLevel("STATUS", logging.INFO + 5)
|
| 35 |
+
|
| 36 |
+
# Add handler if we don't have one.
|
| 37 |
+
if not logger.handlers:
|
| 38 |
+
handler = logging.StreamHandler(sys.stdout)
|
| 39 |
+
handler.setFormatter(
|
| 40 |
+
ColoredFormatter("[%(name)s] %(asctime)s - %(levelname)s - %(message)s",datefmt="%H:%M:%S")
|
| 41 |
+
)
|
| 42 |
+
logger.addHandler(handler)
|
| 43 |
+
|
| 44 |
+
# Configure logger
|
| 45 |
+
loglevel_string = getattr(shared.cmd_opts, "reactor_loglevel", "INFO")
|
| 46 |
+
loglevel = getattr(logging, loglevel_string.upper(), "info")
|
| 47 |
+
# logger.setLevel(loglevel)
|
| 48 |
+
logger.setLevel(logging.DEBUG) # Принудительно устанавливаем DEBUG
|
| 49 |
+
|
| 50 |
+
# Добавим сообщение для проверки, что изменения применились
|
| 51 |
+
logger.warning("--- ReActor LOGGING FORCED TO DEBUG LEVEL ---")
|
| 52 |
+
|
| 53 |
+
# В конце файла reactor_logger.py
|
| 54 |
+
logger.debug("--- ТЕСТОВОЕ DEBUG СООБЩЕНИЕ ---")
|
custom_nodes/comfyui-reactor-node/scripts/reactor_swapper.py
CHANGED
|
@@ -20,7 +20,7 @@ import comfy.model_management as model_management
|
|
| 20 |
from modules.shared import state
|
| 21 |
|
| 22 |
# 1. Добавьте импорт logging наверху файла, если его там нет:
|
| 23 |
-
|
| 24 |
from scripts.reactor_logger import logger
|
| 25 |
from reactor_utils import (
|
| 26 |
move_path,
|
|
@@ -126,21 +126,9 @@ def getFaceSwapModel(model_path: str):
|
|
| 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 |
-
|
| 130 |
-
|
| 131 |
-
model_filename = os.path.basename(model_path)
|
| 132 |
-
|
| 133 |
-
# Определяем правильный путь в зависимости от типа модели
|
| 134 |
-
if "hyperswap" in model_filename.lower():
|
| 135 |
-
# Ищем в директории hyperswap
|
| 136 |
-
correct_path = os.path.join(folder_paths.models_dir, "hyperswap", model_filename)
|
| 137 |
-
FS_MODEL = ort.InferenceSession(correct_path, providers=providers)
|
| 138 |
-
elif "reswapper" in model_filename.lower():
|
| 139 |
-
# Ищем в директории reswapper
|
| 140 |
-
correct_path = os.path.join(folder_paths.models_dir, "reswapper", model_filename)
|
| 141 |
-
FS_MODEL = insightface.model_zoo.get_model(correct_path, providers=providers)
|
| 142 |
else:
|
| 143 |
-
# Для моделей insightface используем оригинальный путь
|
| 144 |
FS_MODEL = insightface.model_zoo.get_model(model_path, providers=providers)
|
| 145 |
return FS_MODEL
|
| 146 |
|
|
@@ -149,10 +137,10 @@ def get_landmarks_5(face):
|
|
| 149 |
# face.landmark_5: np.ndarray shape (5,2)
|
| 150 |
# Если нет, попробуй face.kps или face.landmark
|
| 151 |
if hasattr(face, 'landmark_5') and face.landmark_5 is not None:
|
| 152 |
-
|
| 153 |
return face.landmark_5
|
| 154 |
elif hasattr(face, 'kps') and face.kps is not None:
|
| 155 |
-
|
| 156 |
return face.kps
|
| 157 |
elif hasattr(face, 'landmark') and face.landmark is not None:
|
| 158 |
# 68-точечная разметка, берём нужные индексы
|
|
@@ -160,9 +148,9 @@ def get_landmarks_5(face):
|
|
| 160 |
# Пример: [36, 45, 30, 48, 54] — левый/правый глаз, нос, левый/правый рот
|
| 161 |
if face.landmark.shape[0] >= 68:
|
| 162 |
idxs = [36, 45, 30, 48, 54]
|
| 163 |
-
|
| 164 |
return face.landmark[idxs]
|
| 165 |
-
|
| 166 |
return None
|
| 167 |
|
| 168 |
#### Что проверить:
|
|
@@ -190,14 +178,14 @@ def create_gradient_mask(crop_size=256):
|
|
| 190 |
|
| 191 |
# 3. Рисуем эллипс (заполняем белым цветом, значение=1.0)
|
| 192 |
cv2.ellipse(
|
| 193 |
-
mask,
|
| 194 |
-
center,
|
| 195 |
-
axes,
|
| 196 |
-
angle=0,
|
| 197 |
-
startAngle=0,
|
| 198 |
-
endAngle=360,
|
| 199 |
-
color=1.0,
|
| 200 |
-
thickness=-1
|
| 201 |
)
|
| 202 |
|
| 203 |
# 4. Применяем размытие для плавных краёв
|
|
@@ -224,7 +212,7 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
|
|
| 224 |
mask = create_gradient_mask(crop_size)
|
| 225 |
|
| 226 |
# Сохраняем для отладки
|
| 227 |
-
|
| 228 |
|
| 229 |
# Преобразуем в трехканальную маску
|
| 230 |
mask_3c = np.stack([mask] * 3, axis=2)
|
|
@@ -258,13 +246,13 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
|
|
| 258 |
inv_mask = cv2.GaussianBlur(inv_mask, (3, 3), 0)
|
| 259 |
|
| 260 |
# Отладка
|
| 261 |
-
|
| 262 |
-
|
| 263 |
|
| 264 |
# Визуализация
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
|
| 269 |
# 6. Плавное наложение
|
| 270 |
target_img_float = target_img.astype(np.float32)
|
|
@@ -275,7 +263,7 @@ def paste_back(target_img, swapped_face, M, crop_size=256):
|
|
| 275 |
result = np.clip(result, 0, 255).astype(np.uint8)
|
| 276 |
|
| 277 |
# Сохраняем результат
|
| 278 |
-
|
| 279 |
|
| 280 |
return result
|
| 281 |
|
|
@@ -288,8 +276,8 @@ def visualize_points(img, points, color=(0, 255, 0)):
|
|
| 288 |
img = img.copy()
|
| 289 |
for p in points:
|
| 290 |
cv2.circle(img, tuple(p.astype(int)), 3, color, -1)
|
| 291 |
-
|
| 292 |
-
|
| 293 |
|
| 294 |
# Итоговая функция run_hyperswap с аффинным преобразованием
|
| 295 |
def run_hyperswap(session, source_face, target_face, target_img):
|
|
@@ -301,7 +289,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 301 |
visualize_points(target_img, target_landmarks_5, (0, 255, 0)) # Зеленые точки
|
| 302 |
|
| 303 |
if target_landmarks_5 is None:
|
| 304 |
-
|
| 305 |
# Важно: Если ошибка, возвращаем None и исходную матрицу (или обрабатываем ошибку иначе)
|
| 306 |
return None, None
|
| 307 |
|
|
@@ -316,7 +304,7 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 316 |
|
| 317 |
# Вычисляем аффинную матрицу
|
| 318 |
M = get_affine_transform(target_landmarks_5.astype(np.float32), std_landmarks_256)
|
| 319 |
-
|
| 320 |
|
| 321 |
#### Что проверить:
|
| 322 |
# Матрица `M` не должна содержать `NaN` или бесконечности.
|
|
@@ -329,9 +317,9 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 329 |
#### Что проверить:
|
| 330 |
# Окно `"Crop Before Inference"` должно показывать лицо, вырезанное по аффинному преобразованию.
|
| 331 |
# Если изображение черное — проблема в `M` или `target_landmarks_5`.
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
|
| 336 |
# 4. Преобразуем crop для модели
|
| 337 |
# crop_input = crop[:, :, ::-1] / 255.0
|
|
@@ -345,9 +333,9 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 345 |
# 5. Инференс
|
| 346 |
try:
|
| 347 |
output = session.run(None, {'source': source_embedding, 'target': crop_input})[0][0]
|
| 348 |
-
|
| 349 |
except Exception as e:
|
| 350 |
-
|
| 351 |
return target_img
|
| 352 |
|
| 353 |
# 6. Обратная нормализация
|
|
@@ -365,9 +353,9 @@ def run_hyperswap(session, source_face, target_face, target_img):
|
|
| 365 |
#### Что проверить:
|
| 366 |
# `output` должен быть в диапазоне `[0..255]` и содержать лицо.
|
| 367 |
# Если `output` черный — проблема в нормализации/денормализации или в самой модели.
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
|
| 372 |
# 7. ИЗМЕНЕНИЕ: Возвращаем результат 256x256 И матрицу M
|
| 373 |
# Мы больше не вызываем warp_and_paste_face отсюда.
|
|
@@ -468,13 +456,13 @@ def get_face_single(img_data: np.ndarray, face, face_index=0, det_size=(640, 640
|
|
| 468 |
try:
|
| 469 |
faces_sorted = sort_by_order(face, order)
|
| 470 |
selected_face = faces_sorted[face_index]
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
return selected_face, 0
|
| 477 |
-
|
| 478 |
# return sorted(face, key=lambda x: x.bbox[0])[face_index], 0
|
| 479 |
except IndexError:
|
| 480 |
return None, 0
|
|
@@ -496,14 +484,14 @@ def swap_face(
|
|
| 496 |
codeformer_weight: float = 0.5,
|
| 497 |
interpolation: str = "Bicubic",
|
| 498 |
):
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
|
| 508 |
global SOURCE_FACES, SOURCE_IMAGE_HASH, TARGET_FACES, TARGET_IMAGE_HASH
|
| 509 |
result_image = target_img
|
|
@@ -625,7 +613,7 @@ def swap_face(
|
|
| 625 |
if target_face is not None and wrong_gender == 0:
|
| 626 |
logger.status(f"Swapping...")
|
| 627 |
if "hyperswap" in model:
|
| 628 |
-
|
| 629 |
swapped_face_256, M = run_hyperswap(face_swapper, source_face, target_face, result)
|
| 630 |
if swapped_face_256 is not None:
|
| 631 |
result = paste_back(result, swapped_face_256, M, crop_size=256)
|
|
@@ -811,17 +799,7 @@ def swap_face_many(
|
|
| 811 |
logger.status(f'Source Faces must have no entries (default=0), one entry, or same number of entries as target faces.')
|
| 812 |
elif source_face is not None:
|
| 813 |
results = target_imgs
|
| 814 |
-
|
| 815 |
-
# Определяем путь к модели в зависимости от типа
|
| 816 |
-
if "inswapper" in model:
|
| 817 |
-
model_path = os.path.join(insightface_path, model)
|
| 818 |
-
elif "reswapper" in model:
|
| 819 |
-
model_path = os.path.join(reswapper_path, model)
|
| 820 |
-
elif "hyperswap" in model:
|
| 821 |
-
model_path = os.path.join(hyperswap_path, model)
|
| 822 |
-
else:
|
| 823 |
-
model_path = os.path.join(insightface_path, model)
|
| 824 |
-
|
| 825 |
face_swapper = getFaceSwapModel(model_path)
|
| 826 |
|
| 827 |
source_face_idx = 0
|
|
@@ -845,23 +823,16 @@ def swap_face_many(
|
|
| 845 |
target_face_single, wrong_gender = get_face_single(target_img, target_face, face_index=face_num, gender_target=gender_target, order=faces_order[0])
|
| 846 |
if target_face_single is not None and wrong_gender == 0:
|
| 847 |
result = target_img
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
if "hyperswap" in model:
|
| 851 |
-
# Для Hyperswap используем специальную функцию
|
| 852 |
-
swapped_face_256, M = run_hyperswap(face_swapper, source_face, target_face_single, result)
|
| 853 |
-
if swapped_face_256 is not None:
|
| 854 |
-
result = paste_back(result, swapped_face_256, M, crop_size=256)
|
| 855 |
-
elif face_boost_enabled:
|
| 856 |
logger.status(f"Face Boost is enabled")
|
| 857 |
bgr_fake, M = face_swapper.get(target_img, target_face_single, source_face, paste_back=False)
|
| 858 |
bgr_fake, scale = restorer.get_restored_face(bgr_fake, face_restore_model, face_restore_visibility, codeformer_weight, interpolation)
|
| 859 |
M *= scale
|
| 860 |
result = swapper.in_swap(target_img, bgr_fake, M)
|
| 861 |
else:
|
| 862 |
-
#
|
| 863 |
result = face_swapper.get(target_img, target_face_single, source_face)
|
| 864 |
-
|
| 865 |
results[i] = result
|
| 866 |
pbar.update(1)
|
| 867 |
elif wrong_gender == 1:
|
|
|
|
| 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)
|
|
|
|
| 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))
|
| 254 |
+
# cv2.imshow("Warped Mask", (inv_mask * 255).astype(np.uint8))
|
| 255 |
+
# cv2.waitKey(1)
|
| 256 |
|
| 257 |
# 6. Плавное наложение
|
| 258 |
target_img_float = target_img.astype(np.float32)
|
|
|
|
| 263 |
result = np.clip(result, 0, 255).astype(np.uint8)
|
| 264 |
|
| 265 |
# Сохраняем результат
|
| 266 |
+
cv2.imwrite("debug_result.png", result)
|
| 267 |
|
| 268 |
return result
|
| 269 |
|
|
|
|
| 276 |
img = img.copy()
|
| 277 |
for p in points:
|
| 278 |
cv2.circle(img, tuple(p.astype(int)), 3, color, -1)
|
| 279 |
+
# cv2.imshow("Face Points", img)
|
| 280 |
+
# cv2.waitKey(1)
|
| 281 |
|
| 282 |
# Итоговая функция run_hyperswap с аффинным преобразованием
|
| 283 |
def run_hyperswap(session, source_face, target_face, target_img):
|
|
|
|
| 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 |
|
| 324 |
# 4. Преобразуем crop для модели
|
| 325 |
# crop_input = crop[:, :, ::-1] / 255.0
|
|
|
|
| 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 |
|
| 360 |
# 7. ИЗМЕНЕНИЕ: Возвращаем результат 256x256 И матрицу M
|
| 361 |
# Мы больше не вызываем warp_and_paste_face отсюда.
|
|
|
|
| 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
|
|
|
|
| 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:
|