در نوشته های پَسین (بعدی) درباره (مورد) کلاس Mat گفتگو (صحبت) کرده ایم و برای خواندن فایل تصویر با هر فرمتی مانند jpg, png, gif و دیگر فرمت ها از متدی به نام ()imread (یا Image Read) استفاده کردیم. در این نوشته می خواهیم با جزییات بیشتر در مورد خواندن یک فایل و چگونگی نمایش آن گفتگو کنیم.

تابع ()imread برای خواندن تصویر

تابع ()imread قابلیت خواندن تصویرها با فرمت های مختلف و خواندن از روی دیسک یا از ورودی مانند خط فرمان را دارد. در صورتی که به دلیل اشتباه در تعیین نام و مسیر فایل، عدم داشتن مجوز برای خواندن فایل و یا به دلیل عدم پشتیبانی از فرمت فایل یا فرمت غیر معتبر، تابع ()imread یک ماتریس خالی (empty) را برگشت می دهد.

قطعه کد زیر مثال ساده ای را نشان می دهد. ابتدا توسط تابع ()imread یک فایل از روی دیسک خوانده و ماتریس آن درون شی image ذخیره شد. سپس در خطوط  تا  بررسی شد که آیا از طریق خط فرمان، ورودی به برنامه ارسال شده یا نه؟ اگر ارسال شده پس مقدار argc > 1 است و در نتیجه اولین آرگومان ورودی که از طریق [argv1] به آن اشاره می شود در متغیر image ریخته می شود.

سپس توسط متد ()empty بررسی می شود که آیا تصویر به درستی خوانده و بارگذاری شده است.اگر تصویر بارگذاری نشده باشد، پس یک ماتریس خالی ایجاد شده و در نهایت شرط if در خط. مقدار true را برگشت می دهد. در واقع اگر ماتریس خالی باشد، متد ()empty مقدار درست یا true را برگشت می دهد. در غیر این صورت، تصویر به درستی بارگذاری شده، پس دیگر ماتریس خالی نیست.

توجه کنید که تابع ()imread برای تعیین نوع تصویر کاری به پسوند آن ندارد بلکه از ساختار درونی تصویر است که متوجه نوع آن (jpg,png, gif, jpeg, bmp یا غیره) می شود. یک نکته مهم که پیش از این هم به آن اشاره کرده بودیم اینکه، در OpenCV سیستم RGB به صورت معکوس یعنی BGR در نظر گرفته می شود. به عبارت دیگر در تصویرهای رنگی کانال های مربوط به پیکسل ها به صورت BGR تفسیر می شوند.

شکل زیر (شکل ۱) به این مورد اشاره دارد.همانطور که در شکل زیر می بینید در ماتریس تصاویر رنگی، هر درایه به صورت برداری (Vector) از سه کانال RGB است ولی با این نکته که به صورت معکوس، یعنی BGR ذخیره شده اند.

نمایش تصویر

در OpenCV و از طریق ضمیمه کردن فایل سرآیند highgui.hpp می توانیم تصاویر را از طریق واسط گرافیکی (GUI) نمایش دهیم. دو تابع اصلی برای این کار توابع ()nameWindow و ()imshow نام دارند. در خط سوم از کد بالا دستور زیر باعث ضمیمه شدن فایل سرآیند highgui.hpp شده است.

تابع ()nameWindow یک پنجره را ایجاد می کند که قرار است تصویر در آن نمایش داده شوند. ()nameWindow یک رشته را به عنوان آرگومان نخست دریافت می کند که در واقع نام پنجره خواهد بود. در کد بالا رشته ای به نام Output که به تابع ارسال شده در واقع نام پنجره است ومی تواند هر رشته دیگری باشد.

تابع ()imshow نیز یک تصویر را درون یک پنجره نشان می دهد. ()imshow یک رشته را به عنوان آرگومان نخست دریافت می کند که در واقع نام پنجره ای است که تصویر در آن الحاق و نمایش داده می شود. این نام می تواند نام پنجره ای باشد که از پیش و توسط تابع ()nameWindow تعریف کرده ایم. ولی اگر از پیش پنجره ای را تعریف نکردیم، پس خود ()imshow یک پنجره جدید ایجاد می کند. در نهایت نام متغیر یا شی که تصویر را در خود نگه می دارد به عنوان آرگومان دوم به تابع ()imshow ارسال می شود.

کدهای زیر فرمت استفاده از توابع ()nameWindow و ()imshow را نشان می دهند. اما می توانید برای اطلاع بیشتر در مورد ()nameWindow اینجا و برای ()imshow اینجا را مطالعه کنید.

در خطوط  و  از کد بالا می بینید که ابتدا توسط متد ()nameWindow یک پنجره به نام Output ایجاد کرده ایم و سپس درون تابع ()imshow و به عنوان آرگومان اول، نام همین پنجره، یعنی Output را ارسال کرده ایم. در نهایت در آرگومان دوم تابع ()imshow نام متغیری که ماتریس تصویر را در خود دارد را به تابع ارسال کرده ایم که این متغیر در اینجا image نام دارد.

تابع ()waitKey

همانطور که توضیح دادیم تابع ()imshow تصویر را نمایش می دهد ولی اگر تابع ()waitKey استفاده نشود، تصویر در واقع نمایش داده نمی شود. دلیل آن این است که تابع ()waitKey زمانی برحسب میلی ثاینه را دریافت می کند و سپس صبر می کند تا این زمان به اتمام برسد و خود به خود پنجره را می ببندد یا اینکه تا قبل از اتمام زمان کاربر هر کلیدی از صفحه کلید را روی عکس فشار دهد، عکس بسته می شود.

مثلا اگر بخواهیم عکس به مدت ۳۰ ثاینه نمایش داده شود و سپس بسته شود، باید عد ۳۰۰۰۰ را در تابع ()waitKey تعیین کنید. توجه کنید اگر کاربر قبل از اتمام این زمان روی عکس کلیدی را فشار دهد، عکس بسته می شود.

در خط ۲۹ هیچ مقداری به تابع ()waitKey ارسال نکردیم وبه این معنی است که می خواهیم پنجره مدام نشان داده شود تا زمانی که کاربر روی عکس از صفحه کلید، کلیک کند. همچنین اگر عدد صفر را به تابع ارسال کنیم، پس بازهم به همین معنی است که می خواهیم پنجره مدام نشان داده شود تا زمانی که کاربر روی عکس از صفحه کلید، کلیک کند.

تابع ()destroyAllWindows و ()destroyWindow

همانطور که از نام این دو تابع مشخص است، از این توابع برای از بین بردن و بستن پنجره هایی استفاده می شود که توسط توابع ()nameWindow و یا ()imshow ایجاد کرده بودیم. تابع ()destroyAllWindows خود به خود تمامی پنجره ها را می بندد ولی تابع ()destroyWindow نام یک پنجره را می گیرد و مشخصا همان یک پنجره را می بندد.

برای اطلاع بیشتر در مورد HighGUI در OpenCV این لینک را مطالعه کنید.

دیگر آرگومان تابع ()nameWindow

تابع ()nameWindow به عنوان آرگومان دوم یک سری از مقادیر (Flags یا پرچم های) از پیش تعریف شده و شناخته شده در OpenCV را دریافت می کند که در ادامه آنها را بررسی کرده ایم.

۱ – WINDOW_NORMAL یا WINDOW_AUTOSIZE : WINDOW_NORMAL این قابلیت را می دهد که بتوانیم که اندازه پنجره را تغییر دهیم در حالی که اگر WINDOW_AUTOSIZE را استفاده کرده باشیم، پس دیگر نمی توانیم اندازه پنجره را تغییر دهیم و پنجره به اندازه همان تصویر ایجاد می شود. در هر لحظه تنها یکی از این دو مورد را می توانیم تعیین کنیم.

۲ – WINDOW_FREERATIO یا WINDOW_KEEPRATIO :

۳ – WINDOW_GUI_NORMAL یا WINDOW_GUI_EXPANDED : WINDOW_GUI_NORMAL روشی قدیمی برای ترسیم پنجره ها بود که امکان استفاده از Taskbar و Status Bar در این پنجره وجود ندارد ولی در WINDOW_GUI_EXPANDED اینطور نیست. قطعه کد زیر حالت پیش فرض استفاده از Flag ها را نشان می دهدو در واقع اگر شما تنها نام پنجره (در اینجا Output) را تعیین کنید، OpenCV از Flag های پیش فرض زیر استفاده می کند

بنابراین از هر دسته تنها یک Flag باید تعیین شود و همچنین اگر بخواهیم چندین Flag داشته باشیم، پس باید نام آنها را با علامت | از هم جدا کنیم.

حالت های خواندن تصویر در تابع ()imread

در OpenCV نوع شمارشی (Enumerated Type) به نام ImreadMode وجود دارد که اعضای آن به عنوان آرگومان دوم به تابع ()imread ارسال می شود. در واقع نوع شمارشی ImreadMode حالت خوانده شدن تصویر را تعیین می کند.

اگر IMREAD_GRAYSCALE استفاده شود در هنگام خواندن تصویر، به صورت پیش فرض تصویر به فرمت تک کانالی خاکستری (Grayscale) تبدیل می شود. IMREAD_COLOR  به صورت پیش فرض تصویر به فرمت سه کانالی رنگی (Color Image) تبدیل می شود. فهرست کامل آنرا میتوانید از این لینک مطالعه کنید.

در کد زیر یک تصویر رنگی را دو مرتبه می خوانیم، یکبار به صورت عادی و بار دیگر در حالت تبدیل تصویر خاکستری یا IMREAD_GRAYSCALE. لطفا خودتان کد را با یک تصویر رنگی دلخواه امتحان کنید. فراموش نکنید که باید مسیر فایل خودتان را به برنامه بدهید.

توجه کنید برای برخی از کاربردها و الگوریتم های بینایی ماشین (Computer Vision) لازم است تا تصویر رنگی (RGB) سه کانالی را به تصویر خاکستری تک کانالی تبدیل کنیم که در طول این دوره سعی کردیم تا حد توان برخی از کاربردها را نشان دهیم.

توجه کنید که در زمان بارگذاری یک تصویر رنگی توسط ()imread، اگر آرگومان دوم را تعیین نکنید، به صورت پیشفرض تصویر به همان صورت رنگی و سه کانال بارگذاری می شوند.در واقع مقدار پیش فرض آرگومان دوم IMREAD_COLOR است. ولی اگر بخواهید تصویر را رنگی را صرفا به صورت خاکستری تک کانالی بارگذاری کنید، باید از IMREAD_GRAYSCALE به عنوان آرگومان دوم استفاده کنید.

توجه کنید که استفاده از IMREAD_GRAYSCALE برای بارگذاری تصویر رنگی به صورت تک کانالی با تبدیل (Convert) تصویر رنگی به تصویر خاکستری متفاوت است. در مطلب های بعدی در مورد تبدیل تصویر از یک فضای رنگ (Color Space) به فضای رنگ دیگر توضیح داده شده است.

تابع ()imwrite برای نوشتن تصویر

همانطور که تابع ()imread برای خواندن وبارگذاری تصویر استفاده می شود، تابع ()imwrite (یا Image Write) یک تصویر را درون دیسک می نویسد. در واقع ماتریس تصویر را پیکسل به پیکسل به درون دیسک می نویسد. تابع ()imwrite به دو آرگومان نیاز دارد، آرگومان اول نام و مسیر یک فایل بر روی دیسک که به فایل تصویر اشاره دارد و آرگومان دوم نام یک شی یا ماتریس از کلاس Mat است که می خواهیم آنرا روی دیسک بنویسیم.

در کد زیر می خواهیم یک تصویر رنگی را بخوانیم و سپس آنرا به حالت IMREAD_GRAYSCALE درون برنامه (متغیر originalImage) بارگذاری کنیم. سپس در خط ۱۸ متغیر originalImage را به درون دیسک و تحت یک نام جدید بنویسیم. توجه کنید که در خط ۹ یک فایل png را خوانده ایم ولی در خروجی فایل jpg را نوشته ایم.