آموزش OpenCV
بهنام خدا، ســــــــلام… خوشحالم که یک وبلاگ دیگر را شروع کردم. در این پست طولانی میخواهم شما را با OpenCV جذاب و کاربردی آشنا کنم. از الفبای OpenCV شروع میکنیم و تا سطحی که برای شروع نیاز هست، پیش میرویم. برو بریم…
صحبت خودمانی از OpenCV
در این بخش حرف علمی ندارم. صرفا بخشی از خاطراتم با OpenCV را میخواهم تعریف کنم! دوست داشتید، مطالعه کنید. اگر هم دوست ندارید، از این بخش بپرید.
خاطرات زیادی با OpenCV دارم و بخشی از این خاطرات با درد و رنج همراه هست! اولین بار در مقطع ارشد، در درس بینایی کامپیوتر با موجودی بهنام OpenCV آشنا شدم. تا قبل آن، من و اکثر دوستانم تنها به متلب مسلط بودیم. پایتون هم هنوز بین محققان و دانشجویان شناخته شده نبود. من و دوستانم دانشجوی برق الکترونیک دیجیتال بودیم که میخواستیم در حوزه بینایی کامپیوتر کنیم و به همین خاطر از دانشکده کامپیوتر درس بینایی کامپیوتر را انتخاب کرده بودیم.
به استاد گفتیم که ما برقی هستیم و اجازه بده ما تمرینها را با متلب ارائه دهیم، اما قبول نکرد! آن موقعها OpenCV نسخه پایتونی نداشت و باید حتما با ++C کدنویسی میزدیم. دو ورژن از OpenCV وجود داشت که یکی خیلی خیلی سخت بود! اما متاسفانه ورژن دیگرش هم خیلی سخت بود! 🙄
از کدنویسی سخت اینها بگذریم، راهاندازی OpenCV در محیط ویژوال استودیو خودش یک پروژه بود! برای ما که با این چیزها آشنا نبودیم، اوضاع اصلا خوب نبود. منابع اینترنتی محدود بود و نمیدانم چرا دوستان کامپیوتری چندان کمکمان نمیکردند. تا مدتها دغدغه ما این بود که بتوانیم OpenCV را به ویژوال استودیو اضافه کنیم که تازه بتوانیم کدنویسی را شروع کنیم. موعد تحویل تمرین سری اول رسیده بود و ما همچنان درگیر راهاندازی بودیم! بالاخره بعد از هفتهها توانستیم راهاندازی کنیم! لحظه راهاندازی را هرگز یادم نمیرود. تقریبا شبیه ماجرای بازگشت میمون فضانورد از فضا بود!
تا همین جای کار هم خسته شده بودیم، اما مساله این بود که تازه اول راه بودیم و باید شروع میکردیم به یادگیری ++C و OpenCV… یک کتاب اسکنشده فارسی آموزش ++C از آقای جعفرنژاد قمی به دستمان رسید. خیلی ساده و روان آموزش داده بود. ما هم که به واسطه تسلط بر متلب، کانسپت برنامهنویسی را میدانستیم، سریع ++C را در حد نیاز یاد گرفتیم و رفتیم سراغ OpenCV…
برای OpenCV یکی دو کتاب نسبتا خوب وجود داشت. با همانها شروع کردیم و در ابتدای کار اصلا یک خط کد هم نمیتوانستیم بنویسیم و دائما کپی پیست میکردیم. به انتهای درس رسیدیم و کل تابستان هم درگیر تمرینها و مسابقه درس بینایی کامپیوتر بودیم. اما خوشبختانه آخر کار اوضاعمان بهتر شده بود. خیلی درد و رنج داشت، اما یک مهارت و ابزار خوب به ما اضافه شده بود. بعدها من در محل کارم از OpenCV خیلی استفاده کردم و پروژههای سطح بالایی را با آن پیادهسازی کردم.
قبل از یادگیری عمیق، فریمورک OpenCV با ++C به شدت در کارهای عملی استفاده میشد. بعد از رشد پایتون در حوزه هوش مصنوعی، نسخه پایتون OpenCV هم آمد. امروزه، اکثرا OpenCV را با نسخه پایتونی میشناسند، اما نسخه ++C آن بسیار کاربردی است و رقیبی هم ندارد.
OpenCV چیست؟
OpenCV مخفف عبارت Open Source Computer Vision هست. یعنی یک کتابخانه متن باز در حوزه بینایی کامپیوتر…
نصب OpenCV در پایتون
برای نصب OpenCv در پایتون، ابتدا باید cmd را در ویندوز باز کنید. سپس عبارت زیر را در cmd بنویسید:
pip install opencv-python
با اجرای کد بالا، کتابخانه OpenCV در پایتون نصب خواهد شد. برای استفاده از کتابخانه OpenCV در پایتون، نصب آن کافی نیست و باید آن را فراخوانی یا import کنیم. برای این کار از دستور import استفاده میکنیم:
import cv2
با اجرای دستور بالا کتابخانه OpenCV در پایتون فراخوانی خواهد شد. دقت کنید، اسم کتابخانه OpenCV در پایتون cv2 است. با استفاده از دستور زیر میتوانید ورژن OpenCV را چک کنید:
print(f'OpenCV version: {cv2.__version__}')
OpenCV version: 4.5.5
فراخوانی و نمایش تصویر در OpenCV
یکی از ابتداییترین کارهایی که معمولا در OpenCV انجام میدهیم، فراخوانی و نمایش تصویر هست. ابتدا درمورد فراخوانی تصویر توضیح میدهم و سپس به نمایش تصویر میرسیم.
فراخوانی تصویر
با استفاده از دستور imread میتوانید به راحتی تصاویر با فرمتهای مختلف مانند JPEG BMP PNG و غیره را فراخوانی کنید. بیایید یک نمونه تصویر فراخوانی کنیم. میتوانید از اینجا چند نمونه تصویر دانلود و در سیستم خود تست کنید.
# Load an image from a file image = cv2.imread('image-2.jpg')
بیایید با متغیر image که شامل تصویر ماست کمی کار کنیم. جنس این متغیر چیست؟ برای فهمیدن این مساله باید بنویسیم:
type(image)
numpy.ndarray
جالب شد! نامپای اینجا چه کار میکند؟ تصاویر بعد از فراخوانی، از جنس آرایه نامپای هستند. یادتان باشد این تصاویر رنگی رنگی، پیکسلی و رنگ و لعابدار، بعد از بارگذاری فقط یک آرایه چندبعدی هستند. بسته به نوع تصویر، ممکن هست با آرایه دو بعدی یا سهبعدی مواجه شویم. چگونه میتوانیم ابعاد تصویر را ببینیم؟ با استفاده از shape.:
image.shape
(600, 602, 3)
میدانید منظور از ابعاد چیست؟ همان رزلوشن تصویر که میگوییم مثلا این تصویر 2000×3000 هست. در اینجا تصویر ما 602 در 600 هست. 602 سطر و 600 ستون دارد. فعلا به عدد 3 توجه نکنید! بعدا بیشتر درمورد آن توضیح میدهم.
پرینت کردن image هم خالی از لطف نیست!
print(image)
array([[[29, 25, 14], [29, 25, 14], [29, 25, 14], ..., [29, 25, 14], [29, 25, 14], [29, 25, 14]]], dtype=uint8)
مشاهده میکنید که انباری از اعداد داریم. هرکدام از این اعداد معادل یک پیکسل در تصویر هستند. البته، نکات مهمی در خروجی بالا مشاهده میشود. دیتاتایپ تصویر، unit8 هست؛ یعنی، اعداد درون این آرایه، اعداد صحیح (Integer) بدون علامت (Unsigned) 8 بیتی هستند. بنابراین، اعداد درون این ماتریس باید اعدادی بین 0 تا 255 باشد. نهایتا 256 حالت داریم! اغلب تصاویری که فراخوانی میکنیم، چنین ساختاری دارند. یکسری تصاویر خاص مانند تصاویر پزشکی یا تصاویر ماهوارهای ممکن هست دیتاتایپ متفاوتی داشته باشند.
هشدار: یکی از اشتباهات رایج در بارگذاری تصویر، مسیر اشتباه از تصویر هست. اگر مسیر اشتباه باشد، OpenCV اصلا به روی خودش نمیآورد و هیچ خطایی به شما نشان نمیدهد. وقتی آدرس درست نباشد، None را در متغیر مدنظر میریزد! اینطور نیست که بگوید آدرس اشتباه هست. این باعث میشود که افراد فکر کنند چون خطایی نداریم، پس تصویر به درستی فراخوانی شده! با کد زیر میتوانید خروجی تصویر را چک کنید:
# Load an image from a file image = cv2.imread('image-0.jpg') # Check if the image was loaded successfully if image is not None: print('Image loaded successfully') else: print('Failed to load image')
نمایش تصویر
برای نمایش تصویر بارگذاریشده باید از دستور imshow استفاده کنیم. دستور imshow دو ورودی دریافت میکند:
- عنوان این تصویر هنگام نمایش (نام دلخواه)
- تصویر (نام متغیری که تصویر در آن ریخته شده!)
این هم نحوه استفاده از این دستور:
# Display the loaded image cv2.imshow('Loaded Image', image) cv2.waitkey(0)
و این هم نتیجه 😍:
نکته 1: اگر در محیطی مانند Pycharm یا VSCode کدنویسی میکنید. حتما باید دستور cv2.waitkey را بنویسید تا نتیجه را به شما نشان دهد. عددی که داخل پرانتز این دستور نوشته میشود، زمان انتظار هست. اگر عدد صفر باشد، پنجره نمایش تصویر بهصورت دائمی باز خواهد بود. اما اگر مثلا عدد 1000 بنویسید، بعد از 1 ثانیه بهصورت خودکار بسته میشود.
نکته 2: اگر در محیطهای نوتبوکی کد را اجرا میکنید، بدانید که دستور ()cv2.imshow در این محیط کار نمیکند. برای نمایش تصویر از دستور plt.imshow(img) استفاده کنید. ()plt.imshow یکی از دستورات فریمورک matplotlib هست.
import matplotlib.pyplot as plt plt.imshow(image) plt.axis('off')
نکته 3! احتمالا بعد از اجرای دستورهای نکته 2 میبینید که تصویر شما آبی است و با تصویری که ما نمایش دادیم متفاوت است. دلیل این کار این است که matplotlib تصاویر را به صورت RGB میخواند و پردازش میکند، ولی OpenCV تصاویر را به صورت BGR میبیند! 🤯😵 بنابراین شما برای اینکه تصویر را با matplotlib نمایش دهید نیاز به یک خط کد دارید که تصویر را از BGR به RGB تبدیل کند:
import matplotlib.pyplot as plt image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) plt.imshow(image_rgb) plt.axis('off')
نکته 4: باید دقت کنید که در OpenCV شوخی کردم بابا نکته 4 نداریم دیگه 😜
بسیارخب! تا اینجا فراخوانی و نمایش یک تصویر را یاد گرفتید. باتوجه به اینکه در OpenCV با دادههای تصویری سروکار داریم، پیشنهاد میکنم قبل از مطالعه ادامه مباحث، پست تصویر دیجیتال چیست؟ را مطالعه کنید.
ریسایز تصویر با OpenCV
ریسایز تصویر یکی از کارهای رایج در کار با تصاویر هست. اتفاقا از جمله کارهایی هست که بسیار از کاربرهای کامپیوتر با آن آشنا هستند و با نرمافزارهای ساده مانند Paint هم میتوان چنین کاری را انجام داد. اما میخواهم به زبان علم پردازش تصویر درباره ریسایز صحبت کنم؛ ریسایز تصویر، تغییر رزولوشن (HxW) تصویر… مثلا، میخواهیم یک تصویر به ابعاد 300×300 را به تصویری به ابعاد 100×100 یا به ابعاد 600×600 تبدیل کنیم.
نکته: به ریسایزی که منجر به بزرگ شدن تصویر شود، نمونهبرداری افزایشی (Up sampling) و به ریسایز منجر به کوچک شدن تصویر، نمونهبرداری کاهشی (Down sampling) گفته میشود.
تذکر: اگرچه میتوانیم با ریسایز تصویر را بزرگ کنیم، اما معمولا کیفیت تصویر به صورت قابل توجه افت میکند. بنابراین، معمولا در کدنویسی از ریسایز برای کاهش رزولوشن تصویر استفاده میکنیم. عجب! خب کاهش رزولوشن تصویر هم باعث افت کیفیت تصویر میشود! پس چرا ریسایز افزایشی هست ولی ریسایز کاهشی نیست؟ چون معمولا از ریسایز کاهشی برای کاهش هزینه محاسبات و مصرف رَم استفاده میشود.
حالا با این مقدمه، بیایید ببینیم ریسایز تصویر با OpenCV چگونه انجام میشود. با استفاده از دستور ()cv2.resize میتوانیم به راحتی ریسایز انجام دهیم. اما ورودیهای این دستور:
- src: تصویر ورودی که میخواهیم آن را ریسایز کنیم.
- dsize: سایزی که میخواهیم تصویر را به آن ریسایز کنیم.
- interpolation: تکنیک درونیابی
طبیعتا، دو آرگومان اول واضح هست. اما آرگومان درونیابی؟! ببینید، واقعیت این هست که این آرگومان وابسته به تئوری و ریاضیات هست. یعنی باید اول مفهوم آن را یاد بگیرید تا بتوانید به بهترین شکل از این دستور استفاده کنید. اما اینجا تمرکزم روی بحث تئوری نیست. به صورت خلاصه، برای ریسایز تصویر، تکنیکهای مختلفی در پردازش تصویر وجود دارد. در اینجا، لیستی از رایجترین تکنیکهای ریسایز تصویر فراهم شده که بسته به اهدافتان میتوانید یکی از آنها را انتخاب کنید. بیایید مرور کنیم:
- INTER_NEAREST: یکی از سادهترین روشهای درونیابی براساس نزدیکترین همسایگی
- INTER_LINEAR: روش دوخطی (Bilinear) که بازهم ساده و کمهزینه هست. گزینه پیشفرض هم همین هست.
- INTER_CUBIC: روش کیوبیک که از دو روش اول بهتر هست، اما هزینه محاسبات بیشتری هم دارد. یعنی زمانبرتر هست. زمانبرتر؟! 🤔
- INTER_LANCZOS4: روش لَنکزوس حتی از روش کیوبیک هم هزینه بیشتری دارد.
- موارد دیگری هم هست که بگذریم…
حالا بیایید با یک مثال، نحوه استفاده از این دستور را تمرین کنیم. کد زیر، تصویر ورودی را به ابعاد 250*300 ریسایز میکند. مشاهده میکنید که من تنها دو ورودی تصویر و سایز را مشخص کردهام و درونیابی را تغییر ندادهام.
image_resized = cv2.resize(image, (300, 250)) image_resized.shape
(250, 300, 3)
مشاهده میکنید که ریسایز انجام شده است. اما نکتهای که باید دقت کنید این است که در بخش سایز، اولین عددی که وارد میکنید، عرض تصویر و دومین عدد باید ارتفاع تصویر باشد. ارتفاع تصویر همان تعداد سطرهای آرایهی تصویر بوده و عرض تصویر همان تعداد ستونهای آرایهی تصویر است.
حالا بیایید چند نمونه خروجی از تصویر ریسایز ببینیم؛ از چپ به راست، ابتدا تصویر اصلی را میبینید. سپس، تصاویر ریسایز شده با ابعاد مختلف را مشاهده میکنید. هم تصاویر کوچک و هم تصاویر بزرگ دیده میشود.
تصویر اصلی | تصویر ریسایز شده |
تصویر اصلی با ابعاد 602*600 |
تصویر ریسایز شده با ابعاد 300*300 cv2.resize(image0, (300, 300)) |
تصویر اصلی با ابعاد 750*938 |
تصویر ریسایز شده با ابعاد 100*400 cv2.resize(image1, (100, 400)) |
تصویر اصلی با ابعاد 468*500 |
تصویر ریسایز شده با ابعاد 680*700 cv2.resize(image2, (700, 680)) |
اما نکته مهمی که وجود دارد این هست که بعضی از تصاویر از حالت نرمال خود خارج شدهاند. چرا اینطور شده؟ دلیلش این هست که نسبت ابعاد (Aspect Ratio) را به هم ریختهایم. به سطر دوم از جدول بالا نگاه کنید. نسبت ارتفاع به پهنا در تصویر اصلی ما، 938/750 یعنی 1.25 هست، درحالیکه این نسبت ارتفاع به پهنا در تصویر ریسایز شده برابر با 4 است. وقتی این نسبت را تغییر دهیم، خروجی ما دِفُرمه میشود. همه اینها را در نرمافزارها هم میتوان دید. یک آیکن برای حفظ نسبت ارتفاع به پهنا دارند.
کراپ (برش) تصویر با OpenCV
وقتی از ریسایز صحبت میکنیم، حتما باید از برش (crop) تصویر هم بگوییم. خب، خوشبختانه این مفهوم هم جدید نیست و بسیاری از ما قبلا با گوشی و کامپیوتر تصویر را برش زدهایم و بخشهای غیرضروری یا نامناسب را حذف کردهایم. مثلا، شکل زیر:
خبر خوب اینکه، برای برش تصویر نیازی به opencv نداریم! شاید کسانی که به نامپای مسلط هستند، میدانند که چه میگویم. ببینید، ما بالاتر نشان دادیم که وقتی تصویری با opencv خوانده میشود، یک آرایه نامپای هست. این یعنی، ما میتوانیم با استفاده از تکنیک اسلایس در نامپای، به راحتی تصاویر را برش بزنیم. در برش تصویر، ما به بُعد کانال کاری نداریم. پس آن عدد 3 را کنار بگذارید. ما باید محدوده مدنظرمان روی بُعد ارتفاع و پهنای تصویر مشخص کنیم. بهصورت کلی، فرمول برش تصویر به شکل زیر است:
cropped_image = image[hs:he, ws:we, :]
در فرمول بالا، hs و he بهترتیب به ابتدا و انتهای بازه برش در ارتفاع اشاره میکنند. بهصورت مشابه، ws و we هم به ابتدای و انتهای بازه پهنا اشاره دارند. حالا بیایید، یک نمونه مثال ببینیم:
img = cv2.imread('image-1.jpg') cropped_img = img[110:480, 75:450, :] cropped_img2 = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2RGB) print(f'Original Image: {img.shape}, Cropped Image: {cropped_img.shape}') plt.imshow(img); plt.imshow(cropped_img2);
این هم خروجی:
تمرین: در بالا من یک گل را جدا کردم. شما هم یکی از آن دو گل را انتخاب کنید و برش بزنید.
این هم از برش تصویر که انصافا ساده بود…
دیدگاهتان را بنویسید