هل تساءلتم مسبقًا عن كيفية عمل برامج تعديل الصور وكيف يتم تطبيق الفلاتر على الصورة لتظهر بمظهر مختلف من إزالة للخلفية وتعديل السطوع وتغيير ألوانها وإلى ما ذلك؟ سنتطرق في هذه المقالة إلى شرح هذه المفاهيم ونوضح لكم كيفية بناء برنامج لفلترة الصور باستخدام لغة البرمجة بايثون وبالاستعانة ببعض مكتبات معالجة الصور التي توفرها لنا.
كيف تعمل فلاتر الصور؟
دعونا نتعلّم كيفية عمل فلاتر الصور قبل أن نبدأ ببرمجة البرنامج، إذ سيساعدنا فهم عملها على فهم الشيفرة البرمجية التي نكتبها بشكل أكبر.
كما نعرف فإن الصورة مكوّنة من عدد من البكسلات pixels التي تحتوي على ثلاث قيم من 0 إلى 255 لكل من القنوات اللونية الثلاث وهي الأحمر والأخضر والأزرق التي يرمز لهم بالمصطلح RGB. وبالتالي، يمكننا تمثيل الصورة بمصفوفة عددية كما يوضّح الشكل التالي:
وضعنا هنا صورة صغيرة لأجل اختصار طول المصفوفة العددية، لكن لك أن تتخيل أن كل بيكسل في الصورة يقابله قيمة عددية. وقد مثلنا هذه الصورة باستخدام قناة واحدة وهي تدرجات الرمادي Grayscale، لكن الحالة تنطبق أيضًا على صورة RGB الملونة لكن الفارق في هذه الحالة أننا سنستخدم ثلاث قنوات، أي ثلاث مصفوفات ثنائية البعد كما هو موضح في الصورة التالية:
وبالتالي عندما نطبّق فلتر معين على صورة ما، فإننا نعدّل قيمة المصفوفة العددية هذه بضربها بمصفوفة أخرى نسميها مرشّحًا، أو نجري تغييرًا على كل بكسل فيها وفق معادلة معينة، إليكَ بعض الفلاتر:
لحسن الحظ، ليس علينا القلق بخصوص العمليات الحسابية ومعالجة الصورة كل بيكسل على حدة، إذ ستساعدنا مكتبات بايثون على ذلك عن طريق توابع جاهزة توفرها لتسهل علينا تطبيق الفلاتر.
بعد أن تعرفنا على آلية عمل الفلاتر على الصورة دعونا نبدأ خطوات تطوير تطبيقنا بايثون يمكننا من رفع الصورة المطلوبة وتعديلها بفلاتر مختلفة.
الخطوة الأولى: تجهيز بيئة العمل
لنبدأ أولًا بتجهيز بيئة العمل عن طريق تجهيز المكتبات التي سنستخدمها، بالإضافة لهيكل المشروع من ملفات ومجلدات. سننشئ مجلدًا نسميه image-filters وهو المجلد الذي سيمثل المجلد الجذر لمشروعنا، ومن ثم ننشئ بداخله مجلدين الأول باسم images الذي سنضع فيه الصور التي نريد فلترتها والثاني باسم output وهو المجلد الذي سنخزن فيه الصور الناتجة عن تطبيق الفلتر، وأخيرًا ننشئ ملف image_filters.py الذي سيحتوي الشيفرة البرمجية لبرنامجنا، ويمكننا إنشاء ملف يدعى README.md يشرح كيفية عمل البرنامج ومزاياه.
يصبح لدينا هيكل المشروع في النهاية كما يلي:
image-filters/ ├── images/ # مجلد يحتوي الصور التي تريد فلترتها │ ├── sample.jpg ├── output/ # مجلد نخزن فيه الصور الناتجة عن تطبيق الفلتر ├── image_filters.py # الشيفرة البرمجية ├── README.md # للتوثيق (اختياري)
بعدها، نذهب إلى طرفية سطر الأوامر command line وننفّذ التعليمة التالية:
pip install opencv-python numpy pillow
ستتكفل هذه التعليمة بتحميل المكتبات التي سنستعين بها لتطبيق الفلاتر على الصور، وهي مكتبة opencv ومكتبة numpy ومكتبة pillow لذا يجب التأكد من توفر اتصال الإنترنت قبل تنفيذ الأمر.
يمكننا التأكد من أن عملية التحميل قد تمت بنجاح بتنفيذ السطر التالي:
py -c "import cv2; import numpy; import PIL; print('Packages installed successfully!')"
إن تمت العملية بنجاح سنحصل على رسالة Packages installed successfully! في الطرفية.
الخطوة الثالثة: كتابة الهيكل الأساسي للبرنامج
نبدأ باستيراد import المكاتب التي سنستخدمها في بداية الشيفرة البرمجية داخل ملف image_filters.py سنستخدم أيضًا مكتبة os المضمنة في بايثون التي ستساعدنا على إنشاء المجلدات/المسارات:
import cv2 import numpy as np import os
نريد أن ننشئ واجهة مستخدم بسيطة لنرفع منها الصورة التي نريد فلترتها ومن ثم نختار الفلتر ونطبقه، لتحقيق ذلك نضمّن أيضًا مكتبة tkinter و PIL بالشكل التالي:
import tkinter as tk from tkinter import filedialog, Label, Button, ttk from PIL import Image, ImageTk
نحفظ كل من مسار الصورة مع اسمها واسم الفلتر الذي نختاره وسنبني الفلاتر المتاحة في برنامجنا في الخطوات التالية، ومن ثم نخزّن الصورة في متغير بالاستعانة بمكتبة opencv:
image = cv2.imread(image_path)
ننشئ filters نضمّن فيها جميع الفلاتر التي يدعمها برنامجنا بالشكل التالي:
filters = { "Grayscale": apply_grayscale, "Blur": apply_blur, "Edge Detection": apply_edges, "Sharpen": apply_sharpen, "Cartoon": apply_cartoon, "Remove Background": remove_background }
إن أردنا تجزئة منطق البرنامج المطلوب ليسهل علينا فهمه، يمكننا النظر إليه بكونه يقدّم ثلاث مزايا أساسية:
- تطبيق الفلتر على صورة
- عرض الصورة على الواجهة البرمجية
- حفظ الصورة التي طُبّق عليها الفلتر على الحاسب
لنشرح الشيفرة البرمجية لكل ميزة على حدى بالتفصيل فيما يلي.
تطبيق الفلتر على الصورة
نطبّق الفلتر على الصورة من خلال الدالة التالية، ونضع بعين الاعتبار الحالات التي من الممكن أن تُفشل عمل البرنامج مثل عدم وجود الفلتر الذي تم اختياره في برنامجنا أو عدم استطاعة البرنامج على قراءة الصورة:
def apply_filter(): # جعل نطاق المتغيرات نطاق عام يمكن الوصول إليهما في أي مكان من البرنامج global img, img_display # بحال لم يختر المستخدم الصورة بعد أو لم يستطع البرنامج قراءة الصورة if img is None: return # تخزين الفلتر الذي اختاره المستخدم من الواجهة الرسومية selected_filter = filter_var.get() # في حال لم يكن الفلتر مدعومًا من قبل البرنامج if selected_filter not in filters: return # نسخ الصورة بعد قرائتها وتطبيق الفلتر على الصورة image = img.copy() image = filters[selected_filter](image) # تحويل الصورة إلى صورة بألوان RGB في حال كانت صورة بتدرجات رمادية if len(image.shape) == 2: # Convert grayscale to RGB for display image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # تحويل ترميز الصورة من نظام BGR إلى RGB image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # تحويل مصفوفة القيم إلى صورة image = Image.fromarray(image) # تحويل الصورة إلى ترميز يدعمه Tkinter لعرضها على الواجهة الرسومية img_display = ImageTk.PhotoImage(image) # عرض الصورة على الواجهة الرسومية label_img.config(image=img_display)
ملاحظة: الخطوات الأخيرة هي بسبب اختلاف تعامل كل من مكتبة OpenCV وTkinter -وبالنتيجة مكتبة Pillow- بعرض وتمثيل الصور، إذ تستخدم كل مكتبة ترميزًا معينًا والتحويل السابق بين الترميز ضروري لعرض الصورة على الواجهة الرسومية.
عرض الصورة على الواجهة الرسومية
نريد أيضًا أن نعرض الصورة الأولية بعد أن نختارها من مستعرض الملفات، لذا ننشئ الدالة load_image لتحقيق ذلك:
def load_image(): global img, img_display # فتح نافذة مستعرض الملفات واختيار الصورة بالامتدادت المدعومة المضمنة file_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.jpg;*.png;*.jpeg")]) # في حال تعذّر وجود الصورة if not file_path: return # قراء الصورة وضبط أبعادها وتحضيرها للعرض على الواجهة الرسومية img = cv2.imread(file_path) img_resized = cv2.resize(img, (400, 300)) img_resized = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) img_resized = Image.fromarray(img_resized) # عرض الصورة على الواجهة img_display = ImageTk.PhotoImage(img_resized) label_img.config(image=img_display)
حفظ الصورة الناتجة على الحاسب
أخيرًا، نريد حفظ الصورة الناتجة على حاسبنا، لذا نكتب دالة نسميها save_image بالشكل التالي:
def save_image(): global img # في حال تعذر قراءة الصورة أو كونها فارغة if img is None: return # الحصول على اسم الفلتر الذي اخترناه لتضمين اسمه في الصورة المحفوظة selected_filter = filter_var.get() output_dir = "output" # ننشئ مجلد الصور الناتجة output في حال عدم وجوده if not os.path.exists(output_dir): os.makedirs(output_dir) output_filename = os.path.join(output_dir, f"filtered_{selected_filter}.jpg") cv2.imwrite(output_filename, img)
القيم الموجودة في filters هي جميع الفلاتر التي سندعمها في برنامجنا، وسنبرمج المنطق الخاص بكل واحد منها ولكن دعونا أولًا نبرمج الواجهة الرسومية!
الخطوة الخامسة: برمجة الواجهة الرسومية
نبني الواجهة الرسومية بشكل بسيط بحيث تحتوي على ثلاثة أزرار: زر اختيار الصورة، وزر تطبيق الفلتر وزر حفظ الصورة، بالإضافة إلى قائمة منسدلة تحتوي الفلاتر التي يدعمها البرنامج لكي يستطيع المستخدم الاختيار بينها بالإضافة لإطار يعرض الصورة التي اخترناها أو الصورة الناتجة بعد الفلترة بالشكل التالي:
نكتب الشيفرة البرمجية التالية لتحقيق الشكل السابق:
# إنشاء عقدة جذر البرنامج root = tk.Tk() # ضبط عنوان نافذة البرنامج وأبعادها root.title("Image Filter App") root.geometry("500x550") # إنشاء متغير img وإسناد قيمة أولية له img = None # إنشاء زر إضافة الصورة ونص العنوان Label(root, text="Image Filter Application", font=("Arial", 16)).pack(pady=10) Button(root, text="Load Image", command=load_image).pack(pady=5) label_img = Label(root) label_img.pack(pady=5) # القائمة المنسدلة التي تحتوي على الفلاتر filter_var = tk.StringVar() filter_var.set("Grayscale") # نضبط فلتر التدرج الرمادي كقيمة افتراضية filter_menu = ttk.Combobox(root, textvariable=filter_var, values=list(filters.keys()), state="readonly") filter_menu.pack(pady=5) # إضافة الأزرار Button(root, text="Apply Filter", command=apply_filter).pack(pady=5) Button(root, text="Save Image", command=save_image).pack(pady=5) root.mainloop()
الخطوة الرابعة: كتابة الفلاتر المتاحة
سنضمّن المنطق لكل فلتر من الفلاتر الستة في دالة منفصلة، تسمح لنا هذه الطريقة بمعاملة كل فلتر على حدا بحيث لا يؤثر التعديل عليه أو حذفه أو إضافة فلتر جديد على عمل البرنامج ككل.
فلتر التدرج الرمادي Grayscale
نبدأ بأبسط الفلاتر عملًا وهو فلتر تغيير ألوان الصورة إلى تدرجات اللون الرمادي:
def apply_grayscale(image): return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
تعمل الدالة cvtColor على تغيير قيم البكسلات الموجودة في الصورة التي تحتوي على القيم الثلاث الأحمر والأخضر والأزرق حتى تحتوي على قيمة واحدة تدلّ على شدة اللون مما يمنحنا اللون الرمادي بتدرجاته.
فلتر التغبيش Blur
ننتقل فيما بعد إلى فلتر التغبيش blur ولدى مكتبة opencv دالة جاهزة يمكننا استخدامها ألا وهي GaussianBlur ونمرر لها ثلاث قيم، الصورة ومقدار التشويش والانحراف المعياري للمرشح:
def apply_blur(image, ksize = (15,15)): return cv2.GaussianBlur(image, ksize, 0)
يمكننا زيادة مقدار التغبيش بزيادة قيمة المعامل الثاني ksize.
فلتر الكشف عن الحواف Edge detection
الفلتر الثالث هو فلتر الكشف عن الحواف edge detection وهو فلتر يستخدم بكثرة في تطبيقات الرؤية الحاسوبية للكشف عن الأجسام والتعرف عليها، إذ يعتمد على التغيرات الكبيرة بين بكسل وبكسل آخر يجاوره. لدينا أيضًا تابع جاهز تقدمة مكتبة opencv وهو Canny نمرر له ثلاث قيم وهي الصورة والعتبة الدنيا والعتبة العليا، وهي قيمتين تتحكمان بمقدار الحساسية للكشف عن حافة ما.
def apply_edges(image, low_threshold=100, high_threshold=200): return cv2.Canny(image, low_threshold, high_threshold)
يمكنك تغيير قيم العتبة الدنيا والعليا للتحكم بالحساسية، وسيولد لك البرنامج نتائجًا مختلفة.
فلتر الشحذ Sharpen
ننتقل إلى فلتر الشحذ Sharpen الذي يبرز حواف الصورة بشكل أكبر ويزيد من التباين في الصورة، نستخدم في هذا الفلتر مصفوفة معرّفة وهو أحد المرشحات Kernel التي ذكرناها في بداية المقال بالشكل التالي:
def apply_sharpen(image): kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) return cv2.filter2D(image, -1, kernel)
نستطيع الاستفادة من هذا الفلتر عن طريق توضيح الصور المغبّشة غير الواضحة.
فلتر الرسم الكرتوني Cartoon
ليس من الضرورة أن تكون الفلاتر بسيطة بتعليمة واحدة، بل يمكننا إنشاء فلتر عن طريق استخدام مجموعة من الفلاتر الواحدة تلو الأخرى، كما سنفعل في فلتر الكرتون. إذ نستخدم هذا الفلتر للحصول على نتيجة تشبه صور أفلام الكرتون.
def apply_cartoon(image): # تحويل الصورة إلى صورة ذات تدرج رمادي (بشكل مشابه للفلتر الأول) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # تطبيق تغبيش على الصورة blurred = cv2.medianBlur(gray, 5) # التعرف على الحواف وتنعيمها للحصول على نتيجة تشبه صورة مرسومة edges = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 10) # تغبيش الصورة مع المحافظة على الحواف مشحوذة color = cv2.bilateralFilter(image, 9, 250, 250) # دمج صورة الحواف مع صورة الألوان cartoon = cv2.bitwise_and(color, color, mask=edges) return cartoon
قد يصعب تخيّل نتيجة كل عملية فقط عن طريق قراءة الشيفرة البرمجية، لذا نشجعك هنا على فصل كل تعليمة لوحدها ورؤية نتيجتها على حدى. النتيجة النهائية ستبدو كالشكل التالي:
فلتر إزالة الخلفية Remove Background
أخيرًا، نكتب الفلتر الأخير في هذا البرنامج ألا وهو فلتر إزالة الخلفية. هذا الفلتر مفيد في الحالات التي تريد فيها عزل كائن ما عن البيئة المحيطة به مثل صورة شخصية أو في مثالنا هذا النمر.
def remove_background(image): # ننشئ نسخة بذات أبعاد الصورة لكن نملؤها بالأصفار mask = np.zeros(image.shape[:2], np.uint8) # نستخدم خوارزمية GrabCut الموجودة في opencv لإنشاء نموذج للكائن في الصورة bgd_model = np.zeros((1, 65), np.float64) fgd_model = np.zeros((1, 65), np.float64) # نعرف مستطيلًا حول النموذج الذي أنشأناه rect = (50, 50, image.shape[1] - 50, image.shape[0] - 50) # ننفذ خوارزمية GrabCut cv2.grabCut(image, mask, rect, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_RECT) mask = np.where((mask == 2) | (mask == 0), 0, 1).astype("uint8") # نطبق الصورة ذات القيم الصفرية على صورتنا لإزالة الخلفية result = image * mask[:, :, np.newaxis] return result
نحصل على النتيجة التالية:
سنكتفي بهذه الفلاتر في تطبيقنا، ونترك لكم حرية التجربة وإضافة المزيد من الفلاتر على البرنامج، كل ما عليكم هو كتابة دالة جديدة تحتوي على المنطق البرمجي الذي يستخدمه هذا الفلتر وإضافتها إلى filters واستدعاؤها. ندعوكم لتجربة فلتر آخر وليكن عكس الألوان Invert colors وتجربة تضمينه في البرنامج لتحسينه.
الشيفرة البرمجية الكاملة للتطبيق
إليكم كامل الشيفرة الخاصة بالتطبيق لاختبارها وتجربتها لديكم:
import cv2 import numpy as np import os import tkinter as tk from tkinter import filedialog, Label, Button, ttk from PIL import Image, ImageTk # دالة فلتر التدرج الرمادي def apply_grayscale(image): return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # دالة فلتر التغبيش def apply_blur(image, ksize=(25, 25)): return cv2.GaussianBlur(image, ksize, 0) # دالة الكشف عن الحواف def apply_edges(image, low_threshold=100, high_threshold=200): return cv2.Canny(image, low_threshold, high_threshold) # دالة فلتر الشحذ def apply_sharpen(image): kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) return cv2.filter2D(image, -1, kernel) # دالة فلتر الرسوم الكرتونية def apply_cartoon(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.medianBlur(gray, 5) edges = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 10) color = cv2.bilateralFilter(image, 9, 250, 250) cartoon = cv2.bitwise_and(color, color, mask=edges) return cartoon # دالة فلتر إزالة الخلفية باستخدام خوارزمية Grab cut def remove_background(image): mask = np.zeros(image.shape[:2], np.uint8) bgd_model = np.zeros((1, 65), np.float64) fgd_model = np.zeros((1, 65), np.float64) rect = (50, 50, image.shape[1] - 50, image.shape[0] - 50) cv2.grabCut(image, mask, rect, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_RECT) mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype("uint8") return image * mask2[:, :, np.newaxis] # الفلاتر التي يدعمها البرنامج filters = { "grayscale": apply_grayscale, "blur": apply_blur, "edges": apply_edges, "sharpen": apply_sharpen, "cartoon": apply_cartoon, "remove_bg": remove_background } def apply_filter(): # جعل نطاق المتغيرات نطاق عام يمكن الوصول إليهما في أي مكان من البرنامج global img, img_display # بحال لم يختر المستخدم الصورة بعد أو لم يستطع البرنامج قراءة الصورة if img is None: return # تخزين الفلتر الذي اختاره المستخدم من الواجهة الرسومية selected_filter = filter_var.get() # في حال لم يكن الفلتر مدعومًا من قبل البرنامج if selected_filter not in filters: return # نسخ الصورة بعد قرائتها وتطبيق الفلتر على الصورة image = img.copy() image = filters[selected_filter](image) # تحويل الصورة إلى صورة بألوان RGB في حال كانت صورة بتدرجات رمادية if len(image.shape) == 2: # Convert grayscale to RGB for display image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # تحويل ترميز الصورة من نظام BGR إلى RGB image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # تحويل مصفوفة القيم إلى صورة image = Image.fromarray(image) # تحويل الصورة إلى ترميز يدعمه Tkinter لعرضها على الواجهة الرسومية img_display = ImageTk.PhotoImage(image) # عرض الصورة على الواجهة الرسومية label_img.config(image=img_display) def load_image(): global img, img_display # فتح نافذة مستعرض الملفات واختيار الصورة بالامتدادت المدعومة المضمنة file_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.jpg;*.png;*.jpeg")]) # في حال تعذّر وجود الصورة if not file_path: return # قراء الصورة وضبط أبعادها وتحضيرها للعرض على الواجهة الرسومية img = cv2.imread(file_path) img_resized = cv2.resize(img, (400, 300)) img_resized = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) img_resized = Image.fromarray(img_resized) # عرض الصورة على الواجهة img_display = ImageTk.PhotoImage(img_resized) label_img.config(image=img_display) def save_image(): global img # في حال تعذر قراءة الصورة أو كونها فارغة if img is None: return # الحصول على اسم الفلتر الذي اخترناه لتضمين اسمه في الصورة المحفوظة selected_filter = filter_var.get() output_dir = "output" # ننشئ مجلد الصور الناتجة output في حال عدم وجوده if not os.path.exists(output_dir): os.makedirs(output_dir) output_filename = os.path.join(output_dir, f"filtered_{selected_filter}.jpg") cv2.imwrite(output_filename, img) # إنشاء عقدة جذر البرنامج root = tk.Tk() # ضبط عنوان نافذة البرنامج وأبعادها root.title("Image Filter App") root.geometry("500x550") # إنشاء متغير img وإسناد قيمة أولية له img = None # إنشاء زر إضافة الصورة ونص العنوان Label(root, text="Image Filter Application", font=("Arial", 16)).pack(pady=10) Button(root, text="Load Image", command=load_image).pack(pady=5) label_img = Label(root) label_img.pack(pady=5) # القائمة المنسدلة التي تحتوي على الفلاتر filter_var = tk.StringVar() filter_var.set("Grayscale") # نضبط فلتر التدرج الرمادي كقيمة افتراضية filter_menu = ttk.Combobox(root, textvariable=filter_var, values=list(filters.keys()), state="readonly") filter_menu.pack(pady=5) # إضافة الأزرار Button(root, text="Apply Filter", command=apply_filter).pack(pady=5) Button(root, text="Save Image", command=save_image).pack(pady=5) root.mainloop()
الخاتمة
استعرضنا في هذا المقال كيفية عمل فلاتر الصور، وشرحنا خطوات تطوير تطبيق بايثون البسيط يتضمن بعض الفلاتر بالاستعانة بكل من مكتبة OpenCV وPillow وTkinter، إذ نجد أن تطبيق فلتر إلى الصورة ما هو إلا عمليات حسابية تحدث على بيكسلات الصورة، ويتفاوت تعقيد الفلتر من فلتر بسيط يمكن برمجته بسطر واحد إلى فلتر معقد يحتاج إلى خوارزميات متقدمة لتنفيذه. نرجو أن يكون هذا المقال قد وفر لكم الفهم الأساسي لطريقة عمل برنامج لمعالجة الصور وتطبيق فلاتر منوعة عليها.

أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.