Почему ваш композит выглядит так, будто его делал дизайнер с дальтонизмом
Вы скачали крутую нейросеть для сегментации фона, нагенерировали десяток изображений, наложили одно на другое - и получили мерзкий жёлтый ореол по краям. Знакомо? Эта желтизна - не баг вашей модели, а закономерный результат работы в RGB-пространстве.
RGB - это пространство для мониторов, а не для человеческого восприятия. Когда вы смешиваете пиксели переднего плана и фона, математика RGB делает это грубо: усредняет цвета, игнорируя то, как мы видим светлоту и цветность раздельно. Результат - артефакты, которые выглядят как грязь по контуру.
Lab: пространство, где цвет и светлота живут отдельно
Представьте, что у вас есть два сотрудника: один отвечает за яркость (L), другой - за цвет (a и b каналы). В Lab они не мешают друг другу. Это меняет всё.
Когда вы переходите из RGB в Lab, происходит магия:
- Канал L хранит информацию о светимости - от чёрного к белому
- Канал a - от зелёного к красному
- Канал b - от синего к жёлтому
Секрет в том, что человеческий глаз гораздо чувствительнее к изменениям светлоты (L), чем к изменениям цвета (a,b). И когда вы делаете блендинг только в канале L, оставляя цветовые каналы нетронутыми, артефакты исчезают. Просто потому, что вы не трогаете то, что создаёт цветовые искажения.
Не путайте Lab с HSL/HSV! HSL - это просто преобразование RGB, где H, S, L вычисляются из тех же R,G,B. Lab основан на человеческом восприятии и соответствует тому, как на самом деле работает наш глаз. Разница принципиальная.
Трёхуровневая стратегия: от грубой маски до финального блендинга
Одна только смена цветового пространства не спасёт, если ваша маска похожа на швейцарский сыр. Нужен системный подход.
1 Грубая сегментация: BiRefNet против U²-Net
Начинаем с выбора оружия. U²-Net был королём года три назад, но сегодня BiRefNet делает более чистые маски с меньшим количеством дырок. Особенно на сложных волосах и полупрозрачных объектах.
Почему BiRefNet? У него двунаправленная архитектура, которая смотрит на изображение и сверху вниз, и снизу вверх. Это как иметь двух редакторов: один проверяет общую структуру, другой - детали.
# Пример загрузки BiRefNet через HuggingFace
from transformers import pipeline
segmenter = pipeline("image-segmentation", model="briaai/RMBG-1.4")
result = segmenter("input_image.jpg")
mask = result["mask"] # Готовая маска 0-1
Не забудьте пороговать маску! BiRefNet выдаёт мягкие границы (0-1), но для следующего этапа нужны чёткие 0 или 1. Порог 0.5 обычно работает, но для полупрозрачных тканей можно снизить до 0.3.
2 Уточнение границ: маттинг против эрозии/дилатации
Вот где большинство обламывается. Берут грубую маску и сразу применяют Gaussian blur для размытия краёв. Результат? Тот самый жёлтый ореол, только теперь ещё и размытый.
Правильный путь - маттинг (matting). Алгоритмы вроде Closed-Form Matting или KNN Matting анализируют не только маску, но и исходное изображение, чтобы понять, где действительно полупрозрачность, а где просто шум модели.
| Метод | Плюсы | Минусы | Когда использовать |
|---|---|---|---|
| Эрозия+Дилатация | Быстро, просто | Создаёт ступенчатые края | Только для грубых масок без деталей |
| Gaussian Blur | Плавные переходы | Усиливает цветовые артефакты | Никогда для композитинга |
| Маттинг (KNN) | Учитывает цвета изображения | Медленнее, сложнее | Волосы, мех, полупрозрачные объекты |
Если маттинг для вашего пайплайна слишком тяжёлый, есть компромисс: делайте эрозию/дилатацию с маленьким ядром (3x3), затем применяйте guided filter. Он размывает маску, но ориентируется на градиенты исходного изображения.
3 Финальный блендинг: Lab + правильная формула
Теперь самое важное. У вас есть уточнённая маска (alpha channel), передний план (foreground) и фон (background).
Как НЕ надо делать:
# Плохо: блендинг в RGB
result_rgb = foreground * alpha + background * (1 - alpha)
Эта формула в RGB создаёт цветовые артефакты, потому что смешивает R, G, B независимо, без учёта восприятия.
Правильный способ:
import cv2
import numpy as np
# 1. Конвертируем всё в Lab
fg_lab = cv2.cvtColor(foreground, cv2.COLOR_RGB2LAB)
bg_lab = cv2.cvtColor(background, cv2.COLOR_RGB2LAB)
# 2. Разделяем каналы
fg_L, fg_a, fg_b = cv2.split(fg_lab)
bg_L, bg_a, bg_b = cv2.split(bg_lab)
# 3. Смешиваем ТОЛЬКО канал L (светлоту)
blended_L = fg_L * alpha + bg_L * (1 - alpha)
# 4. Для цветовых каналов используем более умную стратегию:
# Если alpha близок к 1, берём цвет переднего плана
# Если alpha близок к 0, берём цвет фона
# В промежуточной зоне - линейная интерполяция
blended_a = fg_a * alpha + bg_a * (1 - alpha)
blended_b = fg_b * alpha + bg_b * (1 - alpha)
# 5. Собираем обратно и конвертируем в RGB
blended_lab = cv2.merge([blended_L, blended_a, blended_b])
result = cv2.cvtColor(blended_lab, cv2.COLOR_LAB2RGB)
Распространённые ошибки и как их избежать
Даже с правильной теорией можно наступить на грабли. Вот самые частые:
1. Гамма-коррекция забыта
Lab работает с линейной светлотой, а большинство изображений хранятся в sRGB с гамма-коррекцией. Если конвертировать sRGB→Lab напрямую, цвета исказятся.
Решение: Сначала преобразуйте sRGB в линейный RGB, потом в Lab. Или используйте cv2.COLOR_RGB2LAB - OpenCV делает это автоматически.
2. Плохая маска на сложных участках
BiRefNet хорошо справляется с волосами, но может провалиться на полупрозрачных объектах вроде вуали или дыма. Здесь поможет техника итеративного уточнения: сегментируете несколько раз с разными порогами, комбинируете результаты.
3. Цветовой сдвиг после конвертации
Иногда после RGB→Lab→RGB цвета слегка меняются. Это происходит из-за округления и ограниченной точности. Для критичных проектов используйте 16-битные изображения вместо 8-битных.
Деплой на HuggingFace Spaces: production-ready пайплайн
Теперь, когда у вас работает локально, давайте упакуем это в сервис. HuggingFace Spaces идеально подходит для демонстрации композитинга.
# app.py для HuggingFace Spaces
import gradio as gr
import cv2
import numpy as np
from PIL import Image
# Загрузка модели (кешируем для скорости)
from transformers import pipeline
segmenter = pipeline("image-segmentation", model="briaai/RMBG-1.4")
def composite_images(foreground, background):
# Сегментация
result = segmenter(foreground)
mask = result["mask"]
# Конвертация в Lab
fg_lab = cv2.cvtColor(np.array(foreground), cv2.COLOR_RGB2LAB)
bg_lab = cv2.cvtColor(np.array(background), cv2.COLOR_RGB2LAB)
# Блендинг в Lab (как в коде выше)
# ...
return Image.fromarray(result_rgb)
# Интерфейс Gradio
iface = gr.Interface(
fn=composite_images,
inputs=[
gr.Image(label="Передний план"),
gr.Image(label="Фон")
],
outputs=gr.Image(label="Результат"),
title="Чистый композитинг в Lab-пространстве"
)
iface.launch()
На Spaces можно добавить слайдеры для регулировки порога маски, силы блендинга и других параметров. Пользователи смогут экспериментировать без написания кода.
Внимание на память! BiRefNet не маленькая модель. На Spaces с бесплатным CPU она может тормозить. Либо кэшируйте результаты для одинаковых изображений, либо предлагайте пользователям загружать предварительно вырезанные объекты с маской.
А что насчет реального времени?
Всё это работает для офлайн-обработки. Но что если нужно в реальном времени, например, для виртуальных примерочных?
Тогда Lab может быть слишком медленным. Альтернатива - YCbCr. Это не перцептивное пространство, но оно тоже разделяет яркость (Y) и цветность (Cb, Cr). Конвертация быстрее, и для многих случаев результат приемлемый.
Ещё один трюк - предварительно вычислять маски для каталога товаров и хранить их вместе с изображениями. Как в статье про стейджей стабильной генерации контента, где каждый этап оптимизирован для продакшена.
Неочевидный совет: когда НЕ использовать эту технику
Да, бывает и такое. Lab-блендинг не панацея:
- Стилизованная графика: Если вы накладывайте мультяшного персонажа на фэнтези-фон, цветовые артефакты могут быть частью стиля
- Ночные сцены: Lab плохо работает с очень тёмными изображениями, где шум доминирует над сигналом
- HDR-изображения: Lab рассчитан на стандартный динамический диапазон. Для HDR нужны специальные пространства вроде ICtCp
И последнее: если вы делаете композитинг для печати, а не для экрана, вам нужен не Lab, а CMYK. Но это уже совсем другая история с другими граблями.
Главное - понимать, почему появляются артефакты, а не просто применять рецепты. Теперь вы знаете и «почему», и «как». Осталось попробовать и перестать ненавидеть жёлтые ореолы.