圖像分割之(四)OpenCV的GrabCut函數使用和源碼解讀

圖像分割之(四)OpenCV的GrabCut函數使用和源碼解讀

[email protected]

http://blog.csdn.net/zouxy09

 

      上一文對GrabCut做了一個瞭解。OpenCV中的GrabCut算法是依據《"GrabCut" - Interactive Foreground Extraction using Iterated Graph Cuts》這篇文章來實現的。現在我對源碼做了些註釋,以便我們更深入的瞭解該算法。一直覺得論文和代碼是有比較大的差別的,個人覺得脫離代碼看論文,最多能看懂70%,剩下20%或者更多就需要通過閱讀代碼來獲得了,那還有10%就和每個人的基礎和知識儲備相掛鉤了。

      接觸時間有限,若有錯誤,還望各位前輩指正,謝謝。原論文的一些淺解見上一博文:

          http://blog.csdn.net/zouxy09/article/details/8534954

 

一、GrabCut函數使用

      在OpenCV的源碼目錄的samples的文件夾下,有grabCut的使用例程,請參考:

opencv\samples\cpp\grabcut.cpp

grabCut函數的API說明如下:

void cv::grabCut( InputArray _img, InputOutputArray _mask, Rect rect,

                  InputOutputArray _bgdModel, InputOutputArray _fgdModel,

                  int iterCount, int mode )

/*

****參數說明:

         img——待分割的源圖像,必須是83通道(CV_8UC3)圖像,在處理的過程中不會被修改;

         mask——掩碼圖像,如果使用掩碼進行初始化,那麼mask保存初始化掩碼信息;在執行分割的時候,也可以將用戶交互所設定的前景與背景保存到mask中,然後再傳入grabCut函數;在處理結束之後,mask中會保存結果。mask只能取以下四種值:

                   GCD_BGD=0),背景;

                   GCD_FGD=1),前景;

                   GCD_PR_BGD=2),可能的背景;

                   GCD_PR_FGD=3),可能的前景。

                   如果沒有手工標記GCD_BGD或者GCD_FGD,那麼結果只會有GCD_PR_BGDGCD_PR_FGD

         rect——用於限定需要進行分割的圖像範圍,只有該矩形窗口內的圖像部分才被處理;

         bgdModel——背景模型,如果爲null,函數內部會自動創建一個bgdModelbgdModel必須是單通道浮點型(CV_32FC1)圖像,且行數只能爲1,列數只能爲13x5

         fgdModel——前景模型,如果爲null,函數內部會自動創建一個fgdModelfgdModel必須是單通道浮點型(CV_32FC1)圖像,且行數只能爲1,列數只能爲13x5

         iterCount——迭代次數,必須大於0

         mode——用於指示grabCut函數進行什麼操作,可選的值有:

                   GC_INIT_WITH_RECT=0),用矩形窗初始化GrabCut

                   GC_INIT_WITH_MASK=1),用掩碼圖像初始化GrabCut

                   GC_EVAL=2),執行分割。

*/

 

二、GrabCut源碼解讀

       其中源碼包含了gcgraph.hpp這個構建圖和max flow/min cut算法的實現文件,這個文件暫時沒有解讀,後面再更新了。

[cpp] view plain copy
  1. /*M/////////////////////////////////////////////////////////////////////////////////////// 
  2. // 
  3. //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. 
  4. // 
  5. //  By downloading, copying, installing or using the software you agree to this license. 
  6. //  If you do not agree to this license, do not download, install, 
  7. //  copy or use the software. 
  8. // 
  9. // 
  10. //                        Intel License Agreement 
  11. //                For Open Source Computer Vision Library 
  12. // 
  13. // Copyright (C) 2000, Intel Corporation, all rights reserved. 
  14. // Third party copyrights are property of their respective owners. 
  15. // 
  16. // Redistribution and use in source and binary forms, with or without modification, 
  17. // are permitted provided that the following conditions are met: 
  18. // 
  19. //   * Redistribution's of source code must retain the above copyright notice, 
  20. //     this list of conditions and the following disclaimer. 
  21. // 
  22. //   * Redistribution's in binary form must reproduce the above copyright notice, 
  23. //     this list of conditions and the following disclaimer in the documentation 
  24. //     and/or other materials provided with the distribution. 
  25. // 
  26. //   * The name of Intel Corporation may not be used to endorse or promote products 
  27. //     derived from this software without specific prior written permission. 
  28. // 
  29. // This software is provided by the copyright holders and contributors "as is" and 
  30. // any express or implied warranties, including, but not limited to, the implied 
  31. // warranties of merchantability and fitness for a particular purpose are disclaimed. 
  32. // In no event shall the Intel Corporation or contributors be liable for any direct, 
  33. // indirect, incidental, special, exemplary, or consequential damages 
  34. // (including, but not limited to, procurement of substitute goods or services; 
  35. // loss of use, data, or profits; or business interruption) however caused 
  36. // and on any theory of liability, whether in contract, strict liability, 
  37. // or tort (including negligence or otherwise) arising in any way out of 
  38. // the use of this software, even if advised of the possibility of such damage. 
  39. // 
  40. //M*/  
  41.   
  42. #include "precomp.hpp"  
  43. #include "gcgraph.hpp"  
  44. #include <limits>  
  45.   
  46. using namespace cv;  
  47.   
  48. /* 
  49. This is implementation of image segmentation algorithm GrabCut described in 
  50. "GrabCut — Interactive Foreground Extraction using Iterated Graph Cuts". 
  51. Carsten Rother, Vladimir Kolmogorov, Andrew Blake. 
  52.  */  
  53.   
  54. /* 
  55.  GMM - Gaussian Mixture Model 
  56. */  
  57. class GMM  
  58. {  
  59. public:  
  60.     static const int componentsCount = 5;  
  61.   
  62.     GMM( Mat& _model );  
  63.     double operator()( const Vec3d color ) const;  
  64.     double operator()( int ci, const Vec3d color ) const;  
  65.     int whichComponent( const Vec3d color ) const;  
  66.   
  67.     void initLearning();  
  68.     void addSample( int ci, const Vec3d color );  
  69.     void endLearning();  
  70.   
  71. private:  
  72.     void calcInverseCovAndDeterm( int ci );  
  73.     Mat model;  
  74.     double* coefs;  
  75.     double* mean;  
  76.     double* cov;  
  77.   
  78.     double inverseCovs[componentsCount][3][3]; //協方差的逆矩陣  
  79.     double covDeterms[componentsCount];  //協方差的行列式  
  80.   
  81.     double sums[componentsCount][3];  
  82.     double prods[componentsCount][3][3];  
  83.     int sampleCounts[componentsCount];  
  84.     int totalSampleCount;  
  85. };  
  86.   
  87. //背景和前景各有一個對應的GMM(混合高斯模型)  
  88. GMM::GMM( Mat& _model )  
  89. {  
  90.     //一個像素的(唯一對應)高斯模型的參數個數或者說一個高斯模型的參數個數  
  91.     //一個像素RGB三個通道值,故3個均值,3*3個協方差,共用一個權值  
  92.     const int modelSize = 3/*mean*/ + 9/*covariance*/ + 1/*component weight*/;  
  93.     if( _model.empty() )  
  94.     {  
  95.         //一個GMM共有componentsCount個高斯模型,一個高斯模型有modelSize個模型參數  
  96.         _model.create( 1, modelSize*componentsCount, CV_64FC1 );  
  97.         _model.setTo(Scalar(0));  
  98.     }  
  99.     else if( (_model.type() != CV_64FC1) || (_model.rows != 1) || (_model.cols != modelSize*componentsCount) )  
  100.         CV_Error( CV_StsBadArg, "_model must have CV_64FC1 type, rows == 1 and cols == 13*componentsCount" );  
  101.   
  102.     model = _model;  
  103.   
  104.     //注意這些模型參數的存儲方式:先排完componentsCount個coefs,再3*componentsCount個mean。  
  105.     //再3*3*componentsCount個cov。  
  106.     coefs = model.ptr<double>(0);  //GMM的每個像素的高斯模型的權值變量起始存儲指針  
  107.     mean = coefs + componentsCount; //均值變量起始存儲指針  
  108.     cov = mean + 3*componentsCount;  //協方差變量起始存儲指針  
  109.   
  110.     forint ci = 0; ci < componentsCount; ci++ )  
  111.         if( coefs[ci] > 0 )  
  112.              //計算GMM中第ci個高斯模型的協方差的逆Inverse和行列式Determinant  
  113.              //爲了後面計算每個像素屬於該高斯模型的概率(也就是數據能量項)  
  114.              calcInverseCovAndDeterm( ci );   
  115. }  
  116.   
  117. //計算一個像素(由color=(B,G,R)三維double型向量來表示)屬於這個GMM混合高斯模型的概率。  
  118. //也就是把這個像素像素屬於componentsCount個高斯模型的概率與對應的權值相乘再相加,  
  119. //具體見論文的公式(10)。結果從res返回。  
  120. //這個相當於計算Gibbs能量的第一個能量項(取負後)。  
  121. double GMM::operator()( const Vec3d color ) const  
  122. {  
  123.     double res = 0;  
  124.     forint ci = 0; ci < componentsCount; ci++ )  
  125.         res += coefs[ci] * (*this)(ci, color );  
  126.     return res;  
  127. }  
  128.   
  129. //計算一個像素(由color=(B,G,R)三維double型向量來表示)屬於第ci個高斯模型的概率。  
  130. //具體過程,即高階的高斯密度模型計算式,具體見論文的公式(10)。結果從res返回  
  131. double GMM::operator()( int ci, const Vec3d color ) const  
  132. {  
  133.     double res = 0;  
  134.     if( coefs[ci] > 0 )  
  135.     {  
  136.         CV_Assert( covDeterms[ci] > std::numeric_limits<double>::epsilon() );  
  137.         Vec3d diff = color;  
  138.         double* m = mean + 3*ci;  
  139.         diff[0] -= m[0]; diff[1] -= m[1]; diff[2] -= m[2];  
  140.         double mult = diff[0]*(diff[0]*inverseCovs[ci][0][0] + diff[1]*inverseCovs[ci][1][0] + diff[2]*inverseCovs[ci][2][0])  
  141.                    + diff[1]*(diff[0]*inverseCovs[ci][0][1] + diff[1]*inverseCovs[ci][1][1] + diff[2]*inverseCovs[ci][2][1])  
  142.                    + diff[2]*(diff[0]*inverseCovs[ci][0][2] + diff[1]*inverseCovs[ci][1][2] + diff[2]*inverseCovs[ci][2][2]);  
  143.         res = 1.0f/sqrt(covDeterms[ci]) * exp(-0.5f*mult);  
  144.     }  
  145.     return res;  
  146. }  
  147.   
  148. //返回這個像素最有可能屬於GMM中的哪個高斯模型(概率最大的那個)  
  149. int GMM::whichComponent( const Vec3d color ) const  
  150. {  
  151.     int k = 0;  
  152.     double max = 0;  
  153.   
  154.     forint ci = 0; ci < componentsCount; ci++ )  
  155.     {  
  156.         double p = (*this)( ci, color );  
  157.         if( p > max )  
  158.         {  
  159.             k = ci;  //找到概率最大的那個,或者說計算結果最大的那個  
  160.             max = p;  
  161.         }  
  162.     }  
  163.     return k;  
  164. }  
  165.   
  166. //GMM參數學習前的初始化,主要是對要求和的變量置零  
  167. void GMM::initLearning()  
  168. {  
  169.     forint ci = 0; ci < componentsCount; ci++)  
  170.     {  
  171.         sums[ci][0] = sums[ci][1] = sums[ci][2] = 0;  
  172.         prods[ci][0][0] = prods[ci][0][1] = prods[ci][0][2] = 0;  
  173.         prods[ci][1][0] = prods[ci][1][1] = prods[ci][1][2] = 0;  
  174.         prods[ci][2][0] = prods[ci][2][1] = prods[ci][2][2] = 0;  
  175.         sampleCounts[ci] = 0;  
  176.     }  
  177.     totalSampleCount = 0;  
  178. }  
  179.   
  180. //增加樣本,即爲前景或者背景GMM的第ci個高斯模型的像素集(這個像素集是來用估  
  181. //計計算這個高斯模型的參數的)增加樣本像素。計算加入color這個像素後,像素集  
  182. //中所有像素的RGB三個通道的和sums(用來計算均值),還有它的prods(用來計算協方差),  
  183. //並且記錄這個像素集的像素個數和總的像素個數(用來計算這個高斯模型的權值)。  
  184. void GMM::addSample( int ci, const Vec3d color )  
  185. {  
  186.     sums[ci][0] += color[0]; sums[ci][1] += color[1]; sums[ci][2] += color[2];  
  187.     prods[ci][0][0] += color[0]*color[0]; prods[ci][0][1] += color[0]*color[1]; prods[ci][0][2] += color[0]*color[2];  
  188.     prods[ci][1][0] += color[1]*color[0]; prods[ci][1][1] += color[1]*color[1]; prods[ci][1][2] += color[1]*color[2];  
  189.     prods[ci][2][0] += color[2]*color[0]; prods[ci][2][1] += color[2]*color[1]; prods[ci][2][2] += color[2]*color[2];  
  190.     sampleCounts[ci]++;  
  191.     totalSampleCount++;  
  192. }  
  193.   
  194. //從圖像數據中學習GMM的參數:每一個高斯分量的權值、均值和協方差矩陣;  
  195. //這裏相當於論文中“Iterative minimisation”的step 2  
  196. void GMM::endLearning()  
  197. {  
  198.     const double variance = 0.01;  
  199.     forint ci = 0; ci < componentsCount; ci++ )  
  200.     {  
  201.         int n = sampleCounts[ci]; //第ci個高斯模型的樣本像素個數  
  202.         if( n == 0 )  
  203.             coefs[ci] = 0;  
  204.         else  
  205.         {  
  206.             //計算第ci個高斯模型的權值係數  
  207.             coefs[ci] = (double)n/totalSampleCount;   
  208.   
  209.             //計算第ci個高斯模型的均值  
  210.             double* m = mean + 3*ci;  
  211.             m[0] = sums[ci][0]/n; m[1] = sums[ci][1]/n; m[2] = sums[ci][2]/n;  
  212.   
  213.             //計算第ci個高斯模型的協方差  
  214.             double* c = cov + 9*ci;  
  215.             c[0] = prods[ci][0][0]/n - m[0]*m[0]; c[1] = prods[ci][0][1]/n - m[0]*m[1]; c[2] = prods[ci][0][2]/n - m[0]*m[2];  
  216.             c[3] = prods[ci][1][0]/n - m[1]*m[0]; c[4] = prods[ci][1][1]/n - m[1]*m[1]; c[5] = prods[ci][1][2]/n - m[1]*m[2];  
  217.             c[6] = prods[ci][2][0]/n - m[2]*m[0]; c[7] = prods[ci][2][1]/n - m[2]*m[1]; c[8] = prods[ci][2][2]/n - m[2]*m[2];  
  218.   
  219.             //計算第ci個高斯模型的協方差的行列式  
  220.             double dtrm = c[0]*(c[4]*c[8]-c[5]*c[7]) - c[1]*(c[3]*c[8]-c[5]*c[6]) + c[2]*(c[3]*c[7]-c[4]*c[6]);  
  221.             if( dtrm <= std::numeric_limits<double>::epsilon() )  
  222.             {  
  223.                 //相當於如果行列式小於等於0,(對角線元素)增加白噪聲,避免其變  
  224.                 //爲退化(降秩)協方差矩陣(不存在逆矩陣,但後面的計算需要計算逆矩陣)。  
  225.                 // Adds the white noise to avoid singular covariance matrix.  
  226.                 c[0] += variance;  
  227.                 c[4] += variance;  
  228.                 c[8] += variance;  
  229.             }  
  230.               
  231.             //計算第ci個高斯模型的協方差的逆Inverse和行列式Determinant  
  232.             calcInverseCovAndDeterm(ci);  
  233.         }  
  234.     }  
  235. }  
  236.   
  237. //計算協方差的逆Inverse和行列式Determinant  
  238. void GMM::calcInverseCovAndDeterm( int ci )  
  239. {  
  240.     if( coefs[ci] > 0 )  
  241.     {  
  242.         //取第ci個高斯模型的協方差的起始指針  
  243.         double *c = cov + 9*ci;  
  244.         double dtrm =  
  245.               covDeterms[ci] = c[0]*(c[4]*c[8]-c[5]*c[7]) - c[1]*(c[3]*c[8]-c[5]*c[6])   
  246.                                 + c[2]*(c[3]*c[7]-c[4]*c[6]);  
  247.   
  248.         //在C++中,每一種內置的數據類型都擁有不同的屬性, 使用<limits>庫可以獲  
  249.         //得這些基本數據類型的數值屬性。因爲浮點算法的截斷,所以使得,當a=2,  
  250.         //b=3時 10*a/b == 20/b不成立。那怎麼辦呢?  
  251.         //這個小正數(epsilon)常量就來了,小正數通常爲可用給定數據類型的  
  252.         //大於1的最小值與1之差來表示。若dtrm結果不大於小正數,那麼它幾乎爲零。  
  253.         //所以下式保證dtrm>0,即行列式的計算正確(協方差對稱正定,故行列式大於0)。  
  254.         CV_Assert( dtrm > std::numeric_limits<double>::epsilon() );  
  255.         //三階方陣的求逆  
  256.         inverseCovs[ci][0][0] =  (c[4]*c[8] - c[5]*c[7]) / dtrm;  
  257.         inverseCovs[ci][1][0] = -(c[3]*c[8] - c[5]*c[6]) / dtrm;  
  258.         inverseCovs[ci][2][0] =  (c[3]*c[7] - c[4]*c[6]) / dtrm;  
  259.         inverseCovs[ci][0][1] = -(c[1]*c[8] - c[2]*c[7]) / dtrm;  
  260.         inverseCovs[ci][1][1] =  (c[0]*c[8] - c[2]*c[6]) / dtrm;  
  261.         inverseCovs[ci][2][1] = -(c[0]*c[7] - c[1]*c[6]) / dtrm;  
  262.         inverseCovs[ci][0][2] =  (c[1]*c[5] - c[2]*c[4]) / dtrm;  
  263.         inverseCovs[ci][1][2] = -(c[0]*c[5] - c[2]*c[3]) / dtrm;  
  264.         inverseCovs[ci][2][2] =  (c[0]*c[4] - c[1]*c[3]) / dtrm;  
  265.     }  
  266. }  
  267.   
  268. //計算beta,也就是Gibbs能量項中的第二項(平滑項)中的指數項的beta,用來調整  
  269. //高或者低對比度時,兩個鄰域像素的差別的影響的,例如在低對比度時,兩個鄰域  
  270. //像素的差別可能就會比較小,這時候需要乘以一個較大的beta來放大這個差別,  
  271. //在高對比度時,則需要縮小本身就比較大的差別。  
  272. //所以我們需要分析整幅圖像的對比度來確定參數beta,具體的見論文公式(5)。  
  273. /* 
  274.   Calculate beta - parameter of GrabCut algorithm. 
  275.   beta = 1/(2*avg(sqr(||color[i] - color[j]||))) 
  276. */  
  277. static double calcBeta( const Mat& img )  
  278. {  
  279.     double beta = 0;  
  280.     forint y = 0; y < img.rows; y++ )  
  281.     {  
  282.         forint x = 0; x < img.cols; x++ )  
  283.         {  
  284.             //計算四個方向鄰域兩像素的差別,也就是歐式距離或者說二階範數  
  285.             //(當所有像素都算完後,就相當於計算八鄰域的像素差了)  
  286.             Vec3d color = img.at<Vec3b>(y,x);  
  287.             if( x>0 ) // left  >0的判斷是爲了避免在圖像邊界的時候還計算,導致越界  
  288.             {  
  289.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y,x-1);  
  290.                 beta += diff.dot(diff);  //矩陣的點乘,也就是各個元素平方的和  
  291.             }  
  292.             if( y>0 && x>0 ) // upleft  
  293.             {  
  294.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x-1);  
  295.                 beta += diff.dot(diff);  
  296.             }  
  297.             if( y>0 ) // up  
  298.             {  
  299.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x);  
  300.                 beta += diff.dot(diff);  
  301.             }  
  302.             if( y>0 && x<img.cols-1) // upright  
  303.             {  
  304.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x+1);  
  305.                 beta += diff.dot(diff);  
  306.             }  
  307.         }  
  308.     }  
  309.     if( beta <= std::numeric_limits<double>::epsilon() )  
  310.         beta = 0;  
  311.     else  
  312.         beta = 1.f / (2 * beta/(4*img.cols*img.rows - 3*img.cols - 3*img.rows + 2) ); //論文公式(5)  
  313.   
  314.     return beta;  
  315. }  
  316.   
  317. //計算圖每個非端點頂點(也就是每個像素作爲圖的一個頂點,不包括源點s和匯點t)與鄰域頂點  
  318. //的邊的權值。由於是無向圖,我們計算的是八鄰域,那麼對於一個頂點,我們計算四個方向就行,  
  319. //在其他的頂點計算的時候,會把剩餘那四個方向的權值計算出來。這樣整個圖算完後,每個頂點  
  320. //與八鄰域的頂點的邊的權值就都計算出來了。  
  321. //這個相當於計算Gibbs能量的第二個能量項(平滑項),具體見論文中公式(4)  
  322. /* 
  323.   Calculate weights of noterminal vertices of graph. 
  324.   beta and gamma - parameters of GrabCut algorithm. 
  325.  */  
  326. static void calcNWeights( const Mat& img, Mat& leftW, Mat& upleftW, Mat& upW,   
  327.                             Mat& uprightW, double beta, double gamma )  
  328. {  
  329.     //gammaDivSqrt2相當於公式(4)中的gamma * dis(i,j)^(-1),那麼可以知道,  
  330.     //當i和j是垂直或者水平關係時,dis(i,j)=1,當是對角關係時,dis(i,j)=sqrt(2.0f)。  
  331.     //具體計算時,看下面就明白了  
  332.     const double gammaDivSqrt2 = gamma / std::sqrt(2.0f);  
  333.     //每個方向的邊的權值通過一個和圖大小相等的Mat來保存  
  334.     leftW.create( img.rows, img.cols, CV_64FC1 );  
  335.     upleftW.create( img.rows, img.cols, CV_64FC1 );  
  336.     upW.create( img.rows, img.cols, CV_64FC1 );  
  337.     uprightW.create( img.rows, img.cols, CV_64FC1 );  
  338.     forint y = 0; y < img.rows; y++ )  
  339.     {  
  340.         forint x = 0; x < img.cols; x++ )  
  341.         {  
  342.             Vec3d color = img.at<Vec3b>(y,x);  
  343.             if( x-1>=0 ) // left  //避免圖的邊界  
  344.             {  
  345.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y,x-1);  
  346.                 leftW.at<double>(y,x) = gamma * exp(-beta*diff.dot(diff));  
  347.             }  
  348.             else  
  349.                 leftW.at<double>(y,x) = 0;  
  350.             if( x-1>=0 && y-1>=0 ) // upleft  
  351.             {  
  352.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x-1);  
  353.                 upleftW.at<double>(y,x) = gammaDivSqrt2 * exp(-beta*diff.dot(diff));  
  354.             }  
  355.             else  
  356.                 upleftW.at<double>(y,x) = 0;  
  357.             if( y-1>=0 ) // up  
  358.             {  
  359.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x);  
  360.                 upW.at<double>(y,x) = gamma * exp(-beta*diff.dot(diff));  
  361.             }  
  362.             else  
  363.                 upW.at<double>(y,x) = 0;  
  364.             if( x+1<img.cols && y-1>=0 ) // upright  
  365.             {  
  366.                 Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x+1);  
  367.                 uprightW.at<double>(y,x) = gammaDivSqrt2 * exp(-beta*diff.dot(diff));  
  368.             }  
  369.             else  
  370.                 uprightW.at<double>(y,x) = 0;  
  371.         }  
  372.     }  
  373. }  
  374.   
  375. //檢查mask的正確性。mask爲通過用戶交互或者程序設定的,它是和圖像大小一樣的單通道灰度圖,  
  376. //每個像素只能取GC_BGD or GC_FGD or GC_PR_BGD or GC_PR_FGD 四種枚舉值,分別表示該像素  
  377. //(用戶或者程序指定)屬於背景、前景、可能爲背景或者可能爲前景像素。具體的參考:  
  378. //ICCV2001“Interactive Graph Cuts for Optimal Boundary & Region Segmentation of Objects in N-D Images”  
  379. //Yuri Y. Boykov Marie-Pierre Jolly   
  380. /* 
  381.   Check size, type and element values of mask matrix. 
  382.  */  
  383. static void checkMask( const Mat& img, const Mat& mask )  
  384. {  
  385.     if( mask.empty() )  
  386.         CV_Error( CV_StsBadArg, "mask is empty" );  
  387.     if( mask.type() != CV_8UC1 )  
  388.         CV_Error( CV_StsBadArg, "mask must have CV_8UC1 type" );  
  389.     if( mask.cols != img.cols || mask.rows != img.rows )  
  390.         CV_Error( CV_StsBadArg, "mask must have as many rows and cols as img" );  
  391.     forint y = 0; y < mask.rows; y++ )  
  392.     {  
  393.         forint x = 0; x < mask.cols; x++ )  
  394.         {  
  395.             uchar val = mask.at<uchar>(y,x);  
  396.             if( val!=GC_BGD && val!=GC_FGD && val!=GC_PR_BGD && val!=GC_PR_FGD )  
  397.                 CV_Error( CV_StsBadArg, "mask element value must be equel"  
  398.                     "GC_BGD or GC_FGD or GC_PR_BGD or GC_PR_FGD" );  
  399.         }  
  400.     }  
  401. }  
  402.   
  403. //通過用戶框選目標rect來創建mask,rect外的全部作爲背景,設置爲GC_BGD,  
  404. //rect內的設置爲 GC_PR_FGD(可能爲前景)  
  405. /* 
  406.   Initialize mask using rectangular. 
  407. */  
  408. static void initMaskWithRect( Mat& mask, Size imgSize, Rect rect )  
  409. {  
  410.     mask.create( imgSize, CV_8UC1 );  
  411.     mask.setTo( GC_BGD );  
  412.   
  413.     rect.x = max(0, rect.x);  
  414.     rect.y = max(0, rect.y);  
  415.     rect.width = min(rect.width, imgSize.width-rect.x);  
  416.     rect.height = min(rect.height, imgSize.height-rect.y);  
  417.   
  418.     (mask(rect)).setTo( Scalar(GC_PR_FGD) );  
  419. }  
  420.   
  421. //通過k-means算法來初始化背景GMM和前景GMM模型  
  422. /* 
  423.   Initialize GMM background and foreground models using kmeans algorithm. 
  424. */  
  425. static void initGMMs( const Mat& img, const Mat& mask, GMM& bgdGMM, GMM& fgdGMM )  
  426. {  
  427.     const int kMeansItCount = 10;  //迭代次數  
  428.     const int kMeansType = KMEANS_PP_CENTERS; //Use kmeans++ center initialization by Arthur and Vassilvitskii  
  429.   
  430.     Mat bgdLabels, fgdLabels; //記錄背景和前景的像素樣本集中每個像素對應GMM的哪個高斯模型,論文中的kn  
  431.     vector<Vec3f> bgdSamples, fgdSamples; //背景和前景的像素樣本集  
  432.     Point p;  
  433.     for( p.y = 0; p.y < img.rows; p.y++ )  
  434.     {  
  435.         for( p.x = 0; p.x < img.cols; p.x++ )  
  436.         {  
  437.             //mask中標記爲GC_BGD和GC_PR_BGD的像素都作爲背景的樣本像素  
  438.             if( mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD )  
  439.                 bgdSamples.push_back( (Vec3f)img.at<Vec3b>(p) );  
  440.             else // GC_FGD | GC_PR_FGD  
  441.                 fgdSamples.push_back( (Vec3f)img.at<Vec3b>(p) );  
  442.         }  
  443.     }  
  444.     CV_Assert( !bgdSamples.empty() && !fgdSamples.empty() );  
  445.       
  446.     //kmeans中參數_bgdSamples爲:每行一個樣本  
  447.     //kmeans的輸出爲bgdLabels,裏面保存的是輸入樣本集中每一個樣本對應的類標籤(樣本聚爲componentsCount類後)  
  448.     Mat _bgdSamples( (int)bgdSamples.size(), 3, CV_32FC1, &bgdSamples[0][0] );  
  449.     kmeans( _bgdSamples, GMM::componentsCount, bgdLabels,  
  450.             TermCriteria( CV_TERMCRIT_ITER, kMeansItCount, 0.0), 0, kMeansType );  
  451.     Mat _fgdSamples( (int)fgdSamples.size(), 3, CV_32FC1, &fgdSamples[0][0] );  
  452.     kmeans( _fgdSamples, GMM::componentsCount, fgdLabels,  
  453.             TermCriteria( CV_TERMCRIT_ITER, kMeansItCount, 0.0), 0, kMeansType );  
  454.   
  455.     //經過上面的步驟後,每個像素所屬的高斯模型就確定的了,那麼就可以估計GMM中每個高斯模型的參數了。  
  456.     bgdGMM.initLearning();  
  457.     forint i = 0; i < (int)bgdSamples.size(); i++ )  
  458.         bgdGMM.addSample( bgdLabels.at<int>(i,0), bgdSamples[i] );  
  459.     bgdGMM.endLearning();  
  460.   
  461.     fgdGMM.initLearning();  
  462.     forint i = 0; i < (int)fgdSamples.size(); i++ )  
  463.         fgdGMM.addSample( fgdLabels.at<int>(i,0), fgdSamples[i] );  
  464.     fgdGMM.endLearning();  
  465. }  
  466.   
  467. //論文中:迭代最小化算法step 1:爲每個像素分配GMM中所屬的高斯模型,kn保存在Mat compIdxs中  
  468. /* 
  469.   Assign GMMs components for each pixel. 
  470. */  
  471. static void assignGMMsComponents( const Mat& img, const Mat& mask, const GMM& bgdGMM,   
  472.                                     const GMM& fgdGMM, Mat& compIdxs )  
  473. {  
  474.     Point p;  
  475.     for( p.y = 0; p.y < img.rows; p.y++ )  
  476.     {  
  477.         for( p.x = 0; p.x < img.cols; p.x++ )  
  478.         {  
  479.             Vec3d color = img.at<Vec3b>(p);  
  480.             //通過mask來判斷該像素屬於背景像素還是前景像素,再判斷它屬於前景或者背景GMM中的哪個高斯分量  
  481.             compIdxs.at<int>(p) = mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD ?  
  482.                 bgdGMM.whichComponent(color) : fgdGMM.whichComponent(color);  
  483.         }  
  484.     }  
  485. }  
  486.   
  487. //論文中:迭代最小化算法step 2:從每個高斯模型的像素樣本集中學習每個高斯模型的參數  
  488. /* 
  489.   Learn GMMs parameters. 
  490. */  
  491. static void learnGMMs( const Mat& img, const Mat& mask, const Mat& compIdxs, GMM& bgdGMM, GMM& fgdGMM )  
  492. {  
  493.     bgdGMM.initLearning();  
  494.     fgdGMM.initLearning();  
  495.     Point p;  
  496.     forint ci = 0; ci < GMM::componentsCount; ci++ )  
  497.     {  
  498.         for( p.y = 0; p.y < img.rows; p.y++ )  
  499.         {  
  500.             for( p.x = 0; p.x < img.cols; p.x++ )  
  501.             {  
  502.                 if( compIdxs.at<int>(p) == ci )  
  503.                 {  
  504.                     if( mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD )  
  505.                         bgdGMM.addSample( ci, img.at<Vec3b>(p) );  
  506.                     else  
  507.                         fgdGMM.addSample( ci, img.at<Vec3b>(p) );  
  508.                 }  
  509.             }  
  510.         }  
  511.     }  
  512.     bgdGMM.endLearning();  
  513.     fgdGMM.endLearning();  
  514. }  
  515.   
  516. //通過計算得到的能量項構建圖,圖的頂點爲像素點,圖的邊由兩部分構成,  
  517. //一類邊是:每個頂點與Sink匯點t(代表背景)和源點Source(代表前景)連接的邊,  
  518. //這類邊的權值通過Gibbs能量項的第一項能量項來表示。  
  519. //另一類邊是:每個頂點與其鄰域頂點連接的邊,這類邊的權值通過Gibbs能量項的第二項能量項來表示。  
  520. /* 
  521.   Construct GCGraph 
  522. */  
  523. static void constructGCGraph( const Mat& img, const Mat& mask, const GMM& bgdGMM, const GMM& fgdGMM, double lambda,  
  524.                        const Mat& leftW, const Mat& upleftW, const Mat& upW, const Mat& uprightW,  
  525.                        GCGraph<double>& graph )  
  526. {  
  527.     int vtxCount = img.cols*img.rows;  //頂點數,每一個像素是一個頂點  
  528.     int edgeCount = 2*(4*vtxCount - 3*(img.cols + img.rows) + 2);  //邊數,需要考慮圖邊界的邊的缺失  
  529.     //通過頂點數和邊數創建圖。這些類型聲明和函數定義請參考gcgraph.hpp  
  530.     graph.create(vtxCount, edgeCount);  
  531.     Point p;  
  532.     for( p.y = 0; p.y < img.rows; p.y++ )  
  533.     {  
  534.         for( p.x = 0; p.x < img.cols; p.x++)  
  535.         {  
  536.             // add node  
  537.             int vtxIdx = graph.addVtx();  //返回這個頂點在圖中的索引  
  538.             Vec3b color = img.at<Vec3b>(p);  
  539.   
  540.             // set t-weights              
  541.             //計算每個頂點與Sink匯點t(代表背景)和源點Source(代表前景)連接的權值。  
  542.             //也即計算Gibbs能量(每一個像素點作爲背景像素或者前景像素)的第一個能量項  
  543.             double fromSource, toSink;  
  544.             if( mask.at<uchar>(p) == GC_PR_BGD || mask.at<uchar>(p) == GC_PR_FGD )  
  545.             {  
  546.                 //對每一個像素計算其作爲背景像素或者前景像素的第一個能量項,作爲分別與t和s點的連接權值  
  547.                 fromSource = -log( bgdGMM(color) );  
  548.                 toSink = -log( fgdGMM(color) );  
  549.             }  
  550.             else if( mask.at<uchar>(p) == GC_BGD )  
  551.             {  
  552.                 //對於確定爲背景的像素點,它與Source點(前景)的連接爲0,與Sink點的連接爲lambda  
  553.                 fromSource = 0;  
  554.                 toSink = lambda;  
  555.             }  
  556.             else // GC_FGD  
  557.             {  
  558.                 fromSource = lambda;  
  559.                 toSink = 0;  
  560.             }  
  561.             //設置該頂點vtxIdx分別與Source點和Sink點的連接權值  
  562.             graph.addTermWeights( vtxIdx, fromSource, toSink );  
  563.   
  564.             // set n-weights  n-links  
  565.             //計算兩個鄰域頂點之間連接的權值。  
  566.             //也即計算Gibbs能量的第二個能量項(平滑項)  
  567.             if( p.x>0 )  
  568.             {  
  569.                 double w = leftW.at<double>(p);  
  570.                 graph.addEdges( vtxIdx, vtxIdx-1, w, w );  
  571.             }  
  572.             if( p.x>0 && p.y>0 )  
  573.             {  
  574.                 double w = upleftW.at<double>(p);  
  575.                 graph.addEdges( vtxIdx, vtxIdx-img.cols-1, w, w );  
  576.             }  
  577.             if( p.y>0 )  
  578.             {  
  579.                 double w = upW.at<double>(p);  
  580.                 graph.addEdges( vtxIdx, vtxIdx-img.cols, w, w );  
  581.             }  
  582.             if( p.x<img.cols-1 && p.y>0 )  
  583.             {  
  584.                 double w = uprightW.at<double>(p);  
  585.                 graph.addEdges( vtxIdx, vtxIdx-img.cols+1, w, w );  
  586.             }  
  587.         }  
  588.     }  
  589. }  
  590.   
  591. //論文中:迭代最小化算法step 3:分割估計:最小割或者最大流算法  
  592. /* 
  593.   Estimate segmentation using MaxFlow algorithm 
  594. */  
  595. static void estimateSegmentation( GCGraph<double>& graph, Mat& mask )  
  596. {  
  597.     //通過最大流算法確定圖的最小割,也即完成圖像的分割  
  598.     graph.maxFlow();  
  599.     Point p;  
  600.     for( p.y = 0; p.y < mask.rows; p.y++ )  
  601.     {  
  602.         for( p.x = 0; p.x < mask.cols; p.x++ )  
  603.         {  
  604.             //通過圖分割的結果來更新mask,即最後的圖像分割結果。注意的是,永遠都  
  605.             //不會更新用戶指定爲背景或者前景的像素  
  606.             if( mask.at<uchar>(p) == GC_PR_BGD || mask.at<uchar>(p) == GC_PR_FGD )  
  607.             {  
  608.                 if( graph.inSourceSegment( p.y*mask.cols+p.x /*vertex index*/ ) )  
  609.                     mask.at<uchar>(p) = GC_PR_FGD;  
  610.                 else  
  611.                     mask.at<uchar>(p) = GC_PR_BGD;  
  612.             }  
  613.         }  
  614.     }  
  615. }  
  616.   
  617. //最後的成果:提供給外界使用的偉大的API:grabCut   
  618. /* 
  619. ****參數說明: 
  620.     img——待分割的源圖像,必須是8位3通道(CV_8UC3)圖像,在處理的過程中不會被修改; 
  621.     mask——掩碼圖像,如果使用掩碼進行初始化,那麼mask保存初始化掩碼信息;在執行分割 
  622.         的時候,也可以將用戶交互所設定的前景與背景保存到mask中,然後再傳入grabCut函 
  623.         數;在處理結束之後,mask中會保存結果。mask只能取以下四種值: 
  624.         GCD_BGD(=0),背景; 
  625.         GCD_FGD(=1),前景; 
  626.         GCD_PR_BGD(=2),可能的背景; 
  627.         GCD_PR_FGD(=3),可能的前景。 
  628.         如果沒有手工標記GCD_BGD或者GCD_FGD,那麼結果只會有GCD_PR_BGD或GCD_PR_FGD; 
  629.     rect——用於限定需要進行分割的圖像範圍,只有該矩形窗口內的圖像部分才被處理; 
  630.     bgdModel——背景模型,如果爲null,函數內部會自動創建一個bgdModel;bgdModel必須是 
  631.         單通道浮點型(CV_32FC1)圖像,且行數只能爲1,列數只能爲13x5; 
  632.     fgdModel——前景模型,如果爲null,函數內部會自動創建一個fgdModel;fgdModel必須是 
  633.         單通道浮點型(CV_32FC1)圖像,且行數只能爲1,列數只能爲13x5; 
  634.     iterCount——迭代次數,必須大於0; 
  635.     mode——用於指示grabCut函數進行什麼操作,可選的值有: 
  636.         GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut; 
  637.         GC_INIT_WITH_MASK(=1),用掩碼圖像初始化GrabCut; 
  638.         GC_EVAL(=2),執行分割。 
  639. */  
  640. void cv::grabCut( InputArray _img, InputOutputArray _mask, Rect rect,  
  641.                   InputOutputArray _bgdModel, InputOutputArray _fgdModel,  
  642.                   int iterCount, int mode )  
  643. {  
  644.     Mat img = _img.getMat();  
  645.     Mat& mask = _mask.getMatRef();  
  646.     Mat& bgdModel = _bgdModel.getMatRef();  
  647.     Mat& fgdModel = _fgdModel.getMatRef();  
  648.   
  649.     if( img.empty() )  
  650.         CV_Error( CV_StsBadArg, "image is empty" );  
  651.     if( img.type() != CV_8UC3 )  
  652.         CV_Error( CV_StsBadArg, "image mush have CV_8UC3 type" );  
  653.   
  654.     GMM bgdGMM( bgdModel ), fgdGMM( fgdModel );  
  655.     Mat compIdxs( img.size(), CV_32SC1 );  
  656.   
  657.     if( mode == GC_INIT_WITH_RECT || mode == GC_INIT_WITH_MASK )  
  658.     {  
  659.         if( mode == GC_INIT_WITH_RECT )  
  660.             initMaskWithRect( mask, img.size(), rect );  
  661.         else // flag == GC_INIT_WITH_MASK  
  662.             checkMask( img, mask );  
  663.         initGMMs( img, mask, bgdGMM, fgdGMM );  
  664.     }  
  665.   
  666.     if( iterCount <= 0)  
  667.         return;  
  668.   
  669.     if( mode == GC_EVAL )  
  670.         checkMask( img, mask );  
  671.   
  672.     const double gamma = 50;  
  673.     const double lambda = 9*gamma;  
  674.     const double beta = calcBeta( img );  
  675.   
  676.     Mat leftW, upleftW, upW, uprightW;  
  677.     calcNWeights( img, leftW, upleftW, upW, uprightW, beta, gamma );  
  678.   
  679.     forint i = 0; i < iterCount; i++ )  
  680.     {  
  681.         GCGraph<double> graph;  
  682.         assignGMMsComponents( img, mask, bgdGMM, fgdGMM, compIdxs );  
  683.         learnGMMs( img, mask, compIdxs, bgdGMM, fgdGMM );  
  684.         constructGCGraph(img, mask, bgdGMM, fgdGMM, lambda, leftW, upleftW, upW, uprightW, graph );  
  685.         estimateSegmentation( graph, mask );  
  686.     }  
  687. }  
發佈了270 篇原創文章 · 獲贊 208 · 訪問量 136萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章