نمایش/عدم نمایش سایدبار
رفتن به بالای صفحه
أَللّهُمَّ ارْزُقْنی شَفاعَةَ الْحُسَیْنِ یَومَ الْوُرُودِ
مهدی دمیرچیلو

آموزش ماژول Image Segmentation در OpenCV

500

به نام خدا : تو این مطلب توابع grabCut و watershed رو توضیح میدم، اسم ماژول Image Segmentation ( تقسیم بندی تصویر ) هستش، که خب اسم مناسبی برا توابعی نظیر watershed هستش، اما برا تابع grabCut… نمیدونم، فک نکنم، چون بیشتر برا جدا کردن شیء از زمینه بکار میره؛ تابع pyrMeanShiftFiltering ( از مطلب : آموزش ماژول Image Filtering در OpenCV ) هم عملکری شبیه عنوان این مطلب ( Image Segmentation ) داره تا حدودی، که مثالی که در مطلب فوق برا تابع pyrMeanShiftFiltering گویای این حرف منه، به مناطق مشابه، رنگ یکسانی نسبت میده ولی خب به خوبی تابع watershed قطعا نیست! ^_^

آموزش ماژول Image Segmentation در OpenCV

 

1) تابع grabCut

1) تابع grabCut : استخراج پیش زمینه ( foreground )؛ این تابع، الگوریتم تقسیم‌بندی تصویر GrabCut را پیاده‌سازی میکند.

توجه : این تابع مثل ابزارهای Object Selection Tool ( استفاده از Rect برای ایجاد mask اولیه ) و Quick Selection Tool ( اصلاح mask ایجاد شده توسط ابزار قبلی ) در فوتوشاپ کار میکنه! ( 2 ابزار فوق در فوتوشاپ، مکمل یکدیگر هستند )؛ عملکرد این تابع هم مثل ابزارهای فوق هستش، که با تغییر پارامترهاش میتونه مثل ابزار Object Selection Tool عمل کنه و یا مثل Quick Selection Tool.

تاریخچه : الگوریتم GrabCut توسط Carsten Rotherو Vladimir Kolmogorov و Andrew Blake از شرکت Microsoft Research Cambridge ( واقع در انگلستان )، طراحی شده است، که مقاله شون رو در ادامه برای دانلود قرار میدم :

نحوه کار با تابع : در ابتدا، یک مستطیل ( Rect ) در اطراف منطقه پیش زمینه ترسیم میکنیم؛ همه چیز خارج از مستطیل، با اطمینان به عنوان پس‌زمینه در نظر گرفته میشود ( فلذا منطقه پیش زمینه باید کاملاً داخل مستطیل باشه )؛ اما همه چیز داخل مستطیل ناشناخته هستش ( که تابع باید تشخیص بده که چه قسمت هایی پیش-زمینه هستش و چه قسمت هایی پس-زمینه )؛ سپس الگوریتم تصویر رو به صورت تکراری تقسیم بندی میکنه تا بهترین نتیجه رو به دست بیاره؛ در این مرحله، تابع یه mask اولیه یی ایجاد میکنه؛ اما در برخی موارد، تقسیم بندی خوب عمل نمیکنه، مثلا، ممکنه برخی از مناطق پیش زمینه رو به عنوان پس زمینه علامت گذاری بکنه و بالعکس؛ در این صورت، باید با رسم خطوطی با مقادیر ( رنگ ) های مختلف، mask رو اصلاح کنیم؛ که خب 4 نوع مقادیر ( رنگ ) داریم : حتما پیش-زمینه هستش، حتما پس-زمینه هستش، احتمالا پیش-زمینه هستش، احتمالا پس-زمینه هستش؛ بعد از اصلاح mask، دوباره تابع رو فراخونی میکنیم و این بار میبینیم که نتایج بهتری دریافت میکنیم و...

 

تعریف تابع :

توضیح پارامترها :

  • img : تصویر ورودی 8-بیتی و 3-کاناله.
  • mask : ماسک 8-بیتی و 1-کاناله  ورودی/خروجی؛ وقتی پارامتر mode روی GC_INIT_WITH_RECT تنظیم شود، ماسک توسط تابع مقداردهی اولیه میشود.
  • rect : ROI حاوی یک شی قطعه بندی شده ( segmented object ) است؛ پیکسل های خارج از ROI به عنوان "پس زمینه ( background ) آشکار" مشخص میشوند؛ این پارامتر فقط زمانی استفاده میشود که پارامتر mode روی GC_INIT_WITH_RECT تنظیم شود.
  • bgdModel : آرایه موقت برای مدل پس زمینه ( background model )؛ هنگامی که همان تصویر را پردازش میکنید، آن را تغییر ندهید.
  • fgdModel : آرایه موقت برای مدل پیش زمینه ( foreground model )؛ هنگامی که همان تصویر را پردازش میکنید، آن را تغییر ندهید.
  • iterCount : تعداد تکرارهایی که الگوریتم باید قبل از برگرداندن نتیجه انجام دهد؛ توجه داشته باشید که نتیجه را میتوان با تماس‌های بیشتر با حالت GC_INIT_WITH_MASK یا حالت GC_EVAL اصلاح کرد.
  • mode : حالت عملیاتی؛ به کمک نوع شمارشی GrabCutModes مقدار دهی میشود.

 

توجه : تعریف bgdModel و fgdModel به صورت زیر هستش، اما این که، کجا و چطور باید ازشون استفاده کنیم؟؟؟؟

 

نوع شمارشی GrabCutClasses : مقادیر پیکسل های mask به کمک این enum، مقداردهی میشوند :

توضیح گزینه ها :

  • GC_BGD : یک پیکسل پس زمینه آشکار؛ مقدار پیکسل 0 است.
  • GC_FGD : یک پیکسل پیش زمینه ( شیء ) آشکار؛ مقدار پیکسل 1 است.
  • GC_PR_BGD : یک پیکسل پس زمینه احتمالی؛ مقدار پیکسل 2 است.
  • GC_PR_FGD : یک پیکسل پیش زمینه احتمالی؛ مقدار پیکسل 3 است.

توجه : برای نمایش تصویر mask، از اونجایی که مقادیر 0 تا 3، همشون به رنگ سیاه برای ما نمایش داده میشن ( همشونو به رنگ سیاه میبینیم، این جمله درست تره )، میتونیم از تابع normalize باید استفاده کنیم تا محدوده 0 تا 3 به محدوده 0 تا 255 تبدیل بشه!

 

نوع شمارشی GrabCutModes : پارامتر mode به کمک enum زیر مقداردهی میشود.

توضیح گزینه ها :

  • GC_INIT_WITH_RECT : تابع، حالت ( state ) و پارامتر mask را با استفاده از مستطیل ارائه شده ( پارامتر rect ) مقداردهی اولیه میکند؛ پس از آن تکرار الگوریتم ( پارامتر iterCount ) را اجرا میکند.
  • GC_INIT_WITH_MASK : تابع با استفاده از ماسک ارائه شده ( پارامتر mask )، حالت ( state ) را مقداردهی اولیه میکند؛ توجه داشته باشید که مقادیر GC_INIT_WITH_RECT و GC_INIT_WITH_MASK را میتوان ترکیب کرد؛ سپس، تمام پیکسل های خارج از ROI به طور خودکار با GC_BGD مقداردهی اولیه میشوند.
  • GC_EVAL : این مقدار به این معنی است که الگوریتم فقط باید از سر گرفته شود.
  • GC_EVAL_FREEZE_MODEL : این مقدار به این معنی است که الگوریتم فقط باید الگوریتم grabCut ( یک تکرار ) را با مدل ثابت اجرا کند.

توجه : در تمامی حالت های بالا ( به غیر از GC_INIT_WITH_RECT )، پارامتر mask نباید خالی باشد، وگرنه خطا رخ میده!

 

کد نمونه 1 : تو این کد، از پارامتر rect استفاده کردم؛ پارامتر mode رو هم، روی GC_INIT_WITH_RECT تنظیم کردم، تا mask توسط تابع و به کمک اون rect من، ایجاد بشه؛ در کد زیر، پارامتر iterCount، کیفیت کار رو تعیین میکنه، در قسمت نتیجه انواع خروجی به ازای مقادیر مختلف iterCount رو خواهیم دید.

نتیجه کد بالا :

src
آموزش تابع grabCut در OpenCV
dst, iterCount = 1 mask, iterCount = 1
آموزش تابع grabCut در OpenCV آموزش تابع grabCut در OpenCV
dst, iterCount = 2 mask, iterCount = 2
آموزش تابع grabCut در OpenCV آموزش تابع grabCut در OpenCV

 

کد نمونه 2 : استفاده از پارامتر rect برای تصاویر ساده شاید خوب عمل کنه اما برای تصاویر یکم پیچیده و کم کیفیت و یا شلوغ، به همین خوبی کد بالا همیشه کار نمیکنه! در کل استفاده از rect در مرحله اول استفاده از تابع grabCut خوبه ( بهترین و ساده ترین روشه )، در مرحله بعد باید بریم سراغ نقاشی کردن! و ماسک رو ویرایش کنیم! سایت OpenCV یه کد خوب برا این موضوع گزاشته که در مسیر زیر قرار داره و تو محیط Console هستش :

[opencv]/samples/cpp/grabcut.cpp

کد فوق به صورت زیر هستش ( یکمکی ویرایش اش کردم!!! ) : ابتدا باید یه rect تعریف کنید، که با کلیک چپ موس اینکار رو میتویند انجام بدید، بعد میبینید که تابع زیاد خوب عمل نکرده و نیاز به اصلاح mask دارید، برای این کار مناطقی که مطمئن هستید پس زمینه هستند رو با CTRL + left_mouse_button تنظیم میکنید، و مناطقی که مطمئن هستید پیش زمینه ( شیء ) هستند رو با SHIFT + left_mouse_button مشخص میکنید؛ برا پس زمینه و پیش زمینه احتمالی هم به جای کلیک چپ موس، از کلیک راست استفاده کنید.

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
آموزش تابع grabCut در OpenCV آموزش تابع grabCut در OpenCV
2) تابع watershed

2) تابع watershed : با استفاده از الگوریتم آبخیز ( watershed )، تقسیم‌بندی تصویر مبتنی بر نشانگر ( marker-based ) را انجام میدهد؛ این تابع یکی از انواع الگوریتم تقسیم بندی مبتنی بر نشانگر غیر پارامتری آبخیز را که در [173] شرح داده شده است، پیاده سازی میکند.

قبل از ارسال تصویر به تابع، باید به طور تقریبی مناطق مورد نظر را در تصویر markers با مقادیر مثبت ( مقدار 1 تا 255، مقدارش مهم نی، فقط 0 نباشه )، ترسیم کنید؛ تمام پیکسل های دیگر در تصویر markers که رابطه آنها با مناطق مشخص شده، مشخص نیست و باید توسط الگوریتم تعریف شوند، باید روی 0 تنظیم شوند؛ چنین نشانگرهایی را می توان با استفاده از findContours و drawContours از یک ماسک باینری بازیابی کرد ( به کد زیر مراجعه کنید ).

 

تعریف تابع :

توضیح پارامترها :

  • image : تصویر ورودی 8-بیتی و 3-کاناله.
  • markers : تصویر ورودی/خروجی حاوی نشانگرها؛ 32-بیتی و 1-کاناله و هم اندازه با پارامتر image.

 

کد نمونه : این کد در مسیر زیر قرار داره که من یکم ویرایشش کردم :

[opencv]/samples/cpp/watershed.cpp

به کمک کلیک چپ موس، مناطق مدنظر رو رنگ آمیزی ( مشخص ) کنید، بعد کلید w رو بزنید تا الگوریتم watershed اجرا بشه، برا برگردوندن تصویر به حالت اولیه، کلید r رو بزنید، برا خروج هم کلید Esc.

نتیجه کد بالا :

تصویر ورودی
آموزش تابع watershed در OpenCV
خروجی تابع watershed نشانگرهای ترسیم شده
آموزش تابع watershed در OpenCV آموزش تابع watershed در OpenCV

 

مطالب مرتبط :

 

منابع : 

 

نواقص : 

 

تا مطلب بعد اگه زنده بودیم و قسمت شد، یا علی.

 

تعداد مطالب : 367 تا
جنگ ما فتح قدس را به همراه خواهد داشت. [ امام خمینی (ره) ]
بقیه جلسات : آموزش OpenCV
ارسال دیدگاه
0