meanShfit均值漂移算法

meanShfit均值漂移算法是一种通用的聚类算法,它的基本原理是:对于给定的一定数量样本,任选其中一个样本,以该样本为中心点划定一个圆形区域,求取该圆形区域内样本的质心,即密度最大处的点,再以该点为中心继续执行上述迭代过程,直至最终收敛。

可以利用均值偏移算法的这个特性,实现彩色图像分割,OpenCV中对应的函数是pyrMeanShiftFiltering。这个函数严格来说并不是图像的分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在Opencv中它的后缀是滤波“Filter”,而不是分割“segment”。先列一下这个函数,再说一下它“分割”彩色图像的实现过程。

int floodFill( InputOutputArray image, InputOutputArray mask,

Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0,

Scalar loDiff=Scalar(), Scalar upDiff=Scalar(),

int flags=4 );

第一个参数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(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(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;

}

��������:>Y

经pyrMeanShiftFiltering处理过色彩平滑滤波后的结果:

进一步进过floodFill漫水填充后的最终分割效果:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容