Opencv均值漂移pyrMeanShiftFiltering彩色图像分割流程剖析
meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。
可以利用均值偏移算法的这个特性,实现彩色图像分割,Opencv中对应的函数是pyrMeanShiftFiltering。这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在Opencv中它的后缀是滤波“Filter”,而不是分割“segment”。先列一下这个函数,再说一下它“分割”彩色图像的实现过程。
void pyrMeanShiftFiltering( InputArray src, OutputArray dst,
double sp, double sr, int maxLevel=1,
TermCriteria termcrit=TermCriteria(
TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) );
第一个参数src,输入图像,8位,三通道的彩色图像,并不要求必须是RGB格式,HSV、YUV等Opencv中的彩色图像格式均可;
第二个参数dst,输出图像,跟输入src有同样的大小和数据格式;
第三个参数sp,定义的漂移物理空间半径大小;
第四个参数sr,定义的漂移色彩空间半径大小;
第五个参数maxLevel,定义金字塔的最大层数;
第六个参数termcrit,定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合;
pyrMeanShiftFiltering函数的执行过程是这样的:
1. 迭代空间构建:
以输入图像上src上任一点P0为圆心,建立物理空间上半径为sp,色彩空间上半径为sr的球形空间,物理空间上坐标2个—x、y,色彩空间上坐标3个—R、G、B(或HSV),构成一个5维的空间球体。
其中物理空间的范围x和y是图像的长和宽,色彩空间的范围R、G、B分别是0~255。
2. 求取迭代空间的向量并移动迭代空间球体后重新计算向量,直至收敛:
在1中构建的球形空间中,求得所有点相对于中心点的色彩向量之和后,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得的向量和的终点就是该空间球体的中心点Pn,迭代结束。
3. 更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,如此完成一个点的色彩均值漂移。
4. 对输入图像src上其他点,依次执行步骤1,、2、3,遍历完所有点位后,整个均值偏移色彩滤波完成,这里忽略对金字塔的讨论。
在这个过程中,关键参数是sp和sr的设置,二者设置的值越大,对图像色彩的平滑效果越明显,同时函数耗时也越多。以下对一幅图像执行pyrMeanShiftFiltering操作,来看一下效果:
原始图像:
物理空间半径sp=10,色彩空间半径sr=10时色彩滤波效果:
物理空间半径sp=50,色彩空间半径sr=50时色彩滤波效果:
对比可以发现,半径为10时,图像色彩细节大部分存在,半径为50时,山体和草地的色彩细节基本都已经丢失。
到这里,meanShift均值偏移算法对彩色图像的平滑操作就完成了,为了达到分割的目的,需要借助另外一个漫水填充函数的进一步处理来实现,那就是floodFill:
int floodFill( InputOutputArray image, InputOutputArray mask,
Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,
Scalar loDiff=Scalar(), Scalar upDiff=Scalar(),
int flags=4 );
第一个参数image,输入三通道8bit彩色图像,同时作为输出。
第二个参数mask,是掩模图像,它的大小是输入图像的长宽左右各加1个像素,mask一方面作为输入的掩模图像,另一方面也会在填充的过程中不断被更新。floodFill漫水填充的过程并不会填充mask上灰度值不为0的像素点,所以可以使用一个图像边缘检测的输出作为mask,这样填充就不会填充或越过边缘轮廓。mask在填充的过程中被更新的过程是这样的:每当一个原始图上一个点位(x,y)被填充之后,该点位置对应的mask上的点(x+1,y+1)的灰度值随机被设置为1(原本该点的灰度值为0),代表该点已经被填充处理过。
第三个参数seedPoint,是漫水填充的起始种子点。
第四个参数newVal,被充填的色彩值。
第五个参数rect,可选的参数,用于设置floodFill函数将要重绘区域的最小矩形区域;
第六个参数loDiff和第七个参数upDiff,用于定义跟种子点相比色彩的下限值和上限值,介于种子点减去loDiff和种子点加上upDiff的值会被填充为跟种子点同样的颜色。
第八个参数,定义漫水填充的模式,用于连通性、扩展方向等的定义。
这里边好玩的参数是掩模mask,一个像素值全为0的mask在运算过程中,随着填充过程,mask上像素不断被置为1,代表当前位置被充填过了,仍以上图为例,看一下mask的变化:
这个是初始输入的值全为0的mask:
这个是漫水填充执行完后的mask:
OMG!怎么都是两个黑乎乎的图像,并且也没啥变化啊!难道是Opencv欺骗了我们?确实是有人欺骗了我们,这个人不是Opencv而是上帝~~,执行漫水填充后,mask上的值从原本全是0变成了全是1,置1就代表了该点被漫水填充执行过,之所以我们看不出来,是因为人眼对灰阶为1的亮度和灰阶为0的亮度完全区分不出来。
为了显示mask内像素值的变化过程,我们对mask执行0~255的归一化。这个是左上角第一个像素执行完漫水填充后归一化的mask显示:
左上角是天空的开始的图像上半部分是蓝色天空的颜色,占据了很大部分空间,都被填充成一个颜色,对应的在mask上这些区域的灰度值被从全黑的0置为了1。
中间过程1,这时候两朵云所在的区域也被填充:
中间过程2,这时候已经接近填充完成,除了底部,其余区域都被置为了1:
Opencv中自带例程文件meanshift_segmentation.cpp,位置在\opencv\sources\samples\cpp中,其中有一行代码是有关mask掩模的:
if( mask.at<uchar>(y+1, x+1) == 0 ) //非0处即为1,表示已经经过填充,不再处理
{
Scalar newVal( rng(256), rng(256), rng(256) );
floodFill( res, mask, Point(x,y), newVal, 0, Scalar::all(5), Scalar::all(5) ); //执行漫水填充
}
这里在填充过程中对mask的值做了判断,不为0表示已经填充过,不再重复填充,这就是对mask的巧妙运用,可以特高运算效率。当然仅仅是基于效率的考虑,如果不加这个判定条件,重复填充,最终的结果是一样的。
以下Opencv代码是对原有自带例程的简化,代码量较少,核心操作只有两个函数,但需要对这两个操作的逻辑阐述清楚,这就是以上啰嗦了这么多所试图做的事情。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
int main(int argc, char** argv)
{
Mat img = imread( argv[1] ); //读入图像,RGB三通道
imshow("原图像",img);
Mat res; //分割后图像
int spatialRad = 50; //空间窗口大小
int colorRad = 50; //色彩窗口大小
int maxPyrLevel = 2; //金字塔层数
pyrMeanShiftFiltering( img, res, spatialRad, colorRad, maxPyrLevel); //色彩聚类平滑滤波
imshow("res",res);
RNG rng = theRNG();
Mat mask( res.rows+2, res.cols+2, CV_8UC1, Scalar::all(0) ); //掩模
for( int y = 0; y < res.rows; y++ )
{
for( int x = 0; x < res.cols; x++ )
{
if( mask.at<uchar>(y+1, x+1) == 0 ) //非0处即为1,表示已经经过填充,不再处理
{
Scalar newVal( rng(256), rng(256), rng(256) );
floodFill( res, mask, Point(x,y), newVal, 0, Scalar::all(5), Scalar::all(5) ); //执行漫水填充
}
}
}
imshow("meanShift图像分割", res );
waitKey();
return 0;
}
经pyrMeanShiftFiltering处理过色彩平滑滤波后的结果:
进一步进过floodFill漫水填充后的最终分割效果:
Opencv均值漂移pyrMeanShiftFiltering彩色图像分割流程剖析的更多相关文章
- 使用Opencv中均值漂移meanShift跟踪移动目标
Mean Shift均值漂移算法是无参密度估计理论的一种,无参密度估计不需要事先知道对象的任何先验知识,完全依靠训练数据进行估计,并且可以用于任意形状的密度估计,在某一连续点处的密度函数值可由该点邻域 ...
- opencv2对读书笔记——使用均值漂移算法查找物体
一些小概念 1.反投影直方图的结果是一个概率映射,体现了已知图像内容出如今图像中特定位置的概率. 2.概率映射能够找到最初的位置,从最初的位置開始而且迭代移动,便能够找到精确的位置,这就是均值漂移算法 ...
- twemproxy代理主干流程——剖析twemproxy代码正编
在twemproxy的发送和接收流程剖析中,我们已经完全弄清楚twemproxy如何将客户端以及服务端发来的包切分成msg,获得一个独立的msg后twemproxy应该如何处理?这是本文这次需要重点介 ...
- Meanshift均值漂移算法
通俗理解Meanshift均值漂移算法 Meanshift车手?? 漂移?? 秋名山??? 不,不,他是一组算法, 今天我就带大家来了解一下机器学习中的Meanshift均值漂移. Mea ...
- Spring Security Oauth2 单点登录案例实现和执行流程剖析
Spring Security Oauth2 OAuth是一个关于授权的开放网络标准,在全世界得到的广泛的应用,目前是2.0的版本.OAuth2在“客户端”与“服务提供商”之间,设置了一个授权层(au ...
- Jedis cluster命令执行流程剖析
Jedis cluster命令执行流程剖析 在Redis Cluster集群模式下,由于key分布在各个节点上,会造成无法直接实现mget.sInter等功能.因此,无论我们使用什么客户端来操作Red ...
- opencv 彩色图像分割(inrange)
灰度图像大多通过算子寻找边缘和区域生长融合来分割图像. 彩色图像增加了色彩信息,可以通过不同的色彩值来分割图像,常用彩色空间HSV/HSI, RGB, LAB等都可以用于分割! 笔者主要介绍inran ...
- Cocos Creator 资源加载流程剖析【一】——cc.loader与加载管线
这系列文章会对Cocos Creator的资源加载和管理进行深入的剖析.主要包含以下内容: cc.loader与加载管线 Download部分 Load部分 额外流程(MD5 Pipe) 从编辑器到运 ...
- Kafka控制器选举流程剖析
1.概述 平时在使用Kafka的时候,可能关注的更多的是Kafka系统层面的.今天来给大家剖析一下Kafka的控制器,了解一下Kafka控制器的选举流程. 2.内容 Kafka控制器,其实就是一个Ka ...
随机推荐
- (入门整理学习一)Asp.net core
1.安装.net code SDK,vs Code;vs code c#插件可在软件扩展 (我网盘有) vs2015上安装教程:http://www.cnblogs.com/wangrudong00 ...
- amazeui学习笔记--css(HTML元素5)--表格Table
amazeui学习笔记--css(HTML元素5)--表格Table 一.总结 1.基本样式:am-table:直接模块名 <table class="am-table"& ...
- 宏的使用 extern
声明全局变量使用的技术. 有些时候C语言的一些条条框框就像语法.学会C语言就是学会了语法. 但是语法怎样使用就又是另一回事了. 我希望自己能多学习一些技巧,而不是一些固定的C语言语法. 这篇文章真的很 ...
- DC中为什么要用Uniquify?
转自:http://blog.sina.com.cn/s/blog_68c493870101exl7.html 为了在layout中进行时钟树的综合,网表在DC中必须被uniquified.所谓uni ...
- 动词 + to do、动词 + doing
1. 含义有重大区别 动词+to do 与 动词 + doing,具有较大含义上的差别的动词主要有: stop finish forget 在这些单词的后面,自然 to do 表示未做的事,doing ...
- vue项目对axios的全局配置
标准的vue-cli项目结构(httpConfig文件夹自己建的): api.js: //const apiUrl = 'http://test';//测试域名,自己改成自己的 const apiUr ...
- REGEXP_LIKE,REGEXP_INSTR,REGEXP_SUBSTR,REGEXP_REPLACE
参考: http://www.cnblogs.com/scottckt/archive/2012/10/11/2719562.html http://www.jb51.net/article/3842 ...
- Arcgis engine 指定图层对要素进行创建、删除等操作
Arcgis engine 指定图层创建点要素 在指定的图层上创建一个点要素,点要素的位置是通过X,Y坐标指定的,下面是具体的注释 .其中 和IFeatureClassWrite接口有关的代码不要好像 ...
- 自己定义Dialog的具体步骤(实现自己定义样式一般原理)
转载请标注转载http://blog.csdn.net/oqihaogongyuan/article/details/50958659 自己定义Dialog的具体步骤(实现自己定义样式一般原理) ...
- 组合搜索(combinatorial search)在算法求解中的应用
1. 分治.动态规划的局限性 没有合适的分割方式时,就不能使用分治法: 没有合适的子问题或占用内存空间太大时,就不能用动态规划: 此时还需要回到最基本的穷举搜索算法. 穷举搜索(exhaustive ...