API ансамбля
LibreEnsemble прогоняет несколько детекторов по одному и тому же изображению и сливает их детекции в один Results. Слияние происходит после собственной постобработки каждого участника, поэтому участники сохраняют свой размер входа, нормализацию и подавление.
LibreEnsemble
LibreEnsemble(
members,
*,
weights=None,
fusion="wbf",
fusion_iou=0.55,
min_votes=1,
)| Аргумент | По умолчанию | Значение |
|---|---|---|
members | Два или более детектора | |
weights | None | Коэффициенты доверия по участникам; без указания все равны 1.0 |
fusion | "wbf" | "wbf", "wbf_seeded", "nms" или вызываемый объект |
fusion_iou | 0.55 | Порог IoU для кластеризации при слиянии |
min_votes | 1 | Оставлять только рамки, подтверждённые как минимум таким числом участников |
Участником может быть путь к весам, который разрешает фабрика LibreYOLO(),
уже созданная модель, экспортированный бэкенд или ExternalDetector. Каждый
участник должен быть моделью с задачей детекции.
from libreyolo import LibreEnsemble, SAMPLE_IMAGE ens = LibreEnsemble(["LibreYOLO9t.pt", "LibreYOLO9s.pt"]) # Один источник-изображение возвращает один Results, а не список.result = ens(SAMPLE_IMAGE, conf=0.25) print(result.boxes.xyxy)print(result.speed)from libreyolo import LibreEnsemble, SAMPLE_IMAGE ens = LibreEnsemble( ["LibreYOLO9t.pt", "LibreYOLO9s.pt"], weights=[1.0, 2.0], fusion="wbf", fusion_iou=0.55, min_votes=2,)result = ens(SAMPLE_IMAGE, conf=[0.25, 0.4])print(len(result))Создание завершается ошибкой, если участников меньше двух, у списка weights
неверная длина, какой-то вес неположителен, min_votes — не целое
положительное число или min_votes больше числа участников. Сочетание
fusion="nms" с min_votes > 1 тоже вызывает ошибку, потому что NMS
отбрасывает принадлежность к кластеру и не может считать голоса.
weights масштабирует доверие к каждому участнику. Больший вес тянет итоговые
координаты и оценки в сторону этого участника. По соглашению их задают
пропорционально mAP на валидации.
Пространства классов
Участники с одинаковым names проходят насквозь. Иначе пространства классов
объединяются по именам, id классов участников переводятся через таблицы
соответствия, а итоговый Results.names — это объединение. Рамки сливаются
только внутри одного объединённого класса, поэтому класс, который знает
лишь один участник, проходит без слияния. При несовпадении на этапе создания
в лог пишется предупреждение.
min_votes по каждому классу ограничивается сверху числом участников, в чьих
пространствах меток этот класс есть, — так консенсус остаётся осмысленным при
частично общих словарях.
Вызов ансамбля
ens(
source=None,
*,
conf=0.25,
iou=0.45,
imgsz=None,
device=None,
classes=None,
max_det=300,
augment=False,
save=False,
output_path=None,
color_format="auto",
batch=1,
stream=False,
stream_buffer=False,
vid_stride=1,
show=False,
**kwargs,
)predict — псевдоним для __call__. Возвращается обычный Results, у
которого speed расписывает затраты по участникам и добавляет запись
fusion. Один источник-изображение возвращает один такой объект, список или
папка — список, а stream=True — генератор.
conf, iou и device применяются ко всем участникам, но принимают и по
одному значению на участника, поэтому conf=[0.25, 0.4] даёт участнику 0 порог
0.25, а участнику 1 — порог 0.4. imgsz применяется ко всем, когда это int или
кортеж, и читается по участникам, только когда это список, поэтому
imgsz=(480, 640) — один прямоугольный размер для всех, а imgsz=[480, 640] —
480 для участника 0 и 640 для участника 1. Каждое значение должно быть
допустимым для семейства своего участника.
augment применяется к тем участникам, которые поддерживают аугментацию на
инференсе, а экспортированные бэкенды его игнорируют. classes принимает id
объединённых классов, а max_det применяется к итоговому результату, поэтому
участники работают с запасом, а ансамбль обрезает результат один раз. batch
принимается для совместимости API; изображения обрабатываются последовательно.
val() и export() вызывают NotImplementedError. Валидировать и
экспортировать участников нужно по отдельности.
ExternalDetector
ExternalDetector(fn: Callable, names: dict[int, str])Превращает любой вызываемый детектор в участника. fn принимает
PIL-изображение и возвращает (boxes, scores, labels), где рамки заданы как
xyxy в пикселях исходного изображения, а метки — id классов, допустимые в
names. Подходят тензоры, массивы и вложенные списки. LibreYOLO ничего не
импортирует из внешнего кода.
Адаптер проверяет возвращаемое значение: это должен быть кортеж из трёх
элементов, рамки должны иметь форму (N, 4), все три массива — одинаковую
длину, а каждый id класса должен быть в names. Детекции с уверенностью не
выше conf отбрасываются до слияния.
Операции слияния
Примитивы слияния — самостоятельные torch-операции в libreyolo.ops. Они не
привязаны к моделям, и импортировать их можно независимо от ансамбля — поэтому
они и экспортируются отдельно.
import torchfrom libreyolo.ops import weighted_boxes_fusion boxes = torch.tensor([[10.0, 10.0, 50.0, 50.0], [12.0, 11.0, 51.0, 49.0]])scores = torch.tensor([0.9, 0.8])labels = torch.tensor([0, 0])model_ids = torch.tensor([0, 1]) fused = weighted_boxes_fusion( boxes, scores, labels, model_ids, num_models=2, iou_thr=0.55)print(fused)Все три принимают одни и те же позиционные аргументы — boxes, scores, labels, model_ids — и возвращают (boxes, scores, labels).
| Операция | Ключ в реестре | Поведение |
|---|---|---|
weighted_boxes_fusion | wbf | Последовательный weighted boxes fusion, точно по статье |
wbf_seeded | wbf_seeded | Параллельный вариант той же свёртки в один проход |
nms_fusion | nms | Склеивает всё и применяет NMS с учётом классов |
FUSIONS сопоставляет три ключа реестра вызываемым объектам, и
LibreEnsemble ищет в нём значение fusion=.
weighted_boxes_fusion(
boxes, scores, labels, model_ids,
*,
weights=None,
num_models=None,
iou_thr=0.55,
skip_box_thr=0.0,
conf_type="avg",
min_votes=1,
models_per_label=None,
label_weights=None,
)У wbf_seeded сигнатура точно такая же. nms_fusion принимает те же
аргументы, кроме conf_type, и вызывает ValueError, когда min_votes > 1.
В weighted_boxes_fusion детекции обходятся в порядке убывания уверенности,
домноженной на вес. Каждая либо попадает в тот существующий кластер, с чьей
текущей слитой рамкой она пересекается лучше всего — при IoU выше iou_thr и
с той же меткой, — либо открывает новый кластер. Слитая рамка кластера — это
среднее координат его участников, взвешенное по уверенности, а её оценка —
взвешенное среднее или максимум их уверенностей, пересчитанные так, чтобы
рамки, подтверждённые меньшим числом моделей, получали меньшую оценку.
wbf_seeded выбирает затравки кластеров с помощью NMS с учётом классов при
iou_thr, приписывает каждую детекцию к затравке с той же меткой и наибольшим
IoU, а затем сворачивает каждый кластер так же. Формы кластеров не меняются
посреди прохода, поэтому вся операция — тензорная арифметика с фиксированными
формами. Два варианта совпадают, когда кластеры однозначны, и могут слегка
расходиться на цепочках перекрывающихся кластеров.
nms_fusion оставляет рамку с наибольшей уверенностью в каждой перекрывающейся
группе, без изменений. Веса weights по моделям масштабируют уверенности
только для ранжирования при подавлении, а выжившие рамки сохраняют исходные
оценки.
Своё слияние
fusion= принимает и вызываемый объект с той же сигнатурой, что у операций
выше. Его имя записывается в ens.fusion, а если имени нет — "custom".
Возвращаемое значение проверяется: это должна быть тройка
(boxes, scores, labels) с согласованными формами.