Пороги и фильтрация
Какие предсказания выживут, решают четыре аргумента: conf, iou, max_det и classes. Только два из них применимы к каждому семейству, потому что предиктор множества декодирует фиксированный набор запросов и никогда не запускает NMS.
Четыре аргумента
| Аргумент | По умолчанию | Применяется |
|---|---|---|
conf | 0.25 | К каждому семейству |
iou | 0.45 | К семействам, которые запускают немаксимальное подавление (NMS) |
max_det | 300 | К каждому семейству |
classes | None | К каждому семейству |
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt") result = model( SAMPLE_IMAGE, conf=0.25, # оставить предсказания с этой оценкой и выше iou=0.45, # порог перекрытия NMS — там, где NMS запускается max_det=300, # предел на изображение classes=None, # или список id классов)print(len(result.boxes))from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt") for conf in (0.1, 0.25, 0.5, 0.75): result = model(SAMPLE_IMAGE, conf=conf) print(conf, len(result.boxes))libreyolo predict model=LibreYOLO9s.pt conf=0.4 iou=0.5 max_det=100 \ source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgДва из них универсальны, а два нет — это самое полезное, что стоит знать до того, как что-то настраивать.
У валидации значения по умолчанию другие, и это сделано намеренно: val()
работает при conf=0.001 и iou=0.6, потому что средняя точность считается по
полной кривой точности и полноты, а отсечка на 0.25 её обрезала бы.
conf
conf — это оценка, ниже которой предсказание отбрасывается. Он применяется к
каждому семейству, включая те, что никогда не запускают NMS, и это первая ручка,
за которую стоит браться, когда детекций слишком много или слишком мало.
Значение по умолчанию 0.25 подходит, чтобы просто смотреть на картинки. Если
предсказания уходят в следующую систему, обычно нужно значение выше; если
измеряется качество — гораздо ниже.
Валидация детекции предоставляет metrics/best_conf и пороги по классам на основе F1 при IoU 0.50. Используйте их как измеренные отправные значения порогов при развёртывании; см. валидацию. Сегментация не предоставляет эти ключи.
iou
iou — это перекрытие, выше которого немаксимальное подавление убирает ту из двух
рамок одного класса, у которой оценка ниже. Он что-то значит, только если
семейство вообще запускает подавление.
Предиктор множества декодирует фиксированное число запросов и берёт те, у
которых оценка выше. Дубликаты подавляются внутри архитектуры во время обучения,
а не на шаге постобработки, поэтому никакого порога здесь нет. Эти семейства
принимают iou ради совместимости API и игнорируют его:
CenterNet, DEIM, DETR, Deformable DETR, D-FINE, DINO-DETR, EdgeCrafter, Faster R-CNN, LW-DETR, Mask R-CNN, RF-DETR, RT-DETR и end-to-end голова YOLOv9. Варианты, построенные на этих декодерах, наследуют это поведение.
from libreyolo import LibreYOLO, SAMPLE_IMAGE # RF-DETR декодирует фиксированный набор запросов, поэтому iou здесь ничего не меняет.model = LibreYOLO("LibreRFDETRs.pt") loose = model(SAMPLE_IMAGE, iou=0.9)tight = model(SAMPLE_IMAGE, iou=0.1) # Количество одинаковое в обоих случаях. Работают именно conf и max_det.print(len(loose.boxes), len(tight.boxes))Большинство из них сообщают об этом в строках документации к постобработке, но
во время выполнения предупреждение не выдаётся, поэтому перебор iou на RF-DETR
даёт ровную линию, а не ошибку. Faster R-CNN и Mask R-CNN — случай чуть другой:
обе уже запустили NMS внутри модели, с фиксированным порогом из исходной
реализации, изменить который через iou штатным способом нельзя.
А эти семейства его используют: от YOLOv1 до YOLOv4, YOLOv7, YOLOv9, YOLOX, YOLO-NAS, RTMDet, PicoDet, EfficientDet, FCOS, RetinaNet и SSD.
Три параметра на этапе предсказания делают iou значимым даже для предиктора
множества, потому что все три действуют на рамки уже после того, как модель
отработала:
tiling=Trueсогласует перекрывающиеся тайлы поклассовым NMS с порогомiouaugment=Trueобъединяет отражённые виды поклассовым NMS с порогомiouagnostic_nms=Trueподавляет перекрывающиеся рамки между классами с порогомiou, см. ниже
Первые два разобраны в разделе Производительность инференса.
У детекторов с открытым словарём своё правило. Семейство, чей процессор
запускает NMS, объявляет собственный порог по умолчанию и учитывает iou — так
устроен OMDet-Turbo. Семейства, которые ничего не подавляют, — Grounding DINO,
OWLv2 и OV-DEIM — выдают предупреждение, если передать iou. Это единственное
предупреждение такого рода во всей библиотеке.
max_det
max_det ограничивает, сколько предсказаний возвращается для одного
изображения. Он работает везде, но через разные механизмы: семейство с NMS
обрезает список после подавления, а предиктор множества использует его как
размер выборки top-k.
Некоторые семейства ограничивают результат сильнее, чем вы просите, потому что
так устроена их исходная эталонная конфигурация. SSD останавливается на 200, а
FCOS — на собственном лимите детекций на изображение. Поднимать max_det выше
этих значений бесполезно.
max_det применяется централизованно, а не в каждом семействе, в двух случаях:
при потайловом инференсе, где объединённый список обрезается после согласования
тайлов, и при вызовах с classes или agnostic_nms=True, где обрезка идёт после
фильтра или подавления.
Фильтрация классов
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt") # Id классов индексируют model.names. В COCO 0 — это person.result = model(SAMPLE_IMAGE, classes=[0]) print({result.names[int(c)] for c in result.boxes.cls.tolist()})from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt")result = model(SAMPLE_IMAGE) wanted = {"person", "backpack"}ids = [i for i, name in result.names.items() if name in wanted]print(ids) filtered = model(SAMPLE_IMAGE, classes=ids)print(len(filtered.boxes))classes принимает список id классов и оставляет только те предсказания, чей
класс есть в списке. Id индексируют result.names, и надёжнее всего взять
нужный, прочитав names из результата, а не предполагая порядок классов в
датасете.
Фильтрация происходит централизованно, после постобработки каждого семейства, в единой точке, через которую проходит любой путь предсказания. Из этого следуют две вещи, которые стоит знать. Она работает на каждом семействе, включая те, где NMS нет. И заодно она фильтрует данные, привязанные к рамкам, так что маски, ключевые точки и повёрнутые рамки урезаются вместе с ними, а не остаются рассогласованными.
В командной строке classes принимает голое целое число, список или строку со
значениями через запятую:
libreyolo predict model=LibreYOLO9s.pt classes=0 source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpglibreyolo predict model=LibreYOLO9s.pt classes="[0,2,5]" source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgФильтрация не даёт качества бесплатно. Модель всё равно тратит свой бюджет на
предсказание классов, которые вы потом выбрасываете. Если задан classes,
семейство держит бюджет кандидатов в 300 рамок или max_det, если он больше, а
обрезка до max_det идёт после фильтра, поэтому малый max_det не вытесняет ваш
класс. Экспортированные бэкенды используют тот же бюджет, включая батчевые
движки TensorRT. Изображение, где больше 300 рамок ненужных классов с более
высокой оценкой, всё равно может заполнить бюджет раньше, чем дойдёт до вашего
класса; если так вышло, поднимите max_det выше 300.
agnostic_nms
NMS каждого семейства учитывает класс, поэтому по умолчанию две перекрывающиеся
рамки разных классов выживают обе, при любом iou. agnostic_nms=True добавляет
один шаг без учёта классов: из рамок, перекрывающихся сильнее iou, выживает
только рамка с наибольшей оценкой, независимо от их классов.
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreYOLO9s.pt") per_class = model(SAMPLE_IMAGE)agnostic = model(SAMPLE_IMAGE, agnostic_nms=True) # Перекрывающиеся рамки разных классов сворачиваются в одну с наибольшей оценкой.print(len(per_class.boxes), len(agnostic.boxes))libreyolo predict model=LibreYOLO9s.pt agnostic_nms=true \ source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgШаг работает на готовых детекциях семейства, после фильтра classes и до
обрезки до max_det. Поэтому он ведёт себя одинаково на предикторах множества,
которые не запускают собственный NMS, на экспортированных бэкендах и при
augment=True и tiling=True. Маски и ключевые точки отбрасываются вместе со
своей рамкой, а повёрнутые рамки сравниваются по повёрнутому IoU. Он охватывает
задачи, которые возвращают рамки: детекцию, сегментацию, позу и obb.
Для шага семейство держит бюджет кандидатов в 300 рамок или max_det, если он
больше. При max_det ниже 300 кандидаты с меньшей оценкой заполняют места,
которые освободило подавление. При 300 и больше, включая значение по умолчанию,
эти места остаются пустыми.
Где он не применяется:
- На стандартном пути predict задача без рамок вызывает
ValueError. LibreEnsembleи HRNet вызываютNotImplementedError.- Модели
LibreSAMи 3D-детекторы, WildDet3D, 3D-MOOD, FCOS3D и DetAny3D, не принимают такого аргумента и вызываютTypeError. - L2CS-Net, PP-OCRv5, SAM 3D Body и LibreFaceRec идут по собственному пути predict и игнорируют его.
val(agnostic_nms=True) применяет тот же шаг к валидации детекции и сегментации;
см. Валидация и метрики.
Что predict отвергает
Два аргумента не предупреждают, а выбрасывают исключение: visualize и embed —
оба с NotImplementedError. Для эмбеддингов загрузите модель с
task="embed" и вызывайте predict или embed как обычно. agnostic_nms=True
вызывает исключение там, где не применяется, как перечислено выше.
Всё нераспознанное выбрасывает TypeError с перечислением поддерживаемых опций,
так что опечатка сразу приводит к ошибке, а не игнорируется молча.
Эти аргументы принимаются, вызывают предупреждение и отбрасываются: boxes, dnn, half,
line_width, retina_masks, show_conf, show_labels и verbose.