Cuadros delimitadores orientados para objetos girados
Etiqueta OBB de cuatro vértices, entrena yolo26n-obb.pt con DOTA8 y convierte las predicciones xywhr en un eje de orientación estable sin caer en las convenciones de los ángulos.
Los barcos en un puerto, los paquetes en una cinta y los coches en un aparcamiento, vistos desde arriba, comparten un problema: los cuadros alineados con los ejes alrededor de objetos girados y muy juntos se solapan entre sí y abarcan demasiado fondo, por lo que NMS elimina objetos reales y los recuentos quedan por debajo de lo esperado. Los cuadros delimitadores orientados ajustan un rectángulo girado a cada objeto, lo que mejora el recuento en escenas densas y proporciona un ángulo sin coste adicional. Ese ángulo viene acompañado de convenciones que complican casi todas las primeras integraciones: en la imagen del puerto de esta lección, el primer barco indica una rotación de −15°, pero su casco está orientado a 75°.
Práctico
Cuándo importa la rotación#

Estimación de pose y OBB explica cuándo elegir OBB en lugar de detección. La prueba práctica: si los cuadros delimitadores alineados con los ejes alrededor de objetos cercanos se solapan más que los propios objetos, NMS suprimirá detecciones reales y los recuentos quedarán por debajo de lo esperado. Las imágenes aéreas son el caso clásico, por eso el modelo preentrenado yolo26n-obb.pt se entrena con DOTA a imgsz=1024.
| Cuadro alineado con los ejes | Caja orientada | |
|---|---|---|
| Ajustado a un barco girado 45° | Aproximadamente la mitad es fondo | Ajustado |
| Filas densas y giradas | Los cuadros se solapan; NMS elimina objetos reales | Los cuadros quedan bien separados |
| Proporciona un ángulo | no | Sí, como eje (módulo 180°) |
| Campo de resultado | result.boxes | result.obb |
El formato de etiqueta xyxyxyxy#
Una fila de etiqueta OBB contiene el índice de clase seguido de las cuatro esquinas, normalizadas y ordenadas a lo largo del perímetro. Una fila real de DOTA8 (la clase 1 es ship):
1 0.813477 0.523437 0.814453 0.504882 0.87207 0.504882 0.87207 0.524414Léelo como cuatro puntos: (0.813, 0.523) → (0.814, 0.505) → (0.872, 0.505) → (0.872, 0.524). Este barco está casi horizontal: mide aproximadamente 0.059 del ancho de la imagen y 0.019 de alto. Internamente, Ultralytics convierte cada conjunto de esquinas en un centro, ancho, alto y rotación (xywhr) ajustando el rectángulo de área mínima, así que también se puede entrenar con un cuadrilátero ligeramente irregular procedente de una herramienta de anotación. La guía de conjuntos de datos OBB documenta el formato, y convert_dota_to_yolo_obb de ultralytics.data.converter convierte las anotaciones originales de DOTA.

Entrena con DOTA8#
DOTA8 es un pequeño conjunto de datos de cajas orientadas creado para probar flujos de trabajo:

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.832Las métricas OBB se encuentran en metrics.box, pero el IoU se calcula entre rectángulos girados.
La puntuación es alta porque DOTA8 es una parte de los datos de preentrenamiento. Demuestra que la canalización funciona; no dice nada sobre tus imágenes.
Lee las predicciones y normaliza el á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 degLínea por línea:
xywhr: (173, 5): 173 detecciones, cada una con las coordenadas x e y del centro, la anchura y la altura en píxeles, y un giro en radianes.xyxyxyxycontiene las mismas cajas como cuatro puntos de las esquinas, listas paracv2.polylines.- El contador cuenta a partir de
result.obb.cls: 168 barcos y 5 puertos.result.boxesesNoneen los modelos OBB, así que el código de conteo escrito para detección debe cambiar de campos. rawfrente aaxis. El primer barco mide 33 × 108 a −15°: suwes el lado corto, así que el casco sigue la dirección −15° + 90° = 75°. El segundo mide 116 × 33 a 21°; ya tiene primero el lado largo. Si lees los ángulos sin procesar, el eje del primer barco estará desviado 90°; tras normalizarlos, el ángulo de todos los barcos significa lo mismo y se puede comparar entre barcos y fotogramas.
La regla procede del código fuente instalado: la rotación predicha está en [−π/4, 3π/4), aproximadamente entre −45° y 135°, y el mismo rectángulo puede ser (w, h, θ) o (h, w, θ + 90°). Nunca des por hecho que w es el lado largo.
Para los objetos pequeños, usa teselas en vez de reducir el tamaño#
Los objetos aéreos son pequeños, y validar o desplegar por debajo de imgsz durante el entrenamiento reduce rápidamente la exhaustividad. Divide las teselas satélite que superen el tamaño de entrada del modelo en teselas superpuestas, en lugar de reducirlas; de lo contrario, un coche de 30 píxeles se convierte en una mancha de 5 píxeles. La guía de inferencia con teselas de SAHI explica la técnica; en un fotograma de una autopista, un detector aplicado a la imagen completa pasa por alto muchos vehículos lejanos:

y la inferencia con teselas los detecta:

Errores habituales#
Síntoma: una «orientación» que gira 180° entre fotogramas o barcos que parecen navegar marcha atrás. Causa: un rectángulo girado 180° sigue siendo el mismo rectángulo, así que OBB proporciona un eje, no una orientación. Solución: si necesitas saber hacia dónde apunta un barco o una carretilla elevadora, añade un punto clave de pose en la proa o en las puntas de las horquillas, o un clasificador aplicado al recorte.
Síntoma: las curvas de precisión-recall de OBB tienen un aspecto distinto al de las ejecuciones de detección con datos similares. Causa: la validación de OBB usa conf=0.01 de forma predeterminada, mientras que otras tareas usan 0.001. Solución: compara los valores de mAP o establece explícitamente conf cuando necesites que coincidan las curvas de precisión-recall.
Mostrar solución
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 itLa mayoría de los barcos están paralelos, aproximadamente a 20°; los 32 barcos fuera del eje son los valores atípicos que una regla de vigilancia del puerto marcaría para revisión. OBB no puede distinguir la proa de la popa porque un rectángulo es simétrico al girarlo 180°; un punto clave de pose en la proa proporcionaría un rumbo real.