7.13. Подавление немаксимумов

Сеть обнаружения обычно порождает несколько перекрывающихся кандидатных рамок вокруг одного и того же реального объекта: каждый якорь рядом с объектом срабатывает с похожей оценкой, и постпроцессор видит их все. Подавление немаксимумов (NMS) – это шаг, который превращает этот кластер в одну рамку.

Алгоритм короткий: отсортировать кандидатные рамки по оценке, взять рамку с наивысшей оценкой, подавить все остальные рамки, которые перекрываются с ней сверх выбранного порога, затем взять следующую по величине из оставшихся и повторить. Метрикой перекрытия служит отношение пересечения к объединению (IoU) – общая площадь двух рамок, делённая на их суммарную площадь, значение от 0 (нет перекрытия) до 1 (идентичные рамки). Аргумент конструктора nms_threshold у каждого поставляемого постпроцессора – это порог, выше которого рамки считаются дубликатами уже сохранённой рамки.

Слева три перекрывающиеся ограничивающие рамки вокруг одного объекта, помеченные оценками 0.92, 0.83 и 0.71. Стрелка указывает вправо, где остаётся одна уцелевшая рамка с оценкой 0.92. Две рамки с меньшими оценками подавлены рамкой с большей оценкой, потому что их отношение пересечения к объединению с ней превышает порог.

NMS сводит кластер перекрывающихся обнаружений к рамке с наивысшей оценкой.

7.13.1. Soft-NMS

Поставляемый класс ml.utils.NMS реализует Soft-NMS – усовершенствование, которое снижает оценку перекрывающейся рамки на величину, зависящую от степени перекрытия, вместо того чтобы отбрасывать рамку сразу. Если сниженная оценка падает ниже порога, рамка отбрасывается; в противном случае она сохраняется с уменьшенной оценкой и участвует в следующем раунде.

Параметр nms_sigma управляет тем, насколько агрессивно происходит снижение. При малом nms_sigma (поставляемое значение по умолчанию 0.1) снижение крутое: сильно перекрывающаяся рамка получает оценку, доведённую почти до нуля, и Soft-NMS сводится к классическому NMS. При большем nms_sigma снижение мягкое, и перекрывающиеся рамки разных объектов выживают чаще, что важно, когда реальные объекты одного класса действительно перекрываются (толпа лиц, скопление ладоней).

Установка nms_sigma в значение <= 0 полностью отключает снижение: перекрывающиеся рамки проходят со своими исходными оценками, и фильтрует их только порог по оценке.

7.13.2. Создание напрямую

Каждый поставляемый постпроцессор создаёт новый объект NMS на каждый вывод, добавляет в него каждого кандидата и в конце вызывает get_bounding_boxes(). Пользовательский постпроцессор следует тому же шаблону:

from ml.utils import NMS

iw = model.input_shape[0][2]
ih = model.input_shape[0][1]

nms = NMS(iw, ih, inputs[0].roi)
for box, score, class_idx in candidates:
    nms.add_bounding_box(box.xmin, box.ymin,
                         box.xmax, box.ymax,
                         score, class_idx)
result = nms.get_bounding_boxes(threshold=nms_threshold,
                                sigma=nms_sigma)

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

Возвращаемая структура – это список списков по классам, индексируемый по label_index, переданному в add_bounding_box. Пустые списки классов сохраняются, чтобы индекс соответствовал индексу класса модели; enumerate(result) обходит классы вместе с их обнаружениями.