Сегментация изображения с использованием цветовых пространств в OpenCV + Python

2 мая, 2020

Подписывайся на наш канал в Telegram, чтобы ежедневно совершенствоваться в Python. Там выходят задачи, полезные советы и сливы платных курсов - перейти

Перевод статьи

Оглавление

  • Что такое цветовые пространства?
  • Простая сегментация с использованием цветовых пространств
    • Цветовые пространства и чтение изображений в OpenCV
    • Визуализация Немо в цветовом пространстве RGB
    • Визуализация Немо в цветовом пространстве HSV
    • Выбор диапазона
  • Обобщает ли эта сегментация родственников Немо?
  • Вывод

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

В этой статье вы узнаете, как просто сегментировать объект из изображения на основе цвета в Python, используя OpenCV. Популярная библиотека компьютерного зрения, написанная на C / C ++ с привязками для Python, OpenCV предоставляет простые способы манипулирования цветовыми пространствами.

Хотя вам не нужно быть уже знакомым с OpenCV или другими вспомогательными пакетами, используемыми в этой статье, предполагается, что у вас есть хотя бы базовое понимание кодирования в Python .

Что такое цветовые пространства?

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

RGB считается «аддитивным» цветовым пространством , и можно представить, что цвета создаются из яркого количества красного, синего и зеленого света на черном фоне.

Вот еще несколько примеров цветов в RGB:

цветЗначение RGB
красный255, 0, 0
оранжевый255, 128, 0
розовый255, 153, 255

RGB – одна из пяти основных моделей цветового пространства, каждая из которых имеет множество ответвлений. Цветовых пространств так много, потому что разные цветовые пространства полезны для разных целей .

В мире печати CMYK полезен, потому что он описывает цветовые комбинации, необходимые для получения цвета на белом фоне. В то время как кортеж 0 в RGB черный, в CMYK кортеж 0 белый. Наши принтеры содержат баллончики с чернилами голубого, пурпурного, желтого и черного цветов.

В некоторых областях медицины стеклянные предметные стекла, на которых установлены образцы окрашенных тканей, сканируются и сохраняются в виде изображений. Их можно анализировать в пространстве HED , представляющем насыщенность типов пятен – гематоксилин, эозин и DAB – применительно к исходной ткани.

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

На самом деле цвет – это непрерывное явление, означающее, что существует бесконечное количество цветов. Цветовые пространства, однако, представляют цвет через дискретные структуры (фиксированное число целых чисел), что является приемлемым, поскольку человеческий глаз и восприятие также ограничены. Цветовые пространства полностью способны представить все цвета, которые мы можем различить.

Теперь, когда мы понимаем концепцию цветовых пространств, мы можем использовать их в OpenCV.

Простая сегментация с использованием цветовых пространств

Чтобы продемонстрировать технику цветового пространства сегментации, мы приводим небольшой набор данных изображений в клоун материалов Real Python репозитария здесь для вас , чтобы скачать и играть с. Рыбу-клоуна легко узнать по ярко-оранжевому цвету, поэтому он является хорошим кандидатом для сегментации. Давайте посмотрим, как хорошо мы можем найти Немо на изображении.

Ключевыми пакетами Python, которым вы должны следовать, являются NumPy, основной пакет для научных вычислений в Python, Matplotlib, библиотека черчения и, конечно, OpenCV. В этой статье используются OpenCV 3.2.0, NumPy 1.12.1 и Matplotlib 2.0.2. Немного отличающиеся версии не будут иметь существенного значения с точки зрения следования и понимания концепций.

Если вы не знакомы с NumPy или Matplotlib, вы можете прочитать о них в официальном руководстве NumPy и в прекрасной статье Брэда Соломона о Matplotlib .

Цветовые пространства и чтение изображений в OpenCV

Во-первых, вам нужно будет настроить свою среду. В этой статье предполагается, что в вашей системе установлен Python 3.x. Обратите внимание, что хотя текущая версия OpenCV 3.x, имя импортируемого пакета по-прежнему cv2:>>>

>>> import cv2

Если вы ранее не установили OpenCV на свой компьютер, импорт не будет выполнен, пока вы не сделаете это сначала. Вы можете найти удобное руководство по установке в различных операционных системах здесь , а также собственное руководство по установке OpenCV . После того, как вы успешно импортировали OpenCV, вы можете просмотреть все преобразования цветового пространства, которые предоставляет OpenCV, и вы можете сохранить их все в переменную:>>>

>>> flags = [i for i in dir(cv2) if i.startswith('COLOR_')]

Список и количество флагов могут незначительно отличаться в зависимости от вашей версии OpenCV, но в любом случае их будет много! Посмотрите, сколько флагов у вас доступно:>>>

>>> len(flags)
258
>>> flags[40]
'COLOR_BGR2RGB'

Первые символы после COLOR_обозначают исходное цветовое пространство, а символы после 2являются целевым цветовым пространством. Этот флаг представляет собой преобразование из BGR (синий, зеленый, красный) в RGB. Как вы можете видеть, два цветовых пространства очень похожи, только первый и последний каналы поменялись местами.

Вам понадобится matplotlib.pyplotдля просмотра изображений, а NumPy для некоторых манипуляций с изображениями. Если у вас еще не установлены Matplotlib или NumPy, вам нужно будет pip3 install matplotlibи pip3 install numpyдо попытки импорта:>>>

>>> import matplotlib.pyplot as plt
>>> import numpy as np

Теперь вы готовы загрузить и изучить изображение. Обратите внимание, что если вы работаете из командной строки или терминала, ваши изображения появятся во всплывающем окне. Если вы работаете с ноутбуком Jupyter или чем-то подобным, они просто будут отображаться ниже. Независимо от ваших настроек, вы должны увидеть изображение, сгенерированное show()командой:>>>

>>> nemo = cv2.imread('./images/nemo0.jpg')
>>> plt.imshow(nemo)
>>> plt.show()
OpenCV использует BGR по умолчанию

Привет, Немо … или Дори? Вы заметите, что похоже, что синий и красный каналы были перепутаны. Фактически OpenCV по умолчанию считывает изображения в формате BGR. Вы можете использовать cvtColor(image, flag)флаг и флаг, который мы рассмотрели выше, чтобы исправить это:>>>

>>> nemo = cv2.cvtColor(nemo, cv2.COLOR_BGR2RGB)
>>> plt.imshow(nemo)
>>> plt.show()
BGR в RGB

Теперь Немо гораздо больше похож на себя.

Визуализация Немо в цветовом пространстве RGB

HSV – хороший выбор цветового пространства для сегментирования по цвету, но чтобы понять почему, давайте сравним изображение в цветовых пространствах RGB и HSV, визуализируя распределение цвета его пикселей. Трехмерный график показывает это довольно хорошо: каждая ось представляет один из каналов в цветовом пространстве. Если вы хотите узнать, как сделать 3D-график, просмотрите свернутый раздел:

Как сделать цветную трехмерную диаграмму рассеянияПоказать спрятать

Вот цветная диаграмма рассеяния для изображения Nemo в RGB:

3D разброс графика изображения в RGB

Из этого графика видно, что оранжевые части изображения охватывают почти весь диапазон значений красного, зеленого и синего цветов. Поскольку части Nemo растягиваются по всему графику, сегментировать Nemo в пространстве RGB на основе диапазонов значений RGB будет непросто.

Визуализация Немо в цветовом пространстве HSV

Мы видели Немо в пространстве RGB, поэтому теперь давайте посмотрим на него в пространстве HSV и сравним.

Как кратко упомянуто выше, HSV обозначает оттенок, насыщенность и значение (или яркость) и представляет собой цилиндрическое цветовое пространство. Цвета или оттенки моделируются как угловое измерение, вращающееся вокруг центральной вертикальной оси, которая представляет канал значений. Значения переходят от темного (0 внизу) к светлому сверху. Третья ось, насыщенность, определяет оттенки оттенка от наименее насыщенного по вертикальной оси до наиболее насыщенного, наиболее удаленного от центра:

Цилиндр цветового пространства HSV
Изображение: Википедия

Чтобы преобразовать изображение из RGB в HSV, вы можете использовать cvtColor():>>>

>>> hsv_nemo = cv2.cvtColor(nemo, cv2.COLOR_RGB2HSV)

Сейчас hsv_nemoхранит представительство Nemo в HSV. Используя ту же технику, что и выше, мы можем взглянуть на график изображения в HSV, сгенерированный свернутым разделом ниже:

Создание цветной трехмерной диаграммы рассеяния для изображения в HSVПоказать спрятать

3D-график рассеяния изображения в ВПГ

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

Выбор диапазона

Давайте порог Немо просто на основе простого ассортимента апельсинов. Вы можете выбрать диапазон, взглянув на график выше или используя онлайн-приложение для выбора цвета, такое как инструмент RGB to HSV . Образцы, выбранные здесь, являются светло-оранжевым и более темным оранжевым, который является почти красным:>>>

>>> light_orange = (1, 190, 200)
>>> dark_orange = (18, 255, 255)

Если вы хотите использовать Python для отображения выбранных вами цветов, нажмите на свернутый раздел:

Отображение выбранных цветов HSVПоказать спрятать

Что производит эти изображения, заполненные выбранными цветами:

Светлый и темно-оранжевый ассортимент

После того, как вы получите достойную цветовую гамму, вы можете использовать cv2.inRange()порог Nemo. inRange()принимает три параметра: изображение, нижний диапазон и верхний диапазон . Возвращает двоичную маску (с ndarray1 и 0) размером изображения, где значения 1указывают значения в диапазоне, а нулевые значения указывают значения снаружи:>>>

>>> mask = cv2.inRange(hsv_nemo, light_orange, dark_orange)

Чтобы наложить маску поверх исходного изображения, вы можете использовать cv2.bitwise_and(), в котором каждый пиксель данного изображения сохраняется, если соответствующее значение в маске 1:>>>

>>> result = cv2.bitwise_and(nemo, nemo, mask=mask)

Чтобы увидеть, что именно это сделало, давайте рассмотрим маску и исходное изображение с маской сверху:>>>

>>> plt.subplot(1, 2, 1)
>>> plt.imshow(mask, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(result)
>>> plt.show()
Маска и оригинал с наложенной маской

Вот оно! Это уже сделало достойную работу по отлову оранжевых частей рыбы. Единственная проблема в том, что у Немо также есть белые полосы … К счастью, добавление второй маски, которая ищет белые, очень похоже на то, что вы уже делали с апельсинами:>>>

>>> light_white = (0, 0, 200)
>>> dark_white = (145, 60, 255)

После того, как вы определили цветовую гамму, вы можете посмотреть на выбранные вами цвета:

Белый диапазон

Отображение белыхПоказать спрятать

Верхний диапазон, который я здесь выбрал, очень синий, потому что у белых есть оттенки синего в тенях. Давайте создадим вторую маску и посмотрим, захватывает ли она полосы Немо. Вы можете создать вторую маску так же, как вы сделали первую:>>>

>>> mask_white = cv2.inRange(hsv_nemo, light_white, dark_white)
>>> result_white = cv2.bitwise_and(nemo, nemo, mask=mask_white)

>>> plt.subplot(1, 2, 1)
>>> plt.imshow(mask_white, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(result_white)
>>> plt.show()
Маска и оригинал для белых полос

Неплохо! Теперь вы можете комбинировать маски. Добавление двух масок вместе приводит к получению 1значений, где бы ни находился оранжевый или белый, а это именно то, что нужно. Давайте сложим маски вместе и нанесем результаты:>>>

>>> final_mask = mask + mask_white

>>> final_result = cv2.bitwise_and(nemo, nemo, mask=final_mask)
>>> plt.subplot(1, 2, 1)
>>> plt.imshow(final_mask, cmap="gray")
>>> plt.subplot(1, 2, 2)
>>> plt.imshow(final_result)
>>> plt.show()
Финальная комбинированная маска и оригинал

По сути, у вас есть грубая сегментация Nemo в цветовом пространстве HSV. Вы заметите, что вдоль границы сегментации есть несколько случайных пикселей, и, если хотите, вы можете использовать размытие по Гауссу, чтобы убрать небольшие ложные обнаружения.

Размытие по Гауссу – это фильтр изображения, который использует функцию, называемую гауссовой, для преобразования каждого пикселя в изображении. Это приводит к сглаживанию шума изображения и уменьшению детализации. Вот как выглядит размытие для нашего изображения:>>>

>>> blur = cv2.GaussianBlur(final_result, (7, 7), 0)
>>> plt.imshow(blur)
>>> plt.show()
Финальный сегментированный Nemo с размытием

Обобщает ли эта сегментация родственников Немо?

Просто для удовольствия, давайте посмотрим, насколько хорошо эта техника сегментации обобщает изображения других рыб-клоунов. В репозитории есть подборка из шести изображений рыб-клоунов от Google, лицензированных для публичного использования. Изображения находятся в подкаталоге и проиндексированы nemo i .jpg, где i – индекс из 0-5.

Сначала загрузите всех родственников Немо в список:

path = "./images/nemo"

nemos_friends = []
for i in range(6):
   friend = cv2.cvtColor(cv2.imread(path + str(i) + ".jpg"), cv2.COLOR_BGR2RGB)
   nemos_friends.append(friend)

Вы можете объединить весь код, использованный выше, для сегментирования одной рыбы в функцию, которая будет принимать изображение в качестве входного и возвращать сегментированное изображение. Разверните этот раздел, чтобы увидеть, как это выглядит:

Функция Сегмент РыбаПоказать спрятать

С помощью этой полезной функции вы можете сегментировать всю рыбу:

results = [segment_fish(friend) for friend in nemos_friends]

Давайте рассмотрим все результаты, построив их в цикле:

for i in range(1, 6):
    plt.subplot(1, 2, 1)
    plt.imshow(nemos_friends[i])
    plt.subplot(1, 2, 2)
    plt.imshow(results[i])
    plt.show()
Друг Немо 1

На переднем плане рыба-клоун имеет оранжевые оттенки темнее, чем у нас.

Друг Немо 2

Затененная нижняя половина племянника Немо полностью исключена, но кусочки пурпурного анемона на заднем плане ужасно похожи на синие полосатые полосы Немо…

Друг Немо 3
Друг Немо 4
Друг Немо 5

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

Вывод

В этом руководстве вы увидели, что такое несколько разных цветовых пространств, как изображение распределяется по цветовым пространствам RGB и HSV, и как использовать OpenCV для преобразования между цветовыми пространствами и сегментами.

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


Совершенствуй знания каждый день у нас в Телеграм-каналах

Вопросы, реклама — VK | Telegram