Глава 7c. Assign и NMS

Глава 7c. Assign и NMS: кто отвечает за объект и как убрать дубли

Даже идеальная голова бесполезна, если непонятно,
какое предсказание должно учиться на каком объекте —
и какие лишние рамки выживут после постпроцесса.

Главы 7a–7b разобрали кодирование бокса и loss.
Осталась бюрократия детекции: assignment на обучении и подавление дублей на инференсе.

Именно здесь часто прячется разница между «моделью из статьи» и «моделью в камере».


7c.1. Задача assign: свадьба GT и предсказаний

На одном кадре:

  • N объектов (ground truth);
  • M кандидатов (ячейки × якоря / точки × уровни FPN).

Нужно решить, кто positive, кто negative, кто ignore.

Assign кандидатов

Плохой assign даёт два классических ада:

Симптом Типичная причина
Дубли на одном объекте при обучении Слишком много positive без ранжирования
Объект «никто не учит» Слишком жёсткий порог / центр не попал в ячейку
Бокс плывёт, cls кричит уверенно Positive достались удобным cls, но плохим reg

7c.2. Эволюция правил: от порога IoU к качеству

Старый стиль (пороги).
IoU(якорь, GT) > 0.7 → positive; < 0.3 → negative; между — ignore.
Просто. Грубо. Ломается на краях распределения.

Центровочные правила (много anchor-free).
Positive — точки около центра объекта / внутри бокса с радиусом.
Геометрия проще якорей, но край объекта и перекрытия всё ещё боль.

Современный стиль: cost-based / alignment-aware.
Смотрим не только IoU, но и классификационный сигнал, иногда предсказанное качество.
Идея Task-Aligned Assigners (семейство TAL и родственников):

Positive должен быть хорош и для класса, и для бокса.
Не «просто близкий якорь», а кандидат, у которого задачи согласованы.

cost ≈  f(cls_score, IoU/reg_quality, …)

топ по cost относительно каждого GT → positive
остальное → background (с возможными soft-метками)
flowchart TD
    A[Кандидаты × GT] --> B{Как выбрать positive?}
    B --> C[Жёсткий IoU-порог]
    B --> D[Центр / радиус]
    B --> E[Cost: cls × IoU alignment]
    C --> C1[Просто / много краевых ошибок]
    D --> D1[Удобно anchor-free]
    E --> E1[Меньше конфликта задач]

Обмен:

Правило Плюс Минус
Жёсткий IoU Интерпретируемо Много hyperparams, хрупко
Центр Мало магии priors Перекрытия, края
Alignment/TAL Учит «правильных» кандидатов Сложнее, зависит от качества текущих предсказаний

7c.3. NMS: грубый, но живучий постпроцесс

На инференсе сеть любит нарисовать несколько рамок на один объект.
Non-Maximum Suppression оставляет «лучшую» и глушит соседей с большим перекрытием.

1) отсортировать детекции по score
2) взять лучшую
3) удалить остальные с IoU > thresh
4) повторить

NMS до и после

Почему NMS до сих пор везде:

  • дёшево;
  • понятно;
  • хорошо стыкуется с «много кандидатов → один объект».

Почему NMS бесит:

  • два стоящих рядом человека с большим IoU → один исчез;
  • порог 0.45 vs 0.6 меняет метрики и прод;
  • на GPU/edge наивный NMS внезапно съедает заметную долю latency;
  • score калиброван плохо → «лучшая» рамка не лучшая геометрически.

Отсюда Soft-NMS, DIoU-NMS, merge-NMS, и попытки end-to-end без NMS (DETR-линия).
Каждый вариант — обмен между простотой, recall рядом стоящих объектов и скоростью.


7c.4. Assign на train ≠ NMS на val

Частая ловушка отладки:

На обучении объект закреплён за одними кандидатами,
а на инференсе побеждает другой кандидат с другим score.

Тогда вы «чините loss», а ломается согласование training targets ↔ inference selection.

Практические зонды:

  1. Визуализировать positive-маски assigner’а на батче.
  2. Сравнить IoU победителя NMS с IoU лучшего кандидата до NMS.
  3. Смотреть дубли до NMS: их слишком мало (сеть недогенерила) или слишком много (голова/obj раздуты)?
  4. Профилировать postprocess отдельно от backbone — особенно на batch=1.

Детектор как система

Этап Роль
backbone / neck / head Сырые кандидаты
assign (только train) Кого учить
loss За что бить
NMS / post Кого показать пользователю

Если продукт страдает от «двойных рамок» или «пропавших соседей», копайте сюда раньше, чем менять CSP на «новый модный блок».


7c.5. Мини-лаборатория

Баг: два человека вплотную, один систематически пропадает.

Гипотезы:

  1. NMS thresh слишком низкий → сосед глушится.
  2. Assign на train редко давал два strong positive в такой конфигурации.
  3. Крупный уровень FPN «съедает» обоих в одну ячейку ответственности.
  4. Score сильнее коррелирует с площадью, чем с корректностью бокса.

Эксперименты минимальной силы: поднять NMS IoU thresh; Soft-NMS; разметка/аугментации crowded scenes; отдельный отчёт AP при высокой плотности.


Если бы вы были автором статьи…

  1. Зафиксируйте голову и loss, меняйте только assigner (IoU-порог vs TAL-подобный cost).
    Где вырастет AP75, где вырастет число дублей до NMS?

  2. Снимите latency breakdown: backbone / neck / head / NMS.
    При каком числе кандидатов NMS становится топ-3 пожирателем времени?

  3. Спроектируйте метрику «crowded recall» отдельно от общего mAP.
    Почему без неё вы будете вечно чинить не ту боль?

Дальше по эффективности блоков вернёмся к мобильным свёрткам: depthwise и inverted residual — следующий шаг после bottleneck, когда даже 3×3 на узких каналах всё ещё дорог.