判斷區域B是否在區域A內部的快速算法

来源:互联网 发布:上交所数据 编辑:程序博客网 时间:2024/06/05 01:54

http://www.cnblogs.com/xiaotie/archive/2010/11/08/1871424.html

在圖像分析中,經常需要判斷圖像分割所得到的區域之間的關系。通常情況,我們通過八鄰接外輪廓(准確說法是擴展邊緣,但這樣又得費半天口舌解釋什麼是擴展邊緣)來描述一個區域並對區域進行標注,如:

image 

很容易判斷兩個區域是否相鄰(掃描區域的內外邊緣像素,如果相鄰的像素具有不同的標注值,則為鄰居),卻較難判斷一個區域是否在另一個區域的內部。

image

如上圖中,通過相鄰像素的標注值的不同,可以得出A和B互為鄰居,A和C互為鄰居,卻很難知道B和C中,哪個是在A的內部。下面設計算法進行判斷。

====

如果B在A的內部,則B的外輪廓上的每個點在A的外輪廓的內部或邊緣上;如此一來,問題就簡化為判斷點是否在多邊形的邊緣或內部。

判斷點是否在多邊形內部有一個很經典的算法:從該點向任意一方畫射線,數該射線與多邊形的邊的交點數量,如果為奇數則在多邊形內部,如果為偶數則在多邊形的外部。

image

這個算法有兩個特例:

(1)射線和多邊形的邊重合(下圖a,b)

(2)射線經過多邊形的頂點(下圖c,d)

image

顯然,(a)應該算0個交點 ,(b)應該算1個交點,(c)應該算0個交點,(d)應該算1個交點。

總體上來說,這個算法要考慮到幾種特殊情況,還是比較繁瑣的。下面,針對本文的應用來簡化該算法。

數字圖像是離散的,通過邊界跟蹤可以得到全部的輪廓點。

image

上圖是一個輪廓及待判斷點。從該點向X軸畫一個射線,與9個輪廓點相交。如果將輪廓的任意兩個相鄰點的連線作為多邊形的一邊的話,很不幸,全部交點都是特殊情況。這裡假定輪廓點的排列是有序的,也就是說,是有方向的,只考察輪廓點和它一前一後兩個輪廓點之間的關系,則有下面幾類情況:

image

這裡對經典的點在多邊形內部判斷算法進行變形:

(1)如果經過A類中的中間點,則算為 0.5 或 –0.5 個交點;

(2)如果經過B類的中間點,算作1個或-1個交點;

(3)如果經過C類的中間點,算作0個交點。

計算結果——如果交點數加起來是奇數,則點在輪廓的內部,否則在外部。為了避免浮點計算,將交點個數乘於2,即A類的算1個或-1個交點,B類的算2個或-2個交點,C類的算0個交點。交點總數是4的倍數則在輪廓外部,否則,則在內部。

為什麼是1個或-1個,2個或-2個呢?這裡有方向問題。假設箭頭是從下向上的,為-1,箭頭是從上往下的,算1,如果箭頭是水平的,算0.  這樣計算的話,則上圖中A類的2種情況分別為1個、1個交點,B類的2種情況分別為2個、-2個交點,C類的2種情況全為0. 如此以來,完全滿足前面點在多變形內部的經典算法對幾種特殊情況的處理。

為每一個輪廓點賦予一個分值Score,這個分值只與它(Current)和前後兩點(Prev,Next)有關,和其它任何點無關。因此,這個分值是靜態的,不變化的。我們可以把它計算出來緩存在散列表中。

private void ComputeExtendContourPointXScoreDic() 

    List<Point> points = this.ExtendContourPoints; 
    if (points.Count < 3) return; 
    int count = points.Count; 
    for (int i = 0; i < count; i++) 
    { 
        Point current = points[i]; 
        Point prev = points[(i + count - 1) % count]; 
        Point next = points[(i + 1) % count]; 
        int score = current.Y < prev.Y ? 1 : current.Y > prev.Y ? -1 : 0; 
        score += next.Y < current.Y ? 1 : next.Y > current.Y ? -1 : 0; 
        _extendContourPointXScoreDic[current.GetHashCode32()] = score; 
    } 
}

更進一步,score = prev.Y – next.Y:

private void ComputeExtendContourPointXScoreDic() 

    List<Point> points = this.ExtendContourPoints; 
    if (points.Count < 3) return; 
    int count = points.Count; 
    for (int i = 0; i < count; i++) 
    { 
        _extendContourPointXScoreDic[points[i].GetHashCode32()] = points[(i + count - 1) % count].Y - points[(i + 1) % count].Y; 
    } 
}

_extendContourPointXScoreDic 是一個散列表,儲存了輪廓點的Score。因為一般的圖像不會特別大,我為Point添加了一個擴展方法GetHashCode32()來獲得散列值:

public static int GetHashCode32(this Point p) 

    return p.Y * Int16.MaxValue + p.X; 
}

這個散列表還有一個用途——如果某點的散列值在散列表內,則該點在外輪廓上。

判斷點是否在輪廓的內部,只需要向左或向右掃描即可。比較向左和向右的掃描長度,選擇最短的掃描路線,將掃描所經過的輪廓點的散列值加起來,就是交點數量。該交點數量如果是4的倍數,則代表點在輪廓外,否則,則在輪廓內。

在掃描的過程中,向左移一點或向右移一點,對應的點的散列值減1或加1,因此,可以省掉移動的過程,用散列值的變化來表示移動。這樣又可以加速計算。如果已知A、B兩個區域的 Rectangle,可以先判斷,如果B的Rectangle不在A的Rectangle內部,則B一定不在A的內部。這樣也可以節省不少計算。

====

參考資料:Udi Manber. 算法引論——一種創造性方法.  http://www.china-pub.com/26775 P.189。


原创粉丝点击