در مطالب قبلی مجله تم آف، به اپراتور سوبل در پایتون پرداختیم و فیلترهای مورد نیاز را گام به گام پیادهسازی و مفهوم آنها را بررسی کردیم. در این مطلب سعی میکنیم عمل پیچش در پایتون را بررسی کرده و با استفاده از کتابخانه OpenCV از آن استفاده کنیم.
فرآیند پیچش (Convolution) چگونه است؟
در عمل پیچش (Convolution)، فیلتری روی تمامی موقعیتهای ممکن روی یک تصویر قرار میگیرد و هر ضریب موجود در فیلتر، به مقدار پیکسل متناظر ضرب شده، مقادیر حاصل جمع شده و تصویر خروجی حاصل میشود. در تصویر زیر روش انجام این فرآیند را میبینیم.
توجه داشته باشید که عملگر «*» موجود بین ماتریس وصله تصویر (Image Patch) و کرنل یا هسته (Kernel) نشاندهنده ضرب متناظر است.
برای عمل پیچش برخی پارامترهای دیگر نیز قابل تعریف هستند، مانند:
- «گام» (Stride): این عدد نشاندهنده میزان حرکت فیلتر پس از هر بال اعمال است که برای پیچش معمولی برابر با ۱ است.
- «حاشیه» (Padding): این عدد تعیین میکند که چند لایه پیکسل خالی با مقدار ۰ به اطراف تصویر اولیه افزوده شود. با تنظیم Padding میتوان اندازه تصویر خروجی را با تصویر ورودی یکسان کرد. برای مثال، اگر از یک فیلتر ۳×۳ استفاده کنیم، یک Padding با اندازه ۱ باعث میشود تصویر خروجی هماندازه با تصویر اولیه شود.
برای یادگیری برنامهنویسی با زبان پایتون، پیشنهاد میکنیم به مجموعه آموزشهای مقدماتی تا پیشرفته پایتون تم آف مراجعه کنید که لینک آن در ادامه آورده شده است.
پیاده سازی پیچش در پایتون
در این بخش، پیادهسازی پیچش در پایتون را برای دو کتابخانه OpenCV و Scipy بیان میکنیم.
پیاده سازی پیچش در پایتون با استفاده از OpenCV
ابتدا یک تصویر انتخاب و در کنار فایل برنامه قرار میدهیم.
توجه: برای بزرگنمایی تصاویر این آموزش، روی آنها کلیک کنید.
حال میتوانیم کتابخانههای مورد نیاز را فراخوانی کنیم:
import cv2 as cv
import numpy as np
import scipy.signal as sig
این کتابخانهها بهترتیب برای اعمال زیر کاربرد خواهند داشت:
- پردازش روی تصویر و نمایش آنها
- کار با آرایهها و محاسبات برداری
- پردازش سیگنال
حال تصویر انتخابشده را با استفاده از کتابخانه OpenCV میخوانیم (نام این تصویر Picture_1 با پسوند jpg است):
Image = cv.imread('Picture_1.jpg')
برای نمایش تصویر میتوان بهشکل زیر نوشت:
cv.imshow('Original Image', Image)
cv.waitKey()
cv.destroyAllWindows()
حال میتوانیم برخی از فیلترهای پرکاربرد را اعمال و نتایج را مشاهده کنیم:
۱. فیلتر Sharpening
$$ F_{1}=left[begin{array}{ccc}
0 & -1 & 0 \
-1 & 5 & -1 \
0 & -1 & 0
end{array}right] $$
این فیلتر باعث افزایش وضوح (Sharp) نواحی در تصویر میشود. برای اعمال این فیلتر، بهشکل زیر مینویسیم:
F1 = np.array([[ 0, -1, 0],
[-1, +5, -1],
[ 0, -1, 0]])
G1 = cv.filter2D(Image, -1, F1)
برای نمایش و ذخیره تصویر حاصل نیز مینویسیم:
cv.imshow('Sharpening Filter Result', G1)
cv.imwrite('Sharpening Filter Result.jpg', G1)
cv.waitKey()
cv.destroyAllWindows()
پس از اجرای کد فوق، نتیجه زیر حاصل میشود.
به این ترتیب، مشاهده میکنیم که نواحی ریز و برخی حاشیهها، وضوح بیشتری پیدا کردهاند.
۲. فیلتر Bluring
$$F_{2}=frac{1}{9} timesleft[begin{array}{lll}
1 & 1 & 1 \
1 & 1 & 1 \
1 & 1 & 1
end{array}right] $$
این فیلتر برای ایجاد حالت تارشدگی روی تصویر ایجاد میشود. فیلتر Bluring بهنوعی یک میانگین متحرک ساده (Simple Moving Average) است که روی دو بعد کار میکند. برای اعمال این فیلتر بهشکل زیر عمل میکنیم:
F2 = np.ones((3, 3)) / 9
G2 = cv.filter2D(Image, -1, F2)
cv.imshow('Blurint Filter Result', G2)
cv.imwrite('Bluring Filter Result.jpg', G2)
cv.waitKey()
cv.destroyAllWindows()
و تصویر زیر حاصل میشود.
به این ترتیب، محوشدگی در تصویر بهخوبی مشاهده میشود.
۳. فیلتر Outline
$$ F_{3}=left[begin{array}{ccc}
-1 & -1 & -1 \
-1 & 8 & -1 \
-1 & -1 & -1
end{array}right] $$
این فیلتر برای مشخص کردن حاشیهها و مرز به اشیا در تصویر استفاده میشود. برای پیادهسازی آن میتوانیم بهشکل زیر عمل کنیم:
F3 = np.array([[-1, -1, -1],
[-1, +8, -1],
[-1, -1, -1]])
G3 = cv.filter2D(Image, -1, F3)
cv.imshow('Outline Filter Result', G3)
cv.imwrite('Outline Filter Result.jpg', G3)
cv.waitKey()
cv.destroyAllWindows()
که تصویر زیر در خروجی ایجاد خواهد شد.
به این ترتیب، واکنش فیلتر به مرزها کاملاً مشهود است.
اگر این فیلتر را بهشکل زیر تغییر دهیم:
$$ F_{4}=left[begin{array}{ccc}
-1 & -1 & -1 \
-1 & 9 & -1 \
-1 & -1 & -1
end{array}right], $$
تصویر زیر حاصل میشود.
به این ترتیب، مشاهده میکنیم که تصویر با حفظ رنگ اصلی خود، مرزها را نیز مشخص کرده است. علت این اتفاق، در مجموعِ اعداد فیلتر نهفته است. اگر مجموع اعداد یک فیلتر برابر ۰ باشد، تصویر خروجی غالباً در طیف خاکستری خواهد بود، اما اگر مجموع آن ۱ یا بیشتر باشد، رنگ اصلی تصویر نیز حفظ خواهد شد.
۴. فیلتر Emboss
$$ F_{5}=left[begin{array}{ccc}
-2 & -1 & 0 \
-1 & 1 & 1 \
0 & 1 & 2
end{array}right] $$
این فیلتر برای برجسته کردن تصویر و بهنوعی مشتقگیری در جهتی خاص از تصویر استفاده میشود. برای مثال، فیلتر آوردهشده در بالا، نسبت به مرزهایی با زاویه نزدیک به ۴۵ و ۲۲۵ درجه حساسیت بالایی دارد. این فیلترها شامل خانوادهای از فیلترها میشوند که با توجه به نیاز میتوان آنها را طراحی و استفاده کرد.
برای پیادهسازی این فیلتر، مینویسیم:
F5 = np.array([[-2, -1, 0],
[-1, +1, +1],
[ 0, +1, +2]])
G5 = cv.filter2D(Image, -1, F5)
cv.imshow('Emboss Filter Result', G5)
cv.imwrite('Emboss Filter Result.jpg', G5)
cv.waitKey()
cv.destroyAllWindows()
که شکل زیر را خواهیم داشت.
به این ترتیب، واکنش این فیلتر به زوایای ۴۵ و ۲۲۵ درجه کاملاً معلوم است.
۵. فیلتر Sobel
$$ begin{aligned}
&F_{6}=left[begin{array}{ccc}
-1 & 0 & 1 \
-2 & 0 & 2 \
-1 & 0 & 1
end{array}right] \
&F_{7}=left[begin{array}{ccc}
1 & 2 & 1 \
0 & 0 & 0 \
-1 & -2 & -1
end{array}right]
end{aligned} $$
این دو فیلتر نیز بهنوعی نقش گرادیان را ایفا میکنند. میتوان آنها را جزو فیلترهای Emboss در نظر گرفت. فیلتر اول در جهت افقی مشتقگیری میکند و فیلتر دوم در جهت عمودی.
برای پیادهسازی این دو فیلتر، به شکل زیر عمل میکنیم:
F6 = np.array([[-1, 0, +1],
[-2, 0, +2],
[-1, 0, +1]])
G6 = cv.filter2D(Image, -1, F6)
cv.imshow('Sobel(X) Filter Result', G6)
cv.imwrite('Sobel(X) Filter Result.jpg', G6)
cv.waitKey()
cv.destroyAllWindows()
F7 = np.array([[+1, +2, +1],
[ 0, 0, 0],
[-1, -2, -1]])
G7 = cv.filter2D(Image, -1, F7)
cv.imshow('Sobel(Y) Filter Result', G7)
cv.imwrite('Sobel(Y) Filter Result.jpg', G7)
cv.waitKey()
cv.destroyAllWindows()
در خروجی بهترتیب دو تصویر زیر حاصل میشود.
و تصویر دوم در زیر آورده شده است.
مشاهده میکنیم که تصویر اول، برخی خطوط عمودی را بهتر تشخیص داده که با توجه به ذات آن، کاملاً توجیهپذیر است.
برای ترکیب دو تصویر و بهترین عملکرد در تشخیص حاشیهها، میتوان دو خروجی را باهم ادغام یا از فیلتر متفاوتی استفاده کرد.
همچنین میتوان خروجی یک فیلتر را بهعنوان یک ورودی فیلتری دیگر استفاده کرد:
G8 = cv.filter2D(G6, -1, F7)
cv.imshow('Sobel(Y)(Sobel(X)) Filter Result', G8)
cv.imwrite('Sobel(Y)(Sobel(X)) Filter Result.jpg', G8)
cv.waitKey()
cv.destroyAllWindows()
در این حالت نیز تصویر زیر حاصل میشود.
به این ترتیب، میبینیم که فیلتر ترکیبی از رفتار دو فیلتر سوبل را داراست.
پیاده سازی پیچش در پایتون با استفاده از Scipy
توجه داشته باشید که بهجای کتابخانه OpenCV میتوان از کتابخانه Scipy نیز بهشکل زیر استفاده کرد:
G = sig.convolve(Image, F)
از این تابع میتوان برای سیگنالهای تکبعدی و یا با ابعاد بالاتر نیز استفاده کرد.
جمعبندی پیاده سازی پیچش در پایتون
در این مطلب به بررسی دقیقتر عمل پیچش و ویژگی برخی فیلترهای مهم پرداختیم. برای مطالعه بیشتر، میتوان پاسخ پرسشهای زیر را بررسی کرد:
- آیا جز ترکیب خطی از پیکسلهای ورودی، میتوان از توابعی دیگر مانند Min, Max, ReLU, Sigmoid استفاده کرد؟ چگونه میتوان این عمل را انجام داد؟
- در معماری «شبکههای عصبی پیچشی» (Convolutional Neural Networks)، به جز لایه پیچش، از چه لایههای دیگری استفاده میشود؟
- چگونه میتوان بهترین فیلتر را برای مجموعه داده انتخاب کرد؟