بالتأكيد رأيت مرارًا وتكرارًا مثل هذه الصور على الإنترنت:
قررت أن أكتب نصًا عالميًا لإنشاء مثل هذه الصور.
الجزء النظري
دعنا نتحدث قليلا عن كيف سنفعل كل هذا. لنفترض أن هناك مجموعة محدودة من الصور التي يمكننا بها تمهيد اللوحة ، وكذلك صورة واحدة ، والتي يجب تقديمها في صورة فسيفساء. ثم نحتاج إلى تقسيم الصورة التي تحتاج إلى تحويلها إلى مساحات متطابقة ، ثم يتم استبدال كل صورة بصور من مجموعة بيانات تحتوي على صور.
يثير هذا السؤال حول كيفية فهم أي صورة من مجموعة البيانات يجب أن نستبدل بها منطقة معينة. بطبيعة الحال ، فإن البلاط المثالي لمنطقة معينة سيكون في نفس المنطقة. كل مساحة الحجم
يمكن أن يحدد
الأرقام (هنا ، يتوافق كل بكسل مع ثلاثة أرقام - مكوناته R و G و B). بمعنى آخر ، يتم تعريف كل منطقة بواسطة موتر ثلاثي الأبعاد. أصبح من الواضح الآن أنه لتحديد جودة تبليط المنطقة مع صورة ، بشرط أن تتوافق أحجامها ، نحتاج إلى حساب بعض وظائف الفقد. في هذه المشكلة ، يمكننا النظر في MSE من اثنين tensors:
هنا
- عدد العلامات ، في حالتنا
.
ومع ذلك ، لا تنطبق هذه الصيغة على الحالات الحقيقية. الحقيقة هي أنه عندما تكون مجموعة البيانات كبيرة جدًا ، وتكون المساحات التي تنقسم فيها الصورة الأصلية صغيرة جدًا ، سيتعين عليك القيام بالعديد من الإجراءات غير المقبولة ، أي ضغط كل صورة من مجموعة البيانات إلى حجم المنطقة واعتبار MSE بواسطة
الخصائص. بتعبير أدق ، في هذه الصيغة ، الشيء السيئ هو أننا مضطرون إلى ضغط كل صورة على الإطلاق للمقارنة ، أكثر من مرة ، ولكن رقم يساوي عدد المناطق التي تنقسم فيها الصورة الأصلية.
أقترح الحل التالي للمشكلة: سنضحي بجودة منخفضة والآن سنميز كل صورة من مجموعة البيانات بثلاثة أرقام فقط: متوسط RGB في الصورة. بالطبع ، تنشأ العديد من المشكلات من هذا: أولاً ، الآن ، ليس التجانب المثالي للمنطقة هو نفسه فحسب ، بل على سبيل المثال ، هو مقلوب أيضًا (من الواضح أن هذا البلاط أسوأ من الأول) ، وثانياً ، بعد حساب اللون المتوسط ، يمكننا الحصول على R و G و B بحيث لا يكون للصورة حتى بكسل مع هذه المكونات (بمعنى آخر ، من الصعب القول أن العين لدينا تنظر إلى الصورة على أنها مزيج من جميع ألوانها). ومع ذلك ، لم أتوصل إلى طريقة أفضل.
اتضح أنه يمكننا الآن حساب متوسط RGB للصور من مجموعة البيانات مرة واحدة فقط ، ثم استخدام المعلومات الواردة.
بتلخيص ما ورد أعلاه ، نجد أننا نحتاج الآن إلى تحديد منطقة أقرب إليها من بكسل RGB من المجموعة ، ثم تجانب المنطقة بالصورة من مجموعة البيانات التي ينتمي إليها مثل RGB المتوسط. لمقارنة المساحة والبكسل ، سنفعل نفس الشيء: نقوم بتحويل المنطقة إلى ثلاثة أرقام ونجد أقرب متوسط RGB. اتضح أننا نستطيع فقط
تجد في مجموعة من هذا القبيل
أن المسافة الإقليدية بين هاتين النقطتين في الفضاء ثلاثي الأبعاد ستكون ضئيلة:
مجموعة بيانات المعالجة المسبقة
يمكنك بناء مجموعة بيانات الصور الخاصة بك. اعتدت على دمج مجموعات البيانات مع صور القطط
والكلاب .
كما كتبت أعلاه ، يمكننا مرة واحدة حساب متوسط قيم RGB للصور من مجموعة البيانات وحفظها فقط. ماذا نفعل:
import os import cv2 import numpy as np import pickle items = {}
سيتم تنفيذ هذا البرنامج النصي لفترة طويلة نسبيًا ، وبعد ذلك سيتم حفظ المعلومات التي نحتاجها في ملف data.pickle.
خلق فسيفساء
أخيرًا ، دعنا ننتقل إلى إنشاء الفسيفساء. أولاً ، سنكتب عمليات الاستيراد الضرورية ، ونعلن أيضًا عن ثوابت عديدة:
import os import cv2 import pickle import numpy as np from math import sqrt PATH_TO_PICTURE = ''
نحصل على البيانات المحفوظة من الملف:
with open('data.pickle', 'rb') as f: items = pickle.load(f)
نحن تصف وظيفة الخسارة:
def lost_function(r_segm, g_segm, b_segm, arg): r, g, b = arg[1] return sqrt((r - r_segm) ** 2 + (g - g_segm) ** 2 + (b - b_segm) ** 2)
افتح الصورة الأصلية:
file = os.path.join(PATH_TO_PICTURE, PICTURE) img = np.array(cv2.cvtColor(cv2.imread(file), cv2.COLOR_BGR2RGB)) size = img.shape x, y = size[0], size[1]
الآن لاحظ أن التبليط ممكن إذا وفقط إذا
حيث
- حجم الصورة الأصلية ، و
- أبعاد منطقة التبليط. بالطبع ، الشرط أعلاه غير راض دائمًا. لذلك ، سنقوم بقص الصورة الأصلية إلى أحجام مناسبة ، بطرح من حجم الصورة بقاياها من القسمة على حجم المنطقة:
img = cv2.resize(img, (y - (y % VERTICAL_SECTION_SIZE), x - (x % HORIZONTAL_SECTION_SIZE))) size = img.shape x, y = size[0], size[1]
الآن ننتقل مباشرة إلى البلاط:
for i in range(x // HORIZONT AL_SECTION_SIZE): for j in range(y // VERTICAL_SECTION_SIZE): sect = img[i * HORIZONTAL_SECTION_SIZE:(i + 1) * HORIZONTAL_SECTION_SIZE, j * VERTICAL_SECTION_SIZE:(j + 1) * VERTICAL_SECTION_SIZE] r_mean, g_mean, b_mean = sect[:, :, 0].mean(), sect[:, :, 1].mean(), sect[:, :, 2].mean()
هنا ، في السطر قبل الأخير ، يتم تحديد مساحة الصورة المطلوبة ، وفي السطر الأخير ، يتم النظر في متوسط مكونات RGB.
الآن فكر في أحد أهم الخطوط:
current = sorted(items.items(), key=lambda argument: lost_function(r_mean, g_mean, b_mean, argument))[0]
يقوم هذا الخط بفرز جميع صور مجموعة البيانات بترتيب تصاعدي حسب قيمة دالة الخسارة بالنسبة لهم ويحصل على argmin.
الآن علينا فقط قص الصورة واستبدالها بها:
resized = cv2.resize(cv2.cvtColor(cv2.imread(current[0]), cv2.COLOR_BGR2RGB), (VERTICAL_SECTION_SIZE, HORIZONTAL_SECTION_SIZE,)) img[i * HORIZONTAL_SECTION_SIZE:(i + 1) * HORIZONTAL_SECTION_SIZE, j * VERTICAL_SECTION_SIZE:(j + 1) * VERTICAL_SECTION_SIZE] = resized
وأخيرًا ، اعرض الصورة الناتجة على الشاشة:
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) cv2.imshow('ImageWindow', img) cv2.waitKey(0)
أكثر قليلا عن وظيفة الخسارة
بشكل عام ، هناك العديد من الخيارات لوظيفة الخسارة ، كل منها ينطبق نظريًا على هذه المشكلة. لا يمكن تقييم جودتها إلا بشكل تجريبي ، ماذا يمكنك أن تفعل :)
استنتاج
فيما يلي بعض نتائجي:
شفرة المصدر الكاملة ، وملف data.pickle المحسوب بالفعل ، وكذلك الأرشيف مع مجموعة البيانات التي جمعتها ، يمكنك أن ترى في
المستودع .