1. 分水岭分割方法
它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。
基本的步骤:
![7bf9c79221dd1f2d5b82ed9235710240.png](https://img-blog.csdnimg.cn/img_convert/7bf9c79221dd1f2d5b82ed9235710240.png)
例子1 粘连对象分离和计数。
例子代码:
#include#includeusing namespace std;using namespace cv;void test(){ Mat srcImg; srcImg = imread("pill_002.png"); if (srcImg.empty()) { cout <>contours; //找到 marker 的轮廓 findContours(distMaskImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0)); //create marker 填充 marker Mat markersImg = Mat::zeros(srcImg.size(), CV_32SC1); for (int i = 0; i (i), Scalar::all(static_cast(i)+1), -1); } circle(markersImg, Point(5, 5), 3, Scalar(255), -1); //形态学操作 - 彩色图像,目的是去掉干扰,让结果更好。 Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(srcImg, srcImg, MORPH_ERODE, kernel); //完成分水岭变换 watershed(srcImg, markersImg); Mat mark = Mat::zeros(markersImg.size(), CV_8UC1); markersImg.convertTo(mark, CV_8UC1); bitwise_not(mark, mark, Mat()); namedWindow("watershed", CV_WINDOW_AUTOSIZE); imshow("watershed", mark); //下面的步骤可以不做,最好做出来让结果显示更美观。 //生成随机颜色 vectorcolors; for (int i = 0; i (i, j); if (index > 0 && index <= contours.size()) { dstImg.at(i, j) = colors[index - 1]; } else { dstImg.at(i, j) = Vec3b(0, 0, 0); } } } cout <
![53b595c6823d946099d883cbd6d0cabe.png](https://img-blog.csdnimg.cn/img_convert/53b595c6823d946099d883cbd6d0cabe.png)
总结:有时候会导致碎片化,过度分割,因为二值化中如果有很多小的黑点或碎片,在分割的时候导致很多 mask ,即小山头太多了,这个时候我们要考虑怎么去合并它,可以通过联通区域的直方图,或者像素值均值相似程度等。
例子2:图像分割
#include#includeusing namespace std;using namespace cv;//执行分水岭算法函数Mat watershedCluster(Mat &srcImg, int &numSegments);//结果显示函数void DisplaySegments(Mat &markersImg, int numSegments);void test(){ Mat srcImg; srcImg = imread("toux.jpg"); if (srcImg.empty()) { cout <>contours; vectorhireachy; findContours(distImg, contours, hireachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE); if (contours.empty()) { return Mat(); } Mat markersImg(distImg.size(), CV_32S); markersImg = Scalar::all(0); for (int i = 0; i colors; for (int i = 0; i (i, j); if (index > 0 && index <= numSegments) { dstImg.at(i, j) = colors[index - 1]; } else { dstImg.at(i, j) = Vec3b(255, 255, 255); } } } cout <
效果图:
![acd16b313fb1b997258f93b9c7063b81.png](https://img-blog.csdnimg.cn/img_convert/acd16b313fb1b997258f93b9c7063b81.png)
2. GrabCut 算法分割图像
GrabCut 算法的原理前面有介绍过,这里就不在介绍了,具体可以看下文章末尾往期推荐中阅读。下面例子实现图像中对象的抠图。
基本步骤:
![3929cc252aa94166ccbab16ef4ab36fe.png](https://img-blog.csdnimg.cn/img_convert/3929cc252aa94166ccbab16ef4ab36fe.png)
例子代码:
#include#includeusing namespace std;using namespace cv;int numRun = 0; //算法迭代次数bool init = false;Rect rect;Mat srcImg, MaskImg, bgModel, fgModel;//鼠标回调函数void onMouse(int event, int x, int y, int flags, void* param);void showImg(); //显示画的图片void setRoiMask(); //选择 ROI 的函数void runGrabCut(); //执行算法函数static void ShowHelpText(); //提示用户操作函数void test(){ srcImg = imread("toux.jpg"); if (srcImg.empty()) { cout < 1 && rect.height > 1) { showImg(); } break; default: break; }}void showImg(){ Mat result, binMask; binMask.create(MaskImg.size(), CV_8UC1); binMask = MaskImg & 1; if (init) { srcImg.copyTo(result,binMask); } else { srcImg.copyTo(result); } rectangle(result, rect, Scalar(0, 0, 255), 2, 8); namedWindow("Original image", CV_WINDOW_AUTOSIZE); imshow("Original image", result);}void setRoiMask(){ //GC_BGD = 0 明确属于背景的像素 //GC_FGD = 1 明确属于前景的像素 //GC_PR_BGD = 2 可能属于背景的像素 //GC_PR_FGD = 3 可能属于前景的像素 MaskImg.setTo(GC_BGD); //为了避免选择越界 rect.x = max(0, rect.x); rect.y = max(0, rect.y); rect.width = min(rect.width, srcImg.cols - rect.x); rect.height = min(rect.height, srcImg.rows - rect.y); //把我们选取的那一块设为前景 MaskImg(rect).setTo(Scalar(GC_PR_FGD));}void runGrabCut(){ if (rect.width
效果图:
![d9535a7ebfc47ccd6fdbba003b16eeda.png](https://img-blog.csdnimg.cn/img_convert/d9535a7ebfc47ccd6fdbba003b16eeda.png)