5.21. التحجيم والقلب والاقتصاص

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

تكشف وحدة image تلك العائلة عبر ثلاث طرق تتشارك معظم وسيطاتها ومعظم سلوكها:

  • copy() -- تُنتج نسخة من الصورة، ربما مُحجَّمة أو مُقتصَّة أو مُعاد توجيهها.

  • crop() -- العملية نفسها التي تجريها copy، لكن مع توقع أن يختار التطبيق مستطيلاً فرعياً من المصدر.

  • scale() -- العملية نفسها مجدداً، مع توقع أن يعيد التطبيق تحجيم النتيجة.

تتشارك الطرق الثلاث الوسيطات نفسها وآلية التحويل نفسها؛ والفرق هو في المكان الذي تستقر فيه النتيجة افتراضياً. فـ copy() تُنتج صورة جديدة، بينما تعدّل crop() و scale() المصدرَ في مكانه.

5.21.1. الوسيطات المشتركة

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

تُحجِّم x_scale و y_scale الدخلَ على طول المحورين الأفقي والرأسي على نحو مستقل. وتأخذ كلتاهما القيمة الافتراضية 1.0 (بلا تحجيم). وتُنتج القيم المختلفة لكل منهما تحجيماً غير منتظم -- إطاراً مُمطّطاً ليبلغ ضعف عرضه ارتفاعه، على سبيل المثال.

تقصر roi الدخلَ على مستطيل من صورة المصدر، فتأخذ تلك البكسلات وحدها عبر بقية التحويل. وهذا هو الجزء "الاقتصاصي" من العملية: مرّر roi لاستخراج منطقة فرعية.

hint حقل بتّي من الأعلام يختار طريقة الاستيفاء وأي قلبات توجيه. وتتجمع الأعلام المتعددة عبر العملية البتّية OR (hint=image.BILINEAR | image.HMIRROR). وتنقسم الأعلام إلى مجموعتين -- عائلة الاستيفاء وعائلة التوجيه -- لا علاقة لإحداهما بالأخرى لكنهما تتشاركان الحقل البتّي نفسه.

تختار rgb_channel قناة واحدة من مصدر RGB565. فـ 0 تعني الأحمر، و 1 تعني الأخضر، و 2 تعني الأزرق؛ وتخرج النتيجة كصورة بتدرج الرمادي تحتوي تلك القناة وحدها. وهذا مفيد للعَتْبَنة على القناة الحمراء وحدها، على سبيل المثال.

تعيد color_palette و alpha_palette ربطَ قيم البكسلات عبر جدول بحث عند الخروج، بالطريقة نفسها التي تفعلها طريقتا التحويل to_rainbow() و to_ironbow().

تتبع copy=True و copy_to_fb=True العُرف نفسه الذي تستخدمه كل طريقة أخرى تُنتج نتيجة -- في المكان افتراضياً، و copy=True تخصص نتيجة منفصلة، و copy_to_fb=True تضع النتيجة في مخزن الإطارات لمعاينة OpenMV IDE.

5.21.2. الاستيفاء: AREA و BILINEAR و BICUBIC

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

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

تستوفي image.BICUBIC بين بكسلات الدخل الستة عشر الأقرب باستخدام منحنى تكعيبي، مما يُنتج نتائج أنعم بعدُ على حساب مزيد من الحساب مجدداً. وهي أفضل جودة للتطبيقات الحساسة للتكلفة التي تحتاجها؛ ونادراً ما تستحق الحساب الإضافي للإطارات الحية التي لن تعرضها سوى OpenMV IDE.

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

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

5.21.3. التوجيه: القلبات والدورانات

أعلام التوجيه مجموعة صغيرة من التحويلات المنطقية التي تتركب بحرية بعضها مع بعض ومع أعلام الاستيفاء:

  • تقلب image.VFLIP الصورة رأسياً (الأعلى يصبح الأسفل).

  • تعكس image.HMIRROR الصورة أفقياً (اليسار يصبح اليمين).

  • تبدّل image.TRANSPOSE المحورين x و y (الصفوف تصبح أعمدة).

تأتي معظم الدورانات من تركيب تلك الثلاثة. وتكشف الوحدة أيضاً اختصارات مسماة:

  • image.ROTATE_90 (= VFLIP | TRANSPOSE)

  • image.ROTATE_180 (= HMIRROR | VFLIP)

  • image.ROTATE_270 (= HMIRROR | TRANSPOSE)

في الشيفرة:

img.copy(hint=image.ROTATE_90, copy_to_fb=True)

5.21.4. معالجة نسبة الأبعاد

عندما لا تطابق نسبة أبعاد المصدر المستطيلَ الذي يُرسم فيه، تقرر ثلاثة أعلام ما الذي يُفعل بعدم التطابق:

تحفظ image.SCALE_ASPECT_KEEP نسبةَ أبعاد المصدر و تضع أشرطة سوداء حول النتيجة -- إذ يُحجَّم المصدر حتى يتسع داخل الوجهة، مع ملء بقية الوجهة ببكسلات فارغة (صفرية). وهو الخيار الصحيح عندما يكون الحفاظ على المصدر غير مشوَّه أهمَّ من ملء الخرج كله.

تحفظ image.SCALE_ASPECT_EXPAND نسبةَ أبعاد المصدر و تقتصّه -- إذ يُحجَّم المصدر حتى يملأ الوجهة، مع قطع الأجزاء التي تمتد خارج الوجهة. وهو الخيار الصحيح عندما يكون ملء الخرج كله أهمَّ من رؤية كل جزء من المصدر.

تتجاهل image.SCALE_ASPECT_IGNORE نسبةَ الأبعاد وتمطّ المصدر ليملأ الوجهة، قابلةً بأي تشويه يُحدثه ذلك. وهو الخيار الصحيح عندما يكون التطبيق قد راعى التشويه أصلاً -- عندما لا تكون أبعاد الوجهة في الواقع مستطيلاً للمشهد نفسه، على سبيل المثال.

الوضع الافتراضي (بلا ضبط أي علم لنسبة الأبعاد) هو نفسه SCALE_ASPECT_IGNORE: المطّ للملء. والتطبيقات التي تهتم بنسبة الأبعاد تحدد واحداً من الثلاثة صراحةً.

5.21.5. متى تختار كلاً منها

تستخدم معظم عمليات تغيير الحجم scale() مع زوج x_scale / y_scale وتلميح استيفاء:

img.scale(x_scale=0.5, y_scale=0.5, hint=image.AREA)

تستخدم معظم الدورانات الاستدعاء نفسه مع hint=image.ROTATE_90 أو ما شابه.

يستخدم الاقتصاص crop() مع roi غير افتراضي:

img.crop(roi=(40, 30, 200, 150))

عندما يتعين أن ينجو المصدر من العملية -- التقاط إطار مرجعي، أو أخذ صورة مصغرة لإطار على وشك أن يُعالَج بطريقة مُتلِفة -- تُنتج copy() النتيجةَ كصورة جديدة وتترك المصدر سالماً:

thumbnail = img.copy(x_scale=0.25, y_scale=0.25, hint=image.AREA)

ذلك الوضع الافتراضي هو الفرق الحقيقي وراء الأسماء الثلاثة: فـ scale و crop تحولان في المكان، بينما تخصص copy. وتسدّ وسيطات وضع النتيجة الفجوة: فـ copy=True على scale أو crop تخصص النتيجة كمخزن منفصل في الكومة بدلاً من الكتابة فوق المصدر، و copy_to_fb=True على أي من الثلاث تُنزلها في مخزن الإطارات لمعاينة OpenMV IDE.