النقاط الرئيسية المخصصة ومقاييس OKS
صمّم مخططًا للنقاط الرئيسية لكائنات غير بشرية، واكتب YAML مجموعة البيانات وتحقق منه، واضبط قيم سيغما لـ OKS وفقًا لدقة تحديد موضع كل نقطة.
لا تقتصر الوضعيات على الأشخاص. فأطراف شوك الرافعة الشوكية، وزوايا ملصق الطرد، وأنف الحيوان وذيله — كل كائن له نقاط ثابتة يمكن تسميتها يصلح لأن يكون مجموعة بيانات للوضعيات. والمخطط الذي تكتبه في اليوم الأول دائم: إذ يعتمد عليه كل وسم، وكل زوج انعكاس، وكل مقياس، وتغييره لاحقًا يعني إعادة وسم كل شيء. يضمن هذا الدرس صحة المخطط قبل أن يوسم أحد ألف صورة، ويوضح بدقة كيف سيقيّمه المقياس.
عملي
صمّم مخطط النقاط الرئيسية#
تشترك النقاط الرئيسية الجيدة في ثلاث خصائص: واضحة لا لبس فيها (يحدد الموسّمان النقطة في الموضع نفسه)، وثابتة (توجد النقطة في كل وضعية يتخذها الكائن)، ومفيدة (تستخدمها عملية حسابية لاحقة). لنأخذ رافعة شوكية تُرى من كاميرا سقفية، حيث السؤال المطروح عن المنتج هو: "هل الشوكتان مرفوعتان أثناء تحرك الشاحنة؟":
| الفهرس | النقطة الرئيسية | سبب وجودها | النقطة المقابلة في الانعكاس |
|---|---|---|---|
| 0 | left_fork_tip | مدى الشوكة واتجاهها | 1 |
| 1 | right_fork_tip | مدى الشوكة واتجاهها | 0 |
| 2 | mast_top | ارتفاع الشوكة بالنسبة إلى السارية | 2 (نفسها) |
| 3 | mast_base | نقطة مرجعية على الهيكل | 3 (نفسها) |
تستحق كل نقطة مكانها لأنها تسهم في الحساب: فأطراف الشوكتين تحدد الاتجاه، وزوج السارية يحدد الارتفاع. مرّر كل نقطة مرشحة عبر معايير الاختيار نفسها:
قاوم إضافة نقاط «للاحتياط فقط»: فكل نقطة تستهلك وقتًا في الوسم لكل مثيل، والنقاط التي لا يستخدمها أحد لا تضيف إلا ضجيجًا إلى المقياس. وتوضح مجموعات البيانات العامة النطاق: تستخدم Tiger-Pose 12 نقطة دون معلومات الرؤية، وDog-Pose 24 نقطة، وHand Keypoints 21 نقطة:


اكتب ملف YAML لمجموعة البيانات#
path: forklift-pose
train: images/train
val: images/val
kpt_shape: [4, 3] # 4 keypoints, each x, y, visibility
flip_idx: [1, 0, 2, 3] # fork tips swap, mast points stay
kpt_oks_sigmas: [0.05, 0.05, 0.08, 0.08] # tips are sharp, mast ends are fuzzier
names:
0: forklift
kpt_names:
0:
- left_fork_tip
- right_fork_tip
- mast_top
- mast_base- يوثّق
kpt_namesترتيب النقاط للأشخاص والأدوات، لكل فئة. - يجب أن يعكس
flip_idxنفسه: فتطبيقه مرتين يعيد كل فهرس إلى نفسه. - إن
kpt_oks_sigmasاختياري، ويحدد أوزان خسارة النقاط الرئيسية أثناء التدريب وOKS أثناء التحقق؛ وإذا أُدرج، فيجب أن يحتوي على قيمة موجبة واحدة لكل نقطة رئيسية، وإلا سيتوقف التشغيل معValueError.
على Ultralytics Platform، يعمل المخطط نفسه قالبًا للهيكل. ويمكن أن يبدأ القالب الجديد من قالب موجود (ابدأ من…)، لتعيد تسمية النقاط وتحذف ما لا تحتاج إليه بدلًا من وضع كل نقطة رئيسية يدويًا — حافظ على ترتيب الفهرس الوارد في جدول المخطط:

تحقق من التسميات قبل التدريب#
قد تتعطل مجموعات بيانات الوضعيات بصمت: فصف يفتقد نقطة رئيسية واحدة يزيح كل القيم اللاحقة، كما تُقرأ قيمة رؤية مقدارها 3 من مُصدِّر معطوب على أنها أي رقم آخر. يحمّل هذا المدقق ملف YAML باستخدام أداة التحميل الخاصة بـ Ultralytics ويتحقق من كل صف. يعمل كما هو على COCO8-pose؛ استبدل مسار ملف YAML بمسارك:
from pathlib import Path
from ultralytics.data.utils import check_det_dataset
data = check_det_dataset("coco8-pose.yaml") # resolves paths, downloads if needed
k, dims = data["kpt_shape"]
flip = data.get("flip_idx", [])
assert not flip or sorted(flip) == list(range(k)), "flip_idx must list every keypoint index once"
assert all(flip[flip[i]] == i for i in range(len(flip))), "flip_idx must undo itself: pairs swap, center points stay"
expected = 5 + k * dims # class + 4 box values + k keypoints of `dims` values
bad = 0
for label in Path(data["path"], "labels").rglob("*.txt"):
for n, row in enumerate(label.read_text().splitlines(), 1):
values = row.split()
if len(values) != expected:
bad += 1
print(f"{label.name}:{n} has {len(values)} values, expected {expected}")
elif dims == 3 and not {float(v) for v in values[7::3]} <= {0, 1, 2}:
bad += 1
print(f"{label.name}:{n} has a visibility flag outside 0/1/2")
print(f"kpt_shape={data['kpt_shape']}, flip_idx ok, {bad} bad rows")kpt_shape=[17, 3], flip_idx ok, 0 bad rowsيبدأ values[7::3] عند أول علامة رؤية (الفئة، ثم أربع قيم للصندوق، ثم x y v) ويأخذ كل قيمة ثالثة بعد ذلك.
كيف تقيّم OKS النقاط الرئيسية#
يستبدل Pose mAP IoU للصندوق بـ تشابه النقاط الرئيسية للكائن (OKS). بالنسبة إلى كل نقطة رئيسية موسومة، تحوّل OKS المسافة بالبكسل بين التنبؤ والتسمية إلى درجة من 0 إلى 1، مع تحجيمها وفقًا لحجم الكائن وقيمة سيغما الخاصة بتلك النقطة، ثم تحسب متوسط الدرجات للنقاط الرئيسية الموسومة. يحسب Pose mAP50-95 متوسط الدقة عند عتبات OKS من 0.50 إلى 0.95، تمامًا كما يحسب mAP للصندوق المتوسط عند عتبات IoU؛ ويشرح دليل مقاييس الأداء الآلية المشتركة.
يسهل فهم قيم سيغما عند قياسها. يقيّم هذا البرنامج النصي التنبؤ نفسه للرافعة الشوكية باستخدام قيمتي سيغما، عبر الدالة kpt_iou التي يستدعيها مدقق الوضعيات:
import torch
from ultralytics.utils.metrics import kpt_iou
# One labeled forklift, 4 keypoints (x, y, visibility) in pixels, inside a 200 x 120 px box
label = torch.tensor([[[40.0, 30, 2], [40, 90, 2], [170, 20, 2], [170, 100, 2]]])
area = torch.tensor([200 * 120 * 0.53]) # Ultralytics scales box area by 0.53 to approximate object area
for offset in (2, 5, 10, 20):
pred = label.clone()
pred[..., 0] += offset # every predicted keypoint lands `offset` pixels to the right
sharp = kpt_iou(label, pred, area, sigma=[0.025] * 4).item()
loose = kpt_iou(label, pred, area, sigma=[0.10] * 4).item()
print(f"{offset:2d} px off: OKS {sharp:.2f} with sigma 0.025, {loose:.2f} with sigma 0.10") 2 px off: OKS 0.94 with sigma 0.025, 1.00 with sigma 0.10
5 px off: OKS 0.67 with sigma 0.025, 0.98 with sigma 0.10
10 px off: OKS 0.21 with sigma 0.025, 0.91 with sigma 0.10
20 px off: OKS 0.00 with sigma 0.025, 0.67 with sigma 0.10في رافعة شوكية أبعادها 200 × 120 بكسل، يفشل تنبؤ يخطئ بمقدار 10 بكسلات في اجتياز عتبة OKS البالغة 0.50 عند سيغما 0.025، لكنه يجتاز كل العتبات حتى 0.90 عند سيغما 0.10. النموذج نفسه والخطأ نفسه، لكن قيمة Pose mAP مختلفة تمامًا.
| سيغما | التأثير | الاستخدام |
|---|---|---|
| صغيرة (≈ 0.025–0.05) | خطأ ببضعة بكسلات يخفض الدرجة بالفعل | النقاط الدقيقة: العينان، وأطراف الشوكتين، والزوايا |
| كبيرة (≈ 0.08–0.10) | هامش تسامح واسع | النقاط غير المحددة بدقة: الوركان، ووسط السارية |
| افتراضي | قيم سيغما في COCO لـ kpt_shape: [17, 3]؛ وإلا فاستخدم 1/K لكل نقطة | ابدأ من هنا، ثم قِس |
ينبغي أن تعكس قيم سيغما مدى اتساق الموسِّمين في تحديد موضع كل نقطة. قِس ذلك: اطلب من شخصين وسم الصور الخمسين نفسها، ثم قارن المسافات لكل نقطة رئيسية. تستحق النقاط التي يختلفان عليها قيم سيغما أكبر؛ وإلا عاقب المقياس النموذج على ضجيج بشري.
يؤدي تغيير kpt_oks_sigmas إلى تغيير mAP للوضعية دون تغيير النموذج، كما يوضح الجدول أعلاه. ثبّت قيم sigmas قبل خط الأساس الأول، وسجّلها مع كل نتيجة، ولا تقارن مطلقًا تشغيلات جرى التحقق منها بقيم مختلفة.
عرض الحل
مثال لطرد على سير ناقل يُرى من الأعلى، والسؤال المطروح هو: "هل وجه الملصق إلى الأعلى، وفي أي اتجاه دُوّر؟":
| الفهرس | النقطة الرئيسية | الغرض | النقطة المقابلة في الانعكاس | سيغما |
|---|---|---|---|---|
| 0 | label_top_left | اتجاه الملصق | 1 | 0.04 |
| 1 | label_top_right | اتجاه الملصق | 0 | 0.04 |
| 2 | label_bottom_right | اتجاه الملصق | 3 | 0.04 |
| 3 | label_bottom_left | اتجاه الملصق | 2 | 0.04 |
kpt_shape: [4, 3]
flip_idx: [1, 0, 3, 2]
kpt_oks_sigmas: [0.04, 0.04, 0.04, 0.04]يجتاز flip_idx اختبار الانعكاس الذاتي: 0 → 1 → 0 و2 → 3 → 2. يبدّل الملصق المعكوس اليمين واليسار مع إبقاء الأعلى والأسفل كما هما، لذا تقترن الزاوية العلوية اليسرى بالزاوية العلوية اليمنى، لا بالزاوية السفلية اليمنى.
للعثور على هامش الخطأ، اضبط sigma=[0.04] * 4 في oks.py وزِد الإزاحات؛ إذ يحدد صندوق الملصق المقياس، لذا استخدم صندوقًا بحجم ملصق طرد نموذجي في صورك. وتمثل الإزاحة التي تنخفض عندها OKS لأول مرة عن 0.5 أكبر خطأ تسمح به دقة الموسِّمين لديك.