多邊形的掃描轉化算法(python 實現)
實驗目的
實現從多邊形頂點表示到點陣表示的轉換,從多邊形給定的邊界出發,通過掃描線的方式求出位於其內部各個像素,從而達到對多邊形填充的作用。
算法思想
按掃描線順序,計算掃描線與多邊形的相交的交點,這些交點將掃描線分割成落在多邊形內部的線段和落在多邊形外部的線段,並且二者相間排列。再用要求的顏色顯示這些區間的所有象素。
有效邊:指與當前掃描線相交的多邊形的邊,也稱爲活性邊。
有效邊表(AET):把有效邊按與掃描線交點 x 座標遞增的順序存在一個鏈表中,此鏈表稱爲有效邊表。只需對當前掃描線的活動邊表作更新,即可得到下一條掃描線的活動邊表。
爲了方便靈活邊表的建立與更新,我們爲每一條掃描線建立一個新邊表NET,用來存放在該掃描線第一次出現的邊。
存儲內容爲:
ymax:邊的上端點的 y 座標;
x:在 ET 中表示邊的下端點的 x 座標,在 AEL 中則表示邊與掃描線的交點的座標;
Δx:邊的斜率的倒數;next:指向下一條邊的指針。
算法步驟
1、大致確定多邊形的範圍,進而確定掃描線的範圍
2、初始化並建立 NET 表
3、遍歷每一條掃描建立 ET:對於每一個多邊形點,尋找與其構成邊的 兩點,如果尋找到的點在此點的上方(即 y0小於y1),則將此邊加入到ET[i]中(i 對應的 y0 的座標)。這樣每次加入的邊都是向上,不會重複。
4、置 AET 爲空;
5、執行下列步驟直至 NET 和 AET 都爲空.
A.更新。如 ET 中的 y 非空,則將其中所有邊取出並插入 AET 中;
B.填充。對 AEL 中的邊兩兩配對,每對邊中 x 座標按規則取整,獲得有效的填充區段,再填充.
C.排序。如果有新邊插入 AET,則對 AET 中各邊排序;
D.刪除。將 AEL 中滿足 y=ymax 邊刪去(因爲每條邊被看作下閉上開的)
E.對 AEL 中剩下的每一條邊的 x 遞增Δx,即 x = x+Δx;
F.將當前掃描線縱座標 y 值遞值 1;
測試代碼
根據多邊形的分類,用凹多邊形測試更具代表性,而且選取的圖像具有水平線段,這樣更具有良好的測試作用
凹:
polygon=[
[20,20],
[70,100],
[50,80],
[30,120],
[20,50],
[50,50]
]
實驗結果與分析
空間複雜度:數據結構爲鏈表,鏈表中存放的是多邊形的邊長,當多形邊數較少時,空間複雜度可忽略不計
時間複雜度:初始化建立 NET 時,需要兩個 for 循環遍歷每條掃描線與多邊形的邊長數,O((ymax-ymin)*多邊形邊長數),當多邊形邊數較少時,時間複雜度近似爲 O(n)。
注意的問題與解決方法
多邊形的掃描轉化算法是按照掃描線順序,計算掃描線與多邊形的相交區間,來完成填充工作,因爲原理比較簡單,比較需要考慮的是對一些情況的規定注意的問題:若掃描線與多邊形相交的邊分處掃描線的兩側,遵循左開右閉的原則,配對交點;若掃描線與多邊形頂點相交,則判斷此頂點左右兩個頂點y1 座標與此頂點 y 座標的大小,選擇上方頂點(即 y1>y)與此頂點構成的邊加入 NET;若掃描線與多邊形邊界重合,則計一個交點等等。在此編寫代碼期間,忽略一些簡單的編譯錯誤,最值得注意的問題是:
在建立 NET 時,需要通過遍歷每條掃描線與多邊形的頂點 y 座標進行匹配,選擇其左右頂點座標 y1>y 的頂點構成的邊。這個時候就需要使用 if 語句來判斷 y1 與 y 的關係。因爲一個頂點與兩條邊相關聯,所以需要用兩個 if 做分別來判斷。然而我在進行兩個 if 判斷的時候,沒有考慮到重複問題,重複將 NET[i] = SingleLinkList()鏈表。以至於在掃描線 i 的情況下,同一個 NET[i]鏈表化了
代碼
'''
多邊形掃描轉換算法
class SingleLinkList:用類代替鏈表
PoliFill(image, polygon, color):掃描轉換
'''
import numpy as np
import matplotlib.pyplot as plt
class Node:
# 定義節點
def __init__(self, data):
self._data = data
self._next = None
def get_data(self):
return self._data
def get_next(self):
return self._next
def set_data(self, ddata):
self._data = ddata
def set_next(self, nnext):
self._next = nnext
class SingleLinkList:
# 定義鏈表
def __init__(self):
#初始化鏈表爲空
self._head = None
self._size = 0
def get_head(self):
#獲取鏈表頭
return self._head
def is_empty(self):
#判斷鏈表是否爲空
return self._head is None
def append(self, data):
#在鏈表尾部追加一個節點
temp = Node(data)
if self._head is None:
self._head = temp
else:
node = self._head
while node.get_next() is not None:
node = node.get_next()
node.set_next(temp)
self._size += 1
def remove(self, data):
# 在鏈表尾部刪除一個節點
node = self._head
prev = None
while node is not None:
if node.get_data() == data:
if not prev:
# 父節點爲None
self._head = node.get_next()
else:
prev.set_next(node.get_next())
break
else:
prev = node
node = node.get_next()
self._size -= 1
def PoliFill(image, polygon, color):
l = len(polygon)
Ymax=0
Ymin=np.shape(image)[1]
(width, height) = np.shape(image)
#求最大最小邊
for [x, y] in enumerate(polygon):
if y[1] < Ymin:
Ymin=y[1]
if y[1] > Ymax:
Ymax=y[1]
#初始化並建立NET表
NET = []
for i in range(height):
NET.append(None)
for i in range(Ymin, Ymax + 1):
for j in range(0, l):
if polygon[j][1]==i:
#左邊頂點y是否大於y0
if(polygon[(j-1+l)%l][1])>polygon[j][1]:
[x1,y1]=polygon[(j-1+l)%l]
[x0,y0]=polygon[j]
delta_x=(x1-x0)/(y1-y0)
NET[i] = SingleLinkList()
NET[i].append([x0, delta_x, y1])
# 右邊頂點y是否大於y0
if (polygon[(j+1+l)%l][1])>polygon[j][1]:
[x1, y1] = polygon[(j + 1 + l) % l]
[x0, y0] = polygon[j]
delta_x = (x1 - x0) / (y1 - y0)
if(NET[i] is not None):
NET[i].append([x0, delta_x, y1])
else:
NET[i] = SingleLinkList()
NET[i].append([x0, delta_x, y1])
#建立活性邊表
AET = SingleLinkList()
for y in range(Ymin , Ymax+1):
# 更新 start_x
if not AET.is_empty():
node = AET.get_head()
while True:
[start_x,delta_x,ymax] = node.get_data()
start_x += delta_x
node.set_data([start_x,delta_x,ymax])
node = node.get_next()
if node is None:
break
# 填充
if not AET.is_empty():
node = AET.get_head()
x_list = []
# 獲取所有交點的x座標
while True:
[start_x,_,_] = node.get_data()
x_list.append(start_x)
node = node.get_next()
if node is None:
break
# 排序
x_list.sort()
# 兩兩配對填充
for i in range(0,len(x_list),2):
x1 = x_list[i]
x2 = x_list[i+1]
for pixel in range(int(x1),int(x2)+1):
image[y][pixel] = color
if not AET.is_empty():
# 刪除非活性邊
node = AET.get_head()
while True:
[start_x,delta_x,ymax] = node.get_data()
if ymax == y:
AET.remove([start_x,delta_x,ymax])
node = node.get_next()
if node is None:
break
# 添加活性邊
if NET[y] is not None:
node = NET[y].get_head()
while True:
AET.append(node.get_data())
node = node.get_next()
if node is None:
break
if __name__ == '__main__':
image = np.ones([150, 150])
plt.xlim(0,150)
plt.ylim(0,150)
polygon = [
[20, 20],
[120, 20],
[70, 100],
[50, 80],
[30, 120],
[20, 50],
[50, 50]
]
PoliFill(image, polygon,False)
plt.imshow(image, plt.cm.magma)
plt.show()