Ultralytics YOLO27: Встать в лист ожидания.
Get Started
Уроксредний

Ориентированные рамки для повёрнутых объектов

Размечай OBB по четырём углам, обучи yolo26n-obb.pt на DOTA8 и преобразуй предсказания xywhr в устойчивую ось ориентации, не запутавшись в соглашениях об углах.

У кораблей в гавани, посылок на ленте и автомобилей на парковке, снятых сверху, одна и та же проблема: осевые рамки повёрнутых и плотно расположенных объектов перекрывают друг друга и захватывают фон, поэтому NMS удаляет реальные объекты, а итоговые количества занижаются. Ориентированные рамки точно охватывают каждый объект повёрнутым прямоугольником, что упрощает подсчёт на плотных сценах и сразу даёт угол. Но с углами связаны соглашения, которые сбивают с толку почти всех при первой интеграции: на изображении гавани из этого урока поворот первого корабля равен −15°, хотя его корпус расположен под углом 75°.

Результат

Обучи yolo26n-obb.pt на dota8.yaml, посчитай детекции по классам из result.obb и преобразуй xywhr каждого предсказания в ось длинной стороны с углом в диапазоне [0, 180) градусов.

Ускоренное прохождение
Если ты уже освоился, вот краткая версия.
  1. Строка метки: класс, затем координаты четырёх углов x1 y1 … x4 y4, нормализованные и расположенные по периметру по порядку.

  2. yolo obb train data=dota8.yaml model=yolo26n-obb.pt epochs=1 imgsz=320.

  3. Читай result.obb.xywhr (поворот в радианах) или .xyxyxyxy (углы); результаты OBB не заполняют result.boxes.

  4. Нормализуй ориентацию относительно длинной стороны: если w < h, добавь 90°, затем вычисли остаток от деления угла на 180°.

Практические занятия

Когда важен поворот#

Ориентированные рамки Ultralytics YOLO плотно охватывают три пришвартованные яхты на аэрофотоснимке; каждая подписана как Ship и дополнена значением уверенности

В материале Оценка позы и OBB рассказывается, когда стоит выбрать OBB вместо детекции. Практическая проверка: если осевые рамки соседних объектов перекрываются сильнее, чем сами объекты, NMS подавляет истинные детекции, и подсчёт оказывается заниженным. Классический пример — аэрофотоснимки; поэтому предобученная модель yolo26n-obb.pt обучена на DOTA с параметром imgsz=1024.

Осевая рамкаОриентированная рамка
Рамка корабля под углом 45°Примерно половина рамки — фонПлотно охватывает объект
Плотные ряды повёрнутых объектовРамки перекрываются; NMS удаляет настоящие объектыРамки чётко разделены
Указывает уголнетДа, в виде оси (по модулю 180°)
Поле результатаresult.boxesresult.obb

Формат меток xyxyxyxy#

Строка метки OBB содержит индекс класса, а затем координаты четырёх углов, нормализованные и расположенные по периметру по порядку. Пример настоящей строки из DOTA8 (класс 1 — это ship):

1 0.813477 0.523437 0.814453 0.504882 0.87207 0.504882 0.87207 0.524414

Представь её как четыре точки: (0.813, 0.523) → (0.814, 0.505) → (0.872, 0.505) → (0.872, 0.524). В данном случае корабль расположен почти горизонтально: его ширина составляет около 0.059 изображения, а высота — 0.019. Внутри Ultralytics преобразует каждый набор углов в центр, ширину, высоту и угол поворота (xywhr), подбирая прямоугольник минимальной площади, поэтому для обучения подходит даже слегка непрямоугольный четырёхугольник из инструмента разметки. Формат описан в руководстве по датасетам OBB, а convert_dota_to_yolo_obb в ultralytics.data.converter преобразует исходные аннотации DOTA.

Редактор разметки Ultralytics Platform на аэрофотоснимке DOTA8 с повёрнутыми рамками вокруг теннисных кортов, небольших автомобилей и бассейна

Обучение на DOTA8#

DOTA8 — небольшой датасет ориентированных рамок, созданный для тестирования конвейеров:

Мозаика обучения на DOTA8 с аэрофотоснимками: ряды пришвартованных лодок, теннисных кортов, парковки и самолётов на перроне; у каждого объекта есть ориентированная рамка и индекс класса

from ultralytics import YOLO

model = YOLO("yolo26n-obb.pt")
model.train(data="dota8.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"OBB mAP50: {metrics.box.map50:.3f}  mAP50-95: {metrics.box.map:.3f}")
OBB mAP50: 0.995  mAP50-95: 0.832

Метрики OBB находятся в metrics.box, но лежащий в их основе IoU вычисляется между повёрнутыми прямоугольниками.

DOTA8 — это дымовой тест

Высокая оценка объясняется тем, что DOTA8 — это часть данных предобучения. Она подтверждает, что пайплайн работает, но ничего не говорит о твоих изображениях.

Считывание предсказаний и нормализация угла#

import math
from collections import Counter

from ultralytics import YOLO

def long_side_axis(w, h, r):
    """Angle of the box's long side in degrees, in [0, 180), whichever side the model called width."""
    deg = math.degrees(r) + (90 if w < h else 0)
    return deg % 180

model = YOLO("yolo26n-obb.pt")  # trained on DOTAv1, 15 aerial classes
result = model("https://ultralytics.com/images/boats.jpg", verbose=False)[0]
obb = result.obb.cpu()

print("xywhr:", tuple(obb.xywhr.shape), " xyxyxyxy:", tuple(obb.xyxyxyxy.shape))
print(Counter(result.names[int(c)] for c in obb.cls.tolist()))
for (cx, cy, w, h, r), conf in list(zip(obb.xywhr.tolist(), obb.conf.tolist()))[:5]:
    print(f"{conf:.2f} center=({cx:.0f},{cy:.0f}) size={w:.0f}x{h:.0f} raw={math.degrees(r):4.0f} deg  axis={long_side_axis(w, h, r):4.0f} deg")
xywhr: (173, 5)  xyxyxyxy: (173, 4, 2)
Counter({'ship': 168, 'harbor': 5})
0.87 center=(619,850) size=33x108 raw= -15 deg  axis=  75 deg
0.86 center=(1650,937) size=116x33 raw=  21 deg  axis=  21 deg
0.86 center=(1105,571) size=124x35 raw=  21 deg  axis=  21 deg
0.86 center=(1596,1037) size=129x35 raw=  25 deg  axis=  25 deg
0.86 center=(825,940) size=34x99 raw= -15 deg  axis=  75 deg

Построчно:

  • xywhr: (173, 5) — 173 детекции, каждая задаётся координатами центра x и y, шириной и высотой в пикселях, а также углом поворота в радианах. xyxyxyxy содержит те же рамки в виде четырёх угловых точек, готовых для cv2.polylines.
  • Счётчик подсчитывает объекты из result.obb.cls: 168 кораблей и 5 гаваней. Для моделей OBB result.boxes — это None, поэтому код подсчёта, написанный для детекции, нужно переключить на другие поля.
  • raw и axis. Первый корабль имеет размеры 33 × 108 и угол −15°: его w — короткая сторона, поэтому корпус направлен под углом −15° + 90° = 75°. У второго корабля размеры 116 × 33 и угол 21°; здесь сначала указана длинная сторона. Если считывать исходные углы, ось первого корабля будет смещена на 90°; после нормализации угол каждого корабля будет означать одно и то же, и его можно будет сравнивать с углами других кораблей и на других кадрах.

Это правило следует из установленного исходного кода: предсказанный угол поворота лежит в диапазоне [−π/4, 3π/4) — примерно от −45° до 135° — и один и тот же прямоугольник может быть задан как (w, h, θ) или (h, w, θ + 90°). Не считай, что w всегда обозначает длинную сторону.

Маленьким объектам нужны тайлы, а не уменьшение масштаба#

Аэрофотосъёмка содержит мелкие объекты, а проверка или развёртывание с размером меньше обучающего imgsz быстро снижает полноту. Разбивай спутниковые изображения, превышающие входной размер модели, на перекрывающиеся тайлы, а не уменьшай их: иначе автомобиль размером 30 пикселей превратится в размытое пятно размером 5 пикселей. В руководстве по тайловому инференсу SAHI описан этот метод; на кадре шоссе с детектором полноразмерный инференс пропускает много удалённых автомобилей:

Кадр шоссе с детекциями при инференсе всего изображения: многие маленькие удалённые автомобили пропущены

а инференс по тайлам обнаруживает их:

Тот же кадр шоссе при тайловом инференсе SAHI, который обнаруживает гораздо больше маленьких удалённых автомобилей

Подводные камни#

Нет различия между передом и задом

Симптом: «направление» меняется на 180° между кадрами или кажется, что корабли плывут задом наперёд. Причина: прямоугольник, повёрнутый на 180°, остаётся тем же прямоугольником, поэтому OBB определяет ось, а не направление. Решение: если тебе нужно знать, куда направлен корабль или погрузчик, добавь ключевую точку позы на носу или концах вил, либо классификатор для кропа.

Сравнение исходных кривых валидации с результатами детекции

Симптом: кривые precision-recall для OBB отличаются от результатов детекции на похожих данных. Причина: по умолчанию для валидации OBB используется conf=0.01, тогда как для других задач — 0.001. Решение: сравнивай значения mAP или явно задай conf, если нужны совпадающие кривые точности-полноты.

Попробовать

Запусти obb.py, затем напиши скрипт, который вычислит ось длинной стороны каждого корабля на снимке гавани, найдёт наиболее распространённое направление с точностью до ближайших 10° и подсчитает корабли, отклоняющиеся от него более чем на 30°. Помни, что 175° и 5° разделяют всего 10°.

Критерии готовности
Урок считается завершённым, если выполнены все условия ниже.
  • Скрипт выводит количество кораблей, наиболее распространённое направление и количество кораблей, отклоняющихся от него более чем на 30°.

  • При сравнении направлений учитывается переход через 180°, поэтому разница между 175° и 5° считается равной 10°.

  • Ты можешь объяснить, почему OBB не позволяет отличить нос от кормы, и что можно добавить, если нужно определять направление движения.

Показать решение
import math
from collections import Counter

from ultralytics import YOLO

result = YOLO("yolo26n-obb.pt")("https://ultralytics.com/images/boats.jpg", verbose=False)[0]
axes = []
for (cx, cy, w, h, r), cls in zip(result.obb.xywhr.cpu().tolist(), result.obb.cls.tolist()):
    if result.names[int(cls)] == "ship":
        axes.append((math.degrees(r) + (90 if w < h else 0)) % 180)  # long-side axis in [0, 180)

def axis_gap(a, b):
    """Smallest difference between two axes; 175 and 5 degrees are only 10 apart."""
    d = abs(a - b) % 180
    return min(d, 180 - d)

common = Counter(round(a / 10) * 10 % 180 for a in axes).most_common(1)[0][0]
off = [a for a in axes if axis_gap(a, common) > 30]
print(f"{len(axes)} ships, most common axis ~{common} deg, {len(off)} more than 30 deg off it")
168 ships, most common axis ~20 deg, 32 more than 30 deg off it

Большинство лодок стоят параллельно, примерно под углом 20°; 32 корабля, отклонившиеся от этого направления, — выбросы, которые правило мониторинга гавани отметило бы для проверки. OBB не позволяет отличить нос от кормы, поскольку прямоугольник симметричен при повороте на 180°; ключевая точка позы на носу позволила бы определить истинное направление.

Что дальше

Дальше мы полностью откажемся от локализации и обучим классификатор для целых изображений и кропов.

Войди, чтобы сохранять прогресс