5.25. Поиск блобов¶
Пороговая обработка превращает захваченный кадр в бинарную маску: каждый пиксель либо проходит проверку порога, либо нет. Это отвечает на вопрос какие интересующие приложение цвета присутствуют в сцене, но не где – маска представляет собой просто море единиц и нулей. Следующий шаг – обнаружение блобов: обход маски, поиск непрерывных областей из проходящих пикселей и возврат каждой из них в виде объекта с позицией, размером, ориентацией и другими свойствами, на основе которых приложение может действовать.
find_blobs() – это рабочая лошадка для данного шага и наиболее распространённая точка входа в мир объектов-результатов модуля image. Отслеживание цветного мяча, движение вдоль линии, нарисованной на полу, подсчёт того, сколько ярких пятен видит тепловой датчик, определение того, включён синий светодиод или выключен – один и тот же вызов охватывает все эти задачи. Входные данные меняются (пороги, искомая область, фильтры, применяемые к результату), но шаблон вызова остаётся прежним.
5.25.1. Базовый вызов¶
find_blobs принимает список порогов и возвращает список объектов-результатов блобов:
thresholds = [(30, 100, 15, 127, 15, 127)] # LAB threshold for red
blobs = img.find_blobs(thresholds)
for b in blobs:
img.draw_rectangle(b.rect, color=(255, 0, 0))
img.draw_cross(b.cx, b.cy, color=(255, 0, 0))
Каждый кортеж порога имеет ту же форму, что и пороги, передаваемые в binary() – шесть элементов (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) для изображения RGB565 (границы заданы в LAB) и два элемента (lo, hi) для изображения в оттенках серого. В одном вызове можно указать до 32 порогов, что и делает find_blobs() столь гибким: красные, зелёные и синие маяки можно отслеживать одновременно, каждый из них вносит свои блобы в возвращаемый список, а свойство code каждого блоба указывает, какому порогу он соответствует.
Вызовы draw_rectangle() и draw_cross(), приведённые выше, аннотируют захваченный кадр для предпросмотра в IDE. Результат блоба уже содержит b.rect (ограничивающую рамку в виде кортежа из 4 элементов), а также b.cx / b.cy (целочисленный центроид), поэтому отрисовка обнаружения обратно в кадр – это два вызова методов.
5.25.2. Что содержит результат¶
Каждый Blob представляет собой кортеж атрибутов, упаковывающий вместе всё, что детектор измерил об области. Свойства делятся на четыре группы.
Группа ограничивающая рамка и центроид – x, y, w, h, rect, cx, cy, cxf, cyf – описывает положение блоба. rect – это кортеж из 4 элементов (x, y, w, h), который ожидают методы рисования; cx и cy – центроид в целочисленных пиксельных координатах; cxf и cyf – центроид в субпиксельных координатах с плавающей точкой, что полезно, когда вышестоящая калибровка учитывает дробные положения.
Группа дескрипторы формы – pixels, area, density, perimeter, roundness, elongation, compactness, rotation – описывает, как выглядит блоб. pixels – количество проходящих пикселей; area – площадь ограничивающей рамки, выровненной по осям (w * h); density – отношение этих двух величин, которое приближается к 1.0 для сплошного прямоугольника и падает к 0.0 для тонкого диагонального штриха. roundness и compactness обе оценивают, насколько блоб круглый, но с разных геометрических точек зрения (roundness – по моментам второго порядка, compactness – по отношению периметра к площади); elongation для удобства равно 1.0 - roundness. rotation – ориентация главной оси в радианах, которая наиболее точна на вытянутых блобах и становится зашумлённой на почти круглых (у неоднозначной оси нет чётко определённого направления).
Группа метаданные порога и объединения – code, count – указывает, какому порогу соответствует блоб и сколько исходных блобов было объединено в возвращаемый. code – 32-битная битовая карта, в которой установлен один бит на каждый совпадающий порог (один порог даёт code == 1; объединённый многоцветный блоб может иметь несколько установленных битов); count равно 1, если только merge=True не объединил несколько обнаружений в одно.
Группа углы – corners, min_corners – задаёт повёрнутую геометрию блоба. corners – кортеж из 4 элементов (x, y) крайних точек, извлечённых из контура блоба и отсортированных по часовой стрелке начиная с верхнего левого; min_corners – кортеж из 4 углов повёрнутого прямоугольника минимальной площади, охватывающего блоб. Прямоугольник минимальной площади – это плотная подгонка; выровненный по осям rect – свободная подгонка, выровненная по сетке пикселей. Оба варианта полезны в зависимости от того, нужна ли следующему этапу ориентированная рамка или обычная.
Блоб несёт в себе выровненную по осям ограничивающую рамку (rect, x, y, w, h), центроид (cx, cy или субпиксельные cxf, cyf), повёрнутый прямоугольник минимальной площади (min_corners плюс rotation), а также необязательные линии главной / малой оси, вычисляемые с помощью вспомогательных функций уровня модуля, описанных ниже.¶
5.25.3. Фильтрация поиска¶
Захваченный кадр обычно содержит пиксели, которые соответствуют порогу по причинам, не связанным с интересующим приложение объектом: зеркальные блики, удалённые фоновые объекты, пиксели шума изображения, которые случайно попадают в диапазон LAB. Именованные аргументы метода find_blobs() – это первая линия защиты.
roi ограничивает поиск областью кадра, как и любой другой метод модуля image. Приложение, которое знает, что объект может появиться только в нижней половине поля зрения, передаёт roi=(0, h//2, w, h//2) и игнорирует всё, что выше; сэкономленное время возвращается в частоту кадров.
area_threshold и pixels_threshold оба отфильтровывают блобы, которые слишком малы, чтобы о них беспокоиться. area_threshold отбрасывает блобы, ограничивающая рамка которых имеет площадь меньше указанного числа пикселей (полезно для фильтрации разрозненного шума); pixels_threshold отбрасывает блобы, у которых меньше указанного числа проходящих пикселей (полезно для фильтрации блобов, которые велики, но разрежены, например, пороговый точечный узор с одним-двумя совпадающими пикселями тут и там). Оба значения по умолчанию равны 10; повышение их до сотен для переднего плана размером в несколько сантиметров отбрасывает каждую крупицу мелкого шума.
x_stride и y_stride задают шаг в пикселях, который делает сканер, выискивая блоб для начала трассировки. Шаг – это не разрешение трассировки – трассировка всегда следует фактической границе блоба с точностью до одного пикселя – но он управляет тем, как быстро сканирование находит начальный пиксель. Когда известно, что блобы большие (цветная цель размером с кулак в полуметре от камеры, легко в сотню пикселей шириной), x_stride=4, y_stride=4 сокращает время сканирования в шестнадцать раз без практической потери в обнаружении. Когда блобы малы (удалённый светодиодный маяк шириной в несколько пикселей), шаги должны оставаться равными 1, чтобы не перешагнуть через них полностью. invert инвертирует проверку порога: совпадение становится несовпадением, и процедура возвращает блобы из не проходящих пикселей.
threshold_cb – это Python-функция обратного вызова, вызываемая для каждого блоба после пороговой обработки, но до построения окончательного списка результатов. Функция обратного вызова получает блоб и возвращает True, чтобы сохранить его, или False, чтобы отбросить. Это подходящее место для применения произвольных фильтров уровня Python к свойствам, которые именованные аргументы не предоставляют напрямую – минимальная плотность, определённый диапазон вращения, специфическая комбинация битов кода после объединения. Именованные аргументы – это фильтры в нативном коде, работающие быстро; функция обратного вызова выполняется на Python и работает медленнее, но не ограничена в том, что может выразить.
5.25.4. Объединение перекрывающихся блобов¶
merge=True выполняет постобработку списка результатов для объединения блобов, ограничивающие прямоугольники которых перекрываются. Естественное применение – обнаружение цели, цвет которой камера видит как несколько пороговых областей из-за зеркальных бликов, теневых линий или несогласованного освещения по объекту: один красный мяч может вернуться в виде трёх-четырёх небольших красных блобов, которые вместе очерчивают мяч. При merge=True три блоба становятся одним большим, rect покрывает их объединение, code представляет собой побитовое ИЛИ кодов объединённых блобов (так что многоцветное объединение указывает, какие цвета внесли вклад), а count сообщает, сколько исходных блобов было объединено.
margin увеличивает или уменьшает ограничивающие прямоугольники перед проверкой перекрытия. При margin=2 блобы, ограничивающие прямоугольники которых находятся в пределах 2 пикселей друг от друга, всё равно объединяются; при margin=-2 объединяются только блобы, ограничивающие прямоугольники которых перекрываются как минимум на 2 пикселя. Естественная настройка: положительный отступ для обработки блобов, которые порог разбил на соседние части; отрицательный отступ, чтобы держать раздельно тесно сгруппированные различные объекты.
merge_cb выполняется для каждой пары-кандидата перед тем, как происходит объединение. Функция обратного вызова получает два блоба и возвращает True, чтобы разрешить объединение, или False, чтобы предотвратить его. Это правильный инструмент для перекрёстной проверки объединений, которые пропускает геометрическое правило – например, отказ от объединения двух блобов, чьи углы rotation расходятся более чем на порог, или отказ от объединения малого блоба в гораздо больший, если малый – это просто крапинка.
5.25.5. Проекционные гистограммы¶
x_hist_bins_max и y_hist_bins_max прикрепляют необязательные проекционные гистограммы к каждому блобу. Проекционная гистограмма – это подсчёт проходящих пикселей вдоль одной оси: гистограмма по оси X суммирует проходящие пиксели по столбцам внутри ограничивающей рамки блоба, а гистограмма по оси Y суммирует их по строкам. Оба значения по умолчанию равны нулю – гистограммы не вычисляются, пока не указан ненулевой max, поскольку иначе они добавляли бы работу к каждому обнаружению.
Когда они вычислены, гистограммы предоставляют дешёвый одномерный сигнал, над которым приложение может выполнять дальнейший анализ: обнаружение положения вертикальной полосы внутри блоба, поиск точки разрыва двухцветной цели, подсчёт того, сколько пропусков появляется вдоль длинной оси. Они заполняются как свойства x_hist_bins и y_hist_bins каждого Blob.
5.25.6. Дополнительные геометрические помощники¶
Несколько дополнительных геометрических мер существуют в виде функций уровня модуля, которые принимают блоб и возвращают запрошенное измерение:
image.get_solidity()возвращает плотность заполнения блоба – пиксели, делённые на площадь выпуклой оболочки. Сплошная заполненная область близка к1.0; блоб с вогнутостями (подкова, рука с расставленными пальцами) опускается значительно ниже.image.get_convexity()возвращает выпуклость – периметр выпуклой оболочки, делённый на периметр блоба. Идеально выпуклый блоб равен1.0; зазубренные или с вырезами блобы дают меньше.image.get_major_axis_line()иimage.get_minor_axis_line()возвращают объектыLineвдоль главной и малой осей блоба, выведенные из повёрнутого прямоугольника минимальной площади.image.get_enclosing_circle()возвращаетCircle, охватывающий блоб – полезно, когда следующему этапу нужна окружность для рисования или проверки.image.get_enclosed_ellipse()возвращает кортеж из 5 элементов(cx, cy, rx, ry, rotation)для эллипса, вписанного в прямоугольник минимальной площади блоба. Эти значения напрямую передаются вdraw_ellipse().
5.25.7. Автообучение порога¶
Детектор блобов хорош ровно настолько, насколько хороши пороги, с которыми он запускается, а работа по поиску правильного порога для цветовой цели – это отдельная задача. Два распространённых подхода сокращают эту работу.
Первый – это интерактивный выбор в IDE: захватите кадр, перетащите прямоугольник вокруг примера целевого цвета и позвольте редактору порогов IDE сообщить границы LAB, которые он видит. Эти границы вставляются в скрипт как пороги find_blobs(), и детектор готов.
Второй – это программное автообучение: процедура калибровки, работающая на камере, захватывает кадр, берёт гистограмму известного участка, где находится цель (get_histogram() с roi=), и считывает диапазон значений участка с гистограммы с помощью get_percentile(). 5-й процентиль задаёт нижнюю границу каждого канала, а 95-й – верхнюю, игнорируя случайные выбросы пикселей на обоих концах. На изображении RGB565 один вызов процентиля сообщает все три канала LAB сразу, поэтому два вызова дают шесть чисел, которые ожидает find_blobs():
h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
lo.a_value, hi.a_value,
lo.b_value, hi.b_value)