Статья Автор: Деникина Наталья Владимировна

Цветовые модели RGB и HSV

Цветовая модель — это способ представления цветов в виде числовых значений.


Две наиболее часто используемые цветовые модели в компьютерном зрении — это RGB и HSV. Рассмотрим эти цветовые модели, их отличия и случаи, когда предпочтительно использовать одну модель вместо другой.

Цветовая модель RGB



 

RGB (Red, Green, Blue) — это аддитивная цветовая модель, в которой каждый цвет представлен комбинацией трех базовых цветов: красного, зеленого и синего. Каждый из этих компонентов может принимать значения в диапазоне от 0 до 255.

  • R (Red): интенсивность красного цвета.
  • G (Green): интенсивность зеленого цвета.
  • B (Blue): интенсивность синего цвета.

Комбинируя различные значения R, G и B, можно получить широкий спектр цветов. Например:

  • (255, 0, 0) — чистый красный цвет.
  • (0, 255, 0) — чистый зеленый цвет.
  • (0, 0, 255) — чистый синий цвет.
  • (255, 255, 255) — белый цвет.
  • (0, 0, 0) — черный цвет.

В OpenCV изображения обычно загружаются в формате BGR (Blue, Green, Red), где порядок каналов отличается от классического RGB.
 

Цветовая модель HSV

     

 

HSV (Hue, Saturation, Value) — это цветовая модель, основанная на интуитивных характеристиках цвета, таких как оттенок, насыщенность и яркость.

  • H (Hue): оттенок цвета, выражаемый в градусах на цветовом круге. В OpenCV значение H (оттенок) в HSV цветовой модели варьируется от 0 до 179, хотя в теории цветов H варьируется от 0 до 360 градусов. Это сделано для экономии памяти. Так как OpenCV использует целочисленные значения для представления H, диапазон от 0 до 179 позволяет уместить значение в один байт (8 бит), что значительно оптимизирует использование памяти и производительность при обработке изображений.
  • S (Saturation): насыщенность цвета, выражаемая в процентах от 0 до 255, где 0 — это серый цвет, а 255— максимальная насыщенность.
  • V (Value): яркость цвета, выражаемая в процентах от 0 до 255, где 0 — это черный цвет, а 255— максимальная яркость.

HSV часто используется в задачах компьютерного зрения, потому что позволяет легче выделять и обрабатывать определенные цвета, независимо от их освещенности.
 

Преимущества и недостатки моделей RGB и HSV

Цветовая модель Преимущества Недостатки
RGB Легко использовать для отображения и хранения изображений, естественен для устройств отображения (мониторы, камеры) Трудности с выделением цветов при изменениях освещенности, сложность обработки в задачах сегментации
HSV Удобна для анализа и обработки изображений, независимость оттенка от освещенности, упрощает выделение и сегментацию цветов Не так интуитивна для отображения, может требовать преобразования при работе с устройствами отображения

Преобразование между RGB и HSV в OpenCV

OpenCV предоставляет функции для преобразования между RGB и HSV цветовыми моделями:

  • RGB -> HSV:
import cv2

image = cv2.imread('path_to_image.jpg')

# Преобразование из BGR в HSV
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
  • HSV -> RGB:
import cv2

image = cv2.imread('path_to_image.jpg')

# Преобразование из HSV в BGR
bgr_image = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)

Зачем все это нужно?

Одна из популярных задач, которую можно решить при помощи цветовой модели HSV - обнаружение объектов в заданном диапазоне цветов. Отличный объект для исследования - желтые уточки!


Как мы видим, уточка желтая, но за счет теней желтый цвет достаточно "разнообразный"!

В RGB модели желтый цвет представлен высокой интенсивностью красного и зеленого компонентов. Однако, любые изменения в освещении могут привести к тому, что желтый цвет будет трудно обнаружить.

В HSV можно задать диапазон значений, который точно определяет желтый цвет, и выполнить сегментацию.
 

Для подбора диапазонов HSV можно использовать следующий скрипт. Данный скрипт предоставляет большой объем информации, визуализации. Плюс в нем реализована возможность обвести фрагмент изображения и подобрать диапазон почти автоматически!
 

import cv2
import numpy as np

# Глобальные переменные для хранения координат области
ref_point = []
cropping = False
selection_done = False
canvas_copy = None

def create_hsv_circle(radius):
    y, x = np.ogrid[-radius:radius, -radius:radius]
    r = np.sqrt(x**2 + y**2)
    theta = np.arctan2(y, x) * 180 / np.pi
    theta = (theta + 360) % 360

    hsv_circle = np.zeros((radius*2, radius*2, 3), dtype=np.uint8)

    mask = r <= radius
    hsv_circle[mask, 0] = (theta[mask] / 2).astype(np.uint8)  # H: 0-179
    hsv_circle[mask, 1] = (r[mask] / radius * 255).astype(np.uint8)  # S: 0-255
    hsv_circle[mask, 2] = 255  # V: 255

    hsv_circle = cv2.cvtColor(hsv_circle, cv2.COLOR_HSV2BGR)
    return hsv_circle

def resize_with_padding(image, target_size):
    h, w = image.shape[:2]
    scale = target_size / max(h, w)
    new_w = int(w * scale)
    new_h = int(h * scale)
    
    resized_img = cv2.resize(image, (new_w, new_h))
    delta_w = target_size - new_w
    delta_h = target_size - new_h
    top, bottom = delta_h // 2, delta_h - (delta_h // 2)
    left, right = delta_w // 2, delta_w - (delta_w // 2)
    
    color = [0, 0, 0]
    new_image = cv2.copyMakeBorder(resized_img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
    return new_image

def apply_hsv_mask(hsv_circle, h1, s1, v1, h2, s2, v2):
    hsv = cv2.cvtColor(hsv_circle, cv2.COLOR_BGR2HSV)

    mask_h = (hsv[..., 0] >= h1) & (hsv[..., 0] <= h2)
    mask_s = (hsv[..., 1] >= s1) & (hsv[..., 1] <= s2)
    mask_v = (hsv[..., 2] >= v1) & (hsv[..., 2] <= v2)
    
    combined_mask = mask_h & mask_s & mask_v

    masked_hsv_circle = hsv_circle.copy()
    masked_hsv_circle[~combined_mask] = 0
    return masked_hsv_circle

def click_and_crop(event, x, y, flags, param):
    global ref_point, cropping, selection_done, canvas_copy

    if event == cv2.EVENT_LBUTTONDOWN:
        ref_point = [(x, y)]
        cropping = True

    elif event == cv2.EVENT_MOUSEMOVE:
        if cropping:
            # Создаем копию canvas, чтобы не рисовать новый прямоугольник поверх старого
            canvas_copy = canvas.copy()
            cv2.rectangle(canvas_copy, ref_point[0], (x, y), (0, 255, 0), 2)
            cv2.imshow("HSV Tool", canvas_copy)

    elif event == cv2.EVENT_LBUTTONUP:
        ref_point.append((x, y))
        cropping = False
        selection_done = True

        # Рисуем финальный прямоугольник
        cv2.rectangle(canvas, ref_point[0], ref_point[1], (0, 255, 0), 2)
        cv2.imshow("HSV Tool", canvas)

def adjust_hsv_ranges():
    global ref_point

    # Извлечение выделенной области
    x1, y1 = ref_point[0]
    x2, y2 = ref_point[1]

    # Убедимся, что область находится внутри изображения
    x1 = max(0, min(x1, hsv_resized_img.shape[1] - 1))
    x2 = max(0, min(x2, hsv_resized_img.shape[1] - 1))
    y1 = max(0, min(y1, hsv_resized_img.shape[0] - 1))
    y2 = max(0, min(y2, hsv_resized_img.shape[0] - 1))

    if x1 == x2 or y1 == y2:
        return None, None  # Если область нулевая, возвращаем None

    if x1 > x2: x1, x2 = x2, x1
    if y1 > y2: y1, y2 = y2, y1

    roi = hsv_resized_img[y1:y2, x1:x2]

    # Поиск минимальных и максимальных значений HSV в области
    h_min, s_min, v_min = np.min(roi[:, :, 0]), np.min(roi[:, :, 1]), np.min(roi[:, :, 2])
    h_max, s_max, v_max = np.max(roi[:, :, 0]), np.max(roi[:, :, 1]), np.max(roi[:, :, 2])

    # Установка трекбаров в соответствующие значения
    cv2.setTrackbarPos('H_min', 'HSV Tool', h_min)
    cv2.setTrackbarPos('S_min', 'HSV Tool', s_min)
    cv2.setTrackbarPos('V_min', 'HSV Tool', v_min)
    cv2.setTrackbarPos('H_max', 'HSV Tool', h_max)
    cv2.setTrackbarPos('S_max', 'HSV Tool', s_max)
    cv2.setTrackbarPos('V_max', 'HSV Tool', v_max)

    return (h_min, s_min, v_min), (h_max, s_max, v_max)

radius = 150  # Радиус круга HSV

cv2.namedWindow("HSV Tool", cv2.WINDOW_NORMAL)
cv2.resizeWindow("HSV Tool", 600, 700)

cv2.createTrackbar('H_min', 'HSV Tool', 0, 180, lambda x: x)
cv2.createTrackbar('S_min', 'HSV Tool', 0, 255, lambda x: x)
cv2.createTrackbar('V_min', 'HSV Tool', 0, 255, lambda x: x)
cv2.createTrackbar('H_max', 'HSV Tool', 180, 180, lambda x: x)
cv2.createTrackbar('S_max', 'HSV Tool', 255, 255, lambda x: x)
cv2.createTrackbar('V_max', 'HSV Tool', 255, 255, lambda x: x)

# Добавление возможности выделения области мышью
cv2.setMouseCallback("HSV Tool", click_and_crop)

hsv_min = hsv_max = None  # Инициализация переменных

while True:
    img = cv2.imread('duck.png')
    resized_img = resize_with_padding(img, 400)
    hsv_resized_img = cv2.cvtColor(resized_img, cv2.COLOR_BGR2HSV)

    h1 = cv2.getTrackbarPos('H_min', 'HSV Tool')
    s1 = cv2.getTrackbarPos('S_min', 'HSV Tool')
    v1 = cv2.getTrackbarPos('V_min', 'HSV Tool')
    h2 = cv2.getTrackbarPos('H_max', 'HSV Tool')
    s2 = cv2.getTrackbarPos('S_max', 'HSV Tool')
    v2 = cv2.getTrackbarPos('V_max', 'HSV Tool')
    h_min = np.array((h1, s1, v1), np.uint8)
    h_max = np.array((h2, s2, v2), np.uint8)
    
    img_bin = cv2.inRange(hsv_resized_img, h_min, h_max)

    # Подготовка основного изображения 700x800
    canvas = np.zeros((700, 800, 3), dtype=np.uint8)

    # Обновление круга HSV с учетом трекбаров
    hsv_circle = create_hsv_circle(radius)
    masked_hsv_circle = apply_hsv_mask(hsv_circle, h1, s1, v1, h2, s2, 255)

    # Размещение изображений на канвасе
    canvas[:400, :400] = resized_img
    canvas[:400, 400:800] = cv2.cvtColor(img_bin, cv2.COLOR_GRAY2BGR)
    canvas[400:700, :300] = masked_hsv_circle

    # Если выделение завершено, автоматически подобрать диапазоны цветов
    if selection_done:
        hsv_min, hsv_max = adjust_hsv_ranges()
        selection_done = False


    text = f"HSV_min: {h_min}\nHSV_max: {h_max}"

    
    y0, dy = 450, 50
    for i, line in enumerate(text.split('\n')):
        y = y0 + i*dy
        cv2.putText(canvas, line, (320, y), cv2.FONT_HERSHEY_COMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
    
    # Если область выделяется, показать промежуточный результат
    if cropping and canvas_copy is not None:
        cv2.imshow("HSV Tool", canvas_copy)
    else:
        cv2.imshow("HSV Tool", canvas)
    
    ch = cv2.waitKey(5)
    if ch == 27:
        break

cv2.destroyAllWindows()
Это достаточно сложный скрипт, но попробуйте разобраться в его работе. Это будет очень полезно.

Передвигая ползунки, устанавливая минимальные и максимальные значения каналов H S V, чтобы потом, с помощью функции:

img_bin = cv2.inRange(hsv, h_min, h_max)

получить бинаризованное изображение.

Из этого видео видно, что это того, чтобы выделить уточку нужен диапазон от (10, 61, 139) до (75, 255, 255).

В этом случае можно найти объект нужного нам цвета, даже с тенями и меняющимся уровнем освещения!

Пропустить Навигационные Ссылки.
Чтобы оставить комментарий нужна авторизация
Печать