[圖像分割] OpenCV 的 GrabCut 函數使用和源碼解讀

轉自 zouxy09node

GrabCut 原理參考這裏,如下爲 GrabCut 源碼:算法

——看別人寫的好的代碼也很享受,乾淨利落,有些處理的細節也學習一下。express

/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"
#include "gcgraph.hpp"
#include <limits>

using namespace cv;

/*
This is implementation of image segmentation algorithm GrabCut described in
"GrabCut — Interactive Foreground Extraction using Iterated Graph Cuts".
Carsten Rother, Vladimir Kolmogorov, Andrew Blake.
 */

/*
 GMM - Gaussian Mixture Model
*/
class GMM
{
public:
    static const int componentsCount = 5;

    GMM( Mat& _model );
    double operator()( const Vec3d color ) const;
    double operator()( int ci, const Vec3d color ) const;
    int whichComponent( const Vec3d color ) const;

    void initLearning();
    void addSample( int ci, const Vec3d color );
    void endLearning();

private:
    void calcInverseCovAndDeterm( int ci );
    Mat model;
    double* coefs;
    double* mean;
    double* cov;

    double inverseCovs[componentsCount][3][3]; //協方差的逆矩陣
    double covDeterms[componentsCount];  //協方差的行列式

    double sums[componentsCount][3];
    double prods[componentsCount][3][3];
    int sampleCounts[componentsCount];
    int totalSampleCount;
};

//背景和前景各有一個對應的GMM(混合高斯模型)
GMM::GMM( Mat& _model )
{
	//一個像素的(惟一對應)高斯模型的參數個數或者說一個高斯模型的參數個數
	//一個像素RGB三個通道值,故3個均值,3*3個協方差,共用一個權值
    const int modelSize = 3/*mean*/ + 9/*covariance*/ + 1/*component weight*/;
    if( _model.empty() )
    {
		//一個GMM共有componentsCount個高斯模型,一個高斯模型有modelSize個模型參數
        _model.create( 1, modelSize*componentsCount, CV_64FC1 );
        _model.setTo(Scalar(0));
    }
    else if( (_model.type() != CV_64FC1) || (_model.rows != 1) || (_model.cols != modelSize*componentsCount) )
        CV_Error( CV_StsBadArg, "_model must have CV_64FC1 type, rows == 1 and cols == 13*componentsCount" );

    model = _model;

	//注意這些模型參數的存儲方式:先排完componentsCount個coefs,再3*componentsCount個mean。
	//再3*3*componentsCount個cov。
    coefs = model.ptr<double>(0);  //GMM的每一個像素的高斯模型的權值變量起始存儲指針
    mean = coefs + componentsCount; //均值變量起始存儲指針
    cov = mean + 3*componentsCount;  //協方差變量起始存儲指針

    for( int ci = 0; ci < componentsCount; ci++ )
        if( coefs[ci] > 0 )
			 //計算GMM中第ci個高斯模型的協方差的逆Inverse和行列式Determinant
			 //爲了後面計算每一個像素屬於該高斯模型的機率(也就是數據能量項)
             calcInverseCovAndDeterm( ci ); 
}

//計算一個像素(由color=(B,G,R)三維double型向量來表示)屬於這個GMM混合高斯模型的機率。
//也就是把這個像素像素屬於componentsCount個高斯模型的機率與對應的權值相乘再相加,
//具體見論文的公式(10)。結果從res返回。
//這個至關於計算Gibbs能量的第一個能量項(取負後)。
double GMM::operator()( const Vec3d color ) const
{
    double res = 0;
    for( int ci = 0; ci < componentsCount; ci++ )
        res += coefs[ci] * (*this)(ci, color );
    return res;
}

//計算一個像素(由color=(B,G,R)三維double型向量來表示)屬於第ci個高斯模型的機率。
//具體過程,即高階的高斯密度模型計算式,具體見論文的公式(10)。結果從res返回
double GMM::operator()( int ci, const Vec3d color ) const
{
    double res = 0;
    if( coefs[ci] > 0 )
    {
        CV_Assert( covDeterms[ci] > std::numeric_limits<double>::epsilon() );
        Vec3d diff = color;
        double* m = mean + 3*ci;
        diff[0] -= m[0]; diff[1] -= m[1]; diff[2] -= m[2];
        double mult = diff[0]*(diff[0]*inverseCovs[ci][0][0] + diff[1]*inverseCovs[ci][1][0] + diff[2]*inverseCovs[ci][2][0])
                   + diff[1]*(diff[0]*inverseCovs[ci][0][1] + diff[1]*inverseCovs[ci][1][1] + diff[2]*inverseCovs[ci][2][1])
                   + diff[2]*(diff[0]*inverseCovs[ci][0][2] + diff[1]*inverseCovs[ci][1][2] + diff[2]*inverseCovs[ci][2][2]);
        res = 1.0f/sqrt(covDeterms[ci]) * exp(-0.5f*mult);
    }
    return res;
}

//返回這個像素最有可能屬於GMM中的哪一個高斯模型(機率最大的那個)
int GMM::whichComponent( const Vec3d color ) const
{
    int k = 0;
    double max = 0;

    for( int ci = 0; ci < componentsCount; ci++ )
    {
        double p = (*this)( ci, color );
        if( p > max )
        {
            k = ci;  //找到機率最大的那個,或者說計算結果最大的那個
            max = p;
        }
    }
    return k;
}

//GMM參數學習前的初始化,主要是對要求和的變量置零
void GMM::initLearning()
{
    for( int ci = 0; ci < componentsCount; ci++)
    {
        sums[ci][0] = sums[ci][1] = sums[ci][2] = 0;
        prods[ci][0][0] = prods[ci][0][1] = prods[ci][0][2] = 0;
        prods[ci][1][0] = prods[ci][1][1] = prods[ci][1][2] = 0;
        prods[ci][2][0] = prods[ci][2][1] = prods[ci][2][2] = 0;
        sampleCounts[ci] = 0;
    }
    totalSampleCount = 0;
}

//增長樣本,即爲前景或者背景GMM的第ci個高斯模型的像素集(這個像素集是來用估
//計計算這個高斯模型的參數的)增長樣本像素。計算加入color這個像素後,像素集
//中全部像素的RGB三個通道的和sums(用來計算均值),還有它的prods(用來計算協方差),
//而且記錄這個像素集的像素個數和總的像素個數(用來計算這個高斯模型的權值)。
void GMM::addSample( int ci, const Vec3d color )
{
    sums[ci][0] += color[0]; sums[ci][1] += color[1]; sums[ci][2] += color[2];
    prods[ci][0][0] += color[0]*color[0]; prods[ci][0][1] += color[0]*color[1]; prods[ci][0][2] += color[0]*color[2];
    prods[ci][1][0] += color[1]*color[0]; prods[ci][1][1] += color[1]*color[1]; prods[ci][1][2] += color[1]*color[2];
    prods[ci][2][0] += color[2]*color[0]; prods[ci][2][1] += color[2]*color[1]; prods[ci][2][2] += color[2]*color[2];
    sampleCounts[ci]++;
    totalSampleCount++;
}

//從圖像數據中學習GMM的參數:每個高斯份量的權值、均值和協方差矩陣;
//這裏至關於論文中「Iterative minimisation」的step 2
void GMM::endLearning()
{
    const double variance = 0.01;
    for( int ci = 0; ci < componentsCount; ci++ )
    {
        int n = sampleCounts[ci]; //第ci個高斯模型的樣本像素個數
        if( n == 0 )
            coefs[ci] = 0;
        else
        {
            //計算第ci個高斯模型的權值係數
			coefs[ci] = (double)n/totalSampleCount; 

            //計算第ci個高斯模型的均值
			double* m = mean + 3*ci;
            m[0] = sums[ci][0]/n; m[1] = sums[ci][1]/n; m[2] = sums[ci][2]/n;

            //計算第ci個高斯模型的協方差
			double* c = cov + 9*ci;
            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];
            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];
            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];

            //計算第ci個高斯模型的協方差的行列式
			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]);
            if( dtrm <= std::numeric_limits<double>::epsilon() )
            {
                //至關於若是行列式小於等於0,(對角線元素)增長白噪聲,避免其變
				//爲退化(降秩)協方差矩陣(不存在逆矩陣,但後面的計算須要計算逆矩陣)。
				// Adds the white noise to avoid singular covariance matrix.
                c[0] += variance;
                c[4] += variance;
                c[8] += variance;
            }
			
			//計算第ci個高斯模型的協方差的逆Inverse和行列式Determinant
            calcInverseCovAndDeterm(ci);
        }
    }
}

//計算協方差的逆Inverse和行列式Determinant
void GMM::calcInverseCovAndDeterm( int ci )
{
    if( coefs[ci] > 0 )
    {
		//取第ci個高斯模型的協方差的起始指針
        double *c = cov + 9*ci;
        double dtrm =
              covDeterms[ci] = 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]);

        //在C++中,每一種內置的數據類型都擁有不一樣的屬性, 使用<limits>庫能夠獲
		//得這些基本數據類型的數值屬性。由於浮點算法的截斷,因此使得,當a=2,
		//b=3時 10*a/b == 20/b不成立。那怎麼辦呢?
		//這個小正數(epsilon)常量就來了,小正數一般爲可用給定數據類型的
		//大於1的最小值與1之差來表示。若dtrm結果不大於小正數,那麼它幾乎爲零。
		//因此下式保證dtrm>0,即行列式的計算正確(協方差對稱正定,故行列式大於0)。
		CV_Assert( dtrm > std::numeric_limits<double>::epsilon() );
		//三階方陣的求逆
        inverseCovs[ci][0][0] =  (c[4]*c[8] - c[5]*c[7]) / dtrm;
        inverseCovs[ci][1][0] = -(c[3]*c[8] - c[5]*c[6]) / dtrm;
        inverseCovs[ci][2][0] =  (c[3]*c[7] - c[4]*c[6]) / dtrm;
        inverseCovs[ci][0][1] = -(c[1]*c[8] - c[2]*c[7]) / dtrm;
        inverseCovs[ci][1][1] =  (c[0]*c[8] - c[2]*c[6]) / dtrm;
        inverseCovs[ci][2][1] = -(c[0]*c[7] - c[1]*c[6]) / dtrm;
        inverseCovs[ci][0][2] =  (c[1]*c[5] - c[2]*c[4]) / dtrm;
        inverseCovs[ci][1][2] = -(c[0]*c[5] - c[2]*c[3]) / dtrm;
        inverseCovs[ci][2][2] =  (c[0]*c[4] - c[1]*c[3]) / dtrm;
    }
}

//計算beta,也就是Gibbs能量項中的第二項(平滑項)中的指數項的beta,用來調整
//高或者低對比度時,兩個鄰域像素的差異的影響的,例如在低對比度時,兩個鄰域
//像素的差異可能就會比較小,這時候須要乘以一個較大的beta來放大這個差異,
//在高對比度時,則須要縮小自己就比較大的差異。
//因此咱們須要分析整幅圖像的對比度來肯定參數beta,具體的見論文公式(5)。
/*
  Calculate beta - parameter of GrabCut algorithm.
  beta = 1/(2*avg(sqr(||color[i] - color[j]||)))
*/
static double calcBeta( const Mat& img )
{
    double beta = 0;
    for( int y = 0; y < img.rows; y++ )
    {
        for( int x = 0; x < img.cols; x++ )
        {
			//計算四個方向鄰域兩像素的差異,也就是歐式距離或者說二階範數
			//(當全部像素都算完後,就至關於計算八鄰域的像素差了)
            Vec3d color = img.at<Vec3b>(y,x);
            if( x>0 ) // left  >0的判斷是爲了不在圖像邊界的時候還計算,致使越界
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y,x-1);
                beta += diff.dot(diff);  //矩陣的點乘,也就是各個元素平方的和
            }
            if( y>0 && x>0 ) // upleft
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x-1);
                beta += diff.dot(diff);
            }
            if( y>0 ) // up
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x);
                beta += diff.dot(diff);
            }
            if( y>0 && x<img.cols-1) // upright
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x+1);
                beta += diff.dot(diff);
            }
        }
    }
    if( beta <= std::numeric_limits<double>::epsilon() )
        beta = 0;
    else
        beta = 1.f / (2 * beta/(4*img.cols*img.rows - 3*img.cols - 3*img.rows + 2) ); //論文公式(5)

    return beta;
}

//計算圖每一個非端點頂點(也就是每一個像素做爲圖的一個頂點,不包括源點s和匯點t)與鄰域頂點
//的邊的權值。因爲是無向圖,咱們計算的是八鄰域,那麼對於一個頂點,咱們計算四個方向就行,
//在其餘的頂點計算的時候,會把剩餘那四個方向的權值計算出來。這樣整個圖算完後,每一個頂點
//與八鄰域的頂點的邊的權值就都計算出來了。
//這個至關於計算Gibbs能量的第二個能量項(平滑項),具體見論文中公式(4)
/*
  Calculate weights of noterminal vertices of graph.
  beta and gamma - parameters of GrabCut algorithm.
 */
static void calcNWeights( const Mat& img, Mat& leftW, Mat& upleftW, Mat& upW, 
							Mat& uprightW, double beta, double gamma )
{
    //gammaDivSqrt2至關於公式(4)中的gamma * dis(i,j)^(-1),那麼能夠知道,
	//當i和j是垂直或者水平關係時,dis(i,j)=1,當是對角關係時,dis(i,j)=sqrt(2.0f)。
	//具體計算時,看下面就明白了
	const double gammaDivSqrt2 = gamma / std::sqrt(2.0f);
	//每一個方向的邊的權值經過一個和圖大小相等的Mat來保存
    leftW.create( img.rows, img.cols, CV_64FC1 );
    upleftW.create( img.rows, img.cols, CV_64FC1 );
    upW.create( img.rows, img.cols, CV_64FC1 );
    uprightW.create( img.rows, img.cols, CV_64FC1 );
    for( int y = 0; y < img.rows; y++ )
    {
        for( int x = 0; x < img.cols; x++ )
        {
            Vec3d color = img.at<Vec3b>(y,x);
            if( x-1>=0 ) // left  //避免圖的邊界
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y,x-1);
                leftW.at<double>(y,x) = gamma * exp(-beta*diff.dot(diff));
            }
            else
                leftW.at<double>(y,x) = 0;
            if( x-1>=0 && y-1>=0 ) // upleft
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x-1);
                upleftW.at<double>(y,x) = gammaDivSqrt2 * exp(-beta*diff.dot(diff));
            }
            else
                upleftW.at<double>(y,x) = 0;
            if( y-1>=0 ) // up
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x);
                upW.at<double>(y,x) = gamma * exp(-beta*diff.dot(diff));
            }
            else
                upW.at<double>(y,x) = 0;
            if( x+1<img.cols && y-1>=0 ) // upright
            {
                Vec3d diff = color - (Vec3d)img.at<Vec3b>(y-1,x+1);
                uprightW.at<double>(y,x) = gammaDivSqrt2 * exp(-beta*diff.dot(diff));
            }
            else
                uprightW.at<double>(y,x) = 0;
        }
    }
}

//檢查mask的正確性。mask爲經過用戶交互或者程序設定的,它是和圖像大小同樣的單通道灰度圖,
//每一個像素只能取GC_BGD or GC_FGD or GC_PR_BGD or GC_PR_FGD 四種枚舉值,分別表示該像素
//(用戶或者程序指定)屬於背景、前景、可能爲背景或者可能爲前景像素。具體的參考:
//ICCV2001「Interactive Graph Cuts for Optimal Boundary & Region Segmentation of Objects in N-D Images」
//Yuri Y. Boykov Marie-Pierre Jolly 
/*
  Check size, type and element values of mask matrix.
 */
static void checkMask( const Mat& img, const Mat& mask )
{
    if( mask.empty() )
        CV_Error( CV_StsBadArg, "mask is empty" );
    if( mask.type() != CV_8UC1 )
        CV_Error( CV_StsBadArg, "mask must have CV_8UC1 type" );
    if( mask.cols != img.cols || mask.rows != img.rows )
        CV_Error( CV_StsBadArg, "mask must have as many rows and cols as img" );
    for( int y = 0; y < mask.rows; y++ )
    {
        for( int x = 0; x < mask.cols; x++ )
        {
            uchar val = mask.at<uchar>(y,x);
            if( val!=GC_BGD && val!=GC_FGD && val!=GC_PR_BGD && val!=GC_PR_FGD )
                CV_Error( CV_StsBadArg, "mask element value must be equel"
                    "GC_BGD or GC_FGD or GC_PR_BGD or GC_PR_FGD" );
        }
    }
}

//經過用戶框選目標rect來建立mask,rect外的所有做爲背景,設置爲GC_BGD,
//rect內的設置爲 GC_PR_FGD(可能爲前景)
/*
  Initialize mask using rectangular.
*/
static void initMaskWithRect( Mat& mask, Size imgSize, Rect rect )
{
    mask.create( imgSize, CV_8UC1 );
    mask.setTo( GC_BGD );

    rect.x = max(0, rect.x);
    rect.y = max(0, rect.y);
    rect.width = min(rect.width, imgSize.width-rect.x);
    rect.height = min(rect.height, imgSize.height-rect.y);

    (mask(rect)).setTo( Scalar(GC_PR_FGD) );
}

//經過k-means算法來初始化背景GMM和前景GMM模型
/*
  Initialize GMM background and foreground models using kmeans algorithm.
*/
static void initGMMs( const Mat& img, const Mat& mask, GMM& bgdGMM, GMM& fgdGMM )
{
    const int kMeansItCount = 10;  //迭代次數
    const int kMeansType = KMEANS_PP_CENTERS; //Use kmeans++ center initialization by Arthur and Vassilvitskii

    Mat bgdLabels, fgdLabels; //記錄背景和前景的像素樣本集中每一個像素對應GMM的哪一個高斯模型,論文中的kn
    vector<Vec3f> bgdSamples, fgdSamples; //背景和前景的像素樣本集
    Point p;
    for( p.y = 0; p.y < img.rows; p.y++ )
    {
        for( p.x = 0; p.x < img.cols; p.x++ )
        {
            //mask中標記爲GC_BGD和GC_PR_BGD的像素都做爲背景的樣本像素
			if( mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD )
                bgdSamples.push_back( (Vec3f)img.at<Vec3b>(p) );
            else // GC_FGD | GC_PR_FGD
                fgdSamples.push_back( (Vec3f)img.at<Vec3b>(p) );
        }
    }
    CV_Assert( !bgdSamples.empty() && !fgdSamples.empty() );
	
	//kmeans中參數_bgdSamples爲:每行一個樣本
	//kmeans的輸出爲bgdLabels,裏面保存的是輸入樣本集中每個樣本對應的類標籤(樣本聚爲componentsCount類後)
    Mat _bgdSamples( (int)bgdSamples.size(), 3, CV_32FC1, &bgdSamples[0][0] );
    kmeans( _bgdSamples, GMM::componentsCount, bgdLabels,
            TermCriteria( CV_TERMCRIT_ITER, kMeansItCount, 0.0), 0, kMeansType );
    Mat _fgdSamples( (int)fgdSamples.size(), 3, CV_32FC1, &fgdSamples[0][0] );
    kmeans( _fgdSamples, GMM::componentsCount, fgdLabels,
            TermCriteria( CV_TERMCRIT_ITER, kMeansItCount, 0.0), 0, kMeansType );

    //通過上面的步驟後,每一個像素所屬的高斯模型就肯定的了,那麼就能夠估計GMM中每一個高斯模型的參數了。
	bgdGMM.initLearning();
    for( int i = 0; i < (int)bgdSamples.size(); i++ )
        bgdGMM.addSample( bgdLabels.at<int>(i,0), bgdSamples[i] );
    bgdGMM.endLearning();

    fgdGMM.initLearning();
    for( int i = 0; i < (int)fgdSamples.size(); i++ )
        fgdGMM.addSample( fgdLabels.at<int>(i,0), fgdSamples[i] );
    fgdGMM.endLearning();
}

//論文中:迭代最小化算法step 1:爲每一個像素分配GMM中所屬的高斯模型,kn保存在Mat compIdxs中
/*
  Assign GMMs components for each pixel.
*/
static void assignGMMsComponents( const Mat& img, const Mat& mask, const GMM& bgdGMM, 
									const GMM& fgdGMM, Mat& compIdxs )
{
    Point p;
    for( p.y = 0; p.y < img.rows; p.y++ )
    {
        for( p.x = 0; p.x < img.cols; p.x++ )
        {
            Vec3d color = img.at<Vec3b>(p);
			//經過mask來判斷該像素屬於背景像素仍是前景像素,再判斷它屬於前景或者背景GMM中的哪一個高斯份量
            compIdxs.at<int>(p) = mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD ?
                bgdGMM.whichComponent(color) : fgdGMM.whichComponent(color);
        }
    }
}

//論文中:迭代最小化算法step 2:從每一個高斯模型的像素樣本集中學習每一個高斯模型的參數
/*
  Learn GMMs parameters.
*/
static void learnGMMs( const Mat& img, const Mat& mask, const Mat& compIdxs, GMM& bgdGMM, GMM& fgdGMM )
{
    bgdGMM.initLearning();
    fgdGMM.initLearning();
    Point p;
    for( int ci = 0; ci < GMM::componentsCount; ci++ )
    {
        for( p.y = 0; p.y < img.rows; p.y++ )
        {
            for( p.x = 0; p.x < img.cols; p.x++ )
            {
                if( compIdxs.at<int>(p) == ci )
                {
                    if( mask.at<uchar>(p) == GC_BGD || mask.at<uchar>(p) == GC_PR_BGD )
                        bgdGMM.addSample( ci, img.at<Vec3b>(p) );
                    else
                        fgdGMM.addSample( ci, img.at<Vec3b>(p) );
                }
            }
        }
    }
    bgdGMM.endLearning();
    fgdGMM.endLearning();
}

//經過計算獲得的能量項構建圖,圖的頂點爲像素點,圖的邊由兩部分構成,
//一類邊是:每一個頂點與Sink匯點t(表明背景)和源點Source(表明前景)鏈接的邊,
//這類邊的權值經過Gibbs能量項的第一項能量項來表示。
//另外一類邊是:每一個頂點與其鄰域頂點鏈接的邊,這類邊的權值經過Gibbs能量項的第二項能量項來表示。
/*
  Construct GCGraph
*/
static void constructGCGraph( const Mat& img, const Mat& mask, const GMM& bgdGMM, const GMM& fgdGMM, double lambda,
                       const Mat& leftW, const Mat& upleftW, const Mat& upW, const Mat& uprightW,
                       GCGraph<double>& graph )
{
    int vtxCount = img.cols*img.rows;  //頂點數,每個像素是一個頂點
    int edgeCount = 2*(4*vtxCount - 3*(img.cols + img.rows) + 2);  //邊數,須要考慮圖邊界的邊的缺失
    //經過頂點數和邊數建立圖。這些類型聲明和函數定義請參考gcgraph.hpp
	graph.create(vtxCount, edgeCount);
    Point p;
    for( p.y = 0; p.y < img.rows; p.y++ )
    {
        for( p.x = 0; p.x < img.cols; p.x++)
        {
            // add node
            int vtxIdx = graph.addVtx();  //返回這個頂點在圖中的索引
            Vec3b color = img.at<Vec3b>(p);

            // set t-weights			
            //計算每一個頂點與Sink匯點t(表明背景)和源點Source(表明前景)鏈接的權值。
			//也即計算Gibbs能量(每個像素點做爲背景像素或者前景像素)的第一個能量項
			double fromSource, toSink;
            if( mask.at<uchar>(p) == GC_PR_BGD || mask.at<uchar>(p) == GC_PR_FGD )
            {
                //對每個像素計算其做爲背景像素或者前景像素的第一個能量項,做爲分別與t和s點的鏈接權值
				fromSource = -log( bgdGMM(color) );
                toSink = -log( fgdGMM(color) );
            }
            else if( mask.at<uchar>(p) == GC_BGD )
            {
                //對於肯定爲背景的像素點,它與Source點(前景)的鏈接爲0,與Sink點的鏈接爲lambda
				fromSource = 0;
                toSink = lambda;
            }
            else // GC_FGD
            {
                fromSource = lambda;
                toSink = 0;
            }
			//設置該頂點vtxIdx分別與Source點和Sink點的鏈接權值
            graph.addTermWeights( vtxIdx, fromSource, toSink );

            // set n-weights  n-links
            //計算兩個鄰域頂點之間鏈接的權值。
			//也即計算Gibbs能量的第二個能量項(平滑項)
			if( p.x>0 )
            {
                double w = leftW.at<double>(p);
                graph.addEdges( vtxIdx, vtxIdx-1, w, w );
            }
            if( p.x>0 && p.y>0 )
            {
                double w = upleftW.at<double>(p);
                graph.addEdges( vtxIdx, vtxIdx-img.cols-1, w, w );
            }
            if( p.y>0 )
            {
                double w = upW.at<double>(p);
                graph.addEdges( vtxIdx, vtxIdx-img.cols, w, w );
            }
            if( p.x<img.cols-1 && p.y>0 )
            {
                double w = uprightW.at<double>(p);
                graph.addEdges( vtxIdx, vtxIdx-img.cols+1, w, w );
            }
        }
    }
}

//論文中:迭代最小化算法step 3:分割估計:最小割或者最大流算法
/*
  Estimate segmentation using MaxFlow algorithm
*/
static void estimateSegmentation( GCGraph<double>& graph, Mat& mask )
{
    //經過最大流算法肯定圖的最小割,也即完成圖像的分割
	graph.maxFlow();
    Point p;
    for( p.y = 0; p.y < mask.rows; p.y++ )
    {
        for( p.x = 0; p.x < mask.cols; p.x++ )
        {
            //經過圖分割的結果來更新mask,即最後的圖像分割結果。注意的是,永遠都
			//不會更新用戶指定爲背景或者前景的像素
			if( mask.at<uchar>(p) == GC_PR_BGD || mask.at<uchar>(p) == GC_PR_FGD )
            {
                if( graph.inSourceSegment( p.y*mask.cols+p.x /*vertex index*/ ) )
                    mask.at<uchar>(p) = GC_PR_FGD;
                else
                    mask.at<uchar>(p) = GC_PR_BGD;
            }
        }
    }
}

//最後的成果:提供給外界使用的偉大的API:grabCut 
/*
****參數說明:
	img——待分割的源圖像,必須是8位3通道(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_BGD或GCD_PR_FGD;
	rect——用於限定須要進行分割的圖像範圍,只有該矩形窗口內的圖像部分才被處理;
	bgdModel——背景模型,若是爲null,函數內部會自動建立一個bgdModel;bgdModel必須是
		單通道浮點型(CV_32FC1)圖像,且行數只能爲1,列數只能爲13x5;
	fgdModel——前景模型,若是爲null,函數內部會自動建立一個fgdModel;fgdModel必須是
		單通道浮點型(CV_32FC1)圖像,且行數只能爲1,列數只能爲13x5;
	iterCount——迭代次數,必須大於0;
	mode——用於指示grabCut函數進行什麼操做,可選的值有:
		GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
		GC_INIT_WITH_MASK(=1),用掩碼圖像初始化GrabCut;
		GC_EVAL(=2),執行分割。
*/
void cv::grabCut( InputArray _img, InputOutputArray _mask, Rect rect,
                  InputOutputArray _bgdModel, InputOutputArray _fgdModel,
                  int iterCount, int mode )
{
    Mat img = _img.getMat();
    Mat& mask = _mask.getMatRef();
    Mat& bgdModel = _bgdModel.getMatRef();
    Mat& fgdModel = _fgdModel.getMatRef();

    if( img.empty() )
        CV_Error( CV_StsBadArg, "image is empty" );
    if( img.type() != CV_8UC3 )
        CV_Error( CV_StsBadArg, "image mush have CV_8UC3 type" );

    GMM bgdGMM( bgdModel ), fgdGMM( fgdModel );
    Mat compIdxs( img.size(), CV_32SC1 );

    if( mode == GC_INIT_WITH_RECT || mode == GC_INIT_WITH_MASK )
    {
        if( mode == GC_INIT_WITH_RECT )
            initMaskWithRect( mask, img.size(), rect );
        else // flag == GC_INIT_WITH_MASK
            checkMask( img, mask );
        initGMMs( img, mask, bgdGMM, fgdGMM );
    }

    if( iterCount <= 0)
        return;

    if( mode == GC_EVAL )
        checkMask( img, mask );

    const double gamma = 50;
    const double lambda = 9*gamma;
    const double beta = calcBeta( img );

    Mat leftW, upleftW, upW, uprightW;
    calcNWeights( img, leftW, upleftW, upW, uprightW, beta, gamma );

    for( int i = 0; i < iterCount; i++ )
    {
        GCGraph<double> graph;
        assignGMMsComponents( img, mask, bgdGMM, fgdGMM, compIdxs );
        learnGMMs( img, mask, compIdxs, bgdGMM, fgdGMM );
        constructGCGraph(img, mask, bgdGMM, fgdGMM, lambda, leftW, upleftW, upW, uprightW, graph );
        estimateSegmentation( graph, mask );
    }
}

 

其中 gcgraph.hpp 轉自 http://blog.csdn.net/wstcegg/article/details/39495535,先存着尚未看。安全

#ifndef _CV_GCGRAPH_H_
#define _CV_GCGRAPH_H_

template <class TWeight> class GCGraph
{
public:
    GCGraph();
    GCGraph( unsigned int vtxCount, unsigned int edgeCount );
    ~GCGraph();
    void create( unsigned int vtxCount, unsigned int edgeCount );
    int addVtx();
    void addEdges( int i, int j, TWeight w, TWeight revw );
    void addTermWeights( int i, TWeight sourceW, TWeight sinkW );
    TWeight maxFlow();
    bool inSourceSegment( int i );
private:
    class Vtx //結點類型
    {
    public:
        Vtx *next;  //在maxflow算法中用於構建先進-先出隊列
        int parent; //父節點發出的弧
        int first;  //首個相鄰弧
        int ts;     //time-stamp時間戳
        int dist;   //distance,到樹根的距離
        TWeight weight; //t-value, 即到終端結點的權值
        uchar t;    //取值只能是0-1,s->t方向爲0, t->s方向爲1
    };
    class Edge //邊類型
    {
    public:
        int dst;    //弧指向的頂點
        int next;   //同一個原點的下一條弧
        TWeight weight; //n-value, 弧的權值
    };

    std::vector<Vtx> vtcs;  //結點集合
    std::vector<Edge> edges;    //弧集合
    TWeight flow;   //圖的流量
};

template <class TWeight>
GCGraph<TWeight>::GCGraph()
{
    flow = 0;   //流量初始化爲0
}
GCGraph<TWeight>::GCGraph( unsigned int vtxCount, unsigned int edgeCount )
{   
    //構造函數並無實質性的內容,而是把工做交給了create函數
    //你們一塊兒想一想是爲啥~
    create( vtxCount, edgeCount );
}
template <class TWeight>
GCGraph<TWeight>::~GCGraph()
{
}
template <class TWeight>
void GCGraph<TWeight>::create( unsigned int vtxCount, unsigned int edgeCount )
{
    //原來creeate也沒啥東西啊
    //爲了提升內存分配效率,給兩個集合預留足夠空間
    vtcs.reserve( vtxCount );
    edges.reserve( edgeCount + 2 );
    flow = 0;
}

/*
    函數功能:
        添加一個空結點,全部成員初始化爲空
    參數說明:
        無
    返回值:
        當前結點在集合中的編號
*/
template <class TWeight>
int GCGraph<TWeight>::addVtx()
{
    //添加結點
    Vtx v;

    //將結點申請到的內存空間所有清0(第二個參數0)
    //目的:因爲結點中存在成員變量爲指針,指針設置爲null保證安全
    memset( &v, 0, sizeof(Vtx));    
    vtcs.push_back(v);

    return (int)vtcs.size() - 1;   //返回結點集合尺寸-1    
}


/*
    函數功能:
        添加一條結點i和結點j之間的弧n-link弧(普通結點之間的弧)
    參數說明:
        int---i: 弧頭結點編號
        int---j: 弧尾結點編號
        Tweight---w: 正向弧權值
        Tweight---reww: 逆向弧權值
    返回值:
        無

*/
template <class TWeight>
void GCGraph<TWeight>::addEdges( int i, int j, TWeight w, TWeight revw )
{
    // 檢查結點編號有效性
    CV_Assert( i>=0 && i<(int)vtcs.size() );    
    CV_Assert( j>=0 && j<(int)vtcs.size() );
    
    CV_Assert( w>=0 && revw>=0 );   // 檢查弧權值有效性
    CV_Assert( i != j );    // 不存在指向本身的結點

    if( !edges.size() )
        edges.resize( 2 );

    // 正向弧:fromI, 反向弧 toI
    Edge fromI, toI;

    //===================================================//
    //如下爲插入正向弧的操做
    fromI.dst = j;      // 正向弧指向結點j
    fromI.weight = w;   // 正向弧賦予權值w

    //注意,爲便於解釋,下方一行代碼與上面一行代碼交換了順序,沒有任何實際影響
    //每一個結點所發出的所有n-link弧(4個方向)都會被鏈接爲一個鏈表,
    //採用頭插法插入全部的弧
    fromI.next = vtcs[i].first;         //將正向弧指向結點i的鏈表首部     
    vtcs[i].first = (int)edges.size();  //修改結點i的第一個弧爲當前正向弧

    edges.push_back( fromI );           //正向弧加入弧集合

    //===================================================//
    //如下爲插入反向弧的操做,與上相同,不做解釋
    toI.dst = i;
    toI.weight = revw;
    toI.next = vtcs[j].first;
    vtcs[j].first = (int)edges.size();
    edges.push_back( toI );
}

/*
    函數功能:
        爲結點i的添加一條t-link弧(到終端結點的弧)
    參數說明:
        int---i: 結點編號
        Tweight---sourceW: 正向弧權值
        Tweight---sinkW: 逆向弧權值
*/
template <class TWeight>
void GCGraph<TWeight>::addTermWeights( int i, TWeight sourceW, TWeight sinkW )
{
    CV_Assert( i>=0 && i<(int)vtcs.size() );    //結點編號有效性

    TWeight dw = vtcs[i].weight;    
    if( dw > 0 )
        sourceW += dw;
    else
        sinkW -= dw;
    flow += (sourceW < sinkW) ? sourceW : sinkW;
    vtcs[i].weight = sourceW - sinkW;
}

template <class TWeight>
 {
    //本函數中僅有的可能出現的負值,下面若是存在判別某值是否小於0,
    //意味着判斷當前結點是否爲終端結點,或者孤立點
    const int TERMINAL = -1, ORPHAN = -2;   

    //先進先出隊列,保存當前活動結點,stub爲哨兵結點
    Vtx stub, *nilNode = &stub, *first = nilNode, *last = nilNode;

    int curr_ts = 0;        //當前時間戳
    stub.next = nilNode;    //初始化活動結點隊列,首結點指向本身

    Vtx *vtxPtr = &vtcs[0];     //結點指針
    Edge *edgePtr = &edges[0];  //弧指針

    std::vector<Vtx*> orphans;  //孤立點集合

    // 遍歷全部的結點,初始化活動結點(active node)隊列
    for( int i = 0; i < (int)vtcs.size(); i++ )
    {
        Vtx* v = vtxPtr + i;
        v->ts = 0;
        if( v->weight != 0 ) //當前結點t-vaule(即流量)不爲0
        {
            last = last->next = v;      //入隊,插入到隊尾
            v->dist = 1;                //路徑長度記1
            v->parent = TERMINAL;   //標註其雙親爲終端結點

            //t爲uchar類型, 例如: t=(1>0), t=1
            //                     t=(1<0), t=0
            //實際效果紀錄當前結點流向, 正向取0,逆向取1
            v->t = v->weight < 0;      
        }
        else
            v->parent = 0; //保持不變,孤兒結點
    }

    first = first->next; //首結點做爲哨兵使用,自己無實際意義,移動到下一節點,即第一個有效結點
    last->next = nilNode; //哨兵放置到隊尾了。。。,檢測到哨兵說明一層查找結束
    nilNode->next = 0; //哨兵後面啥都沒有,清空

    //很長的循環,每次都按照如下三個步驟運行:
    //搜索路徑->拆分爲森林->樹的重構
    for(;;)
    {
        Vtx* v, *u;     // v表示當前元素,u爲其相鄰元素
        int e0 = -1, ei = 0, ej = 0;
        TWeight minWeight, weight;      // 路徑最小割(流量), weight當前流量
        uchar vt;                       // 流向標識符,正向爲0,反向爲1

        //===================================================//
        // 第一階段: S 和 T 樹的生長,找到一條s->t的路徑

        while( first != nilNode ) // 存在活動結點
        {
            v = first;          // 取第一個元素存入v,做爲當前結點
            if( v->parent )     // v非孤兒點
            {
                vt = v->t;      // 紀錄v的流向
                // 廣度優先搜索,以此搜索當前結點全部相鄰結點, 方法爲:遍歷全部相鄰邊,調出邊的終點就是相鄰結點
                for( ei = v->first; ei != 0; ei = edgePtr[ei].next ) 
                {
                    // 此路不通,跳過,一般兩個相鄰點都會有關聯的,不過若是關聯值weight太小,爲防止下溢出,置0!
                    // 每對結點都擁有兩個反向的邊,ei^vt代表檢測的邊是與v結點同向的
                    if( edgePtr[ei^vt].weight == 0 ) 
                        continue;
                    u = vtxPtr+edgePtr[ei].dst;     // 取出鄰接點u
                    if( !u->parent ) // 無父節點,即爲孤兒點,v接受u做爲其子節點
                    {
                        u->t = vt;          // 設置結點u與v的流向相同
                        u->parent = ei ^ 1; // ei的末尾取反。。。
                        u->ts = v->ts;      // 更新時間戳,因爲u的路徑長度經過v計算獲得,所以有效性相同
                        u->dist = v->dist + 1;  // u深度等於v加1
                        if( !u->next )       // u不在隊列中,入隊,插入位置爲隊尾,
                        {
                            u->next = nilNode;   // 修改下一元素指針指向哨兵
                            last = last->next = u; // 插入隊尾
                        }
                        continue;   //continue_for() [遍歷鄰接點]
                    }

                    if( u->t != vt )    // u和v的流向不一樣,u能夠到達另外一終點,則找到一條路徑
                    {
                        e0 = ei ^ vt;   //
                        break;  //break_for() [遍歷鄰接點]
                    }

                    // u已經存在父節點,可是若是u的路徑長度大於v+1,說明u走彎路了,修改u的路徑,使其成爲v的子結點
                    // 進入條件:u的路徑長度大於v的長度+1,且u的時間戳較早
                    if( u->dist > v->dist+1 && u->ts <= v->ts )   
                    {
                        // 重新設置u的父節點爲v(編號ei),記錄爲當前的弧
                        u->parent = ei ^ 1;
                        u->ts = v->ts;          // 更新u的時間戳與v相同
                        u->dist = v->dist + 1;  // u爲v的子結點,路徑長度加1
                    }
                }
                if( e0 > 0 )
                    break;  //break_while() [查找s->t路徑]
            }
            // 將剛處理完的結點從活動隊列移除
            first = first->next;
            v->next = 0;
        }

        if( e0 <= 0 )   //所有搜索結束,e0=0說明全部結點都已經處理完畢,e0<0說明已經搜索到了終端結點了。。
            break;  //break_for(;;)[max_flow]

        //===================================================//
        // 第二階段: 流量統計與樹的拆分

        //===第一節===//
        //查找路徑中的最小權值
        minWeight = edgePtr[e0].weight;
        assert( minWeight > 0 );

        // 遍歷整條路徑分兩個方向進行,從當前結點開始,向前回溯s樹,向後回溯t樹
        // 2次遍歷, k=1: 回溯s樹, k=0: 回溯t樹
        for( int k = 1; k >= 0; k-- )
        {
            //回溯的方法爲:取當前結點的父節點,判斷是否爲終端結點
            for( v = vtxPtr+edgePtr[e0^k].dst;/*此處無退出條件*/; v = vtxPtr+edgePtr[ei].dst )
            {
                if( (ei = v->parent) < 0 )   //回溯,ei紀錄當前點的父邊,回溯至終端結點,退出
                    break;
                weight = edgePtr[ei^k].weight;       //取當前弧的權值
                minWeight = MIN(minWeight, weight);  //紀錄當前路徑最小流
                assert( minWeight > 0 );
            }
            weight = fabs(v->weight);
            minWeight = MIN(minWeight, weight);     //取s樹和t樹的最小流
            assert( minWeight > 0 );
        }

        //===第二節===//
        // 修改當前路徑中的全部的weight權值
        /* 注意到任什麼時候候s和t樹的結點都只有一條弧使其鏈接到樹中,
           當這條弧權值減小爲0則此結點從樹中斷開,
           若其無子結點,則成爲孤立點,
           若其擁有子結點,則獨立爲森林,可是ei的子結點還不知道他們被孤立了!
        */
        edgePtr[e0].weight -= minWeight;    //正向路徑權值減小
        edgePtr[e0^1].weight += minWeight;  //反向路徑權值增長
        flow += minWeight;      //修改當前流量

        // k=1: source tree, k=0: destination tree
        for( int k = 1; k >= 0; k-- )
        {
            for( v = vtxPtr+edgePtr[e0^k].dst;/*此處無退出條件*/; v = vtxPtr+edgePtr[ei].dst )
            {
                if( (ei = v->parent) < 0 )  //某一方向搜索結束,退出
                    break;
                edgePtr[ei^(k^1)].weight += minWeight;  //n-value逆向增長
                //n-value正向減小,若是權值減小至0,則將ei標註爲孤兒
                if( (edgePtr[ei^k].weight -= minWeight) == 0 )  
                {
                    orphans.push_back(v);   //加入孤兒集合
                    v->parent = ORPHAN; //修改父節點標記
                }
            }

            v->weight = v->weight + minWeight*(1-k*2);  //t-value修改(減小或增長)
            //若是權值減小至0,則將ei標註爲孤兒
            if( v->weight == 0 )
            {
               orphans.push_back(v);
               v->parent = ORPHAN;
            }
        }

        //===================================================//
        // 第三階段: 樹的重構

        // 爲孤兒找到新的父節點,恢復樹結構
        curr_ts++;
        while( !orphans.empty() ) //存在孤兒
        {
            Vtx* v2 = orphans.back(); //取一個孤兒,記爲v2
            orphans.pop_back();  //刪除棧頂元素,兩步操做等價於出棧

            int d, minDist = INT_MAX; 
            e0 = 0;
            vt = v2->t;

            //  遍歷當前結點的相鄰點,ei爲當前弧的編號
            for( ei = v2->first; ei != 0; ei = edgePtr[ei].next )
            {
                if( edgePtr[ei^(vt^1)].weight == 0 )    // 找到權值爲0的邊,無效,繼續找
                    continue;
                u = vtxPtr+edgePtr[ei].dst;     // 鄰接點記爲u
                if( u->t != vt || u->parent == 0 )  // 不一樣方向的邊,或者找到的點也是孤立點,繼續找
                    continue;
                // 計算當前點路徑長度
                for( d = 0;; )
                {
                    if( u->ts == curr_ts )  // 找到時間戳符合的結點,即此結點路徑長度有效
                    {
                        d += u->dist;   // 最終路徑長度等於到u結點的距離加u結點的路徑長度
                        break;
                    }
                    ej = u->parent; // 繼續尋找u的父節點
                    d++;            // 距有效點距離加1
                    if( ej < 0 )    // TERMINAL = -1, ORPHAN = -2
                    {
                        if( ej == ORPHAN )  // 找到的父節點是孤立點
                            d = INT_MAX-1;  // 紀錄d無窮大,即沒法到達終端結點
                        else    // 找到的是終端結點
                        {
                            u->ts = curr_ts;    // 更改時間戳爲當前時刻,本次大循環中其路徑長度是有效的!
                            u->dist = 1;        // 路徑長度爲1
                        }
                        break;
                    }
                    u = vtxPtr+edgePtr[ej].dst; // u指向父節點,繼續回溯
                }

                // 更新子結點的路徑長度
                if( ++d < INT_MAX )     // 當前結點找到了父節點
                {
                    if( d < minDist )   // 
                    {
                        minDist = d;    // 更新minDist
                        e0 = ei;        // e0記錄找到的v2父弧
                    }
                    //
                    for( u = vtxPtr+edgePtr[ei].dst; u->ts != curr_ts; u = vtxPtr+edgePtr[u->parent].dst )
                    {
                        u->ts = curr_ts;
                        u->dist = --d;
                    }
                }
            }//end_for

            if( (v2->parent = e0) > 0 )
            {
                v2->ts = curr_ts;   //v2時間戳爲當前時刻,本次大循環中其路徑長度是有效的!
                v2->dist = minDist;
                continue;
            }

            // 未找到父節點,將此結點和其全部子節點置爲孤兒
            v2->ts = 0;     // v2爲當前結點,時間戳置0
            for( ei = v2->first; ei != 0; ei = edgePtr[ei].next )
            {
                u = vtxPtr+edgePtr[ei].dst; // 鄰接點
                ej = u->parent;             // 鄰接點的父節點
                if( u->t != vt || !ej )     // 鄰接點無父節點或與本結點不一樣向?
                    continue;
                if( edgePtr[ei^(vt^1)].weight && !u->next )     //u和v反向,則加入活動隊列 (vt^1表示對vt取反)
                {
                    u->next = nilNode;  
                    last = last->next = u;
                }
                if( ej > 0 && vtxPtr+edgePtr[ej].dst == v2 )    //當前節點確實是v2的子節點
                {
                    orphans.push_back(u);   //加入孤立點隊列
                    u->parent = ORPHAN;     //標記其無父節點
                }
            }
        }//end_while
    }//end_for(;;)

    return flow; //返回最大流量
}

template <class TWeight>
bool GCGraph<TWeight>::inSourceSegment( int i )
{
    CV_Assert( i>=0 && i<(int)vtcs.size() );
    return vtcs[i].t == 0;
}

#endif
相關文章
相關標籤/搜索