Цветовые пространства и сегментация по диапазонам: HSV, Lab и битовые маски
Цветовые пространства и сегментация по диапазонам: HSV, Lab и битовые маски
Попробуйте выделить спелый красный помидор на фоне зеленых листьев с помощью порогов по каналам RGB. При идеальном студийном освещении условие «много красного, мало зеленого» сработает. Но стоит включить боковой направленный фонарь — и на освещенной стороне помидора значения , и взлетят почти до 255 (блик станет белым). А на затененной стороне все три канала упадут до значений вроде , смешавшись по яркости с темной землей на заднем плане.
В цветовой модели RGB (и BGR в OpenCV) хроматическая информация (сам оттенок) намертво сцеплена с фотометрической (яркостью освещения). Изменение освещенности масштабирует вектор во всем трехмерном пространстве. Чтобы компьютерное зрение устойчиво распознавало цвета под солнцем, в тени или под люминесцентной лампой, необходимы цветовые пространства, в которых яркость изолирована от хроматической составляющей.
Почему RGB непригоден для цветовой сегментации
Пространство RGB представляет собой трехмерный куб, где координаты определяют интенсивность каждого базового излучателя. Когда интенсивность источника света меняется, точка в этом кубе смещается вдоль луча, проведенного из начала координат .
Если вы зададите фиксированные границы в RGB:
# Попытка выделить чистый красный цвет в BGR
lower_bound = np.array([0, 0, 150], dtype=np.uint8)
upper_bound = np.array([80, 80, 255], dtype=np.uint8)
вы вырежете жесткий параллелепипед внутри куба. При этом:
- Любая глубокая тень на объекте выведет пиксели ниже границы
lower_boundпо каналу . - Любой яркий блик добавит значения в каналы и , выбросив пиксели за пределы
upper_bound.
Чтобы изолировать цвет от условий освещения, классическое машинное зрение использует альтернативные системы координат.
Цветовое пространство HSV: тон, насыщенность и яркость
Модель HSV (Hue, Saturation, Value — цветовой тон, насыщенность, значение яркости) переводит кубическую декартову систему в цилиндрические координаты:
- Hue (, Цветовой тон): угол на цветовом круге, задающий чистый спектральный цвет (красный, желтый, зеленый, голубой, синий, пурпурный). В классической колориметрии измеряется в градусах от до .
- Saturation (, Насыщенность): удаленность от центральной оси (степень разбавления цвета белым). — серый/ахроматический, максимум — спектрально чистый оттенок.
- Value (, Яркость): высота вдоль вертикальной оси цилиндра. — абсолютная темнота (черный), максимум — максимально светлый оттенок при заданной насыщенности.
Красный (0°)
Пурпурный \ / Желтый
(300°) \/ (60°)
/\
Синий / \ Зеленый
(240°) / \ (120°)
Голубой (180°)
Критическая особенность OpenCV: Стандартный тип данных для изображений —
uint8, максимальное значение которого равно 255. Чтобы вместить угловой диапазон в один байт, разработчики OpenCV разделили угол на 2:
В OpenCV диапазон канала составляет от до . Каналы и нормализованы к диапазону .
Если оттенок зеленого равен , в массиве OpenCV ему будет соответствовать значение . Если вы зададите порог , алгоритм вернет пустой результат, так как максимальное значение в OpenCV — .
Конвертация выполняется вызовом cv2.cvtColor:
import cv2
# Загрузка кадра и перевод из BGR в HSV
hsv_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2HSV)
Проблема красного сектора в HSV
Поскольку шкала представляет собой замкнутый круг (), красный сектор оказывается «разорванным» пополам на границе шкалы:
- Начало спектра: (в OpenCV: ).
- Конец спектра: (в OpenCV: ).
Для надежного выделения красного цвета всегда требуется объединять два раздельных диапазона.
Цветовое пространство CIELAB (Lab): перцептивная равномерность
Пространство Lab создано Международной комиссией по освещению (CIE) с целью математически смоделировать нелинейность зрительного восприятия человека:
- (Lightness): перцептивная яркость от 0 (черный) до 100 (белый). В OpenCV масштабируется в диапазон по формуле .
- : хроматическая ось «зеленый красный». Отрицательные значения соответствуют зеленому, положительные — красному/пурпурному. В OpenCV смещается: .
- : хроматическая ось «синий желтый». Отрицательные значения соответствуют синему, положительные — желтому. В OpenCV: .
| Параметр | HSV | CIELAB (Lab) |
|---|---|---|
| Геометрия пространства | Цилиндр (углы и радиусы) | Декартово ортогональное пространство |
| Физический смысл осей | Тон (), Чистота (), Яркость () | Яркость (), Зеленый-Красный (), Синий-Желтый () |
| Перцептивная равномерность | Низкая (евклидово расстояние не отражает различимость цветов глазом) | Высокая (одинаковое евклидово расстояние означает одинаковую видимую разницу) |
| Диапазон в OpenCV | , , | |
| Основное применение | Сегментация объектов по оттенку при умеренных изменениях света | Промышленный контроль качества цвета, цветокоррекция, сегментация при неравномерном освещении |
Преобразование выполняется аналогично:
lab_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2LAB)
Пороговая фильтрация диапазонов: cv2.inRange
Чтобы локализовать пиксели, попадающие в заданный цветовой интервал, используется функция cv2.inRange. Она выполняет поэлементную проверку массива:
Функция возвращает одноканальное 8-битное бинарное изображение (маску), где 255 обозначает пиксели, удовлетворяющие всем условиям, а 0 — пиксели фона.
import cv2
import numpy as np
# Выделение желтого объекта в пространстве HSV
# Желтый цвет в углах: ~60 градусов -> в OpenCV H ~ 30
lower_yellow = np.array([20, 100, 100], dtype=np.uint8)
upper_yellow = np.array([35, 255, 255], dtype=np.uint8)
mask_yellow = cv2.inRange(hsv_image, lower_yellow, upper_yellow)
Здесь границы S и V намеренно ограничены снизу значением 100: это предотвращает ошибочное включение белых, серых и слишком темных пикселей, где координата тона нестабильна.
Битовые операции и наложение масок
Маска представляет собой одноканальный трафарет. Чтобы извлечь изолированный объект из исходного кадра или объединить несколько условий фильтрации, применяются побитовые логические операции:
cv2.bitwise_and(src1, src2, mask=mask): логическое И (). Если указан аргументmask, операция выполняется только для тех пикселей, где .cv2.bitwise_or(src1, src2): логическое ИЛИ (). Используется для объединения нескольких бинарных масок.cv2.bitwise_not(src): логическое НЕ (). Инвертирует маску ().
Практический пример: сегментация красного объекта
Соберем законченный пайплайн, объединяющий обработку двух диапазонов красного цвета и извлечение целевой области из оригинального кадра.
import cv2
import numpy as np
def extract_red_object(bgr_frame: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""
Выделяет красные зоны на изображении и возвращает бинарную маску
вместе с изолированным цветным объектом на черном фоне.
"""
# 1. Перевод в цветовое пространство HSV
hsv = cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2HSV)
# 2. Нижний диапазон красного цвета (0 - 10 в терминах OpenCV)
lower_red_1 = np.array([0, 120, 70], dtype=np.uint8)
upper_red_1 = np.array([10, 255, 255], dtype=np.uint8)
mask1 = cv2.inRange(hsv, lower_red_1, upper_red_1)
# 3. Верхний диапазон красного цвета (170 - 179 в терминах OpenCV)
lower_red_2 = np.array([170, 120, 70], dtype=np.uint8)
upper_red_2 = np.array([179, 255, 255], dtype=np.uint8)
mask2 = cv2.inRange(hsv, lower_red_2, upper_red_2)
# 4. Объединение двух масок логическим ИЛИ
full_mask = cv2.bitwise_or(mask1, mask2)
# 5. Применение маски к исходному цветному изображению
# Пиксели, где full_mask == 0, станут черными [0, 0, 0]
extracted_foreground = cv2.bitwise_and(bgr_frame, bgr_frame, mask=full_mask)
return full_mask, extracted_foreground
Вызов cv2.bitwise_and(bgr_frame, bgr_frame, mask=full_mask) выполняет побитовое умножение кадра самого на себя исключительно в тех позициях, где маска равна 255. Фоновые пиксели гарантированно зануляются.
Полученная бинарная маска содержит изолированный объект, однако на практике реальные кадры содержат сенсорный шум, блики и мелкие посторонние артефакты. Для их устранения перед поиском контуров маску необходимо подвергнуть пространственной фильтрации и сглаживанию.