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

استخدم الأقنعة برمجيًا: المساحة والتداخل والمخططات والقصاصات

اقرأ masks.data وmasks.xy بصورة صحيحة، وقِس مساحة الكائن ونسبة امتلائه، واختبر التداخل باستخدام الأقنعة بدلًا من الصناديق، واقصص الكائنات من الإطار.

لا يحقق نموذج التجزئة فائدته إلا عندما يحوّل البرنامج أقنعته إلى قرار. في صورة الحافلة، يتداخل صندوق كل شخص مع صندوق الحافلة بنسبة 56–74%، بينما تتداخل أقنعتهم مع قناع الحافلة بنسبة 0% — وسينطلق تنبيه «شخص داخل منطقة المركبة» المبني على الصناديق لكل الأشخاص الأربعة الواقفين على الرصيف. تعطي الأقنعة إجابات صحيحة، لكن طرق عرضها الافتراضية تنطوي على مشكلات في الحجم والتنسيق تنتج أرقامًا تبدو معقولة لكنها خاطئة.

النتيجة

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

المسار السريع
إذا كنت تعرف الأساسيات بالفعل، فإليك النسخة المختصرة.
  1. استخدم retina_masks=True عند إجراء القياسات أو القص، لكي تطابق الأقنعة حجم الصورة الأصلية.

  2. استخدم masks.data (N × H × W) لحساب المساحات والتداخل والقصاصات؛ واستخدم masks.xy (مضلعات بالبكسل) لرسم المخططات وتصديرها.

  3. المساحة هي mask.sum()؛ ونسبة الامتلاء هي مساحة القناع ÷ مساحة الصندوق؛ والتداخل هو (mask_a & mask_b).sum().

  4. تحقق من result.masks is None قبل قراءة الأقنعة: تكون قيمته None عند عدم العثور على شيء.

عملي

عرضان للقناع نفسه#

شخص معزول باستخدام قناع تجزئة من Ultralytics YOLO، مع بقائه في موضعه ضمن الإطار الكامل على خلفية سوداء

يخزن result.masks قناع كل مثيل بتمثيلين. اختر التمثيل وفق ما ينفذه السطر البرمجي التالي؛ ويسرد مرجع Results كل السمات.

السمةالشكلالإحداثياتالاستخدام
masks.dataموتر بحجم N × H × W وقيم 0/1حجم الاستدلال افتراضيًا؛ والحجم الأصلي باستخدام retina_masks=Trueمساحات البكسل، والتداخل بين الأقنعة، والقصاصات
masks.xyقائمة من N مصفوفات، أبعاد كل منها P × 2بكسلات الصورة الأصليةرسم المخططات، وتصدير المضلعات
masks.xynقائمة من N مصفوفات، أبعاد كل منها P × 2مطبّعة إلى 0–1إخراج تسميات مضلعات YOLO
تكون masks.data افتراضيًا بدقة الاستدلال

في صورة الحافلة ذات الأبعاد 1080 × 810، تعود masks.data بالقيمة (5, 640, 480) — وهي دقة الاستدلال بعد إضافة الحواف — لذلك تكون أعداد البكسلات أصغر بنحو 2.85 مرة، ولا يمكن وضع القناع فوق الصورة الأصلية. ترفع retina_masks=True دقة الأقنعة إلى الدقة الأصلية بتكلفة بسيطة في المعالجة اللاحقة. استخدمها كلما أجريت قياسًا أو قصًا.

قِس كل كائن واقصصه#

يقرأ هذا البرنامج النصي كلا العرضين لكل كائن: مساحة البكسل من masks.data، وحجم المخطط من masks.xy، وقصاصة PNG شفافة تستخدم القناع قناة ألفا. يتناول دليل عزل كائنات التجزئة مزيدًا من أشكال القص.

import cv2
import numpy as np

from ultralytics import YOLO

model = YOLO("yolo26n-seg.pt")
result = model("https://ultralytics.com/images/bus.jpg", retina_masks=True)[0]  # masks at original resolution

print("masks.data:", tuple(result.masks.data.shape), "orig_shape:", result.orig_shape)
for i, box in enumerate(result.boxes):
    name = result.names[int(box.cls)]
    mask = result.masks.data[i].cpu().numpy().astype(np.uint8)  # H x W, 1 = object
    polygon = result.masks.xy[i]  # P x 2 outline in original pixels
    x1, y1, x2, y2 = box.xyxy[0].int().tolist()
    mask_area = int(mask.sum())
    box_area = (x2 - x1) * (y2 - y1)
    print(f"{name:6s} conf={float(box.conf):.2f} mask={mask_area:7d}px box={box_area:7d}px fill={mask_area / box_area:.2f} polygon={len(polygon)} pts")

    # Cut the object out on a transparent background
    cutout = np.dstack([result.orig_img, mask * 255])[y1:y2, x1:x2]
    cv2.imwrite(f"{i}_{name}.png", cutout)
masks.data: (5, 1080, 810) orig_shape: (1080, 810)
person conf=0.88 mask=  49331px box=  97219px fill=0.51 polygon=575 pts
person conf=0.86 mask=  21316px box=  67760px fill=0.31 polygon=408 pts
bus    conf=0.86 mask= 255265px box= 409822px fill=0.62 polygon=1172 pts
person conf=0.81 mask=  33524px box=  55842px fill=0.60 polygon=350 pts
person conf=0.64 mask=  11109px box=  23828px fill=0.47 polygon=211 pts

ما ينبغي قراءته فيه:

  • تطابق masks.data: (5, 1080, 810) مع orig_shape، ما يعني أن retina_masks=True أدى مهمته وأن كل عدد للبكسلات محسوب بالبكسلات الأصلية.
  • يربط الفهرس i بين الاثنين. ينتمي result.masks.data[i] إلى result.boxes[i]، لذا تأتي الأسماء ودرجات الثقة من الصندوق، وتأتي وحدات البكسل من القناع.
  • تكشف نسبة الامتلاء ما لا يستطيع الكاشف إظهاره: تشغل الحافلة 62% من صندوقها، بينما لا يشغل الشخص الثاني، الذي يُقصه الطرف الأيمن من الإطار ويقف ملاصقًا للحافلة، سوى 31% من صندوقه.
  • القصاصات هي صور PNG بتنسيق BGRA تحتوي على الشخص أو الحافلة دون أي شيء آخر — جاهزة لكتالوج أو نموذج ثانٍ أو قائمة انتظار للمراجعة.

يستخدم الإصدار المقصوص في دليل العزل خلفية سوداء بدلًا من الخلفية الشفافة:

الشخص نفسه مقصوصًا ضمن صندوق إحاطته، مع جعل كل بكسل خارج القناع أسود

التداخل: موضع الصناديق#

تعتمد قواعد مثل «شخص داخل المنطقة المحظورة» أو «منتج يغطي الناقل» على اختبارات التداخل. نفّذ الاختبار نفسه باستخدام الصناديق والأقنعة:

import numpy as np

from ultralytics import YOLO

result = YOLO("yolo26n-seg.pt")("https://ultralytics.com/images/bus.jpg", retina_masks=True, verbose=False)[0]
names = [result.names[int(c)] for c in result.boxes.cls]
masks = result.masks.data.cpu().numpy().astype(bool)  # N x H x W at original resolution
boxes = np.zeros_like(masks)
for i, (x1, y1, x2, y2) in enumerate(result.boxes.xyxy.int().tolist()):
    boxes[i, y1:y2, x1:x2] = True  # the same objects as filled rectangles

bus = names.index("bus")
for i, name in enumerate(names):
    if name == "person":
        by_box = (boxes[i] & boxes[bus]).sum() / boxes[i].sum()
        by_mask = (masks[i] & masks[bus]).sum() / masks[i].sum()
        print(f"person {i}: {by_box:4.0%} of box overlaps the bus box, {by_mask:4.0%} of mask overlaps the bus mask")
person 0:  67% of box overlaps the bus box,   0% of mask overlaps the bus mask
person 1:  69% of box overlaps the bus box,   0% of mask overlaps the bus mask
person 3:  74% of box overlaps the bus box,   0% of mask overlaps the bus mask
person 4:  56% of box overlaps the bus box,   0% of mask overlaps the bus mask

يقف الأشخاص أمام الحافلة، لذا يتوقف قناع الحافلة عند مخطط كل شخص ولا تشترك الأقنعة في أي بكسلات. أما الصناديق فهي مستطيلات مرسومة حول الاثنين، ولذلك تتداخل بدرجة كبيرة. ينطبق الحساب نفسه على أي منطقة ترسمها بنفسك: املأ مضلعًا في مصفوفة أصفار باستخدام cv2.fillPoly واستخدمه بدلًا من قناع الحافلة.

قاعدة اتخاذ القرار

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

من البكسلات إلى إجابات عن العالم الحقيقي#

السؤالالحساباتانتبه إلى
ما حجمه؟mask.sum() × (سم² لكل بكسل)عاير قيمة سم² لكل بكسل مرة واحدة باستخدام جسم معلوم. في الكاميرا المائلة، تتغير المساحة مع المسافة: عاير كل منطقة على حدة أو قِس في منطقة واحدة فقط.
ما نسبة المنطقة المغطاة؟بكسلات قناع الفئة ÷ بكسلات المنطقةحدّد ما إذا كانت الحالات المتداخلة تُحتسب مرة واحدة أم مرتين.
هل يقع داخل المنطقة؟(mask & zone).sum() / mask.sum()ارسم المنطقة بالحجم الأصلي للصورة.
ما شكل حدوده؟masks.xy[i]راجع التحذير أدناه بشأن الأجسام المنقسمة قبل حساب المساحة منه.

مواطن الخلل#

قراءة الأقنعة عند عدم العثور على أي شيء

العَرَض: AttributeError: 'NoneType' object has no attribute 'xy' على إطارات لا تحتوي على كائنات. السبب: تكون result.masks مساويةً لـ None عندما لا يجد النموذج شيئًا، وليست كائنًا فارغًا. الحل: كرّر على result.boxes (فارغ، وليس None مطلقًا في نماذج التقسيم) وفهرس الأقنعة داخل الحلقة، كما يفعل masks.py، أو تحقّق من result.masks is None أولًا.

مساحة المضلع للكائنات المنقسمة

العَرَض: مساحات غير صحيحة إلى حد كبير للكائنات المحجوبة. السبب: عندما يقسم جسم حاجب كائنًا، يضم masks.xy كل الحدود في مصفوفة واحدة، ويقيس cv2.contourArea على تلك المصفوفة شكلًا غير موجود. الحل: احسب المساحات من أعداد بكسلات masks.data؛ واحتفظ بالمضلعات للرسم.

خلط درجات الدقة

العَرَض: تختلف المساحات بين تشغيلين بعامل يقارب 2.85. السبب: استخدم أحد التشغيلين retina_masks=False، لذا حُسبت مساحاته ببكسلات الاستدلال. الحل: استخدم retina_masks=True للقياس في كل الحالات، وعلى CUDA استدعِ .cpu() قبل .numpy().

جرّبه

شغّل masks.py على صورة خاصة بك تحتوي على ثلاثة أجسام على الأقل، وأبلغ عن الجسم ذي أقل نسبة امتلاء وسبب المبالغة في تقدير حجمه بواسطة صندوقه. أعد التشغيل من دون retina_masks=True، وسجّل كيف تتغير masks.data.shape والمساحات. ثم عدّل overlap.py لاختبار ما إذا كان أي جسم في صورتك يتداخل مع مستطيل ترسمه فوق الثلث السفلي من الإطار.

متى يُعدّ مكتملًا
تكون قد أتممت الدرس عند تحقق جميع الشروط التالية.
  • لديك صورة PNG شفافة مقصوصة لكل جسم مكتشَف في صورتك.

  • يمكنك ذكر أقل نسبة امتلاء وشرح الفرق بين masks.data مع retina_masks=True ومن دونه.

  • يعرض اختبار المنطقة لديك مقدار التداخل لكل جسم بالاستناد إلى الصندوق والقناع، ويمكنك تفسير أي جسم تختلف فيه النتيجتان.

عرض الحل

من دون retina_masks=True، تعيد صورة الحافلة masks.data بالشكل (5, 640, 480): تتقلص مساحة كل بكسل بعامل (1080 × 810) / (640 × 480) ≈ 2.85، ولا يعود القناع متطابقًا مع orig_img المخصّص للصور المقصوصة. عندما تختلف نسبة أبعاد الصورة عن شكل الاستدلال، تضيف حشوة letterbox اختلالًا ثانيًا في المحاذاة.

أقل نسبة امتلاء في صورة الحافلة تخص الشخص الثاني (0.31): يحجبه طرف الإطار الأيمن جزئيًا ويقف ملاصقًا للحافلة، لذا تتكون معظم مساحة الصندوق من الخلفية والحافلة.

للمنطقة، أنشئها مرة واحدة بالحجم الأصلي وأعد استخدام اختبار القناع:

import cv2

h, w = result.orig_shape
zone = np.zeros((h, w), np.uint8)
cv2.fillPoly(zone, [np.array([[0, 2 * h // 3], [w, 2 * h // 3], [w, h], [0, h]])], 1)
zone = zone.astype(bool)
for i, name in enumerate(names):
    print(f"{name} {i}: box {(boxes[i] & zone).sum() / boxes[i].sum():.0%}, mask {(masks[i] & zone).sum() / masks[i].sum():.0%}")

في صورة الحافلة:

person 0: box 36%, mask 29%
person 1: box 33%, mask 37%
bus 2: box 4%, mask 0%
person 3: box 31%, mask 21%
person 4: box 47%, mask 53%

تقع أرجل الأشخاص فعلًا في الثلث السفلي، لذا تتفق نتائج الصندوق والقناع بفارق يقارب عشر نقاط. أما الحافلة فهي الإنذار الكاذب: يدخل صندوقها بنسبة 4% في المنطقة، بينما لا يدخل قناعها فيها إطلاقًا. وفق قاعدة «أكثر من 0% داخل المنطقة»، ستشير الصناديق إلى الحافلة، ولن تفعل الأقنعة ذلك.

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

بعد ذلك، نتخلى تمامًا عن الحالات ونصنّف كل بكسل باستخدام التجزئة الدلالية.

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