Ultralytics YOLO27: انضم إلى قائمة الانتظار.
الدرسمتوسط

النقاط الرئيسية المخصصة ومقاييس OKS

صمّم مخططًا للنقاط الرئيسية لكائنات غير بشرية، واكتب YAML مجموعة البيانات وتحقق منه، واضبط قيم سيغما لـ OKS وفقًا لدقة تحديد موضع كل نقطة.

لا تقتصر الوضعيات على الأشخاص. فأطراف شوك الرافعة الشوكية، وزوايا ملصق الطرد، وأنف الحيوان وذيله — كل كائن له نقاط ثابتة يمكن تسميتها يصلح لأن يكون مجموعة بيانات للوضعيات. والمخطط الذي تكتبه في اليوم الأول دائم: إذ يعتمد عليه كل وسم، وكل زوج انعكاس، وكل مقياس، وتغييره لاحقًا يعني إعادة وسم كل شيء. يضمن هذا الدرس صحة المخطط قبل أن يوسم أحد ألف صورة، ويوضح بدقة كيف سيقيّمه المقياس.

النتيجة

أنشئ ملف YAML لمجموعة بيانات يتضمن مخططًا مخصصًا للنقاط الرئيسية وأزواج الانعكاس وقيم سيغما لـ OKS، ومدققًا للتسميات يجتاز الاختبار على بيانات حقيقية، وتوقعًا لعدد البكسلات التي يسمح بها الخطأ لكل قيمة سيغما.

المسار السريع
إذا كنت تعرف الأساسيات بالفعل، فإليك النسخة المختصرة.
  1. اختر من 3 إلى 12 نقطة واضحة لا لبس فيها، وتوجد في كل وضعية، وتستخدمها الشيفرة اللاحقة.

  2. رتّبها، ثم اكتب flip_idx بتبديل الشركاء الأيمن والأيسر؛ أما النقاط الوسطية فتُطابق نفسها.

  3. أضف kpt_oks_sigmas عندما تختلف النقاط في دقة تحديد مواضعها من قِبل الموسِّمين.

  4. شغّل مدققًا للتسميات للتحقق من عدد الأعمدة، وقيم الرؤية، وأن flip_idx معكوس ذاتيًا، وذلك قبل التدريب.

عملي

صمّم مخطط النقاط الرئيسية#

تشترك النقاط الرئيسية الجيدة في ثلاث خصائص: واضحة لا لبس فيها (يحدد الموسّمان النقطة في الموضع نفسه)، وثابتة (توجد النقطة في كل وضعية يتخذها الكائن)، ومفيدة (تستخدمها عملية حسابية لاحقة). لنأخذ رافعة شوكية تُرى من كاميرا سقفية، حيث السؤال المطروح عن المنتج هو: "هل الشوكتان مرفوعتان أثناء تحرك الشاحنة؟":

الفهرسالنقطة الرئيسيةسبب وجودهاالنقطة المقابلة في الانعكاس
0left_fork_tipمدى الشوكة واتجاهها1
1right_fork_tipمدى الشوكة واتجاهها0
2mast_topارتفاع الشوكة بالنسبة إلى السارية2 (نفسها)
3mast_baseنقطة مرجعية على الهيكل3 (نفسها)

تستحق كل نقطة مكانها لأنها تسهم في الحساب: فأطراف الشوكتين تحدد الاتجاه، وزوج السارية يحدد الارتفاع. مرّر كل نقطة مرشحة عبر معايير الاختيار نفسها:

قاوم إضافة نقاط «للاحتياط فقط»: فكل نقطة تستهلك وقتًا في الوسم لكل مثيل، والنقاط التي لا يستخدمها أحد لا تضيف إلا ضجيجًا إلى المقياس. وتوضح مجموعات البيانات العامة النطاق: تستخدم Tiger-Pose 12 نقطة دون معلومات الرؤية، وDog-Pose 24 نقطة، وHand Keypoints 21 نقطة:

مخطط النقاط الرئيسية لليد مع 21 معلمًا مرقمًا، من المعصم 0 مرورًا بمفاصل كل إصبع وصولًا إلى طرف الخنصر 20

أمثلة Dog-Pose: جرو موسوم بصندوق كلب ونقاط رئيسية ملوّنة على وجهه وأرجله وجسمه

اكتب ملف 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، يعمل المخطط نفسه قالبًا للهيكل. ويمكن أن يبدأ القالب الجديد من قالب موجود (ابدأ من…)، لتعيد تسمية النقاط وتحذف ما لا تحتاج إليه بدلًا من وضع كل نقطة رئيسية يدويًا — حافظ على ترتيب الفهرس الوارد في جدول المخطط:

مربع حوار قالب الهيكل الجديد في Ultralytics Platform: هيكل بشري من 17 نقطة بدأ من قالب موجود، مع إدراج النقاط الرئيسية من nose إلى left_wrist لإعادة تسميتها

تحقق من التسميات قبل التدريب#

قد تتعطل مجموعات بيانات الوضعيات بصمت: فصف يفتقد نقطة رئيسية واحدة يزيح كل القيم اللاحقة، كما تُقرأ قيمة رؤية مقدارها 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 قبل خط الأساس الأول، وسجّلها مع كل نتيجة، ولا تقارن مطلقًا تشغيلات جرى التحقق منها بقيم مختلفة.

جرّبه

صمّم مخططًا للنقاط الرئيسية لكائن من مجال عملك: اذكر من 3 إلى 8 نقاط رئيسية مع سبب من سطر واحد لكل منها، ونقطة مقابلة في الانعكاس، وقيمة سيغما. اكتب ملف YAML، وشغّل check_pose_labels.py على COCO8-pose ثم على عينة صغيرة من تسمياتك، واستخدم oks.py لمعرفة عدد البكسلات التي يمكن أن يخطئ بها أدق نقاطك قبل أن تنخفض OKS عن 0.5.

متى يُعدّ مكتملًا
تكون قد أتممت الدرس عند تحقق جميع الشروط التالية.
  • يسمّي جدول المخطط كل نقطة رئيسية، ويحدد الغرض منها، ونقطتها المقابلة في الانعكاس، وقيمة سيغما الخاصة بها.

  • يحتوي ملف YAML على kpt_shape وflip_idx معكوس ذاتيًا، وعلى قيمة سيغما موجبة واحدة لكل نقطة رئيسية (إن استُخدمت)، ويُبلغ المدقق عن 0 صفوف غير صالحة في عينتك.

  • يمكنك تحديد مقدار الخطأ بالبكسل الذي يجعل أدق نقطة رئيسية لديك تتوقف عن احتسابها عند OKS 0.5.

عرض الحل

مثال لطرد على سير ناقل يُرى من الأعلى، والسؤال المطروح هو: "هل وجه الملصق إلى الأعلى، وفي أي اتجاه دُوّر؟":

الفهرسالنقطة الرئيسيةالغرضالنقطة المقابلة في الانعكاسسيغما
0label_top_leftاتجاه الملصق10.04
1label_top_rightاتجاه الملصق00.04
2label_bottom_rightاتجاه الملصق30.04
3label_bottom_leftاتجاه الملصق20.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 أكبر خطأ تسمح به دقة الموسِّمين لديك.

ما الخطوة التالية

بعد ذلك، نتناول الكائنات التي يهم فيها الاتجاه لكن شكلها مستطيل بسيط: صناديق الإحاطة الموجّهة.

سجّل الدخول لحفظ التقدم