1. 程式人生 > >經典圖割演算法中圖的構建及實現:Graph-Cut

經典圖割演算法中圖的構建及實現:Graph-Cut

經典圖割演算法中圖的構建及實現之graph-cut

本文目的:

講解目前典型的3種圖割演算法:graph-cut、grab-but、one-cut。本文主要講解graph-cut的方法在應用時,準則函式與圖構建關係,如何構建圖,以及如何程式碼實現圖的構建。圖割的原理網上文章和論文已介紹比較詳細,不再詳細介紹。

一.graph-cut:準則函式

該方法可謂是圖割方法的開山鼻祖。該方法的準則函式如下:

R(A)是先驗懲罰項,B(A)是區域相似度懲罰項,\lambda是平衡因子。

該準則函式意義:同類間,顏色差別小;異類間,顏色差別大。原則上該準則可解決影象任意類分割,並且一定是有全域性最優解得,但在無種子點的超過2分類的問題時,該優化是個NP難問題,需要進行指數級的比較才能獲得最優解,無工程價值。

二.Graph-cut:圖的建立

1.術語:

  1. )與S和T連結的邊叫t-link(紅線與綠線),領域之間的連結邊叫n-link(黑線)。其中紅線進一步稱為s-t-link,綠線進一步稱為t-t-link。
  2. )黑線的權值對應的是B(A)項,紅線與綠線的權值對應的是R(A)項。
  3. )權值用w表示。
  4. )藍色節點表示類別標誌節點,S表示正類類標節點,T表示負類類標節點,黃色節點是影象中的每一個畫素點。

       最終通過求最小割之後,與節點S相連的所有黃色節點(影象畫素點)屬於一類,同理與節點T相連的所有黃色節點屬於另一類。兩類被最小割割開,割值即是準則函式的值。

2.圖的建立

       拿到待分割的影象後,圖的節點與邊已確定,即圖的形狀已確定下來。僅僅需要做的就是給圖中所有邊賦值相應的權值。

圖中的邊有3種情況:種子點的t-link;非種子點的t-link;畫素領域關係的n-link。接下來將說明每一種邊的權值取值。

1).種子點t-link權值:種子點認為是硬約束,其使用者預設類別後,類別不會隨分割演算法而改變。

a.對於正類別種子點,s-t-link必須保留,t-t-link必須割去。工程中,通過將s-t-link權值設定為超級大值,t-t-link設定為0。保證一定僅僅割去t-t-link,否則一定不是最小割,因為當前w(s-t-link)權值是超級大值,割去這條邊的代價一定是最大的。

           b.反之同理。

2).非種子點的t-link權值:通過正負類種子點,我們能建立2類的顏色直方圖。將直方圖歸一化成概率密度函式,定義為H_F,H_B。其中s-t-link權值為-ln(H_F(x)),t-t-link權值為-ln(H_B(x)),x為該畫素點顏色值。

3).n-link權值:n-link用於度量相鄰畫素點之間顏色的差異性。設一對相鄰點Pi,Pj,則n-link(Pi-Pj)的權值w等於:

w = \exp ( - \frac{{{{({x_i} - {x_j})}^2}}}{{2{\sigma ^2}}}) \cdot \frac{1}{{dist({P_i},{P_j})}}

         其中,dist()是距離函式,表示點之間的影象距離。即4領域下,所以領域點距離均為1,;8領域下,對角畫素點距離為\sqrt 2;在5*5領域下,對角畫素點距離為\2\sqrt 2

         設種子點的超級大值是1000,\sigma=1。影象是3*2的灰度圖,數字表示灰度值,紅色和藍色節點表示使用者選擇的正負種子點。當然種子點過少時,計算的H_FH_B可能不準,可將種子點附近的畫素點也算入先驗直方圖中,往往可以取得更好效果

  

       如上圖所示,將所有邊的權值賦值後,圖就建立完畢。剩餘則直接運用最小割演算法即可求解。最小割演算法有很多,包括graph-cut作者提出的快速演算法An Experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision。Opencv即採用該演算法計算最小割。

3.建立圖的程式碼實現:

void GCApplication::graphConstruct(const Mat& img, GCGraphMy<double>& graph)
{
    lambda = 1000;//極大值
    beta = calcBeta(*(this->image));

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

    int vtxCount = img.cols*img.rows,
        edgeCount = 2 * (4 * img.cols*img.rows - 3 * (img.cols + img.rows) + 2);

    fillSeedToMask(this->mask);
    calSeedPHist(img, this->mask);

    graph.create(vtxCount, edgeCount);
    Point p;
    double a = 1.5;
    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
            double fromSource, toSink;
            if (mask.at<uchar>(p) == 0)//非種子點的t-link
            {
                fromSource = -a*log(calBgdPrioriCost(color));
                toSink = -a*log(calFgdPrioriCost(color));
            }
            else if (mask.at<uchar>(p) == MASK_BG_COLOR)//負種子點t-link
            {
                fromSource = 0;
                toSink = lambda;
            }
            else if (mask.at<uchar>(p) == MASK_FG_COLOR) //正種子點t-link
            {
                fromSource = lambda;
                toSink = 0;
            }
            graph.addTermWeights(vtxIdx, fromSource, toSink);

            // set n-link-weights,每個點只需要與左上4個點進行邊連線即可,這樣可以不重複的新增所有的N-8-edge
            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);
            }
        }
    }
}

2)分割效果:

       從實驗結果來看,圖中如果有多個類,該方法一般不能取得較好結果。對於2類的影象,該方法效果很好,最後僅需再加上一些空洞填補、小區域過濾等操作就好。