OpenCV之meanshift分割详解

来源:互联网 发布:vender软件 编辑:程序博客网 时间:2024/06/05 22:41

1. 原理

    用meanshift做图像平滑和分割,其实是一回事。其本质是经过迭代,将收敛点的像素值代替原来的像素值,从而去除了局部相似的纹理,同时保留了边缘等差异较大的特征。


        OpenCV中自带有基于meanshift的分割方法pyrMeanShiftFiltering()。由函数名pyrMeanShiftFiltering可知,这里是将meanshift算法和图像金字塔相结合用来分割的。

[cpp] view plain copy
  1. <span style="font-size:18px;">void PyrMeanShiftFiltering( const CvArr* srcarr,          //输入图像  
  2.                     CvArr* dstarr,        //输出图像  
  3.                    double  sp,            //颜色域半径  
  4.                     double sr,            //空间域半径  
  5.                        int max_level,     //金字塔最大层数                      
  6.                 CvTermCriteria termcrit )     //迭代终止条件</span>  

    要求输入和输出图像都是CV_8UC3类型,而且两者尺寸一样。实际上并不需要去先定义dstarr,因为程序里会将srcarr的格式赋值给dstarr。

    termcrit有三种情况,迭代次数、迭代精度和两者同时满足。默认为迭代次数为5同时迭代精度为1。termcrit是个结构体,其结构如下

[cpp] view plain copy
  1. <span style="font-size:18px;">typedef struct CvTermCriteria  
  2. {  
  3.     int    type;        /*CV_TERMCRIT_ITER或CV_TERMCRIT_EPS 或二者都是*/  
  4.     int    max_iter;   /* 最大迭代次数 */  
  5.     double epsilon;    /* 结果的精确性 */  
  6. }  
  7. CvTermCriteria;</span>  
     使用pyrMeanShiftFiltering()进行图像分割非常简单,只需要定义sp0,sr,max_level和termrit,然后调用pyrMeanShiftFiltering()就行了。

    在实际操作时,为了使分割的结果显示得更明显,经常用floodFill( )将不同连通域涂上不同的颜色。具体情况参看下 面的实例。


2. 程序实例

    来看看opencv自带的一个用meanshift进行分割的例子

    原程序见   “  .\OpenCV249\sources\samples\cpp\meanshift_segmentation.cpp”

[cpp] view plain copy
  1. <span style="font-size:18px;">#include "opencv2/highgui/highgui.hpp"  
  2. #include "opencv2/core/core.hpp"  
  3. #include "opencv2/imgproc/imgproc.hpp"  
  4.   
  5. #include <iostream>  
  6.   
  7. using namespace cv;  
  8. using namespace std;  
  9.   
  10. static void help(char** argv)  
  11. {  
  12.     cout << "\nDemonstrate mean-shift based color segmentation in spatial pyramid.\n"  
  13.     << "Call:\n   " << argv[0] << " image\n"  
  14.     << "This program allows you to set the spatial and color radius\n"  
  15.     << "of the mean shift window as well as the number of pyramid reduction levels explored\n"  
  16.     << endl;  
  17. }  
  18.   
  19. //This colors the segmentations  
  20. static void floodFillPostprocess( Mat& img, const Scalar& colorDiff=Scalar::all(1) )  
  21. {  
  22.     CV_Assert( !img.empty() );  
  23.     RNG rng = theRNG();  
  24.     Mat mask( img.rows+2, img.cols+2, CV_8UC1, Scalar::all(0) );  
  25.     forint y = 0; y < img.rows; y++ )  
  26.     {  
  27.         forint x = 0; x < img.cols; x++ )  
  28.         {  
  29.             if( mask.at<uchar>(y+1, x+1) == 0 )  
  30.             {  
  31.                 Scalar newVal( rng(256), rng(256), rng(256) );  
  32.                 floodFill( img, mask, Point(x,y), newVal, 0, colorDiff, colorDiff );  
  33.             }  
  34.         }  
  35.     }  
  36. }  
  37.   
  38. string winName = "meanshift";  
  39. int spatialRad, colorRad, maxPyrLevel;  
  40. Mat img, res;  
  41.   
  42. static void meanShiftSegmentation( intvoid* )  
  43. {  
  44.     cout << "spatialRad=" << spatialRad << "; "  
  45.          << "colorRad=" << colorRad << "; "  
  46.          << "maxPyrLevel=" << maxPyrLevel << endl;  
  47.     pyrMeanShiftFiltering( img, res, spatialRad, colorRad, maxPyrLevel );  
  48.     //Mat imgGray;  
  49.     //cvtColor(res,imgGray,CV_RGB2GRAY);  
  50.     //imshow("res",res);  
  51.     floodFillPostprocess( res, Scalar::all(2) );  
  52.     imshow( winName, res );  
  53. }  
  54.   
  55. int main(int argc, char** argv)  
  56. {         
  57.     img = imread("rubberwhale1.png");  
  58.     //img = imread("pic2.png");   
  59.       
  60.       
  61.     if( img.empty() )  
  62.         return -1;  
  63.   
  64.     spatialRad = 10;    
  65.     colorRad = 10;  
  66.     maxPyrLevel = 1;  
  67.   
  68.     namedWindow( winName, WINDOW_AUTOSIZE );  
  69.     //imshow("img",img);      
  70.   
  71.   
  72.     createTrackbar( "spatialRad", winName, &spatialRad, 80, meanShiftSegmentation );  
  73.     createTrackbar( "colorRad", winName, &colorRad, 60, meanShiftSegmentation );  
  74.     createTrackbar( "maxPyrLevel", winName, &maxPyrLevel, 5, meanShiftSegmentation );  
  75.   
  76.     meanShiftSegmentation(0, 0);  
  77.     //floodFillPostprocess( img, Scalar::all(2) );  
  78.     //imshow("img2",img);  
  79.     waitKey();  
  80.     return 0;  
  81. }</span>  


程序很简单,来看看floodFill()函数,有两种形式
    int floodFill( InputOutputArray image, Point seedPoint, Scalar newVal, CV_OUT Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 );
    int floodFill( InputOutputArray image,  InputOutputArray mask, Point seedPoint,  Scalar newVal, 
CV_OUT Rect* rect=0,  Scalar loDiff=Scalar(),  Scalar upDiff=Scalar(),  int flags=4 );

     InputOutputArray image    输入输出图像,要求格式为1通道或3通道,8位或浮点

     InputOutputArray mask   掩膜,比image的宽和高各大两像素点

     Point seedPoint    填充的起始点

    Scalar newVal   像素点被染色的值

    CV_OUT Rect* rect=0  可选参数,设置floodFill()要重绘区域的最小边界矩形区域

    Scalar loDiff=Scalar()  定义当前像素值与起始点像素值的亮度或颜色负差的最大值

    Scalar upDiff=Scalar()  定义当前像素值与起始点像素值的亮度或颜色正差的最大值

    flags 操作标志符    


程序结果


    处理后一些细小的纹理都平滑掉了,例如图中绿色线条所指示的区域。未填充时,很多地方看得并不明显,填充后就能明显看出差别来了。填充后的图很好地体现了meanshift聚类的思想!

    

    再来看一组更“夸张”的效果图


    使用meanshift方法进行处理后,原来的三个矩形区域消失了!平滑掉了!    

   

    meanshift算法的两个关键参数是空间域半径sr和颜色域半径sp,别说max_level,那是构建图像金字塔的参数好吧。最后,我们来看看sr和sp对结果的影响。


       显然颜色域半径sp对结果的影响比空间域半径sr对结果的影响大。sp和sr越小,细节保留得越多,sp和sr越大,平滑力度越大。边缘和颜色突变的区域的特征保留的较好。因为meanshift要对每个像素点进行操作,所以算法的时间花销很大。