經典圖割演算法中圖的構建及實現:Graph-Cut
經典圖割演算法中圖的構建及實現之graph-cut
本文目的:
講解目前典型的3種圖割演算法:graph-cut、grab-but、one-cut。本文主要講解graph-cut的方法在應用時,準則函式與圖構建關係,如何構建圖,以及如何程式碼實現圖的構建。圖割的原理網上文章和論文已介紹比較詳細,不再詳細介紹。
一.graph-cut:準則函式
該方法可謂是圖割方法的開山鼻祖。該方法的準則函式如下:
R(A)是先驗懲罰項,B(A)是區域相似度懲罰項,是平衡因子。
該準則函式意義:同類間,顏色差別小;異類間,顏色差別大。原則上該準則可解決影象任意類分割,並且一定是有全域性最優解得,但在無種子點的超過2分類的問題時,該優化是個NP難問題,需要進行指數級的比較才能獲得最優解,無工程價值。
二.Graph-cut:圖的建立
1.術語:
- )與S和T連結的邊叫t-link(紅線與綠線),領域之間的連結邊叫n-link(黑線)。其中紅線進一步稱為s-t-link,綠線進一步稱為t-t-link。
- )黑線的權值對應的是B(A)項,紅線與綠線的權值對應的是R(A)項。
- )權值用w表示。
- )藍色節點表示類別標誌節點,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等於:
其中,dist()是距離函式,表示點之間的影象距離。即4領域下,所以領域點距離均為1,;8領域下,對角畫素點距離為;在5*5領域下,對角畫素點距離為。
設種子點的超級大值是1000,=1。影象是3*2的灰度圖,數字表示灰度值,紅色和藍色節點表示使用者選擇的正負種子點。當然種子點過少時,計算的H_F,H_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類的影象,該方法效果很好,最後僅需再加上一些空洞填補、小區域過濾等操作就好。