Цветовая модель — это способ представления цветов в виде числовых значений.
Две наиболее часто используемые цветовые модели в компьютерном зрении — это 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 цветовыми моделями:
import cv2
image = cv2.imread('path_to_image.jpg')
# Преобразование из BGR в HSV
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
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)
.
В этом случае можно найти объект нужного нам цвета, даже с тенями и меняющимся уровнем освещения!