Gán nhãn và huấn luyện model pose
Viết nhãn keypoint kèm thông tin visibility, thiết lập chính xác kpt_shape và flip_idx, huấn luyện trên COCO8-pose, rồi chuyển keypoint thành góc khớp có áp dụng ngưỡng confidence.
Ước lượng pose chuyển mỗi người thành 17 điểm được đánh số; từ các điểm này, ta tính được những chỉ số có giá trị thương mại: góc khuỷu tay cho công thái học, góc đầu gối cho phục hồi chức năng, chiều cao hông để phát hiện té ngã. Hai khóa trong tập dữ liệu quyết định liệu có thể tin cậy các điểm đó hay không, và nếu thiết lập sai một trong hai thì sẽ không có lỗi nào xuất hiện — chỉ có một model nhầm trái với phải hoặc âm thầm huấn luyện mà không dùng phép tăng cường dữ liệu đơn giản nhất.
Thực hành
Hàng nhãn keypoint#

Một hàng nhãn pose là một hàng detection có thêm keypoint. Mọi tọa độ đều được chuẩn hóa về 0–1 và keypoint xuất hiện theo cùng một thứ tự cố định trên mọi hàng. Một hàng thực tế của COCO8-pose, đã lược bớt sau bốn keypoint:
0 0.671279 0.617945 0.645759 0.726859 0.519751 0.381250 2.000000 0.550936 0.348438 2.000000 0.488565 0.367188 2.000000 0.642412 0.354687 2.000000 ...| Giá trị | Ý nghĩa |
|---|---|
0 | chỉ số lớp (người) |
0.671279 0.617945 0.645759 0.726859 | tâm box x, tâm y, chiều rộng, chiều cao |
0.519751 0.381250 2 | keypoint 0 (mũi): x, y, visibility |
| … | 16 keypoint còn lại, theo cùng cấu trúc: 5 + 17 × 3 = 56 giá trị mỗi hàng |
Cờ visibility tuân theo quy ước COCO: 2 là nhìn thấy, 1 là đã gán nhãn nhưng bị che khuất, 0 là không được gán nhãn. Ultralytics loại các keypoint có v = 0 khỏi loss, để cổ tay bị thiếu không khiến model học cách dự đoán cổ tay tại (0, 0).

Dùng 1 cho khớp bị che khuất mà bạn có thể xác định vị trí một cách chắc chắn — chẳng hạn hông phía sau bàn, cổ tay phía sau lưng; các khớp này vẫn được dùng để huấn luyện. Chỉ dùng 0 cho các khớp không ai có thể xác định vị trí. Gán 0 cho mọi khớp bị che khuất sẽ loại bỏ chính những trường hợp khó mà model cần học.
Hướng dẫn về tập dữ liệu pose cũng trình bày biến thể hai giá trị (x y chỉ có, kpt_shape: [K, 2]) dành cho tập dữ liệu không có thông tin visibility.
kpt_shape và flip_idx#
YAML của tập dữ liệu bổ sung hai khóa vào các khóa thông thường path, train, val và names:
kpt_shape: [17, 3] # 17 keypoints, each x, y, visibility
flip_idx: [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15]
flip_idx cho phép phép tăng cường lật ngang biết mỗi keypoint sẽ trở thành keypoint nào trong ảnh phản chiếu. Khi lật gương một người, cổ tay trái (9) sẽ nằm ở vị trí cổ tay phải (10), vì vậy vị trí 9 trong flip_idx chứa giá trị 10 và vị trí 10 chứa giá trị 9; mũi (0) ánh xạ về chính nó. Mọi phép tăng cường đều dịch chuyển bộ xương cùng với các pixel; phép lật là phép duy nhất cần thêm bản đồ chỉ số:

Điều gì xảy ra khi flip_idx không đúng, được xác minh trong source đã cài đặt:
- Thiếu: quá trình huấn luyện ghi cảnh báo và đặt
fliplrcùngflipudthành 0, khiến bạn mất phép tăng cường ít tốn kém nhất. - Sai độ dài: quá trình huấn luyện dừng với một
ValueErrornêu rõ điểm không khớp — đây là lỗi duy nhất được báo rõ ràng. - Đúng độ dài, sai cặp: không có cảnh báo nào. Một nửa số mẫu được lật khiến model học rằng trái là phải, còn ảnh validation cho thấy các bên trái-phải bị hoán đổi ở những pose đối xứng.
Huấn luyện trên COCO8-pose#

from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
model.train(data="coco8-pose.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"box mAP50-95: {metrics.box.map:.3f}")
print(f"pose mAP50-95: {metrics.pose.map:.3f}") Class Images Instances Box(P R mAP50 mAP50-95) Pose(P R mAP50 mAP50-95)
all 4 14 1 0.783 0.893 0.643 0.966 0.571 0.575 0.326
box mAP50-95: 0.643
pose mAP50-95: 0.326Tương tự segmentation, bảng có hai phần. Box đánh giá bounding box người; Pose đánh giá vị trí keypoint bằng Độ tương đồng điểm khóa đối tượng (OKS) trong cùng cơ chế mAP. Pose mAP50-95 là tiêu chí để phát hành sản phẩm pose — bài tiếp theo sẽ cho thấy OKS quyết định một keypoint có chính xác hay không như thế nào.
Từ keypoint đến góc khớp#
result.keypoints chứa xy (N × K × 2, pixel), xyn (đã chuẩn hóa) và conf (N × K), theo thứ tự COCO được trình bày ở trên. Góc tại một khớp là góc giữa hai vector chi đang gặp nhau tại đó — tính bằng tích vô hướng:
import numpy as np
from ultralytics import YOLO
model = YOLO("yolo26n-pose.pt")
result = model("https://ultralytics.com/images/bus.jpg")[0]
kpts = result.keypoints.cpu().numpy() # people x 17 x (x, y, conf)
SHOULDER, ELBOW, WRIST = 5, 7, 9 # left arm, COCO keypoint order
for person, (xy, conf) in enumerate(zip(kpts.xy, kpts.conf)):
if conf[[SHOULDER, ELBOW, WRIST]].min() < 0.5:
print(f"person {person}: left arm not visible enough")
continue
a, b, c = xy[SHOULDER], xy[ELBOW], xy[WRIST]
cos = np.dot(a - b, c - b) / (np.linalg.norm(a - b) * np.linalg.norm(c - b))
print(f"person {person}: left elbow angle {np.degrees(np.arccos(cos)):.0f} deg")person 0: left elbow angle 71 deg
person 1: left elbow angle 96 deg
person 2: left elbow angle 59 deg
person 3: left arm not visible enough
person 4: left arm not visible enoughTừng dòng: kpts.xy[p] là 17 điểm của người p tính bằng pixel; a - b và c - b là cánh tay trên và cẳng tay, biểu diễn dưới dạng vector xuất phát từ khuỷu tay; tích vô hướng đã chuẩn hóa của hai vector là cosine của góc giữa chúng. Cánh tay duỗi thẳng cho giá trị gần 180°, còn cánh tay gập hết cho giá trị gần 30°. Người số 3 và 4 có ít nhất một khớp tay trái với độ tin cậy dưới 0.5, vì vậy script không đo các khớp đó.
Triệu chứng: góc trông có vẻ đáng tin cậy đối với người có cánh tay nằm ngoài khung hình. Nguyên nhân: model trả về tọa độ cho mọi keypoint, dù bị che khuất hay không. Cách khắc phục: bỏ qua mọi phép tính có các khớp thấp hơn ngưỡng độ tin cậy (ở đây là 0.5), đồng thời ghi log tần suất bỏ qua — tỷ lệ bỏ qua tăng là dấu hiệu sớm cho thấy camera hoặc ánh sáng đã thay đổi.
Hiển thị lời giải
import numpy as np
from ultralytics import YOLO
ARMS = {"left": (5, 7, 9), "right": (6, 8, 10)} # shoulder, elbow, wrist
kpts = YOLO("yolo26n-pose.pt")("https://ultralytics.com/images/bus.jpg")[0].keypoints.cpu().numpy()
for person, (xy, conf) in enumerate(zip(kpts.xy, kpts.conf)):
for side, (s, e, w) in ARMS.items():
if conf[[s, e, w]].min() < 0.5:
continue
a, b, c = xy[s], xy[e], xy[w]
cos = np.dot(a - b, c - b) / (np.linalg.norm(a - b) * np.linalg.norm(c - b))
print(f"person {person}: {side} elbow {np.degrees(np.arccos(cos)):.0f} deg")person 0: left elbow 71 deg
person 0: right elbow 94 deg
person 1: left elbow 96 deg
person 2: left elbow 59 deg
person 2: right elbow 135 degCó thể đo được cả hai cánh tay của người số 0 và 2. Cánh tay phải của người số 1 và cả hai cánh tay của người số 3 và 4 đều thấp hơn ngưỡng độ tin cậy 0.5, vì vậy script bỏ qua thay vì đoán.
Thiếu flip_idx sẽ tắt phép tăng cường lật kèm cảnh báo; sai độ dài sẽ khiến quá trình huấn luyện dừng với một ValueError; flip_idx trông có vẻ hợp lệ nhưng chứa các cặp sai sẽ khiến model âm thầm huấn luyện trên nhãn bị đảo gương và gây ra hiện tượng hoán đổi trái-phải khi validation.