做形态学方法的团队_图像分割实战-分水岭分割方法和GrabCut 算法

7ccf6042bbb685fef07a4b371c2e96dd.png

1. 分水岭分割方法

它是依赖于形态学的,图像的灰度等级不一样,如果图像的灰度等级一样的情况下怎么人为的把它造成不一样?可以通过距离变换实现,这样它们的灰度值就有了阶梯状的变换。风水岭算法常见的有三种方法:(1)基于浸泡理论的分水岭分割方法;(2)基于连通图方法;(3)基于距离变换的方法。OpenCV 中是基于距离变换的分割方法,就相当于我们的小山头(认为造成的)。

基本的步骤:

33a779960cbda5c182f0e2029a7d142c.png

例子1 粘连对象分离和计数。

例子代码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
void test()
{Mat srcImg;srcImg = imread("pill_002.png");if (srcImg.empty()){cout << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", srcImg);Mat grayImg, binaryImg, shiftedImg;//做滤波,使图像更加平滑,保留边缘,类似于双边滤波pyrMeanShiftFiltering(srcImg, shiftedImg, 21, 51);  namedWindow("shifted", CV_WINDOW_AUTOSIZE);imshow("shifted", shiftedImg);cvtColor(shiftedImg, grayImg, COLOR_BGR2GRAY);  //转为灰度图像//二值化threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU);  namedWindow("binary", CV_WINDOW_AUTOSIZE);imshow("binary", binaryImg);//距离变换Mat distImg;distanceTransform(binaryImg, distImg, DistanceTypes::DIST_L2, 3, CV_32F);//归一化,因为距离变换后得出来的值都比较小。normalize(distImg, distImg, 0, 1, NORM_MINMAX);  namedWindow("distance", CV_WINDOW_AUTOSIZE);imshow("distance", distImg);//这个二值化的作用是寻找局部最大。threshold(distImg, distImg, 0.4, 1, THRESH_BINARY);namedWindow("distance_binary", CV_WINDOW_AUTOSIZE);imshow("distance_binary", distImg);//生成 markerMat distMaskImg;// distImg 得到的是 0- 1之间的数,转化成8位单通道的。distImg.convertTo(distMaskImg, CV_8U);  vector<vector<Point>>contours;//找到 marker 的轮廓findContours(distMaskImg, contours, RETR_EXTERNAL,CHAIN_APPROX_SIMPLE, Point(0, 0));//create marker 填充 markerMat  markersImg = Mat::zeros(srcImg.size(), CV_32SC1);for (int i = 0; i < contours.size(); i++){drawContours(markersImg, contours, static_cast<int>(i),Scalar::all(static_cast<int>(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);//下面的步骤可以不做,最好做出来让结果显示更美观。//生成随机颜色vector<Vec3b>colors;for (int i = 0; i < contours.size(); i++){int r = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int b = theRNG().uniform(0, 255);colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));}//颜色填充和最终显示Mat dstImg = Mat::zeros(markersImg.size(), CV_8UC3);int index = 0;for (int i = 0; i < markersImg.rows; i++){for (int j = 0; j < markersImg.cols; j++){index = markersImg.at<int>(i, j);if (index > 0 && index <= contours.size()){dstImg.at<Vec3b>(i, j) = colors[index - 1];}else{dstImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);}}}cout << "number of objects:" << contours.size() << endl;namedWindow("Final Result", CV_WINDOW_AUTOSIZE);imshow("Final Result", dstImg);
}
int main()
{test();waitKey(0);return 0;
}

效果:

7654a9a8dfcd55f9e74e9ee1684e98cc.png

总结:有时候会导致碎片化,过度分割,因为二值化中如果有很多小的黑点或碎片,在分割的时候导致很多 mask ,即小山头太多了,这个时候我们要考虑怎么去合并它,可以通过联通区域的直方图,或者像素值均值相似程度等。

例子2:图像分割

#include<opencv2/opencv.hpp>
#include<iostream>
using 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 << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", srcImg);int numSegments;Mat markers = watershedCluster(srcImg, numSegments);DisplaySegments(markers, numSegments);
}Mat watershedCluster(Mat &srcImg, int &numSegments)
{//二值化Mat grayImg, binaryImg;cvtColor(srcImg, grayImg, COLOR_BGR2GRAY);threshold(grayImg, binaryImg, 0, 255, THRESH_BINARY | THRESH_OTSU);//形态学和距离变换Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));morphologyEx(binaryImg, binaryImg, MORPH_OPEN, kernel, Point(-1, -1));Mat distImg;distanceTransform(binaryImg, distImg, DistanceTypes::DIST_L2, 3, CV_32F);normalize(distImg, distImg, 0.0, 1.0, NORM_MINMAX);//开始生成标记threshold(distImg, distImg, 0.1, 1.0, THRESH_BINARY);normalize(distImg, distImg, 0, 255, NORM_MINMAX);distImg.convertTo(distImg, CV_8UC1);  //CV_32F 转成 CV_8UC1//标记开始vector<vector<Point>>contours;vector<Vec4i>hireachy;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 < contours.size(); i++){drawContours(markersImg, contours, i, Scalar(i + 1), -1, 8, hireachy, INT_MAX);}circle(markersImg, Point(5, 5) ,3, Scalar(255), -1);//分水岭变换watershed(srcImg, markersImg);numSegments = contours.size();return markersImg;
}void DisplaySegments(Mat &markersImg, int numSegments)
{//生成随机颜色vector<Vec3b>colors;for (int i = 0; i < numSegments; i++){int r = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int b = theRNG().uniform(0, 255);colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));}//颜色填充和最终显示Mat dstImg = Mat::zeros(markersImg.size(), CV_8UC3);int index = 0;for (int i = 0; i < markersImg.rows; i++){for (int j = 0; j < markersImg.cols; j++){index = markersImg.at<int>(i, j);if (index > 0 && index <= numSegments){dstImg.at<Vec3b>(i, j) = colors[index - 1];}else{dstImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);}}}cout << "number of objects:" << numSegments << endl;namedWindow("Final Result", CV_WINDOW_AUTOSIZE);imshow("Final Result", dstImg);
}
int main()
{test();waitKey(0);return 0;
}

效果图:

2d853c4feb51bce22d93cdd4f75488eb.png

2. GrabCut 算法分割图像

GrabCut 算法的原理前面有介绍过,这里就不在介绍了,具体可以看下文章末尾往期推荐中阅读。下面例子实现图像中对象的抠图。

基本步骤:

e057b09ebdd896efa3863092264e3674.png

例子代码:

#include<opencv2/opencv.hpp>
#include<iostream>
using 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 << "could not load image...n" << endl;}namedWindow("Original image", CV_WINDOW_AUTOSIZE);imshow("Original image", srcImg);//初始化 mask,单通道 8 位MaskImg.create(srcImg.size(), CV_8UC1);  //在不知道它是前景还是背景的情况下,把它全部设为背景。MaskImg.setTo(Scalar::all(GC_BGD));  //结果不是 0 就是 1  GC_BGD为0setMouseCallback("Original image", onMouse, 0);while (true){char c = (char)waitKey(0);if (c == 'n')  // 按下 n 建开始执行算法{runGrabCut();numRun++;showImg();cout << "current iteative times:" << numRun << endl;}if (c == 27){break;}}
}void onMouse(int event, int x, int y, int flags, void* param)
{switch (event){case EVENT_LBUTTONDOWN:rect.x = x;rect.y = y;rect.width = 1;rect.height = 1;break;case EVENT_MOUSEMOVE:if (flags& EVENT_FLAG_LBUTTON){rect = Rect(Point(rect.x, rect.y), Point(x, y));showImg();}break;case EVENT_LBUTTONUP:if (rect.width > 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 < 2 || rect.height < 2){return;}if (init){grabCut(srcImg, MaskImg, rect, bgModel, fgModel, 1);}else{grabCut(srcImg, MaskImg, rect, bgModel, fgModel, 1, GC_INIT_WITH_RECT);init = true;}
}static void ShowHelpText()
{cout << "请先用鼠标在图片窗口中标记出属于前景的区域" << endl;cout << "然后再按按键【n】启动算法" << endl;cout << "按键【ESC】- 退出程序" << endl;
}int main()
{ShowHelpText();test();waitKey(0);return 0;
}

效果图:

471ba07b04f8494dba66941a3672fb59.png

d7c08aa3cff111d9b13324dd9f66ae3f.png

欢迎关注我的微信公众号“OpenCV图像处理算法”,主要是记录自己学习图像处理算法的历程,包括特征提取、目标跟踪、定位、机器学习和深度学习,每一个例子都会提供源码和例子所用的资料,欢迎同行的同学关注我和我一起虚度光阴吧!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/560011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机gt的使用方法,旗舰级综合效果器 BOSS GT-1000使用宝典(二) | 基础操作

原标题&#xff1a;旗舰级综合效果器 BOSS GT-1000使用宝典(二) | 基础操作在上一期的使用宝典中&#xff0c;B老板为大家介绍了GT-1000内置的前级&#xff0c;箱体及麦克风等&#xff0c;&#x1f449; GT-1000使用宝典(一) | 了解你的神器 想必各位对自己手中的神器有了比较全…

日期格式化为yyyymmdd_你还在用SimpleDateFormat格式化时间嘛

Jdk1.8之时间处理该文章已经同步到Github&#xff1a;https://github.com/stackInk/makerstack1. 传统时间处理的问题1.1 多线程环境下的SimpleDateFormat当多个线程使用同一个时间处理对象进行对日期的格式化的时候&#xff0c;会出现java.lang.NumberFormatException: multip…

存储ic载板_延伸IC领域 崇达技术拟将持有普诺威55%股权

立即加星标每天看好文PCB网城讯崇达技术7月1日公告&#xff0c;6月30日&#xff0c;崇达技术股份有限公司(以下简称“崇达技术”)与朱小红、马洪伟在公司会议室签署了《关于江苏普诺威电子股份有限公司之股份转让协议》(以下简称“协议”)。根据协议&#xff0c;公司拟以自有资…

绘图python_Python绘图

1.二维绘图a. 一维数据集用 Numpy ndarray 作为数据传入 ply1.import numpy as npimport matplotlib as mplimport matplotlib.pyplot as pltnp.random.seed(1000)y np.random.standard_normal(10)print "y %s"% yx range(len(y))print "x%s"% xplt.plo…

文件隐藏服务器版本信息,如何隐藏Apache版本号和其他敏感信息

当远程请求发送到您的Apache Web服务器时&#xff0c;默认情况下&#xff0c;一些有价值的信息&#xff0c;如Web服务器版本号&#xff0c;服务器操作系统详细信息&#xff0c;已安装的Apache模块等等&#xff0c;在服务器生成的文档中发送回客户端。这是攻击者利用漏洞并访问您…

微云服务器失败原因_梦幻西游:服务器发生异常?游戏出现明显卡顿感,正在排查问题...

就在刚刚&#xff0c;不少梦幻玩家都在讨论一个情况&#xff0c;那就是服务器出现了明显的卡顿感&#xff0c;一些商人也陆续掉线&#xff0c;难道是服务器出现了异常&#xff1f;退出游戏之后&#xff0c;一直无法进入&#xff0c;登录界面总是停留在"正在连接某某服务器…

maven多模块项目部署到服务器,GitHub - baxias/foweb: 一个基于 Spring+SpringMVC+Mybatis 的Maven多模块项目。(实现前后端分离的服务器端)...

Foweb FrameworkA multi-modules maven project base on SpringSpringMVCMybatis.一个基于 SpringSpringMVCMybatis 的Maven多模块项目。使用文档两种使用方式&#xff1a;1. 直接将项目download下来&#xff0c;然后在IDE(Eclipse或者IDEA)中以maven项目导入&#xff0c;注意这…

python内建函数测试对象身份_Python学习笔记 03 Python对象

1、Python对象Python对象都拥有三个特性&#xff1a;身份、类型和值。身份&#xff1a;每一个对象都有一个唯一的身份标识自己&#xff0c;任何对象的身份都可以使用内建函数id()来得到。这个值可以被认为是该对象的内存地址。类型&#xff1a;对象的类型决定了该对象可以保存什…

ajax中的换行符,jQuery中的换行符ajax html回调导致错误

我从$.ajax调用返回一大块HTML.来自PHP的字符串在开头有两个换行符,例如$data "Here is some text";这是$.ajax调用&#xff1a;$(form#form_id).submit(function(e){e.preventDefault();$form $(this);$.ajax({url: $form.attr(action),type: $form.attr(method),…

网站需要数据库服务器吗,网站需要独立的服务器数据库吗

网站需要独立的服务器数据库吗 内容精选换一换文档数据库服务提供使用数据管理服务(Data Admin Service&#xff0c;简称DAS)、内网和公网的连接方式。文档数据库服务默认为您开通了远程主机登录权限&#xff0c;推荐您使用更安全便捷的数据管理服务连接实例&#xff0c;具体请…

android 获取monkey日志_安卓app测试之Monkey日志分析

转:原文&#xff1a;https://blog.csdn.net/a136332462/article/details/76066909一、一般测试结果分析-搜索关键字&#xff1a;1、无响应问题可以在日志中搜索 “ANR” 。2、崩溃问题搜索 “CRASH” 。3、内存泄露问题搜索"GC"(需进一步分析)。4、异常问题搜索 “Ex…

只提取单元格中的数字_提取Excel单元格中的数字(4类)及原理

文中总结了4类从包含有中文、字母、数字的单元格数据中只提取数字的公式。(使用的时候把单元格名称改为实际的单元格名称即可)。1.提取文本左侧数据当单元格中数字在左侧&#xff0c;文字内容在右侧时&#xff0c;我们可以使用一下公式来将数字快速提取出来。函数公式&#xff…

delphi 执行长时间存储过程 显示进度_项目管理_十大管理体系之「项目进度管理」知识整理及心得分享...

项目进度管理项目进度管理在项目是项目管理三大要素之一&#xff0c;在项目管理中占有非常重要的地位。往往很多项目要求进度节点是一个固定期限&#xff0c;无法修改&#xff0c;所以进度把控就显得尤为重要。什么是进度管理&#xff0c;在你写project项目计划时就非常清楚的可…

bettertouchtool闪退_BetterTouchTool for Mac 3.238 无闪退 触控板增强工具

BetterTouchTool为魔术鼠标添加了许多新的完全可自定义的手势&#xff0c;Multi-Touch MacBook触控板和Magic Trackpad。这些手势是可定制的&#xff1a;魔术鼠标&#xff1a;捏合/缩小(缩放)单指点击左边单指轻拍右键单指中间单指轻拍单指点击中间双指轻敲双指点击双指轻扫(上…

宝塔php扩展fil_宝塔面板安装Redis给WordPress网站加速优化教程

宝塔面板php安装redis缓存以加快WordPress的开启速度&#xff0c;因为WordPress打开速度慢&#xff0c;所以霜天 seo使用多种方式来提高wordpress的加载速度&#xff0c;其中一个好办法是&#xff1a;使用redis加速缓存。本文的主题是宝塔面板php安装redis缓存加速WordPress&am…

常量缓存与integer比较_Integer缓存范围到底是多少?

本文主要大致思路为&#xff1a;不管从工作中还是面试&#xff0c;这篇文章都应该好好看完&#xff0c;本人认为是非常有用的。案例Integer是基本类型int的封装类。平时不管是入坑多年的小伙伴还在入坑路上的小伙伴&#xff0c;都应该知道的使用频率是相当高。下面模仿订单支付…

mysql数据库中删除列的内容_如何在数据库中删除列

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航&#xff0c;为用户…

mysql语句创建临时表并存入数据_mysql实例:在存储过程中创建临时表并储存数据...

在mysql存储过程中创建临时表&#xff0c;并保存数据到该表&#xff0c;然后根据存储过程调用的例子。是学习mysql存储过程的好例子&#xff0c;值得参考。代码&#xff1a;mysql>mysql> CREATE TABLE Employee( //创建普通表-> id int,-> first_name VARCHAR(15),…

虚拟主机安装mysql_如何虚拟主机安装mysql

匿名用户1级2008-10-20 回答在虚拟机上安装mysql的步骤为&#xff1a;1、下载MySQL-5.5.24-1.rhel5.i386.tar在/usr/local 下建立必要文件夹# cd /usr/local# mkdir mysql# cd /usr/local/mysql# mkdir src# mkdir data用xftp将MySQL-5.5.16-1.rhel5.i386.tar 上传到虚拟机src目…