Один объект найден в двух тайлах: как убрать дубли детектора после склейки
Большую фотографию склада разбили на перекрывающиеся квадраты, чтобы детектор увидел мелкие коробки. На каждом квадрате результат выглядит хорошо. После объединения одна коробка появилась в списке дважды: она попала в область перекрытия. Подавление повторов внутри каждого запуска модели эту проблему не решает, потому что два запуска не знают о находках друг друга.
Сначала нужна одна система координат
Каждый тайл хранит положение своего левого верхнего угла на исходном изображении. К локальным x рамки прибавляем смещение тайла по горизонтали, к y — по вертикали. Если перед инференсом тайл дополнительно масштабировали или дополняли полями, сначала обращаем это преобразование и только потом прибавляем смещение. Иначе похожие рамки разных тайлов могут вообще не пересечься.
Сохраняйте идентификатор исходного изображения. Нельзя выполнять общую NMS над рамками двух независимых фото, даже если их координаты совпали. Далее учитывайте класс: коробка и наклейка могут занимать почти одинаковое место, но это разные объекты задачи. В примере повтором считаются пересекающиеся рамки одного класса на одном изображении.
Используем простую жадную NMS. Берём рамку с наибольшим score, оставляем её и удаляем рамки того же класса, у которых IoU с ней выше порога. IoU — площадь пересечения, делённая на площадь объединения. Затем повторяем процесс для оставшихся кандидатов. Этот способ выбирает одну рамку, не усредняя координаты повторных обнаружений.
Посмотрим, какие находки останутся
Для прозрачности приведена реализация на стандартном Python. Она иллюстрирует склейку выходов, уже полученных от детектора; обучение и распознавание изображения здесь не выполняются. Формат рамки x1,y1,x2,y2, площади считаются как непрерывные прямоугольники. У всех рамок положительная ширина и высота.
def iou(a, b):
width = max(0, min(a[2], b[2]) - max(a[0], b[0]))
height = max(0, min(a[3], b[3]) - max(a[1], b[1]))
intersection = width * height
area_a = (a[2] - a[0]) * (a[3] - a[1])
area_b = (b[2] - b[0]) * (b[3] - b[1])
return intersection / (area_a + area_b - intersection)
# id, class, score, tile_offset, local_xyxy
raw = [
("left_box", "box", 0.95, (0, 0), (80, 20, 120, 60)),
("right_box", "box", 0.90, (64, 0), (18, 22, 58, 62)),
("label", "label", 0.92, (64, 0), (18, 22, 58, 62)),
("other_box", "box", 0.85, (64, 0), (80, 20, 110, 60)),
]
global_rows = []
for id_, cls, score, (dx, dy), (x1, y1, x2, y2) in raw:
global_rows.append((id_, cls, score, (x1 + dx, y1 + dy, x2 + dx, y2 + dy)))
pending = sorted(global_rows, key=lambda r: (-r[2], r[0]))
kept = []
while pending:
best = pending.pop(0)
kept.append(best)
pending = [r for r in pending if r[1] != best[1] or iou(r[3], best[3]) <= 0.5]
print("overlap", round(iou(global_rows[0][3], global_rows[1][3]), 4))
print("kept", [r[0] for r in kept])
assert [r[0] for r in kept] == ["left_box", "label", "other_box"]
После переноса right_box оказывается в (82,22,122,62), почти совпадая с left_box. IoU около 0,8223, поэтому остаётся более уверенная left_box. Наклейка с теми же координатами сохраняется: у неё другой класс. Отдельная коробка справа тоже остаётся. Результат содержит три находки вместо четырёх, при этом идентификаторы позволяют проследить происхождение каждой.
В промышленной реализации можно использовать готовые операции, но их контракт нужно согласовать с форматом данных. Например, torchvision.ops.nms работает с одним набором рамок и score; разделение по изображениям и классам организуют снаружи либо подходящей пакетной операцией. При одинаковых score выбор рамки в разных вычислительных средах может различаться, поэтому пример использует явное вторичное упорядочивание по id.
Почему NMS может оставить повтор или удалить нужное
Если коробка обрезана краем одного тайла, её частичная рамка может иметь небольшой IoU с полной рамкой другого. Тогда обычная NMS сохранит обе. Начните диагностику с визуализации кандидатов у швов: сохраните границы тайлов, исходные и глобальные рамки. Увеличение перекрытия или отбор детекций из центральной области тайла иногда помогает, но каждое правило нужно проверять на объектах у края всего изображения.
При плотной укладке соседние коробки одного класса тоже могут сильно пересекаться в проекции. Слишком низкий порог удалит настоящие объекты. Выберите порог на размеченных целых фотографиях после полной склейки, а не на отдельных тайлах. Измеряйте одновременно пропущенные объекты и повторные обнаружения. Для задачи подсчёта дополнительно проверяйте итоговое количество на сцене.
Сохраняйте параметры разбиения вместе с версией пайплайна. Изменение размера тайла меняет масштаб объектов, контекст и число кандидатов, а значит, требует повторной приёмки. Удобный артефакт — одно изображение с итоговыми рамками и таблица, где видны id сохранённых и подавленных кандидатов. Такой отчёт помогает понять, нужна ли новая модель или достаточно исправить склейку.
Для больших изображений учитывайте и стоимость склейки. Простая реализация многократно сравнивает пары рамок, поэтому при тысячах кандидатов время постобработки может стать заметным. Сначала замерьте число кандидатов после порога уверенности и время этапов отдельно: подготовку тайлов, инференс и объединение. Если проблема именно в количестве сравнений, пригодятся готовая векторизованная операция или пространственное разбиение кандидатов, а качество проверяется тем же набором целых сцен.
Нельзя заменять геометрическую проверку округлением координат и удалением одинаковых строк. Два предсказания одного предмета почти всегда немного различаются, поэтому точное совпадение редкое. При этом два разных предмета способны иметь близкие центры. Для разметки ошибок сохраняйте пары рамок, из-за которых принято решение о подавлении. Тогда на конкретном шве видно, удалён дубль одной коробки или ошибочно исчезла соседняя.
В платном курсе «Computer Vision Engineer» вы работаете с детекцией, качеством данных, NMS и правилами постобработки, затем собираете скрипт инференса и сервис. Это следующий шаг от отдельных удачных тайлов к принимаемому результату целой сцены. Откройте программу курса.
Продолжить на практике
- Computer Vision Engineer: от детекции и Doc-AI до продакшна ↗
Практический курс по Computer Vision. Соберёте продакшн-сервис: датасет → обучение (YOLO/RT-DETR, SAM), трекинг, OCR/Doc-AI, веб-приложение (UI+API) и мониторинг. Экспорт в ONNX/TensorRT/OpenVINO, батчинг/квантование под CPU/GPU. Плюс профессиональные блоки: mAP/IoU/Dice и калибровка доверия, QA разметки и аугментации, p95/стоимость, дрейф данных и алерты, guardrails (NMS/правила), A/B-тесты, Triton/gRPC/FastAPI.
