5.25. إيجاد الكتل¶
حوّلت عملية وضع العتبة الإطار الملتقط إلى قناع ثنائي: كل بكسل إما يجتاز اختبار العتبة أو لا يجتازه. وهذا يجيب عن سؤال أي الألوان التي يهتم بها التطبيق تظهر في المشهد، لكنه لا يجيب عن سؤال أين -- فالقناع مجرد بحر من الأصفار والآحاد. الخطوة التالية هي كشف الكتل: المرور على القناع، وإيجاد المناطق المتصلة من البكسلات المجتازة، وإعادة كل واحدة منها ككائن له موضع وحجم واتجاه وبقية الخصائص التي يمكن للتطبيق أن يتصرف بناءً عليها.
find_blobs() هي الدالة الأساسية لتلك الخطوة، وهي أكثر نقاط الدخول شيوعًا إلى عالم كائنات النتائج في وحدة image. تتبّع كرة ملونة، أو متابعة خط مرسوم على الأرض، أو عدّ كم بقعة ساطعة يراها مستشعر حراري، أو تقرير ما إذا كان LED أزرق مضاءً أم مطفأً -- يغطي النداء نفسه كل هذه الحالات. تتغير المدخلات (العتبات، والمنطقة المبحوث فيها، والمرشحات المطبّقة على النتيجة)، لكن نمط النداء يبقى ذاته.
5.25.1. النداء الأساسي¶
تأخذ find_blobs قائمة من العتبات وتعيد قائمة من كائنات نتائج الكتل:
thresholds = [(30, 100, 15, 127, 15, 127)] # LAB threshold for red
blobs = img.find_blobs(thresholds)
for b in blobs:
img.draw_rectangle(b.rect, color=(255, 0, 0))
img.draw_cross(b.cx, b.cy, color=(255, 0, 0))
لكل ثلاثية عتبة الصيغة نفسها التي تتبعها العتبات المُمررة إلى binary() -- ست مدخلات (l_lo, l_hi, a_lo, a_hi, b_lo, b_hi) لصورة RGB565 (الحدود بنظام LAB)، ومدخلان (lo, hi) لصورة بتدرج الرمادي. يمكن تزويد ما يصل إلى 32 عتبة في نداء واحد، وهذا ما يجعل find_blobs() بهذه المرونة: يمكن تتبّع منارات حمراء وخضراء وزرقاء في آن واحد، تساهم كل منها بكتلها الخاصة في القائمة المعادة، وتحدد خاصية code لكل كتلة أي عتبة طابقتها.
نداءا draw_rectangle() و draw_cross() أعلاه يوسمان الإطار الملتقط لأجل المعاينة في الـ IDE. تحمل نتيجة الكتلة بالفعل b.rect (مربع الإحاطة كرباعية) و b.cx / b.cy (المركز كأعداد صحيحة)، لذا فإن رسم الكشف عائدًا إلى الإطار هو مجرد نداءين لدالتين.
5.25.2. ما الذي تحتويه النتيجة¶
كل Blob هي رباعية سمات تجمع معًا كل ما قاسه الكاشف عن المنطقة. تنقسم الخصائص إلى أربع مجموعات.
مجموعة مربع الإحاطة والمركز -- x و y و w و h و rect و cx و cy و cxf و cyf -- تصف موضع الكتلة. rect هي رباعية (x, y, w, h) التي تتوقعها دوال الرسم؛ و cx و cy هما المركز بإحداثيات البكسل الصحيحة؛ و cxf و cyf هما المركز بإحداثيات عشرية دون مستوى البكسل، وهي مفيدة عندما تهتم معايرة سابقة بالمواضع الكسرية.
مجموعة واصفات الشكل -- pixels و area و density و perimeter و roundness و elongation و compactness و rotation -- تصف كيف تبدو الكتلة. pixels هي عدد البكسلات المجتازة؛ و area هي مساحة مربع الإحاطة المحاذي للمحاور (w * h)؛ و density هي نسبة الاثنين، وتقترب من 1.0 لمستطيل مصمت وتنخفض نحو 0.0 لخط مائل رفيع. تقيس كل من roundness و compactness مدى استدارة الكتلة، من منظورين هندسيين مختلفين (roundness من العزوم من الرتبة الثانية، و compactness من نسبة المحيط إلى المساحة)؛ و elongation هي 1.0 - roundness للتسهيل. rotation هي اتجاه المحور الرئيسي بالراديان، وتكون أكثر دقة في الكتل المستطيلة وتصبح مشوّشة في الكتل شبه المستديرة (المحور الملتبس ليس له اتجاه محدد جيدًا).
مجموعة البيانات الوصفية للعتبة والدمج -- code و count -- تحدد أي عتبة طُوبقت وكم عدد الكتل المصدرية التي دُمجت في الكتلة المعادة. code هي خريطة بتات 32 بت ببت واحد مضبوط لكل عتبة مطابِقة (العتبة الواحدة تعطي code == 1؛ والكتلة المدمجة متعددة الألوان قد يكون لها عدة بتات مضبوطة)؛ و count هي 1 ما لم يكن merge=True قد جمع عدة عمليات كشف في واحدة.
مجموعة الزوايا -- corners و min_corners -- تعطي الهندسة المُدارة للكتلة. corners هي رباعية (x, y) للنقاط القصوى المستخرجة من محيط الكتلة، مرتّبة باتجاه عقارب الساعة بدءًا من أعلى اليسار؛ و min_corners هي رباعية الزوايا للمستطيل المُدار ذي المساحة الدنيا الذي يحيط بالكتلة. مستطيل المساحة الدنيا هو الملاءمة المحكمة؛ و rect المحاذي للمحاور هو الملاءمة الفضفاضة المحاذية لشبكة البكسلات. كلاهما مفيد بحسب ما إذا كانت مرحلة لاحقة تحتاج إلى مربع موجّه أم عادي.
تحمل الكتلة مربع الإحاطة المحاذي للمحاور (rect و x و y و w و h)، والمركز (cx و cy أو ما دون البكسل cxf و cyf)، والمستطيل المُدار ذا المساحة الدنيا (min_corners إضافة إلى rotation)، وخطوط المحور الرئيسي / الثانوي الاختيارية المحسوبة بدوال المساعدة على مستوى الوحدة أدناه.¶
5.25.3. ترشيح البحث¶
عادةً ما يحتوي الإطار الملتقط على بكسلات تطابق العتبة لأسباب أخرى غير الكائن الذي يهتم به التطبيق: انعكاسات لامعة، وكائنات خلفية بعيدة، وبكسلات ضوضاء الصورة التي تصادف وقوعها في نطاق LAB. الوسائط المسماة لـ find_blobs() هي خط الدفاع الأول.
يقيّد roi البحث إلى منطقة من الإطار، بالطريقة نفسها التي تفعلها كل دوال وحدة image الأخرى. التطبيق الذي يعلم أن الكائن لا يمكن أن يظهر إلا في النصف السفلي من مجال الرؤية يمرر roi=(0, h//2, w, h//2) ويتجاهل كل ما فوقه؛ والوقت الموفَّر يعود لصالح معدل الإطارات.
يرشّح كل من area_threshold و pixels_threshold الكتل الأصغر من أن يُهتم بها. يُسقط area_threshold الكتل التي يحتوي مربع إحاطتها على مساحة أقل من ذلك العدد من البكسلات (جيد لترشيح الضوضاء المتناثرة)؛ ويُسقط pixels_threshold الكتل التي لها عدد من البكسلات المجتازة أقل من ذلك العدد (جيد لترشيح الكتل الكبيرة لكن المتفرقة، مثل نمط نقطي معتّب ببكسل أو بكسلين مطابقين هنا وهناك). القيمة الافتراضية لكليهما هي 10؛ ورفعها إلى المئات لهدف أمامي بعرض بضعة سنتيمترات يتخلص من كل ذرة ضوضاء صغيرة.
يضبط x_stride و y_stride خطوة البكسل التي يأخذها الماسح أثناء البحث عن كتلة ليبدأ تتبّعها. الخطوة ليست دقة التتبّع -- فالتتبّع يتبع دائمًا حدود الكتلة الفعلية بتفصيل بكسل واحد -- لكنها تتحكم في مدى سرعة عثور المسح على بكسل بداية. عندما يُعلم أن الكتل كبيرة (هدف ملون بحجم القبضة على بُعد قدم من الكاميرا، يسهل أن يكون بعرض مئة بكسل)، فإن x_stride=4, y_stride=4 يقطع زمن المسح بمقدار ستة عشر ضعفًا دون خسارة عملية في الكشف. وعندما تكون الكتل صغيرة (منارة LED بعيدة، بعرض بضعة بكسلات)، يجب أن تبقى الخطوات عند 1 لتفادي تخطّيها كليًا. يقلب invert اختبار العتبة: تصبح المطابقة عدم مطابقة ويعيد الروتين كتل البكسلات غير المجتازة بدلًا من ذلك.
threshold_cb هي دالة رد نداء بـ Python تُستدعى على كل كتلة بعد وضع العتبة لكن قبل بناء قائمة النتائج النهائية. تتلقى دالة رد النداء الكتلة وتعيد True للإبقاء عليها أو False لإسقاطها. هذا هو المكان لتطبيق مرشحات اعتباطية على مستوى Python على خصائص لا تكشفها الوسائط المسماة مباشرة -- كثافة دنيا، أو نطاق دوران محدد، أو تركيبة مخصصة من بتات الرمز بعد الدمج. الوسائط المسماة هي مرشحات في الشيفرة الأصلية وتعمل بسرعة؛ بينما تعمل دالة رد النداء في Python وهي أبطأ لكنها غير محدودة فيما يمكنها التعبير عنه.
5.25.4. دمج الكتل المتداخلة¶
تُجري merge=True معالجة لاحقة لقائمة النتائج لدمج الكتل التي تتداخل مستطيلات إحاطتها. الاستخدام الطبيعي هو كشف هدف ترى الكاميرا لونه كعدة مناطق معتّبة بسبب الانعكاسات اللامعة، أو خطوط الظل، أو الإضاءة غير المتطابقة عبر الكائن: قد تعود كرة حمراء واحدة كثلاث أو أربع كتل حمراء صغيرة، مجتمعةً، ترسم الكرة. مع merge=True، تصبح الكتل الثلاث كتلة واحدة كبيرة، ويغطي rect الاتحاد، ويكون code هو OR بتيًا لرموز الكتل المدموجة (بحيث يحدد الدمج متعدد الألوان أي الألوان ساهمت)، ويُبلّغ count عن عدد الكتل المصدرية التي جُمعت.
ينمّي margin مستطيلات الإحاطة أو يقلّصها قبل اختبار التداخل. مع margin=2، تظل الكتل التي تقترب مستطيلات إحاطتها بمسافة 2 بكسل من بعضها تُدمج؛ ومع margin=-2، تُدمج فقط الكتل التي تتداخل مستطيلات إحاطتها بمقدار 2 بكسل على الأقل. الضبط الطبيعي: هامش موجب للتعامل مع الكتل التي كسّرتها العتبة إلى قطع متجاورة؛ وهامش سالب لإبقاء الكائنات المتميزة المتجمّعة بإحكام منفصلة.
تعمل merge_cb على كل زوج مرشّح قبل حدوث الدمج. تتلقى دالة رد النداء الكتلتين وتعيد True للسماح بالدمج أو False لمنعه. هذه هي الأداة المناسبة للتحقق المتبادل من عمليات الدمج التي تفوتها القاعدة الهندسية -- على سبيل المثال، رفض دمج كتلتين تختلف زاويتا rotation لديهما بأكثر من عتبة معينة، أو رفض دمج كتلة صغيرة في كتلة أكبر منها بكثير إذا كانت الصغيرة مجرد بقعة.
5.25.5. مدرّجات الإسقاط التكرارية¶
يرفق x_hist_bins_max و y_hist_bins_max مدرّجات إسقاط تكرارية اختيارية بكل كتلة. مدرّج الإسقاط التكراري هو عدد البكسلات المجتازة على امتداد محور واحد: يجمع مدرّج محور X البكسلات المجتازة لكل عمود داخل مربع إحاطة الكتلة، ويجمع مدرّج محور Y لكل صف. تكون قيمة كليهما الافتراضية صفرًا -- فلا تُحسب المدرّجات ما لم تُزوَّد قيمة max غير صفرية، إذ إنها وإلا ستضيف عملًا إلى كل عملية كشف.
عندما تُحسب، توفّر المدرّجات إشارة أحادية البعد رخيصة يمكن للتطبيق إجراء تحليل إضافي عليها: كشف موضع شريط عمودي داخل الكتلة، أو إيجاد نقطة انكسار هدف ثنائي اللون، أو عدّ كم فجوة تظهر على امتداد المحور الطويل. تُملأ كخاصيتي x_hist_bins و y_hist_bins على كل Blob.
5.25.6. دوال مساعدة هندسية إضافية¶
تعيش حفنة من المقاييس الهندسية الإضافية كدوال على مستوى الوحدة تأخذ كتلة وتعيد القياس المطلوب:
تعيد
image.get_solidity()صلابة الكتلة -- البكسلات مقسومة على مساحة الغلاف المحدّب. المنطقة المصمتة الممتلئة قريبة من1.0؛ والكتلة ذات التقعّرات (حدوة حصان، أو يد بأصابع مفرودة) تنخفض إلى ما دون ذلك بكثير.تعيد
image.get_convexity()التحدّب -- محيط الغلاف المحدّب مقسومًا على محيط الكتلة. الكتلة المحدّبة تمامًا تساوي1.0؛ والكتل المسننة أو المشرومة تكون أقل.تعيد
image.get_major_axis_line()وimage.get_minor_axis_line()كائناتLineعلى امتداد المحورين الرئيسي والثانوي للكتلة، مشتقّة من المستطيل المُدار ذي المساحة الدنيا.تعيد
image.get_enclosing_circle()كائنCircleيحيط بالكتلة -- وهو مفيد عندما تريد مرحلة لاحقة دائرة لرسمها أو للاختبار في مقابلها.تعيد
image.get_enclosed_ellipse()الخماسية(cx, cy, rx, ry, rotation)لقطع ناقص محفور في المستطيل ذي المساحة الدنيا للكتلة. تُغذّى القيم مباشرة فيdraw_ellipse().
5.25.7. التعلّم التلقائي لعتبة¶
كاشف الكتل ليس أفضل من العتبات التي يُشغّل بها، وعمل إيجاد العتبة الصحيحة للون هدف هو مشكلة بحد ذاتها. هناك نمطان شائعان يقلّلان ذلك العمل.
الأول هو الاختيار التفاعلي في الـ IDE: التقط إطارًا، واسحب مستطيلًا حول مثال للون الهدف، ودع محرر العتبات في الـ IDE يُبلّغ عن حدود LAB التي يراها. تُسقط تلك الحدود في البرنامج النصي كعتبات لـ find_blobs() ويصبح الكاشف جاهزًا.
الثاني هو التعلّم التلقائي البرمجي: روتين معايرة يعمل على الكاميرا يلتقط إطارًا، ويأخذ مدرّجًا تكراريًا لرقعة معلومة حيث يوجد الهدف (get_histogram() مع roi=)، ويقرأ نطاق قيم الرقعة من المدرّج بـ get_percentile(). يضبط المئين الخامس الحد الأدنى لكل قناة والمئين الخامس والتسعون حدّها الأعلى، متجاهلًا البكسلات الشاذة المتفرقة عند الطرفين. على صورة RGB565 يُبلّغ نداء مئين واحد عن قنوات LAB الثلاث دفعةً واحدة، فينتج النداءان الأرقام الستة التي تتوقعها find_blobs():
h = img.get_histogram(roi=patch)
lo = h.get_percentile(0.05)
hi = h.get_percentile(0.95)
threshold = (lo.l_value, hi.l_value,
lo.a_value, hi.a_value,
lo.b_value, hi.b_value)