5.27. العثور على الدوائر والمستطيلات

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

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

5.27.1. دوائر Hough

تشغّل find_circles() المتغير الدائري من تحويل Hough. يصوّت كل بكسل حافة من معالجة Sobel التمهيدية لكل دائرة يمكن أن تمر عبره؛ وتُرجَع الدوائر التي تجمع أصواتًا كافية.

circles = img.find_circles(threshold=3500,
                            x_margin=10, y_margin=10, r_margin=10,
                            r_min=10, r_max=80, r_step=2)

for c in circles:
    img.draw_circle((c.x, c.y, c.r), color=(255, 0, 0))

إن threshold هي الحد الأدنى لمجموع مقادير حواف Sobel على امتداد الدائرة المرشحة. ترسم الدوائر الأكبر عددًا أكبر من البكسلات ولذلك تحتاج إلى عتبات أعلى لتجتاز؛ فالقيمة التي تعثر على عملة معدنية نصف قطرها 20 بكسل ستُطلق أيضًا على الضوضاء حول حافة بطول 100 بكسل، في حين أن القيمة المضبوطة للعملة الكبيرة ستفوّت العملة الصغيرة. عندما يكون نطاق نصف القطر المستهدف معروفًا، تتناسب العتبة الصحيحة مع المحيط -- إذ يعطي threshold = 50 * 2 * pi * r تقريبًا نقطة انطلاق معقولة، وتأتي القيمة الصحيحة من جولة ضبط وجيزة.

تحدد r_min وr_max وr_step بحث نصف القطر. فبدون حدود، سيبحث الكاشف في كل نصف قطر بدءًا من بضعة بكسلات حتى نصف عرض الصورة، وهو أمر بطيء ووصفة لاكتشافات خاطئة معًا. وضبط r_min وr_max بحيث يحيطان بالحجم المستهدف المتوقع بهامش سخي (مثلًا r_min=15, r_max=25 لعملة معروف أنها حوالي 20 بكسل) يقلل العمل بشكل كبير ويحسّن نسبة الإشارة إلى الضوضاء للأصوات. تتحكم r_step في دقة البحث؛ فالخطوات الأكبر تعمل بشكل أسرع وقد تفوّت دائرة يقع نصف قطرها الحقيقي بين قيمتين معاينتين. والقيمة الافتراضية r_step=2 حل وسط معقول.

تتحكم x_margin وy_margin وr_margin في دمج عمليات الكشف المتقاربة، تمامًا كما تفعل theta_margin وrho_margin لكشف الخطوط. فالدائرة الفيزيائية الواحدة في الصورة تصوّت لعنقود من الدوائر المرشحة التي تتفق مراكزها وأنصاف أقطارها في حدود بضعة بكسلات؛ وتطوي الهوامش كل عنقود إلى ذروته قبل بناء قائمة النتائج. تُرجِع الهوامش الأكبر عمليات كشف أقل عددًا وأكثر ثقة؛ بينما تُرجِع الهوامش الأصغر عمليات كشف أكثر مع نسخ شبه مكررة محتملة.

تخطو x_stride وy_stride في مسح التصويت، بالطريقة نفسها التي تخطو بها في الكاشفات الأخرى. والقيمة الافتراضية 2 و1 مناسبة لمعظم الصور؛ ورفع كليهما إلى 4 هو المقايضة المعتادة في السرعة لصورة معروف أنها تحتوي على دوائر كبيرة.

تحمل كل Circle مُرجَعة x وy وr (المركز ونصف القطر) وmagnitude (مجموع الأصوات، وهو مفيد كدرجة ثقة للترتيب أو التصفية). ورسم عملية الكشف مرة أخرى في الإطار هو استدعاء واحد -- إذ تأخذ draw_circle() المجموعة الثلاثية (x, y, r)، المتاحة كـ (c.x, c.y, c.r) مباشرة من النتيجة.

5.27.2. المستطيلات

تستعير find_rects() كاشف الأشكال رباعية الأضلاع من خط أنابيب AprilTag -- وهو الإجراء نفسه الذي يحدد موقع المربع الأسود حول الوسم، معروضًا بمفرده ككاشف مستطيلات عام الغرض.

rects = img.find_rects(threshold=12000)

for r in rects:
    img.draw_rectangle(r.rect, color=(0, 255, 0))
    for corner in r.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))

إن threshold هي الحد الأدنى لمجموع مقادير الحواف حول محيط المستطيل. يتجاوز المستطيل المطبوع بالأسود على الأبيض في إطار جيد الإضاءة قيمة 10000 بسهولة؛ وقد يحتاج مستطيل باهت على خلفية ذات نسيج إلى الانخفاض إلى 2000 -- مقايضةً للاكتشافات الخاطئة بالحساسية. وكما هو الحال مع كاشف الدوائر، تأتي القيمة الصحيحة من جولة ضبط سريعة مع وجود الأهداف المقصودة في المشهد.

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

تحمل كل Rect مُرجَعة مربع الإحاطة المحاذي للمحاور -- x وy وw وh، إضافة إلى المجموعة الرباعية rect التي تتوقعها draw_rectangle() -- و الزوايا الأربع المكتشفة بصيغة corners. مربع الإحاطة هو ما يستخدمه التطبيق للموضع والحجم التقريبيين؛ بينما تصف الزوايا الشكل الرباعي الإسقاطي نفسه. وعندما تنظر الكاميرا إلى هدف مسطح من زاوية ويحتاج التطبيق إلى إلغاء تشوه شبه المنحرف -- لقراءة نص على تسمية، أو أخذ عينة لون من رقعة مسطحة -- تُغذّى الزوايا مباشرة إلى rotation_corr() عبر الكلمة المفتاحية corners= (انظر تصحيح العدسة والمنظور)، ويكون الناتج هو المستطيل المُصحَّح الجاهز لأي تحليل يأتي بعده.

تحذير

نظرًا لأن الكاشف مضبوط لما يحتاجه خط أنابيب AprilTag -- أشكال رباعية الأضلاع ذات حدود قوية وعالية التباين، مثل مخطط وسم أسود على ورق أبيض -- فهو ليس عملية تعثر على كل مستطيل. فالمستطيلات ذات التباين الناعم، أو الحواف ذات النسيج، أو المحيط المزدحم قد تبقى دون اكتشاف تمامًا. وتعتمد كفاءته على الموقف: اختبره مقابل الأهداف الحقيقية مبكرًا، قبل بناء خط أنابيب حوله.

5.27.3. عندما يخطئ الكاشف

تستفيد الدوائر على وجه الخصوص من تصفية تمهيدية للمدخلات. فالإطار المليء بالضوضاء يُنتج الكثير من بكسلات الحواف الشاردة التي تصوّت جميعها، ويكون لفضاء Hough الناتج ذرى عريضة وغامضة يصعب على عملية الدمج فصلها. وتمريرة gaussian() أو mean() قبل find_circles() تنعّم الضوضاء وتترك الحواف الحقيقية سليمة؛ فيُرجِع الكاشف ذرى أنظف في وقت أقل.

أما بالنسبة للمستطيلات، فإن نمط الفشل الشائع هو العكس: انخفاض التباين بين المستطيل وخلفيته يعني أن مجموع مقادير الحواف لا يتجاوز threshold أبدًا. وتمريرة histeq() لإعادة توزيع نطاق السطوع عبر الامتداد الكامل من 0 إلى 255 تستعيد التباين الذي يحتاجه الكاشف. (يجب أن يكون التباين موجودًا في مكان ما في الصورة؛ إذ لا يمكن لمعادلة المدرج التكراري إلا أن تضخّم ما هو موجود بالفعل.)