Caixas delimitadoras orientadas para objetos girados
Rotule caixas delimitadoras orientadas (OBB) de quatro cantos, treine yolo26n-obb.pt com DOTA8 e converta previsões xywhr em um eixo de orientação estável sem cair nas armadilhas das convenções angulares.
Navios em um porto, pacotes em uma esteira e carros em um estacionamento vistos de cima compartilham um problema: caixas delimitadoras alinhadas aos eixos em torno de objetos girados e muito próximos umas das outras se sobrepõem e incluem áreas de fundo, então o NMS exclui objetos reais e as contagens ficam abaixo do valor correto. As caixas delimitadoras orientadas ajustam um retângulo girado a cada objeto, resolvendo a contagem em cenas densas e fornecendo também um ângulo. Esse ângulo vem com convenções que confundem quase todas as primeiras integrações — na imagem do porto desta lição, o primeiro navio indica uma rotação de −15°, mas o casco está orientado a 75°.
Prático
Quando a rotação importa#

Estimativa de pose e OBB explica quando escolher OBB em vez de detecção. O teste prático: se as caixas delimitadoras alinhadas aos eixos em torno de objetos vizinhos se sobrepõem mais do que os próprios objetos, o NMS suprime detecções verdadeiras e as contagens ficam abaixo do valor correto. Imagens aéreas são o caso clássico, por isso o modelo pré-treinado yolo26n-obb.pt é treinado com DOTA em imgsz=1024.
| Caixa alinhada aos eixos | Caixa orientada | |
|---|---|---|
| Ajustada a um navio a 45° | cerca de metade é fundo | justa |
| Fileiras densas e giradas | as caixas se sobrepõem; o NMS descarta objetos verdadeiros | as caixas ficam claramente separadas |
| Fornece um ângulo | não | sim, como um eixo (módulo 180°) |
| Campo de resultado | result.boxes | result.obb |
O formato de rótulo xyxyxyxy#
Uma linha de rótulo OBB contém o índice da classe seguido dos quatro cantos, normalizados e ordenados ao longo do perímetro. Uma linha real do DOTA8 (a classe 1 é ship):
1 0.813477 0.523437 0.814453 0.504882 0.87207 0.504882 0.87207 0.524414Leia como quatro pontos: (0.813, 0.523) → (0.814, 0.505) → (0.872, 0.505) → (0.872, 0.524). Este navio está quase na horizontal, com cerca de 0.059 da largura e 0.019 da altura da imagem. Internamente, a Ultralytics converte cada conjunto de cantos em centro, largura, altura e rotação (xywhr) ajustando o retângulo de área mínima; assim, uma quadrilateral ligeiramente irregular de uma ferramenta de anotação ainda pode ser usada no treinamento. O guia de conjuntos de dados OBB documenta o formato, e convert_dota_to_yolo_obb em ultralytics.data.converter converte as anotações originais do DOTA.

Treinar com DOTA8#
DOTA8 é um pequeno conjunto de dados de caixas orientadas criado para testar pipelines:

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.832As métricas de OBB estão em metrics.box, mas o IoU usado nelas é calculado entre retângulos rotacionados.
A pontuação é elevada porque DOTA8 é uma amostra dos dados de pré-treino. Prova que o pipeline funciona; não diz nada sobre as tuas imagens.
Ler as previsões e normalizar o ângulo#
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 degLinha por linha:
xywhr: (173, 5)— 173 detecções, cada uma com centro x, centro y, largura e altura em pixels e uma rotação em radianos.xyxyxyxycontém as mesmas caixas como quatro pontos de canto, prontas paracv2.polylines.- O contador conta a partir de
result.obb.cls: 168 navios e 5 portos.result.boxeséNonenos modelos OBB, então o código de contagem escrito para detecção precisa usar campos diferentes. rawversusaxis. O primeiro navio mede 33 × 108 a −15°: owdele é o lado curto, então o casco está alinhado a −15° + 90° = 75°. O segundo mede 116 × 33 a 21°, já com o lado longo primeiro. Se você ler os ângulos brutos, o eixo do primeiro navio estará errado em 90°; após a normalização, o ângulo de cada navio significa a mesma coisa e pode ser comparado entre navios e quadros.
A regra vem do código-fonte instalado: a rotação prevista está no intervalo [−π/4, 3π/4) — aproximadamente de −45° a 135° — e o mesmo retângulo pode ser (w, h, θ) ou (h, w, θ + 90°). Nunca presuma que w é o lado longo.
Objetos pequenos precisam de blocos, não de redução de tamanho#
Os objetos aéreos são pequenos, e validar ou implantar com uma resolução inferior à de treinamento imgsz reduz rapidamente o recall. Divida blocos de imagens de satélite maiores que a entrada do modelo em blocos sobrepostos, em vez de reduzi-los, ou um carro de 30 pixels vira uma mancha de 5 pixels. O guia de inferência em blocos do SAHI mostra a técnica; em um quadro de rodovia com um detector, a inferência da imagem inteira deixa passar muitos veículos distantes:

e a inferência em blocos os encontra:

Armadilhas#
Sintoma: uma "orientação" que muda 180° entre fotogramas ou navios que parecem navegar para trás. Causa: um retângulo rodado 180° continua a ser o mesmo retângulo, por isso OBB indica um eixo, não uma orientação. Solução: se precisares de saber para que lado aponta um navio ou empilhador, adiciona um ponto-chave de pose na proa ou nas pontas dos garfos, ou um classificador aplicado ao recorte.
Sintoma: curvas de precisão-revocação OBB que parecem diferentes das suas execuções de detecção em dados semelhantes. Causa: a validação OBB usa conf=0.01 por padrão, enquanto outras tarefas usam 0.001. Solução: compare os valores de mAP ou defina conf explicitamente quando precisar de curvas de precisão-revocação correspondentes.
Mostrar solução
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 itA maioria dos barcos está paralela, em torno de 20°; os 32 navios fora do eixo são os casos atípicos que uma regra de monitoramento do porto sinalizaria para revisão. OBB não consegue distinguir a proa da popa porque um retângulo é simétrico sob uma rotação de 180°; um ponto-chave de pose na proa forneceria um rumo verdadeiro.