پردازش تصویر در OpenCV
تبدیلات مورفولوژیکی (Morphological Transformations)
هدف#
در این فصل،
- عملیات مورفولوژیکی مختلفی مانند فرسایش (Erosion)، انبساط (Dilation)، بازشدن (Opening)، بستهشدن (Closing) و غیره را یاد خواهیم گرفت.
- توابع مختلفی مانند: cv.erode()، cv.dilate()، cv.morphologyEx() و غیره را خواهیم دید.
تئوری#
تبدیلات مورفولوژیکی عملیاتهای سادهای هستند که بر اساس شکل تصویر انجام میشوند. معمولاً بر روی تصاویر دودویی (binary) اعمال میشوند. دو ورودی نیاز دارند: یکی تصویر اصلی ما و دیگری عنصر ساختاری (structuring element) یا کرنل (kernel) نام دارد که ماهیت عملیات را تعیین میکند. دو عملگر مورفولوژیکی پایه، فرسایش و انبساط هستند. سپس اشکال متنوع آنها مانند بازشدن (Opening)، بستهشدن (Closing)، گرادیان (Gradient) و غیره نیز وارد میشوند. آنها را یکییکی با کمک تصویر زیر میبینیم:

۱. فرسایش (Erosion)#
ایده اصلی فرسایش دقیقاً مانند فرسایش خاک است؛ مرزهای شیء پیشزمینه (foreground) را میساید (همیشه سعی کنید پیشزمینه را به رنگ سفید نگه دارید). پس چه اتفاقی میافتد؟ کرنل از روی تصویر میگذرد (مانند کانولوشن دوبعدی). یک پیکسل در تصویر اصلی (یا ۱ یا ۰) تنها در صورتی ۱ در نظر گرفته میشود که تمام پیکسلهای زیر کرنل ۱ باشند، در غیر این صورت فرسوده میشود (به صفر تبدیل میشود).
پس اتفاقی که میافتد این است که تمام پیکسلهای نزدیک به مرز بسته به اندازه کرنل حذف میشوند. بنابراین ضخامت یا اندازه شیء پیشزمینه کاهش مییابد یا به سادگی ناحیه سفید در تصویر کاهش مییابد. برای حذف نویزهای سفید کوچک (همانطور که در فصل فضای رنگی دیدیم)، جداسازی دو شیء متصل و غیره مفید است.
در اینجا، به عنوان مثال، از یک کرنل 5x5 پر از یکها استفاده میکنم. بیایید ببینیم چگونه کار میکند:
import cv2 as cv
import numpy as np
img = cv.imread('j.png', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
kernel = np.ones((5,5),np.uint8)
erosion = cv.erode(img,kernel,iterations = 1)
نتیجه:

۲. انبساط (Dilation)#
این دقیقاً برعکس فرسایش است. در اینجا، یک عنصر پیکسل اگر حداقل یک پیکسل زیر کرنل '1' باشد، '1' است. بنابراین ناحیه سفید در تصویر یا اندازه شیء پیشزمینه افزایش مییابد. معمولاً، در مواردی مانند حذف نویز، فرسایش پس از انبساط دنبال میشود. چون فرسایش نویزهای سفید را حذف میکند، اما شیء ما را هم کوچک میکند. پس آن را منبسط میکنیم. از آنجایی که نویز رفته، دیگر برنمیگردد، اما مساحت شیء ما افزایش مییابد. همچنین در اتصال قطعات شکسته یک شیء مفید است.
dilation = cv.dilate(img,kernel,iterations = 1)
نتیجه:

۳. بازشدن (Opening)#
بازشدن نام دیگری برای فرسایش پس از انبساط است. در حذف نویز مفید است، همانطور که در بالا توضیح دادیم. در اینجا از تابع cv.morphologyEx() استفاده میکنیم.
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
نتیجه:

۴. بستهشدن (Closing)#
بستهشدن عکس بازشدن است، یعنی انبساط پس از فرسایش. برای بستن حفرههای کوچک داخل اشیاء پیشزمینه، یا نقاط سیاه کوچک روی شیء مفید است.
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)
نتیجه:

۵. گرادیان مورفولوژیکی (Morphological Gradient)#
این اختلاف بین انبساط و فرسایش یک تصویر است.
نتیجه مانند طرح کلی (outline) شیء به نظر میرسد.
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)
نتیجه:

۶. کلاهبالایی (Top Hat)#
این اختلاف بین تصویر ورودی و بازشدن تصویر است. مثال زیر برای یک کرنل 9x9 انجام شده است.
tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)
نتیجه:

۷. کلاهسیاه (Black Hat)#
این اختلاف بین بستهشدن تصویر ورودی و تصویر ورودی است.
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)
نتیجه:

عنصر ساختاری (Structuring Element)#
در مثالهای قبلی، عناصر ساختاری را با کمک NumPy به صورت دستی ایجاد کردیم. شکل آنها مستطیلی بود. اما در برخی موارد، ممکن است به کرنلهای بیضوی/دایرهای شکل نیاز داشته باشید. برای این منظور، OpenCV تابعی به نام cv.getStructuringElement() دارد. کافی است شکل و اندازه کرنل را ارسال کنید تا کرنل مورد نظر را دریافت کنید.
# Rectangular Kernel
>>> cv.getStructuringElement(cv.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)
# Elliptical Kernel
>>> cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)
# Cross-shaped Kernel
>>> cv.getStructuringElement(cv.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
# Diamond-shaped Kernel
>>> cv.getStructuringElement(cv.MORPH_DIAMOND,(5,5))
array([[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[1, 1, 1, 1, 1],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0]], dtype=uint8)
منابع تکمیلی#
- عملیات مورفولوژیکی در HIPR2