به نام خدا : تو این مطلب توابع grabCut و watershed رو توضیح میدم، اسم ماژول Image Segmentation ( تقسیم بندی تصویر ) هستش، که خب اسم مناسبی برا توابعی نظیر watershed هستش، اما برا تابع grabCut… نمیدونم، فک نکنم، چون بیشتر برا جدا کردن شیء از زمینه بکار میره؛ تابع pyrMeanShiftFiltering ( از مطلب : آموزش ماژول Image Filtering در OpenCV ) هم عملکری شبیه عنوان این مطلب ( Image Segmentation ) داره تا حدودی، که مثالی که در مطلب فوق برا تابع pyrMeanShiftFiltering گویای این حرف منه، به مناطق مشابه، رنگ یکسانی نسبت میده ولی خب به خوبی تابع watershed قطعا نیست! ^_^
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، دوباره تابع رو فراخونی میکنیم و این بار میبینیم که نتایج بهتری دریافت میکنیم و...
تعریف تابع :
1 2 3 4 5 6 7 8 |
CV_EXPORTS_W void grabCut( InputArray img, InputOutputArray mask, Rect rect, InputOutputArray bgdModel, InputOutputArray fgdModel, int iterCount, int mode = GC_EVAL ); |
توضیح پارامترها :
- 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 به صورت زیر هستش، اما این که، کجا و چطور باید ازشون استفاده کنیم؟؟؟؟
1 2 |
cv::Mat bgdModel = cv::Mat(1, 65, CV_64FC1); cv::Mat fgdModel = cv::Mat(1, 65, CV_64FC1); |
نوع شمارشی GrabCutClasses : مقادیر پیکسل های mask به کمک این enum، مقداردهی میشوند :
1 2 3 4 5 6 7 |
enum GrabCutClasses { GC_BGD = 0, GC_FGD = 1, GC_PR_BGD = 2, GC_PR_FGD = 3 }; |
توضیح گزینه ها :
- 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 زیر مقداردهی میشود.
1 2 3 4 5 6 7 |
enum GrabCutModes { GC_INIT_WITH_RECT = 0, GC_INIT_WITH_MASK = 1, GC_EVAL = 2, GC_EVAL_FREEZE_MODEL = 3 }; |
توضیح گزینه ها :
- 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 رو خواهیم دید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
void image_segmentation_example1::grabCut_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/crow.jpg"); cv::Mat dst = src.clone(); cv::Mat mask, bgdModel, fgdModel; cv::Rect rect(cv::Point(100, 50), cv::Point(700, 506)); cv::grabCut(src, mask, rect, bgdModel, fgdModel, 10, cv::GC_INIT_WITH_RECT); // draw foreground for (int r = 0; r < dst.rows; r++) { for (int c = 0; c < dst.cols; c++) { if (mask.at<uchar>(r,c) == 0 || mask.at<uchar>(r,c) == 2) { //src.at<cv::Vec3b>(i,j) = cv::Vec3b(0,0,0); dst.at<cv::Vec3b>(r,c)[0] = 0; dst.at<cv::Vec3b>(r,c)[1] = 0; dst.at<cv::Vec3b>(r,c)[2] = 0; } } } // draw grab rect cv::Scalar color(0, 0, 255); cv::Point point1(rect.x, rect.y); cv::Point point2(rect.x + rect.width, rect.y + rect.height); cv::rectangle(dst, point1, point2, color); /// show src cv::imshow("src", src); /// show dst cv::imshow("dst", dst); /// show mask // for debug. //std::cout << mask << std::endl; // We use the normalize function to make the mask visible. cv::normalize(mask, mask, 0, 255, cv::NormTypes::NORM_MINMAX); cv::imshow("mask", mask); } |
نتیجه کد بالا :
کد نمونه 2 : استفاده از پارامتر rect برای تصاویر ساده شاید خوب عمل کنه اما برای تصاویر یکم پیچیده و کم کیفیت و یا شلوغ، به همین خوبی کد بالا همیشه کار نمیکنه! در کل استفاده از rect در مرحله اول استفاده از تابع grabCut خوبه ( بهترین و ساده ترین روشه )، در مرحله بعد باید بریم سراغ نقاشی کردن! و ماسک رو ویرایش کنیم! سایت OpenCV یه کد خوب برا این موضوع گزاشته که در مسیر زیر قرار داره و تو محیط Console هستش :
[opencv]/samples/cpp/grabcut.cpp
کد فوق به صورت زیر هستش ( یکمکی ویرایش اش کردم!!! ) : ابتدا باید یه rect تعریف کنید، که با کلیک چپ موس اینکار رو میتویند انجام بدید، بعد میبینید که تابع زیاد خوب عمل نکرده و نیاز به اصلاح mask دارید، برای این کار مناطقی که مطمئن هستید پس زمینه هستند رو با CTRL + left_mouse_button تنظیم میکنید، و مناطقی که مطمئن هستید پیش زمینه ( شیء ) هستند رو با SHIFT + left_mouse_button مشخص میکنید؛ برا پس زمینه و پیش زمینه احتمالی هم به جای کلیک چپ موس، از کلیک راست استفاده کنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
#include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" #include <iostream> int BGD_KEY = cv::EVENT_FLAG_CTRLKEY; int FGD_KEY = cv::EVENT_FLAG_SHIFTKEY; enum { NOT_SET = 0, IN_PROCESS = 1, SET = 2 }; cv::Mat image; cv::Mat mask; cv::Rect rect; cv::Mat bgdModel, fgdModel; std::string winName; std::vector<cv::Point> fgdPxls, bgdPxls, prFgdPxls, prBgdPxls; int iterCount = 0; int radius = 2; int thickness = -1; uchar rectState = NOT_SET, lblsState = NOT_SET, prLblsState = NOT_SET; bool isInitialized = false; void showImage() { if( image.empty() || winName.empty() ) return; cv::Mat res; cv::Mat binMask; image.copyTo( res ); if( isInitialized ) { if( mask.empty() || mask.type()!=CV_8UC1 ) { CV_Error( cv::Error::StsBadArg, "comMask is empty or has incorrect type (not CV_8UC1)" ); } binMask.create( mask.size(), CV_8UC1 ); binMask = mask & 1; cv::Mat black (binMask.size(), CV_8UC3, cv::Scalar::all(0)); black.setTo(cv::Scalar::all(255), binMask); addWeighted(black, 0.5, res, 0.5, 0.0, res); } std::vector<cv::Point>::const_iterator it; for( it = bgdPxls.begin(); it != bgdPxls.end(); ++it ) circle( res, *it, radius, cv::Scalar(255,0,0), thickness ); // BLUE for( it = fgdPxls.begin(); it != fgdPxls.end(); ++it ) circle( res, *it, radius, cv::Scalar(0,0,255), thickness ); // RED for( it = prBgdPxls.begin(); it != prBgdPxls.end(); ++it ) circle( res, *it, radius, cv::Scalar(255,255,160), thickness ); // LIGHTBLUE for( it = prFgdPxls.begin(); it != prFgdPxls.end(); ++it ) circle( res, *it, radius, cv::Scalar(230,130,255), thickness ); // PINK if( rectState == IN_PROCESS || rectState == SET ) rectangle( res, cv::Point( rect.x, rect.y ), cv::Point(rect.x + rect.width, rect.y + rect.height ), cv::Scalar(0,255,0), 2); // GREEN imshow( winName, res ); } void setLblsInMask( int flags, cv::Point p, bool isPr ) { std::vector<cv::Point> *bpxls, *fpxls; uchar bvalue, fvalue; if( !isPr ) { bpxls = &bgdPxls; fpxls = &fgdPxls; bvalue = cv::GC_BGD; fvalue = cv::GC_FGD; } else { bpxls = &prBgdPxls; fpxls = &prFgdPxls; bvalue = cv::GC_PR_BGD; fvalue = cv::GC_PR_FGD; } if( flags & BGD_KEY ) { bpxls->push_back(p); circle( mask, p, radius, bvalue, thickness ); } if( flags & FGD_KEY ) { fpxls->push_back(p); circle( mask, p, radius, fvalue, thickness ); } } void nextIter() { if( isInitialized ) { grabCut( image, mask, rect, bgdModel, fgdModel, 1, cv::GrabCutModes::GC_EVAL ); } else { if( rectState != SET ) return; if( lblsState == SET || prLblsState == SET ) grabCut( image, mask, rect, bgdModel, fgdModel, 1, cv::GrabCutModes::GC_INIT_WITH_MASK ); else grabCut( image, mask, rect, bgdModel, fgdModel, 1, cv::GrabCutModes::GC_INIT_WITH_RECT ); isInitialized = true; } iterCount++; bgdPxls.clear(); fgdPxls.clear(); prBgdPxls.clear(); prFgdPxls.clear(); } static void on_mouse( int event, int x, int y, int flags, void* param ) { bool isb = flags & BGD_KEY; bool isf = flags & FGD_KEY; // TODO add bad args check switch( event ) { case cv::EVENT_LBUTTONDOWN: // set rect or GC_BGD(GC_FGD) labels if( rectState == NOT_SET && !isb && !isf ) { rectState = IN_PROCESS; rect = cv::Rect( cv::Point(x,y), cv::Point(x,y) ); } if( rectState == SET && (isb || isf) ) lblsState = IN_PROCESS; break; case cv::EVENT_RBUTTONDOWN: // set GC_PR_BGD(GC_PR_FGD) labels if ( rectState == SET && (isb || isf) ) prLblsState = IN_PROCESS; break; case cv::EVENT_LBUTTONUP: if( rectState == IN_PROCESS ) { if(rect.x == x || rect.y == y) { rectState = NOT_SET; } else { rect = cv::Rect( cv::Point(rect.x, rect.y), cv::Point(x,y) ); rectState = SET; //setRectInMask CV_Assert( !mask.empty() ); mask.setTo( cv::GC_BGD ); rect.x = cv::max(0, rect.x); rect.y = cv::max(0, rect.y); rect.width = cv::min(rect.width, image.cols-rect.x); rect.height = cv::min(rect.height, image.rows-rect.y); (mask(rect)).setTo( cv::Scalar(cv::GC_PR_FGD) ); CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); } showImage(); } if( lblsState == IN_PROCESS ) { setLblsInMask(flags, cv::Point(x,y), false); lblsState = SET; nextIter(); showImage(); } else { if(rectState == SET) { nextIter(); showImage(); } } break; case cv::EVENT_RBUTTONUP: if( prLblsState == IN_PROCESS ) { setLblsInMask(flags, cv::Point(x,y), true); prLblsState = SET; } if( rectState == SET ) { nextIter(); showImage(); } break; case cv::EVENT_MOUSEMOVE: if( rectState == IN_PROCESS ) { rect = cv::Rect( cv::Point(rect.x, rect.y), cv::Point(x,y) ); CV_Assert( bgdPxls.empty() && fgdPxls.empty() && prBgdPxls.empty() && prFgdPxls.empty() ); showImage(); } else if( lblsState == IN_PROCESS ) { setLblsInMask(flags, cv::Point(x,y), false); showImage(); } else if( prLblsState == IN_PROCESS ) { setLblsInMask(flags, cv::Point(x,y), true); showImage(); } break; } } int main() { // ESC - quit the program // r - restore the original image // n - next iteration // left mouse button - set rectangle // CTRL + left mouse button - set GC_BGD pixels // SHIFT + left mouse button - set GC_FGD pixels // CTRL + right mouse button - set GC_PR_BGD pixels // SHIFT + right mouse button - set GC_PR_FGD pixels image = imread("C:/Users/Mahdi/Desktop/Flower_src.jpg", cv::IMREAD_COLOR); mask.create( image.size(), CV_8UC1); winName = "image"; cv::namedWindow( winName, cv::WINDOW_AUTOSIZE ); cv::setMouseCallback( winName, on_mouse ); showImage(); while(true) { switch( (char)cv::waitKey(0) ) { case '\x1b': std::cout << "Exiting ..." << std::endl; cv::destroyWindow( winName ); break; case 'r': std::cout << std::endl; // reset if( !mask.empty() ) mask.setTo(cv::Scalar::all(cv::GC_BGD)); bgdPxls.clear(); fgdPxls.clear(); prBgdPxls.clear(); prFgdPxls.clear(); isInitialized = false; rectState = NOT_SET; lblsState = NOT_SET; prLblsState = NOT_SET; iterCount = 0; showImage(); break; case 'n': int oldIterCount = iterCount; nextIter(); if( iterCount > oldIterCount ) { showImage(); std::cout << "iterCount : " << oldIterCount << " >>> " << iterCount << std::endl; } else { std::cout << "rect must be determined" << std::endl; } break; } } return 0; } |
نتیجه کد بالا :
2) تابع watershed : با استفاده از الگوریتم آبخیز ( watershed )، تقسیمبندی تصویر مبتنی بر نشانگر ( marker-based ) را انجام میدهد؛ این تابع یکی از انواع الگوریتم تقسیم بندی مبتنی بر نشانگر غیر پارامتری آبخیز را که در [173] شرح داده شده است، پیاده سازی میکند.
قبل از ارسال تصویر به تابع، باید به طور تقریبی مناطق مورد نظر را در تصویر markers با مقادیر مثبت ( مقدار 1 تا 255، مقدارش مهم نی، فقط 0 نباشه )، ترسیم کنید؛ تمام پیکسل های دیگر در تصویر markers که رابطه آنها با مناطق مشخص شده، مشخص نیست و باید توسط الگوریتم تعریف شوند، باید روی 0 تنظیم شوند؛ چنین نشانگرهایی را می توان با استفاده از findContours و drawContours از یک ماسک باینری بازیابی کرد ( به کد زیر مراجعه کنید ).
تعریف تابع :
1 |
CV_EXPORTS_W void watershed( InputArray image, InputOutputArray markers ); |
توضیح پارامترها :
- image : تصویر ورودی 8-بیتی و 3-کاناله.
- markers : تصویر ورودی/خروجی حاوی نشانگرها؛ 32-بیتی و 1-کاناله و هم اندازه با پارامتر image.
کد نمونه : این کد در مسیر زیر قرار داره که من یکم ویرایشش کردم :
[opencv]/samples/cpp/watershed.cpp
به کمک کلیک چپ موس، مناطق مدنظر رو رنگ آمیزی ( مشخص ) کنید، بعد کلید w رو بزنید تا الگوریتم watershed اجرا بشه، برا برگردوندن تصویر به حالت اولیه، کلید r رو بزنید، برا خروج هم کلید Esc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
cv::Mat markerMask, img; cv::Point prevPt(-1, -1); static void onMouse( int event, int x, int y, int flags, void* ) { if( x < 0 || x >= img.cols || y < 0 || y >= img.rows ) return; if( event == cv::EVENT_LBUTTONUP || !(flags & cv::EVENT_FLAG_LBUTTON) ) { prevPt = cv::Point(-1,-1); } else if( event == cv::EVENT_LBUTTONDOWN ) { prevPt = cv::Point(x,y); } else if( event == cv::EVENT_MOUSEMOVE && (flags & cv::EVENT_FLAG_LBUTTON) ) { cv::Point pt(x, y); if( prevPt.x < 0 ) prevPt = pt; line( markerMask, prevPt, pt, cv::Scalar::all(255), 5, 8, 0 ); line( img, prevPt, pt, cv::Scalar::all(255), 5, 8, 0 ); prevPt = pt; imshow("image", img); } } void image_segmentation_example1::watershed_test1() { // Esc : quit the program. // r : restore the original image. // w : run watershed segmentation algorithm. cv::Mat img0 = cv::imread("C:/Users/Mahdi/Desktop/Home.jpg"), imgGray; img0.copyTo(img); cvtColor(img, markerMask, cv::COLOR_BGR2GRAY); cvtColor(markerMask, imgGray, cv::COLOR_GRAY2BGR); markerMask = cv::Scalar::all(0); cv::namedWindow("image"); cv::setMouseCallback("image", onMouse); imshow("image", img); while(true) { char c = (char)cv::waitKey(0); if( c == 27 ) break; if( c == 'r' ) { markerMask = cv::Scalar::all(0); img0.copyTo(img); imshow( "image", img ); } if( c == 'w' ) { int i, j, compCount = 0; std::vector<std::vector<cv::Point> > contours; std::vector<cv::Vec4i> hierarchy; findContours(markerMask, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE); if( contours.empty() ) continue; cv::Mat markers(markerMask.size(), CV_32S); markers = cv::Scalar::all(0); for( int idx = 0; idx >= 0; idx = hierarchy[idx][0], compCount++ ) drawContours(markers, contours, idx, cv::Scalar::all(compCount+1), -1, 8, hierarchy, INT_MAX); if( compCount == 0 ) continue; std::vector<cv::Vec3b> colorTab; for( i = 0; i < compCount; i++ ) { int b = cv::theRNG().uniform(0, 255); int g = cv::theRNG().uniform(0, 255); int r = cv::theRNG().uniform(0, 255); colorTab.push_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r)); } watershed( img0, markers ); cv::Mat wshed(markers.size(), CV_8UC3); // paint the watershed image for( i = 0; i < markers.rows; i++ ) { for( j = 0; j < markers.cols; j++ ) { int index = markers.at<int>(i,j); if( index == -1 ) wshed.at<cv::Vec3b>(i,j) = cv::Vec3b(255,255,255); else if( index <= 0 || index > compCount ) wshed.at<cv::Vec3b>(i,j) = cv::Vec3b(0,0,0); else wshed.at<cv::Vec3b>(i,j) = colorTab[index - 1]; } } cv::addWeighted(wshed, 0.5, imgGray, 0.5, 0, wshed); // wshed = wshed*0.5 + imgGray*0.5; imshow( "watershed transform", wshed ); } } } |
نتیجه کد بالا :
مطالب مرتبط :
منابع :
- Image Segmentation
- Image segmentation [ Contrib ]
- Foreground Extraction using GrabCut Algorithm [ JS ]
- Interactive Foreground Extraction using GrabCut Algorithm [ Python ]
نواقص :
- اضافه کردن توابع و کلاس های Image Segmentation موجود در ماژول contrib؛ لینک زیر :
- Extended Image Processing > Image segmentation
تا مطلب بعد اگه زنده بودیم و قسمت شد، یا علی.