Детекция повёрнутых рамок
Детекция повёрнутых объектов описывает положение каждого экземпляра повёрнутым прямоугольником, а не рамкой по осям, поэтому наклонённый объект охватывается плотно, а не рамкой, заполненной фоном. Ключ задачи — obb.
Определение
Детекция повёрнутых рамок добавляет к детекции одно число — угол. Каждый экземпляр получает повёрнутый прямоугольник, класс и оценку. Выигрыш — в плотности прилегания. Судно под 45 градусов, крыша склада, ряд припаркованных грузовиков: рамка по осям вокруг любого из них состоит в основном из фона, а две соседние рамки перекрываются даже тогда, когда сами объекты не перекрываются. Поэтому эта задача стандартна для аэрофотосъёмки и разбора вёрстки документов, и поэтому эталонный датасет для неё — DOTA.
obb — канонический ключ задачи, а суффикс -obb в имени файла чекпойнта
выбирает её, поэтому при загрузке опубликованных весов task= не нужен.
predict() заполняет result.obb. .xywhr — каноническая форма (N, 5):
центр x, центр y, ширина, высота и угол в радианах, задающий поворот стороны
ширины вокруг центра. .conf и .cls несут оценку и индекс класса в
result.names, а .id — идентификатор трека при трекинге. .xyxyxyxy
переводит каждую строку в четыре угловые точки — массив (N, 4, 2) в пикселях,
.xyxyxyxyn нормализует эти вершины, а .xyxy даёт охватывающую рамку по осям:
именно её нужно брать, когда код дальше по цепочке понимает только
прямоугольники. result.boxes тоже заполняется — рамками по осям.
Модели
Эту задачу обслуживают два семейства, и какое из них брать, зависит от того, нужно ли вам обучать.
RF-DETR — семейство, которое обучается. Оно поддерживает
предсказание, обучение, валидацию и экспорт повёрнутых рамок и публикует
чекпойнты для повёрнутых рамок в четырёх размерах: n, s, m и l. Ему нужен
собственный extra, pip install "libreyolo[rfdetr]", а на странице модели
указаны лицензия весов и их происхождение.
Прежде чем строить на них планы, прочитайте раздел ниже о том, что эти чекпойнты предсказывают на самом деле.
RT-DETRv2 — семейство с весами для аэрофотоснимков. Оно
публикует от LibreRTDETRv2n-obb.pt до LibreRTDETRv2x-obb.pt — официальные
одномасштабные чекпойнты DOTA v1.0, конвертированные в формат LibreYOLO и
покрывающие 15 классов DOTA при 1024 px. Кроме базового пакета ему ничего не
нужно, повёрнутый граф распознаётся по собственным тензорам чекпойнта, а
предсказание, валидация и экспорт в ONNX и TorchScript поддерживаются. Обучение
— нет: в этом семействе задача с повёрнутыми рамками доступна только для
инференса, train() выбрасывает исключение, и переноса с его весов детекции
нет — там другой бэкбон. Трекинг и аугментация во время инференса (TTA) для повёрнутых
рамок тоже недоступны.
Итого: категории DOTA из коробки — RT-DETRv2. Своя разметка с повёрнутыми рамками — RF-DETR.
Предсказание
Веса скачиваются с Hugging Face при первом запуске и кэшируются локально.
# Нужен extra rfdetr: pip install "libreyolo[rfdetr]"from libreyolo import LibreYOLO, SAMPLE_IMAGE # Суффикс -obb в имени файла выбирает задачу, поэтому аргумент# task не нужен.model = LibreYOLO("LibreRFDETRs-obb.pt")result = model(SAMPLE_IMAGE, save=True) obb = result.obbprint(obb.xywhr) # (N, 5): центр x, центр y, ширина, высота, радианыprint(obb.conf, obb.cls)libreyolo predict model=LibreRFDETRs-obb.pt save=True \ source=https://raw.githubusercontent.com/LibreYOLO/libreyolo/release/libreyolo/assets/parkour.jpgfrom libreyolo import LibreYOLO, SAMPLE_IMAGE result = LibreYOLO("LibreRFDETRs-obb.pt")(SAMPLE_IMAGE)obb = result.obb print(obb.xyxyxyxy.shape) # (N, 4, 2) угловые точки в пикселяхprint(obb.xyxyxyxyn.shape) # то же самое, нормализованоprint(obb.xyxy.shape) # (N, 4) охватывающая рамка по осямfrom libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreRFDETRn-obb.pt")result = model(SAMPLE_IMAGE) print(result.obb.xywhr.shape)from libreyolo import LibreYOLO # Веса DOTA v1.0, 15 классов аэросъёмки при 1024 px. Повёрнутый граф# распознаётся по собственным тензорам чекпойнта, аргумент task не нужен.model = LibreYOLO("LibreRTDETRv2n-obb.pt")result = model("aerial.png", save=True) obb = result.obbprint(obb.xywhr)print(result.names) # plane, ship, harbor, helicopter и ещё 11Прежде чем запускать опубликованные чекпойнты RF-DETR, разберитесь, что это за веса. Хотя эталонный бенчмарк для этой задачи — DOTA, на нём эти веса не обучались. Все четыре инициализированы из весов детекции RF-DETR и дообучены на одном датасете Roboflow Universe со съёмкой с беспилотников и шестью классами транспорта: bike, bus, car, other_vehicle, taxi и truck. Их карточки моделей описывают эти веса как веса для разработки, полученные при проверке поддержки обучения повёрнутых рамок, и указывают, что читать их как продакшен-веса или официальные веса для бенчмарка не стоит.
На практике это значит, что они — рабочая отправная точка для повёрнутых рамок
на транспорте, снятом сверху, и для проверки того, что ваш пайплайн работает от
начала до конца. Любая другая предметная область означает обучение на своей
разметке с повёрнутыми рамками, а для тех категорий аэросъёмки, которыми
известна DOTA, на этих данных действительно обучены чекпойнты RT-DETRv2.
conf и max_det
формируют выход так же, как и при детекции. Про источники, стриминг и работу с
результатом смотрите предсказание.
Формат датасета
Раскладка та же, что и у детекции: один файл разметки .txt на изображение;
путь к нему получается, если заменить images на labels в пути к изображению
и сменить расширение.
dataset/
data.yaml
images/
train/P0001.png
val/P0101.png
labels/
train/P0001.txt
val/P0101.txtВ строке ровно девять полей: индекс класса, а за ним четыре угловые точки по порядку:
<class_id> <x1> <y1> <x2> <y2> <x3> <y3> <x4> <y4>Четыре точки — нормализованные вещественные числа в [0, 1], и они должны
образовывать невырожденный повёрнутый прямоугольник. Угол в файле разметки не
хранится: загрузчик выводит каноническое xywhr из вершин. Парсер по умолчанию
строгий и отклоняет координаты вне диапазона; при загрузке датасета и на
валидации координаты могут сначала обрезаться до [0, 1], если разметка на
границе кропа в остальном корректна, — но вырожденные рамки всё равно
отклоняются.
Разбор строки зависит от задачи. Девять полей означают повёрнутую рамку только в
режиме obb; в режиме segment та же строка читается как полигон из четырёх
точек.
YAML — тот же, что и у детекции:
path: dataset
train: images/train
val: images/val
names:
0: plane
1: shipРодной COCO JSON тоже загружается — с разделом annotations, который
сопоставляет имя сплита с JSON-файлом. Аннотации читаются в порядке приоритета:
поле obb из восьми координат вершин в пикселях, поле obb вида
[cx, cy, w, h, angle] с углом в
радианах, полигон segmentation или RLE, пересчитанный в прямоугольник
минимальной площади, либо обычный COCO bbox, который трактуется как
прямоугольник по осям и приводится к каноническому xywhr.
Канонический парсер строки — libreyolo.data.parse_yolo_obb_label_line.
Обучение
from libreyolo import LibreYOLO # Продолжает обучение с опубликованных весов для повёрнутых рамок. data должен# указывать на датасет, в строках разметки которого четыре вершины.model = LibreYOLO("LibreRFDETRs-obb.pt")model.train(data="my-obb-dataset.yaml", epochs=50, imgsz=512, batch=8, lr0=1e-4)libreyolo train model=LibreRFDETRs-obb.pt data=my-obb-dataset.yaml \ epochs=50 imgsz=512 batch=8 lr0=1e-4# В весах детекции нет предсказания угла, поэтому это явный перенос.# Разрешает его именно запрос task=obb.libreyolo train model=LibreRFDETRs.pt data=my-obb-dataset.yaml \ task=obb epochs=50 imgsz=512Если нужно обучать, это RF-DETR. По умолчанию обучение продолжается с
опубликованного чекпойнта -obb. Старт с весов детекции — осознанный перенос:
эти веса не предсказывают угол, и разрешает подмену именно передача task=obb.
Держите lr0 на уровне 1e-4 или ниже, как и в других задачах этого семейства.
Чекпойнты RT-DETRv2 с повёрнутыми рамками дообучать нельзя: используйте их как
есть или обучите модель RF-DETR на своей разметке. Про датасеты, аугментацию,
обучение на нескольких GPU и логгеры смотрите обучение.
Валидация
val() возвращает обычный словарь с ключами metrics/. Сопоставление идёт по
повёрнутому IoU, который считается между повёрнутыми прямоугольниками, а не
между их охватывающими рамками по осям, поэтому предсказание с верным положением
и неверным углом засчитывается как промах.
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs-obb.pt") # val() возвращает обычный dict, а не объект.metrics = model.val(data="my-obb-dataset.yaml") print(metrics["metrics/mAP50-95"])print(metrics["metrics/mAP50"], metrics["metrics/mAP75"])print(metrics["metrics/precision"], metrics["metrics/recall"])libreyolo val model=LibreRFDETRs-obb.pt data=my-obb-dataset.yamllibreyolo val model=LibreRTDETRv2n-obb.pt data=my-obb-dataset.yamlmetrics/mAP50-95 — средняя точность, усреднённая по порогам IoU от
0.50 до 0.95 с шагом 0.05, и это главное число. В отличие от пути COCO, который
используется при детекции, эта задача учитывает iou_thresholds в конфигурации
валидации, так что перебор порогов можно изменить. metrics/mAP50 и
metrics/mAP75 — версии с одним порогом. metrics/precision и metrics/recall
— настоящие точность и полнота при IoU 0.50, снятые в самой мягкой рабочей
точке: считается каждое предсказание, прошедшее порог уверенности, а этот порог
на валидации по умолчанию равен 0.001. Поэтому повышение conf их сдвигает, а
числа mAP, которые используют всю кривую precision-recall, остаются на месте.
Четыре из них повторяются с суффиксом (OBB): metrics/mAP50-95(OBB),
metrics/mAP50(OBB), metrics/precision(OBB) и metrics/recall(OBB) — так
вызывающий код отличает повёрнутый результат от результата по осям, когда оба
лежат в одной таблице. У metrics/mAP75 двойника с суффиксом нет.
Две опции на этой задаче ничего не делают. save_json и save_plots
принимаются, но лишь выдают предупреждение: выгрузка предсказаний с повёрнутыми
рамками и графики валидации не реализованы.
Экспорт
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs-obb.pt")model.export(format="onnx", imgsz=512)libreyolo export model=LibreRFDETRs-obb.pt format=onnx imgsz=512# Здесь проверенные цели — ONNX и TorchScript, при FP32,# батче 1, на фиксированном холсте 1024 на 1024.libreyolo export model=LibreRTDETRv2n-obb.pt format=onnx imgsz=1024libreyolo export model=LibreRTDETRv2n-obb.pt format=torchscript imgsz=1024from libreyolo import LibreYOLO, SAMPLE_IMAGE # Фабрика выбирает загрузчик по суффиксу файла, поэтому экспортированный# артефакт загружается как чекпойнт и возвращает тот же объект Results.model = LibreYOLO("LibreRFDETRs-obb.onnx")result = model(SAMPLE_IMAGE) print(result.obb.xywhr)Экспортированный артефакт загружается обратно через LibreYOLO() по суффиксу
файла, поэтому файл .onnx или .engine ведёт себя как чекпойнт и возвращает
тот же Results. Покрытие форматов в одном семействе различается по задачам, а
матрица на странице модели генерируется из проверенного набора и называет
причину, по которой цель недоступна. Про форматы, их extra и ограничения
смотрите экспорт и развёртывание.