Bounding Box Xoay cho Đối tượng Xoay
Gán nhãn OBB bốn góc, huấn luyện yolo26n-obb.pt trên DOTA8 và chuyển dự đoán xywhr thành trục hướng ổn định mà không mắc phải các quy ước về góc.
Tàu trong cảng, bưu kiện trên băng chuyền và ô tô trong bãi đỗ nhìn từ trên xuống có chung một vấn đề: bounding box căn theo trục bao quanh các đối tượng xoay và nằm sát nhau sẽ chồng lấn lên nhau, đồng thời chiếm nhiều nền, khiến NMS xóa nhầm đối tượng thật và số lượng đếm được thấp hơn thực tế. Bounding box xoay ôm sát từng đối tượng bằng hình chữ nhật xoay, khắc phục vấn đề đếm trong cảnh dày đặc và cung cấp sẵn góc xoay. Góc này đi kèm các quy ước khiến hầu hết lần tích hợp đầu tiên gặp trục trặc — trong ảnh cảng ở bài học này, con tàu đầu tiên có góc xoay −15°, nhưng thân tàu nằm theo hướng 75°.
Thực hành
Khi hướng xoay quan trọng#

Ước tính pose và OBB giải thích khi nào nên chọn OBB thay cho detection. Cách kiểm tra thực tế: nếu bounding box căn theo trục quanh các đối tượng lân cận chồng lấn lên nhau nhiều hơn chính các đối tượng đó, NMS sẽ loại bỏ detection đúng và số lượng đếm được sẽ thấp hơn thực tế. Ảnh chụp từ trên cao là trường hợp điển hình, đó là lý do model pretrained yolo26n-obb.pt được huấn luyện trên DOTA tại imgsz=1024.
| Bounding box căn theo trục | Bounding box định hướng | |
|---|---|---|
| Khít quanh tàu ở góc 45° | Khoảng một nửa là nền | Khít sát |
| Các hàng đối tượng xoay, dày đặc | Các box chồng lấn; NMS loại bỏ đối tượng thật | Các box tách biệt rõ ràng |
| Cung cấp góc | không | Có, dưới dạng một trục (modulo 180°) |
| Trường kết quả | result.boxes | result.obb |
Định dạng nhãn xyxyxyxy#
Một hàng nhãn OBB gồm chỉ số class, theo sau là bốn góc đã chuẩn hóa và sắp theo thứ tự quanh chu vi. Một hàng thực tế trong DOTA8 (class 1 là ship):
1 0.813477 0.523437 0.814453 0.504882 0.87207 0.504882 0.87207 0.524414Đọc hàng này thành bốn điểm: (0.813, 0.523) → (0.814, 0.505) → (0.872, 0.505) → (0.872, 0.524). Con tàu này gần như nằm ngang, rộng khoảng 0.059 và cao 0.019 so với kích thước ảnh. Bên trong, Ultralytics chuyển mỗi bộ góc thành tâm, chiều rộng, chiều cao và góc xoay (xywhr) bằng cách khớp hình chữ nhật có diện tích nhỏ nhất, vì vậy các tứ giác hơi lệch khỏi hình chữ nhật do công cụ gán nhãn tạo ra vẫn dùng được để huấn luyện. Hướng dẫn về dataset OBB mô tả định dạng này, còn convert_dota_to_yolo_obb trong ultralytics.data.converter chuyển đổi nhãn DOTA gốc.

Huấn luyện trên DOTA8#
DOTA8 là một bộ dữ liệu hộp định hướng nhỏ được xây dựng để kiểm thử pipeline:

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.832Các chỉ số OBB nằm trong metrics.box, nhưng IoU phía sau chúng được tính giữa các hình chữ nhật đã xoay.
Điểm số cao vì DOTA8 là một phần của dữ liệu pretraining. Điểm số này chứng minh pipeline chạy được, chứ không nói lên điều gì về hình ảnh của bạn.
Đọc các dự đoán và chuẩn hóa góc#
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 degGiải thích từng dòng:
xywhr: (173, 5)— 173 detection, mỗi detection có tâm x, tâm y, chiều rộng, chiều cao tính bằng pixel và góc xoay tính bằng radian.xyxyxyxychứa các hộp tương tự dưới dạng bốn điểm góc, sẵn sàng chocv2.polylines.- Counter đếm từ
result.obb.cls: 168 tàu và 5 bến cảng.result.boxeslàNonetrên các model OBB, vì vậy mã đếm được viết cho detection phải chuyển đổi trường. rawso vớiaxis. Chiếc tàu đầu tiên có kích thước 33 × 108 ở góc −15°:wcủa tàu này là cạnh ngắn, vì vậy thân tàu nằm dọc theo −15° + 90° = 75°. Chiếc thứ hai có kích thước 116 × 33 ở góc 21°, với cạnh dài được ghi trước. Đọc góc thô sẽ khiến trục của chiếc tàu đầu tiên sai 90°; sau khi chuẩn hóa, góc của mọi tàu đều biểu thị cùng một hướng và có thể so sánh giữa các tàu cũng như giữa các frame.
Quy tắc này xuất phát từ source đã cài đặt: góc xoay được dự đoán nằm trong [−π/4, 3π/4) — xấp xỉ −45° đến 135° — và cùng một hình chữ nhật có thể là (w, h, θ) hoặc (h, w, θ + 90°). Đừng bao giờ mặc định w là cạnh dài.
Đối tượng nhỏ cần tile, không phải thu nhỏ ảnh#
Đối tượng chụp từ trên không thường nhỏ, và việc validation hoặc triển khai ở độ phân giải thấp hơn imgsz dùng khi huấn luyện sẽ nhanh chóng làm giảm recall. Chia ảnh vệ tinh thành các tile chồng lấp nếu ảnh lớn hơn kích thước đầu vào của model, thay vì thu nhỏ ảnh; nếu không, một chiếc ô tô 30 pixel sẽ biến thành vệt mờ 5 pixel. Hướng dẫn inference theo tile SAHI trình bày kỹ thuật này; trên một frame đường cao tốc dùng detector, inference trên toàn ảnh bỏ sót nhiều phương tiện ở xa:

còn inference theo tile phát hiện được chúng:

Các lỗi thường gặp#
Triệu chứng: "hướng" lật 180° giữa các frame, hoặc tàu dường như chạy lùi. Nguyên nhân: hình chữ nhật xoay 180° vẫn là cùng một hình chữ nhật, nên OBB chỉ cho biết trục chứ không cho biết hướng. Cách khắc phục: nếu cần biết tàu hoặc xe nâng đang hướng về phía nào, hãy thêm keypoint pose ở mũi tàu hoặc đầu càng, hoặc dùng classifier trên crop.
Triệu chứng: Các đường cong precision-recall của OBB trông khác với các lần chạy detection trên dữ liệu tương tự. Nguyên nhân: Validation OBB mặc định dùng conf=0.01, trong khi các tác vụ khác dùng 0.001. Cách khắc phục: so sánh các giá trị mAP hoặc đặt rõ conf khi cần các đường cong độ chính xác-độ bao phủ tương ứng.
Hiển thị lời giải
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 itPhần lớn thuyền nằm song song ở góc khoảng 20°; 32 tàu lệch trục là các trường hợp ngoại lệ mà một quy tắc giám sát bến cảng sẽ đánh dấu để xem xét. OBB không thể phân biệt mũi tàu với đuôi tàu vì hình chữ nhật đối xứng khi xoay 180°; keypoint tư thế ở mũi tàu sẽ cung cấp hướng thực.