Intelligent Scissors 原理與實現
GitHub倉庫地址:https://github.com/djybbb/Intelligent-Scissors
邊緣特徵
我們觀察到,磁力套索會自動地去貼合影象中物體的邊緣,而影象的邊緣特徵可以由影象畫素的一階導數和二階導數描述,故影象中梯度幅值和拉普拉斯運算元響應值較高的更可能是影象中的邊緣。
演算法思路
設定初始點
整個程式的操作流程是這樣的,使用者首先在想要的輪廓附近設定一個初始點(稱之為seed point),然後移動滑鼠,在這個過程中曲線會自動地去貼合影象物體的輪廓。
最小代價路徑
而該過程可以建模成Dijkstra演算法,即設定seed point後,程式計算出影象中所有的畫素點到seed point的最小代價路徑。
回想到前面所說的邊緣特徵,此處的代價與邊緣特徵有關,即邊緣特徵越明顯的畫素點,其代價越低,這樣便使得最小代價路徑總是會去“貼合”影象的邊緣。
畫素節點類
從前面可以看到,每個畫素會被標記,記錄其要到seed point的話,下一步該走到哪一個畫素,故其類應該要包含指向另一個畫素節點的指標。
同時,每個節點都要知道自己到seed point的總代價(該值的存在是為了不斷地做對比來得到最短路徑)。
故畫素節點類最基本的元素如下:
class GraphNode {
public:
int x;
int y;
double global_cost;//到seed point的總代價
GraphNode * back_pointer;//最小代價路徑的方向
...//
};
Active List類
回憶Dijkstra演算法,我們需要不停地知道當前最小代價的路徑是哪一處,以決定我們下一次迴圈將要處理哪個節點類。於是我們使用Active List類來儲存當前待遍歷的畫素點,並且Active List能夠直接訪問到當前代價最低的畫素點。
論文中用了較為複雜的桶排序,其效率很高。我的實現中使用了一個一直維持有序的List,可以參考程式碼中的ActiveList.cpp。這裡就不介紹了。。。(效率不高的實現,但是省事)
代價Local Costs描述
我們稱之前提到的代價為local costs,論文一中,Local Costs由三個影象特徵組成
local costs通過這三個Image Feature的權重合得到:
其中最為重要的是fG,即影象的梯度幅值。
fG的實現
設影象畫素座標為(x,y)處的梯度幅值為M(x,y),有:
使用sobleFilter,其最小的3X3模版使用以z5為中心的一個3X3領域對gx和gy的近似如下式所示:
梯度幅值越大,意味著具有更多的邊緣特徵,在前面的建模中提到邊緣特徵意味著路徑代價低,故f(G)應該這樣設定:
實現了fG之後就可以看到明顯的效果,建議先實現f(G)再慢慢補充fZ和fD。fZ和fD的具體實現可以參考論文,其作用分別是使得曲線更加貼合邊緣和更加平滑。
fZ的實現
意義
fZ是拉普拉斯零交(Laplacian Zero-Crossing)帶來的代價,拉普拉斯運算元的卷積就是原影象素二維導數,表示的影象一維導數變化的速率。拉普拉斯運算元卷積為0的地方意味著該點是一維導數變化的極值點,其變化速率最快,我們認為這是邊緣的特徵,故其代價應該較低。我們將拉普拉斯運算元卷積為0的地方的代價設為0,不為0的地方設為1。
特殊處理
由於拉普拉斯運算元卷積剛好為0的地方極少,所以我們需要特殊處理以增多代價為0的點。
我們認為,當兩個臨近畫素出現異號時,其中間肯定有卷積為0的位置,但是由於畫素是離散的,我們設卷積值離0近的畫素的代價為0,這樣就增多了代價為0的點,使得拉普拉斯零交特徵的影響增大。
fD的實現
意義
fD是為了使得邊緣平滑,實現平滑的方式是通過為劇烈變化的邊界方向設定較高的代價。
實現方法
設D(p)為某個畫素點的梯度方向(Ix,Iy),其中Ix和Iy分別為影象在x方向和y方向上的梯度,D’(p)為(Iy,-Ix),與D(p)正交。設L(p,q)為p到q的單位向量。fD的詳盡計算公式參考論文。
示例如下
演算法流程描述
下圖(a)描述了各個畫素點的local costs,(a)中畫圈的local cost為2的畫素點代表著seed point,設其為S
設定seed point後,計算影象所有畫素到該點的代價,其過程如(b) (c) (d)所示:
(b)
第一步,計算S的鄰域(8個畫素點)到S的代價,注意對照(a),S本身的local cost為2沒有任何作用,因為S到自身的代價為0(這不是廢話嘛。。。),鄰域到S的代價不是local costs相減(例如S的左邊畫素自身的local costs為4,它到S的代價不是4-2,而是4),還有就是對角線上的畫素到S的代價是其自身的local cost乘上了根號2,這是因為對角線的長度比直線長度長。
(c)
第二步,選取當前代價最低的路徑(回憶Dijkstra演算法)(a)中此時的路徑只有其鄰域到S的路徑,其右方鄰域畫素N1到S的代價最低,標記N1:它要到達seed point,就走它的左鄰域,這樣可以使得總代價最小。
其中,N1的代價指的是便是N1到seed point的代價和。
然後計算N1的鄰域到N1的代價,N1的鄰域的代價不僅僅是其local cost的值,還要加上N1自身的代價(這樣才是總代價)。觀察下(b)圖中S的右上方鄰域畫素(其 local cost為11)N2,對照©,發現其箭頭指向了N1,並且其代價值更新為了9,這是因為在第二步中,計算得到N2的代價為8+1 = 9,它比原來8*根號2 = 11要小,故需要進行更新。
更新意味著,N2此時知道自己要去到seed point的最小代價路徑是走下方而不是左下方直接去seed point,這裡也需要一個標記動作。(類似前面標記N1)
一次expand
以上兩步可以稱為一次expand
下一次expand也是類似,選取當前代價最低的路徑,例如下一次expand應該選取的是S的上方畫素(可以看到,是從全域性來選取)。
虛擬碼參考
論文中把得到最小路徑的演算法稱為:Live-Wire 2-D DP graph search
該虛擬碼的實現可參考專案程式碼中GraphSearch.cpp中的void calPath(CvPoint start_point)函式。
系列部落格與總結
Intelligent Scissors 介紹 :https://blog.csdn.net/DdogYuan/article/details/80371070
Intelligent Scissors 繪圖功能的構建:https://blog.csdn.net/DdogYuan/article/details/80371116
Intelligent Scissors 原理與實現:https://blog.csdn.net/DdogYuan/article/details/80554873