Pull to refresh

О различении объектов по цвету

Reading time7 min
Views8.5K
Статья предназначена, в первую очередь, для людей ранее не работавших с цветом. Она описывает те нюансы, интересные моменты и подводные камни, которые я узнал, когда впервые начал работать с распознаванием цветов (задачи типа сличения цвета двух объектов, нахождения нужного объекта роботом по запросу человека и т.д.).

image

Рассмотрим оригинальную систему, которая придумала понятие цвета — человек. Компьютер, распознающий цвета, должен подражать именно человеческой системе восприятия.
Так что вот основной вопрос: как видит цвета человек?

Опустим пока как взаимодействует материал предметов в поле зрения человека со светом, рассмотрим уже пришедшие в глаз волны. Непосредственное определение частоты света нам недоступно, мы лишь сравниваем с ожидаемыми показателями по интенсивности излучения на трёх базовых частотах (RGB). Это три базовых цвета, для каждого из которых в глазу есть свой детектор.

Дальше происходит главное — значение со всяких колбочек и палочек необходимо преобразовать в цвет. Причём производит сравнение человек не в абсолютных значениях, как люди с абсолютным слухом — с частотой взятой ноты, а в относительных - когда на предмет падают тени, блики и т.п. После чего он идентифицирует группы тонов и (это важно!) оттенков в блоки под одну метку — к примеру: «это жёлтый».

→ Дополнительно можно почитать про относительность восприятия цветов в принципе, и про то, как именно, и в каком порядке производилась маркировка цветов

Теперь о компьютере.

На камеру приходит сигнал, матрица распознаёт те же три основных цвета, а дальше… А дальше она сжимает данные, иногда производит базовую коррекцию линзовых аббераций и стримит полученное на компьютер. Компьютер уже может дешифровать поток при помощи разнообразных кодеков и получить видео. Но это — уже совсем не то видео, которое мы ищем.
Опустим пока разрешение и искажения, сосредоточимся всё таки на цвете. Если мы покажем на камеру однотонный, равномерно освещённый прямоугольник, пиксели составляющие его изображение в кадре будут разными. Можно даже пойти немного дальше. Подгрузить на одном из этапов такой предобработки видео вставку в виде блока пикселей. Тот же тестовый прямоугольник, только мы можем не волноваться об уровне света и текстуре окраски.

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

image

Теперь эта картинка (уже с мусором), должна быть сграблена в программу, которая будет её обрабатывать. Я для этих целей применял библиотеку OpenCV. И в ней также есть ряд особенностей. Посвящены они тому, как картинка хранится и передаётся. Существуют различные режимы чтения. Суть проста: некоторые трёхканальные картинки передаются устройствами как четырёхканальные, даже если четвёртого канала нет. Библиотечная функция чтения отсчитывает байты ожидая структуру {26,54,250}{40,47,245}{30,26,255}…

Тогда как потоковый файл имеет формат{26,54,250,111}{40,47,245,110}{30,26,255,112}…
Чтение по три байта даст следующий результат {26,54,250,111}{40,47,245,110}{30,26,255,112}

image

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

Наконец мы получили правильный файл. Но как выделить на нём цвет — неясно. Если бы объект на самом деле обозначали пиксели одного цвета, не было бы никаких проблем — просто перебрать пиксели и выбрать каких там больше всего. Но есть две проблемы. Первая — освещение. Вторая — обработка изображения той платкой, на которой сидит камера. Можно ввести порог разницы, при котором считать похожие пиксели одинаковыми — это тоже самое, как если бы мы понизили разрешение цветов до нашего собственного. Но к сожалению, есть целых две причины так не делать. Первая — пиксели одного цвета могут очень сильно различаться по значениям RGB. И вторая — объём в RGB-пространстве каждого цвета (так, как их определили люди) разный. Ну, а раз так, то можно выделить блок под каждый цвет, указать 6 ограничивающих координат и поручить сети их точно определить… Но опять нет — блоки в RGB имеют сложную форму, это не параллелепипеды. Придётся добавлять параметры, если уменьшать шаг (вплоть до 1/255-ой доли необязательно, но всё же уменьшить его придётся) будет увеличиваться число входных данных сети, и мусорные данные, к сожалению вырастут быстрее чем полезные. Нужна предобработка. Присмотревшись к пикселям объекта можно найти отличную штуку — различия в параметре hue в пространстве HLS будет меньше, чем у любого параметра в RGB.

image

Так выглядит HLS

Я перевёл все пиксели в HLS (на всякий случай вручную написал функцию — существуют разные системы перевода, потому встроенную библиотечную функцию я не трогал, а сымитировал ту, что заложена в мой графический редактор), а затем указал базовые граничные значения, отделяющие цвета. Взять их можно, например, внимательно посмотрев на эту диаграмму:

image

Теперь к сложному. От остальных параметров мы всё ещё не избавились. Белый, чёрный, серый, коричневый — вполне базовые цвета в представлении человека, однако к hue имеют довольно слабое отношение — без освещённости и сатурации с ними не разобраться. Чтож, добавляем такие же базовые барьеры на глаз, и смотрим как ведёт себя система.

Я выделил и промаркировал 15 основных цветов, затем начал тестировать. Я воспользовался стандартными инструментами поиска гугла по картинкам, чтобы отфильтровать результаты по цвету, и брал для начала не фото с объектом и фоном, а более монотонные картинки (к примеру, трава, или жёлтые листья). Просмотреть и отфильтровать ненужные в датасете картинки самостоятельно — обязательная часть обучения. Сеть у меня была без тренера, я подкручивал значения вручную.

image

Затем, получив сотню правильно опознаных цветов подряд, и перешёл к объектам на фоне (взял уже из рабочей среды, те, для которых будет применятся алгоритм). Здесь есть хитрость, и она состоит в том, что при голосовании пикселей за определённый цвет, нужно под каждый кластер пикселей создавать свою ветку, своего кандидата. Перебор всё равно делается по всем пикселям, так что несложно создать несколько ячеек под один цвет. К примеру на следующей картинке построчный перебор выявит несвязность множеств красных пикселей и создаст две записи формата «red» — 0. Первую будут инкрементить пиксели одного красного шарика, за вторую будут голосовать пиксели второго.

image

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

Чуть более подробно:
В моём случае
— вычисляем разницу между результатами и если она большая (в 2.5 раз и больше) — выводи первый результат
— вычисляем засветки и снег. Для этого проводим цветокоррекцию и повторяем вычисления.
— используем информацию об ожидаемом положении объекта на снимке чтобы уменьшить вес присвоенный цвету фона.

Пример «неправильной» картинки:

image

Теперь всё ещё очень мешает опознанию объекта того же цвета разность в освещённости. В ряде случаев подходит цветокоррекция, но иногда она беспомощна — серое засвечивается до белого, чёрное — до серого или белого.

Для этого используется информация об объекте (который мы отличили по цвету на одном из предыдущих кадров и отслеживаем как на него падает тень, либо блик). Мы считываем все те пиксели, которые голосовали за определившийся цвет. И теперь считаем среднее их значение. Получаем базовый цвет объекта и составляем палитру возможных изменений.

Очень важная вещь: цвет воспроизводится монитором так, чтобы соответствовать восприятию человека; поэтому и хранение данных о цвете, вслед за самими устройствами воспроизведения, оптимизировано под глаза пользователей, и значения RGB не соответствуют реальной доли этих цветов в воспринимаемом нами цвете (а взглянув на широкие спектры отражённого света можно убедиться, что в них помещаются все три базовых цвета).

Поэтому смешивая базовый цвет с другими, и тестируя блики — нужно возводить значения RGB в квадрат перед началом всех процедур, и брать корень в конце. После чего я переводил в HLS и выполнял проверку того, насколько изменился цвет. Я брал базовый цвет, смешивал его по очереди с чёрным, белым, красным, синим, зелёным, и цветом из основного набора, который определился первым алгоритмом. После этого повторял процедуру несколько раз, чтобы получить несколько градаций каждого варианта. Для сличения с частотой 1/30 и 1/24 можно брать только две итерации — этого хватит с головой (даже можно удалить самые дальние значения, чтобы перестраховаться — значения заведомо будут совпадать).

Затем делается что-то вроде хэшфункции — берутся расстояния между цветами в палитре (я брал за расстояние L1 — L2 + G1 — G2, так как эта странная метрика хорошо себя показала в эксперименте, вообще стоит попробовать любые комбинаций разностей RGB и HLS), и затем расставляются в сортированный список, в порядке убывания.

примеры автоматически сформированных палитр (для системы объектов разных цветов все палитры объединялись в одну в целях оптимизации, но ряд всё равно расчитывался только по тем цветам, которые относятся только к одному объекту):

image

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

После обязательно нужно откалибровать пороговые значения под конкретную задачу.

Картинок с финальных тестов нет, но в конце я загрузил в алгоритм то самое платье и получил «white» и «yellow».

Итак, что же в итоге?

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

Моменты, на которые стоит обратить внимание:

  • кадр с камеры приходит с искажениями в значениях RGB, которые разные для каждого пикселя;
  • иногда приходит лишний, четвёртый канал, который не пишется, но попадает в картинку из программ;
  • значения на мониторе правильные, но хранятся не они, а искажённые координаты, пропорциональные корню из реальных значений.

Теперь о том, где можно применить такую штуку, и поиграться с ней:

  1. в системах Person Recognition, где кроме лица человека можно занести в базу информацию о телосложении, сумке и цвете любимой футболки;
  2. в трекерах, как замену грубым начальным фильтрам (типа по значению диффузии);
  3. в технике для поиска разнотипных объектов для захвата манипулятором;
  4. для сегментации тепловых и вибро- изображений, уже приведённых к некоторому диапазону цветов;
  5. для чего угодно! Компьютер различает цвета как человек. Для удобства можно возвращать с вычисленным лейблом и усреднённый цвет, и какие-то служебные коды. Я пользовался Vec3b форматом вывода, и слегка кастомизировал этот тип чтобы передавать здесь же ошибку рапознавания и степень уверенности в ответе.

Картинка с маркировкой цветов отсюда
Tags:
Hubs:
+12
Comments1

Articles