Ultralytics YOLO27: Únete a la lista de espera.
Get Started
Lecciónintermedio

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°.

Resultado

Entrena yolo26n-obb.pt con dota8.yaml, cuenta las detecciones de cada clase a partir de result.obb y convierte el xywhr de cada predicción en un eje del lado largo, en grados dentro del intervalo [0, 180).

Vía rápida
Si ya tienes experiencia, aquí tienes un resumen.
  1. Fila de etiqueta: clase seguida de las cuatro esquinas x1 y1 … x4 y4, normalizadas y ordenadas a lo largo del perímetro.

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

  3. Lee result.obb.xywhr (rotación en radianes) o .xyxyxyxy (esquinas); los resultados OBB no rellenan result.boxes.

  4. Normaliza la orientación respecto al lado largo: suma 90° cuando w < h y, después, aplica el módulo 180° al ángulo.

Práctico

Cuándo importa la rotación#

Cuadros delimitadores orientados de Ultralytics YOLO ajustados alrededor de tres yates amarrados en una fotografía aérea, cada uno etiquetado como Ship con su nivel de confianza

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 ejesCaja orientada
Ajustado a un barco girado 45°Aproximadamente la mitad es fondoAjustado
Filas densas y giradasLos cuadros se solapan; NMS elimina objetos realesLos cuadros quedan bien separados
Proporciona un ángulonoSí, como eje (módulo 180°)
Campo de resultadoresult.boxesresult.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.524414

Lé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.

Editor de anotaciones de Ultralytics Platform en una imagen aérea de DOTA8, con cuadros girados alrededor de pistas de tenis, vehículos pequeños y una piscina

Entrena con DOTA8#

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

Mosaico de entrenamiento de DOTA8 con teselas aéreas: filas de barcos amarrados, pistas de tenis, un aparcamiento y aviones en una plataforma, cada objeto con una caja orientada y un índice de clase

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

Las métricas OBB se encuentran en metrics.box, pero el IoU se calcula entre rectángulos girados.

DOTA8 es una prueba rápida

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 deg

Lí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. xyxyxyxy contiene las mismas cajas como cuatro puntos de las esquinas, listas para cv2.polylines.
  • El contador cuenta a partir de result.obb.cls: 168 barcos y 5 puertos. result.boxes es None en los modelos OBB, así que el código de conteo escrito para detección debe cambiar de campos.
  • raw frente a axis. El primer barco mide 33 × 108 a −15°: su w es 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:

Fotograma de una autopista con detecciones de inferencia sobre la imagen completa, que no detecta muchos de los vehículos pequeños y lejanos

y la inferencia con teselas los detecta:

El mismo fotograma de una autopista con inferencia por teselas de SAHI, que detecta muchos más vehículos pequeños y lejanos

Errores habituales#

Sin orientación delantera o trasera

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.

Comparación de curvas de validación sin procesar con ejecuciones de detección

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.

Pruébalo

Ejecuta obb.py y escribe un script que calcule el eje del lado largo de cada barco en la imagen del puerto, encuentre el eje más frecuente redondeado a los 10° más cercanos y cuente los barcos que se desvían más de 30°. Recuerda que 175° y 5° solo distan 10°.

Cuándo se considera terminado
Has terminado la lección cuando se cumplen todas estas condiciones.
  • El script muestra el número de barcos, el eje más frecuente y el número de barcos que se desvían más de 30°.

  • La comparación de ejes tiene en cuenta el paso por 180°, así que 175° y 5° cuentan como separados por 10°.

  • Puedes explicar por qué OBB no puede distinguir la proa de la popa y qué añadirías si necesitaras un rumbo.

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 it

La 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.

Qué viene después

A continuación, prescindimos por completo de la localización y entrenamos un clasificador para imágenes completas y recortes.