Сегментация экземпляров
Сегментация экземпляров находит каждый экземпляр объекта и возвращает для него попиксельную маску — вместе с рамкой, классом и оценкой, которые выдаёт детектор. Ключ задачи — segment.
Определение
Сегментация экземпляров — это детекция плюс форма. Каждый экземпляр объекта по-прежнему получает рамку, класс и оценку, а вдобавок — бинарную маску, которая покрывает принадлежащие ему пиксели. Маски могут перекрываться, а пиксели, не принадлежащие ни одному объекту, остаются без назначения — именно это отличает задачу от семантической сегментации и паноптической сегментации.
segment — канонический ключ задачи, а суффикс -seg в имени файла чекпойнта
выбирает её, поэтому при загрузке опубликованных весов task= не нужен.
predict() заполняет result.masks рядом с result.boxes. .data — это
стек масок (N, H, W) размером с исходное изображение, построчно выровненный
с рамками, так что маска i относится к рамке i. .xy превращает каждую маску
в её наибольший внешний контур — массив пикселей (P, 2), а .xyn даёт тот же
контур в нормализованном виде.
Модели
Обучать и предсказывать маски умеют четыре семейства:
RF-DETR, EdgeCrafter,
D-FINE и RTMDet. Для RF-DETR
нужна своя дополнительная зависимость, pip install "libreyolo[rfdetr]";
остальные три работают на базовом пакете.
Mask R-CNN предсказывает, валидирует и экспортирует
маски, но его train() выбрасывает NotImplementedError.
EoMT предсказывает и валидирует маски и тоже не
поддерживает обучение, а его экспорт ограничен ещё сильнее: export()
принимает только семантическую задачу и выбрасывает NotImplementedError для
segment и panoptic, потому что контракт среды выполнения для query-масок,
который нужен этим двум, пока не определён. Используйте EoMT для масок экземпляров в Python, а не через
экспортированный граф.
Отдельная группа сегментирует по промпту, а не по списку классов: клик, рамка или фраза указывают на объект, и модель возвращает его маску. Так работают SAM, SAM 2, SAM 3, MobileSAM, EdgeTAM и PicoSAM3, а также SenseNova-Vision, у которого сегментация идёт по описанию (referring): модель принимает фразу, называющую один объект. Они загружаются через собственную фабрику и дополнительные зависимости, а точный вызов приведён на странице каждой модели.
Предсказание
Веса скачиваются с Hugging Face при первом запуске и кэшируются локально.
from libreyolo import LibreYOLO, SAMPLE_IMAGE # Суффикс -seg в имени файла выбирает голову масок, поэтому# аргумент task не нужен.model = LibreYOLO("LibreDFINEn-seg.pt")result = model(SAMPLE_IMAGE, save=True) print(result.masks.data.shape) # (N, H, W), по одной маске на детекциюprint(result.boxes.xyxy.shape) # (N, 4), те же N строкlibreyolo predict model=LibreDFINEn-seg.pt save=True \ source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgfrom libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreDFINEn-seg.pt")result = model(SAMPLE_IMAGE) # .xy — список контуров (P, 2) в пикселях, .xyn — те же, но нормализованные.for name, contour in zip(result.boxes.cls, result.masks.xy): print(result.names[int(name)], contour.shape)from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreRTMDets-seg.pt")result = model(SAMPLE_IMAGE) print(result.masks.data.shape)conf и max_det формируют вывод так же, как в детекции, а маски фильтруются
вместе с рамками, которым принадлежат. Про источники, стриминг и обработку
результатов — в разделе предсказание.
Формат датасета
Структура та же, что и в детекции: один файл разметки .txt на изображение;
путь к нему получается, если заменить images на labels в пути к
изображению и сменить расширение.
dataset/
data.yaml
images/
train/000001.jpg
val/000101.jpg
labels/
train/000001.txt
val/000101.txtМеняется сама строка. Сегмент — это индекс класса, за которым идёт плоский список координат полигона:
<class_id> <x1> <y1> ... <xN> <yN>Точек должно быть не меньше трёх, то есть количество координат после индекса
класса чётное и не меньше шести, а полигон не должен быть вырожденным.
Координаты — числа с плавающей точкой в [0, 1] относительно ширины и высоты
исходного изображения. Строка детекции из пяти полей тоже принимается в
датасете сегментации и читается как прямоугольный сегмент — благодаря этому
датасет только с рамками загружается без отдельного прохода конвертации.
YAML — тот же, что и в детекции:
path: dataset
train: images/train
val: images/val
names:
0: person
1: bicycleНативный COCO JSON тоже работает: добавьте раздел annotations, который
сопоставляет имя сплита с JSON-файлом, а путь сплита задаёт корневой каталог
изображений.
Обучение
from libreyolo import LibreYOLO # Обучение продолжается с опубликованных весов сегментации, с головой масок.# data должен указывать на датасет, в разметке которого есть полигоны.model = LibreYOLO("LibreDFINEn-seg.pt")model.train(data="my-dataset.yaml", epochs=50, imgsz=640, batch=8, lr0=2e-4)libreyolo train model=LibreDFINEn-seg.pt data=my-dataset.yaml \ epochs=50 imgsz=640 batch=8 lr0=2e-4# В весах детекции нет головы масок, поэтому это явный перенос:# голова стартует необученной. Разрешает его именно явно# указанный task=segment.libreyolo train model=LibreDFINEn.pt data=my-dataset.yaml \ task=segment epochs=50 imgsz=640По умолчанию обучение продолжается с опубликованного чекпойнта -seg.
Стартовать с весов детекции тоже можно, но это осознанный перенос: в таких
весах нет головы масок, поэтому она стартует необученной, а разрешает такую
замену именно task=segment. Про датасеты, аугментацию, обучение на нескольких
GPU и логгеры — в разделе обучение.
Валидация
val() возвращает обычный словарь с ключами metrics/. Рамки и маски
оцениваются отдельно, обе — по протоколу COCO, и основными считаются числа по
маскам.
from libreyolo import LibreYOLO model = LibreYOLO("LibreDFINEn-seg.pt")metrics = model.val(data="my-dataset.yaml") print(metrics["metrics/mAP50-95"]) # маскиprint(metrics["metrics/mAP50-95(M)"]) # маски, явноprint(metrics["metrics/mAP50-95(B)"]) # рамкиlibreyolo val model=LibreDFINEn-seg.pt data=my-dataset.yamlКлючи без суффикса содержат результаты по маскам: metrics/mAP50-95,
metrics/mAP50, metrics/mAP75, затем metrics/mAP_small,
metrics/mAP_medium и metrics/mAP_large по площади объекта, а также
metrics/AR1, metrics/AR10, metrics/AR100, metrics/AR_small,
metrics/AR_medium, metrics/AR_large для средней полноты. В
metrics/AR_max_det и metrics/max_det записан лимит числа детекций, с
которым шёл запуск.
Четыре величины публикуются ещё и с явным суффиксом — (M) для маски и (B)
для рамки, — чтобы сравнение не зависело от того, какое число семейство решило
считать основным: metrics/mAP50-95(M) и metrics/mAP50-95(B),
metrics/mAP50(M) и metrics/mAP50(B), metrics/precision(M) и
metrics/precision(B), metrics/recall(M) и metrics/recall(B). Ключей
metrics/precision и metrics/recall без суффикса в этой задаче нет.
Ключи precision и recall читайте внимательно. Они оставлены для обратной
совместимости и служат псевдонимами, а не рабочей точкой: в
metrics/precision(M) лежит то же значение, что и в metrics/mAP50-95(M), а в
metrics/recall(M) — то же, что и AR по маскам при 100 детекциях; с (B) для
рамок всё устроено так же. График по паре таких ключей покажет одно и то же
число дважды.
Экспорт
from libreyolo import LibreYOLO model = LibreYOLO("LibreDFINEn-seg.pt")model.export(format="onnx", imgsz=640)libreyolo export model=LibreDFINEn-seg.pt format=onnx imgsz=640from libreyolo import LibreYOLO, SAMPLE_IMAGE # Фабрика выбирает загрузчик по суффиксу файла, поэтому экспортированный# артефакт загружается как чекпойнт и возвращает тот же объект Results.model = LibreYOLO("LibreDFINEn-seg.onnx")result = model(SAMPLE_IMAGE) print(result.masks.data.shape)Экспортированный артефакт загружается обратно через LibreYOLO() по суффиксу
файла, поэтому файл .onnx или .engine ведёт себя как чекпойнт и возвращает
тот же Results. Покрытие форматов для сегментации уже, чем для детекции у
того же семейства. Матрица на странице каждой модели генерируется из
проверенного набора и называет причину, по которой цель недоступна. Про
форматы, их дополнительные зависимости и ограничения — в разделе
экспорт и развёртывание.