5.27. Поиск окружностей и прямоугольников

Линии и сегменты охватывают прямые границы в захваченном кадре, но многие реальные объекты, которые ищет камера, не являются прямыми. Монета, лежащая на столе, – это окружность. Печатная этикетка, стикер, верх коробки, видимый под углом, – это четырёхугольник. Модуль image предоставляет отдельный детектор для каждого случая: поиск окружностей в стиле Хафа и поиск четырёхсторонних фигур, заимствованный из AprilTag.

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

5.27.1. Окружности Хафа

find_circles() выполняет вариант преобразования Хафа для окружностей. Каждый пиксель границы из предварительного прохода Собеля голосует за каждую окружность, которая могла бы через него пройти; окружности, набравшие достаточно голосов, возвращаются.

circles = img.find_circles(threshold=3500,
                            x_margin=10, y_margin=10, r_margin=10,
                            r_min=10, r_max=80, r_step=2)

for c in circles:
    img.draw_circle((c.x, c.y, c.r), color=(255, 0, 0))

threshold – это минимальная сумма величин границ Собеля вдоль кандидата окружности. Более крупные окружности обводят больше пикселей и поэтому требуют более высоких порогов для прохождения; значение, находящее монету радиусом 20 пикселей, также сработает на шуме вокруг границы в 100 пикселей, тогда как значение, настроенное на крупную монету, пропустит малую. Когда диапазон целевого радиуса известен, правильный порог масштабируется с длиной окружности: примерно threshold = 50 * 2 * pi * r даёт разумную отправную точку, а правильное значение находится после короткого прохода настройки.

r_min, r_max и r_step задают поиск по радиусу. Без границ детектор перебирал бы каждый радиус от нескольких пикселей до половины ширины изображения, что одновременно медленно и ведёт к ложным срабатываниям. Установка r_min и r_max так, чтобы с щедрым запасом охватить ожидаемый размер цели (например, r_min=15, r_max=25 для монеты с известным радиусом около 20 пикселей), существенно сокращает работу и улучшает отношение сигнал/шум голосов. r_step управляет детализацией поиска; большие шаги работают быстрее и могут пропустить окружность, истинный радиус которой попадает между двумя выбранными значениями. Значение по умолчанию r_step=2 является разумным компромиссом.

x_margin, y_margin и r_margin управляют слиянием близких обнаружений так же, как theta_margin и rho_margin делают это для обнаружения линий. Одна физическая окружность на изображении голосует за кластер окружностей-кандидатов, чьи центры и радиусы совпадают с точностью до нескольких пикселей; поля схлопывают каждый кластер до его пика перед построением списка результатов. Большие поля возвращают меньше, но более достоверных обнаружений; меньшие поля возвращают больше обнаружений с возможными почти-дубликатами.

x_stride и y_stride задают шаг сканирования голосования так же, как и в других детекторах. Значения по умолчанию 2 и 1 подходят для большинства изображений; увеличение обоих до 4 является стандартным компромиссом ради скорости для изображения, заведомо содержащего крупные окружности.

Каждая возвращаемая Circle несёт x, y, r (центр и радиус) и magnitude (суммарное число голосов, полезное как оценка достоверности для сортировки или фильтрации). Отрисовка обнаружения обратно в кадр выполняется одним вызовом: draw_circle() принимает 3-кортеж (x, y, r), доступный как (c.x, c.y, c.r) прямо из результата.

5.27.2. Прямоугольники

find_rects() заимствует детектор четырёхугольников из конвейера AprilTag – та же процедура, которая находит чёрный квадрат вокруг тега, предоставлена сама по себе как универсальный искатель прямоугольников.

rects = img.find_rects(threshold=12000)

for r in rects:
    img.draw_rectangle(r.rect, color=(0, 255, 0))
    for corner in r.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))

threshold – это минимальная сумма величин границ по периметру прямоугольника. Печатный чёрный прямоугольник на белом фоне в хорошо освещённом кадре легко преодолевает 10000; слабый прямоугольник на текстурированном фоне может потребовать снижения до 2000 – меняя ложные срабатывания на чувствительность. Как и для детектора окружностей, правильное значение находится после быстрого прохода настройки с целевыми объектами в кадре.

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

Каждый возвращаемый Rect несёт ограничивающую рамку, выровненную по осям, – x, y, w, h, плюс 4-кортеж rect, который ожидает draw_rectangle(), – и четыре обнаруженных угла как corners. Ограничивающая рамка – это то, что приложение использует для грубой позиции и размера; углы описывают сам проективный четырёхугольник. Когда камера смотрит на плоскую цель под углом и приложению нужно устранить трапециевидное искажение – прочитать текст на этикетке, взять образец цвета с плоского участка – углы передаются напрямую в rotation_corr() с именованным аргументом corners= (см. коррекция объектива и перспективы), и на выходе получается выпрямленный прямоугольник, готовый к дальнейшему анализу.

Предупреждение

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

5.27.3. Когда детектор даёт сбой

Окружности в особенности выигрывают от предварительной фильтрации входных данных. Шумный кадр даёт множество случайных пикселей границ, которые все голосуют, и получающееся пространство Хафа имеет широкие размытые пики, которые механизму слияния трудно разделить. Проход gaussian() или mean() перед find_circles() сглаживает шум, оставляя истинные границы нетронутыми; детектор возвращает более чистые пики за меньшее время.

Для прямоугольников распространённый режим отказа противоположный: низкий контраст между прямоугольником и его фоном означает, что сумма величин границ никогда не преодолевает threshold. Проход histeq() для перераспределения диапазона яркости по всему диапазону от 0 до 255 восстанавливает контраст, который нужен детектору. (Контраст должен существовать где-то на изображении; эквализация гистограммы может лишь усилить то, что уже есть.)