1. 程式人生 > >OpenCV之meanshift分割詳解

OpenCV之meanshift分割詳解

1. 原理

    用meanshift做影象平滑和分割,其實是一回事。其本質是經過迭代,將收斂點的畫素值代替原來的畫素值,從而去除了區域性相似的紋理,同時保留了邊緣等差異較大的特徵。


        OpenCV中自帶有基於meanshift的分割方法pyrMeanShiftFiltering()。由函式名pyrMeanShiftFiltering可知,這裡是將meanshift演算法和影象金字塔相結合用來分割的。

  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是個結構體,其結構如下

  1. <span style="font-size:18px;">typedefstruct 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”

  1. <span style="font-size:18px;">#include "opencv2/highgui/highgui.hpp"
  2. #include "opencv2/core/core.hpp"
  3. #include "opencv2/imgproc/imgproc.hpp"
  4. #include <iostream>
  5. usingnamespace cv;  
  6. usingnamespace std;  
  7. staticvoid help(char** argv)  
  8. {  
  9.     cout << "\nDemonstrate mean-shift based color segmentation in spatial pyramid.\n"
  10.     << "Call:\n   " << argv[0] << " image\n"
  11.     << "This program allows you to set the spatial and color radius\n"
  12.     << "of the mean shift window as well as the number of pyramid reduction levels explored\n"
  13.     << endl;  
  14. }  
  15. //This colors the segmentations
  16. staticvoid floodFillPostprocess( Mat& img, const Scalar& colorDiff=Scalar::all(1) )  
  17. {  
  18.     CV_Assert( !img.empty() );  
  19.     RNG rng = theRNG();  
  20.     Mat mask( img.rows+2, img.cols+2, CV_8UC1, Scalar::all(0) );  
  21.     forint y = 0; y < img.rows; y++ )  
  22.     {  
  23.         forint x = 0; x < img.cols; x++ )  
  24.         {  
  25.             if( mask.at<uchar>(y+1, x+1) == 0 )  
  26.             {  
  27.                 Scalar newVal( rng(256), rng(256), rng(256) );  
  28.                 floodFill( img, mask, Point(x,y), newVal, 0, colorDiff, colorDiff );  
  29.             }  
  30.         }  
  31.     }  
  32. }  
  33. string winName = "meanshift";  
  34. int spatialRad, colorRad, maxPyrLevel;  
  35. Mat img, res;  
  36. staticvoid meanShiftSegmentation( intvoid* )  
  37. {  
  38.     cout << "spatialRad=" << spatialRad << "; "
  39.          << "colorRad=" << colorRad << "; "
  40.          << "maxPyrLevel=" << maxPyrLevel << endl;  
  41.     pyrMeanShiftFiltering( img, res, spatialRad, colorRad, maxPyrLevel );  
  42.     //Mat imgGray;
  43.     //cvtColor(res,imgGray,CV_RGB2GRAY);
  44.     //imshow("res",res);
  45.     floodFillPostprocess( res, Scalar::all(2) );  
  46.     imshow( winName, res );  
  47. }  
  48. int main(int argc, char** argv)  
  49. {         
  50.     img = imread("rubberwhale1.png");  
  51.     //img = imread("pic2.png"); 
  52.     if( img.empty() )  
  53.         return -1;  
  54.     spatialRad = 10;    
  55.     colorRad = 10;  
  56.     maxPyrLevel = 1;  
  57.     namedWindow( winName, WINDOW_AUTOSIZE );  
  58.     //imshow("img",img);    
  59.     createTrackbar( "spatialRad", winName, &spatialRad, 80, meanShiftSegmentation );  
  60.     createTrackbar( "colorRad", winName, &colorRad, 60, meanShiftSegmentation );  
  61.     createTrackbar( "maxPyrLevel", winName, &maxPyrLevel, 5, meanShiftSegmentation );  
  62.     meanShiftSegmentation(0, 0);  
  63.     //floodFillPostprocess( img, Scalar::all(2) );
  64.     //imshow("img2",img);
  65.     waitKey();  
  66.     return 0;  
  67. }</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要對每個畫素點進行操作,所以演算法的時間花銷很大。