Глава 7c. Assign и NMS: кто отвечает за объект и как убрать дубли
Даже идеальная голова бесполезна, если непонятно,
какое предсказание должно учиться на каком объекте —
и какие лишние рамки выживут после постпроцесса.
Главы 7a–7b разобрали кодирование бокса и loss.
Осталась бюрократия детекции: assignment на обучении и подавление дублей на инференсе.
Именно здесь часто прячется разница между «моделью из статьи» и «моделью в камере».
7c.1. Задача assign: свадьба GT и предсказаний
На одном кадре:
- N объектов (ground truth);
- M кандидатов (ячейки × якоря / точки × уровни FPN).
Нужно решить, кто positive, кто negative, кто ignore.
Плохой 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 бесит:
- два стоящих рядом человека с большим 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.
Практические зонды:
- Визуализировать positive-маски assigner’а на батче.
- Сравнить IoU победителя NMS с IoU лучшего кандидата до NMS.
- Смотреть дубли до NMS: их слишком мало (сеть недогенерила) или слишком много (голова/obj раздуты)?
- Профилировать postprocess отдельно от backbone — особенно на batch=1.
Детектор как система
| Этап | Роль |
|---|---|
| backbone / neck / head | Сырые кандидаты |
| assign (только train) | Кого учить |
| loss | За что бить |
| NMS / post | Кого показать пользователю |
Если продукт страдает от «двойных рамок» или «пропавших соседей», копайте сюда раньше, чем менять CSP на «новый модный блок».
7c.5. Мини-лаборатория
Баг: два человека вплотную, один систематически пропадает.
Гипотезы:
- NMS thresh слишком низкий → сосед глушится.
- Assign на train редко давал два strong positive в такой конфигурации.
- Крупный уровень FPN «съедает» обоих в одну ячейку ответственности.
- Score сильнее коррелирует с площадью, чем с корректностью бокса.
Эксперименты минимальной силы: поднять NMS IoU thresh; Soft-NMS; разметка/аугментации crowded scenes; отдельный отчёт AP при высокой плотности.
Если бы вы были автором статьи…
-
Зафиксируйте голову и loss, меняйте только assigner (IoU-порог vs TAL-подобный cost).
Где вырастет AP75, где вырастет число дублей до NMS? -
Снимите latency breakdown: backbone / neck / head / NMS.
При каком числе кандидатов NMS становится топ-3 пожирателем времени? -
Спроектируйте метрику «crowded recall» отдельно от общего mAP.
Почему без неё вы будете вечно чинить не ту боль?
Дальше по эффективности блоков вернёмся к мобильным свёрткам: depthwise и inverted residual — следующий шаг после bottleneck, когда даже 3×3 на узких каналах всё ещё дорог.