一、區域填充概念
區域:指已經表示成點陣形式的填充圖形,是象素的集合。
區域填充:將區域內的一點(常稱種子點)賦予給定顏色,然后將這種顏色擴展到整個區域內的過程。
區域填充算法要求區域是連通的,因為只有在連通區域中,才可能將種子點的顏色擴展到區域內的其它點。
1、區域有兩種表示形式
- 內點表示:
- 枚舉出區域內部的所有象素
- 內部所有象素着同一個顏色
- 邊界像素着與內部象素不同的顏色。
- 邊界表示:
- 枚舉出區域外部的所有象素
- 邊界上的所有象素着同一個顏色
- 內部像素着與邊界象素不同的顏色。
2、區域連通
- 四向連通區域:從區域上一點出發可通過上、下、左、右四個方向移動的組合,在不越出區域的前提下,到達區域內的任意象素。
- 八向連通區域:從區域上一點出發可通過上、下、左、右、左上、右上、左下、右下八個方向移動的組合,在不越出區域的前提下,到達區域內的任意象素。
- 四連通與八連通區域的區別
- 連通性:四連通可以看作八連通的自己,但是對邊界有要求
二、簡單種子填充算法
1、基本思想
給定區域G一種子點(x, y),首先判斷該點是否是區域內的一點,如果是,則將該點填充為新的顏色,然后將該點周圍的四個點(四連通)或八個點(八連通)作為新的種子點進行同樣的處理,通過這種擴散完成對整個區域的填充。
這里給出一個四連通的種子填充算法(區域填充遞歸算法),使用【棧結構】來實現
原理算法原理如下:種子像素入棧,當【棧非空】時重復如下三步:
2、算法代碼
這里給出八連通的種子填充算法的代碼:
void flood_fill_8(int[] pixels, int x, int y, int old_color, int new_color) {
if (x < w && x > 0 && y < h && y > 0) {
// 如果是舊的顏色而且還沒有給他填充過
if (pixels[y * w + x] == old_color) {
// 填充為新的顏色
pixels[y * w + x]== new_color);
// 遞歸
flood_fill_8(pixels, x, y + 1, old_color, new_color);
flood_fill_8(pixels, x, y - 1, old_color, new_color);
flood_fill_8(pixels, x - 1, y, old_color, new_color);
flood_fill_8(pixels, x + 1, y, old_color, new_color);
flood_fill_8(pixels, x + 1, y + 1, old_color, new_color);
flood_fill_8(pixels, x + 1, y - 1, old_color, new_color);
flood_fill_8(pixels, x - 1, y + 1, old_color, new_color);
flood_fill_8(pixels, x - 1, y - 1, old_color, new_color);
}
}
}
3、OpenCV實現
import cv2
def seed_fill(img):
ret, img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)
label = 100
stack_list = []
h, w = img.shape
for i in range(1, h-1, 1):
for j in range(1, w-1, 1):
if (img[i][j] == 255):
img[i][j] = label
stack_list.append((i, j))
while len(stack_list) != 0:
cur_i = stack_list[-1][0]
cur_j = stack_list[-1][1]
img[cur_i][cur_j] = label
stack_list.remove(stack_list[-1])
# 四鄰域,可改為八鄰域
if (img[cur_i-1][cur_j] == 255):
stack_list.append((cur_i-1, cur_j))
if (img[cur_i][cur_j-1] == 255):
stack_list.append((cur_i, cur_j-1))
if (img[cur_i+1][cur_j] == 255):
stack_list.append((cur_i+1, cur_j))
if (img[cur_i][cur_j+1] == 255):
stack_list.append((cur_i, cur_j+1))
cv2.imwrite('./result.jpg', img)
cv2.imshow('img', img)
cv2.waitKey()
if __name__ == '__main__':
img = cv2.imread('./test.jpeg', 0)
seed_fill(img)
4、簡單種子填充算法的優點和缺點
優點:
- 該算法也可以填充有孔區域
缺點:
- 有些像素會多次入棧,降低算法效率,棧結構占空間
- 遞歸執行,算法簡單,但效率不高,區域內每一像素都要進/出棧,費時費內存
- 改進算法,減少遞歸次數,提高效率
三、掃描線種子填充算法
- 目標:減少遞歸層次
- 適用於邊界表示的4連通區間
1、基本思想
在任意不間斷區間中只取一個種子像素(不間斷區間指在一條掃描線上一組相鄰元素),填充當前掃描線上的該段區間;然后確定與這一區段相鄰的上下兩條掃描線上位於區域內的區段,並依次把它們保存起來,反復進行這個過程,直到所保存的各個區段都填充完畢。
2、算法步驟
- 初始化:將算法設置的堆棧置為空。將給定的種子點\((x, y)\)壓入堆棧
- 出棧:如果堆棧為空,算法結束;否則取棧頂元素\((x, y)\)作為種子點
- 區段填充:從種子點\((x, y)\)開始,沿縱坐標為y的當前掃描線向左右兩個方向逐個像素用新的顏色值進行填充,直到邊界為止即象素顏色等於邊界色。設區間兩邊界的橫坐標分別為xleft 和xright。
- 在與當前掃描線相鄰的上下兩條掃描線上,以區間[xleft, xright]為搜索范圍,求出需要填充的各小區間,把各小區間中最右邊的點並作為種子點壓入堆棧,轉到步驟2。
3、算法的關鍵原則
-
搜索原則:
從前一個填充的區間(邊界之間的范圍xleft, xright)作為后一條掃描線種子點尋找的范圍。
-
填充原則:
從種子點往左,右填,填到邊界
4、實例
上述算法的描述過於抽象,直接看演示
5、算法
Stack stack = new Stack(); //堆棧 pixel_stack初始化
Stack.push(point); //(x,y)是給定的種子像素
while (!stack.empty()) {
p = (Point)(stack.pop()); //出棧,從堆棧中取一像素作種子像素
x = p.x;
y = p.y;
savex = x; //保存種子點的橫坐標x的值
while (pixels[y * w + x] != boundary_color) {
pixels[y * w + x] = new_color;
x++;
} //從種子像素開始向右填充到邊界
xright = x–1; //保存線段的右端點
x = savex–1; //設定種子點往左填充的起點
while (pixels[y * w + x] != boundary_color) {
pixels[y * w + x] = new_color;
x = x–1;
}
//從種子像素開始向左填充到邊界,以上兩步完成區間填充。
xleft = x + 1; //保存線段的左端點,加1是因為前面 循環時多減一次
x = xleft; //起點是上次的左端點
y = y + 1; //開始處理上一條掃描線
while (x <= xright) { //在上一條掃描線上檢查是否需要填充
span_need_fill = false; //先設定為不需要填充
while (pixels[y * w + x] == old_color && x <= xright) {
//待填充的線段
span_need_fill = true; //發現有舊象素,需要填充
x = x + 1;
} //待填充的線段處理完,即遇到邊界色,!=old_color跳出
if (span_need_fill) { //如果區間需要填充,則將其右端點作為種子點壓進堆棧
p = new Point(x - 1, y);
stack.push(p); //進棧
span_need_fill = false;
}
//繼續向右檢查以防有遺漏
while (pixels[y * w + x] != old_color && x <= xright)
x = x + 1;
} //在上一條掃描線上檢查完
x=xleft;
y = y–2; //形成下一條掃描線的y值
//在下一條掃描線上從左向右檢查位於區間[xleft,xright]上的像素,其方法與在上一條掃描線上檢查的情況完全一樣,見書。
} //出棧完
6、OpenCV實現
# coding:utf8
import cv2
import numpy as np
frame = cv2.cvtColor(cv2.imread(
'fr_seg_open22_result.pgm'), cv2.COLOR_BGR2GRAY)
ret, frame = cv2.threshold(frame, 127, 255, cv2.THRESH_BINARY)
contour_index = 1
def scan_line_seed(i, j):
stack = []
return_value = 0
if frame[i, j] == 255:
stack.append((i, j))
while len(stack) != 0:
seed = stack.pop()
x, y = seed
while frame[x, y] == 255:
frame[x, y] = contour_index * 10
x += 1
x_right = x-1
x, y = seed
x -= 1
while frame[x, y] == 255:
frame[x, y] = contour_index * 10
x -= 1
x_left = x + 1
# 如果左右端點為空則加入種子點
if frame[x_left, y - 1] == 255:
stack.append((x_left, y-1))
if frame[x_left, y + 1] == 255:
stack.append((x_left, y+1))
if frame[x_right, y - 1] == 255:
stack.append((x_right, y-1))
if frame[x_right, y + 1] == 255:
stack.append((x_right, y+1))
for x in range(x_left, x_right+1):
# 上左
if frame[x, y-1] == 255 and frame[x-1, y-1] != 255:
stack.append((x, y-1))
# 下左
if frame[x, y+1] == 255 and frame[x-1, y+1] != 255:
stack.append((x, y+1))
# 上右
if frame[x, y-1] != 255 and frame[x-1, y-1] == 255:
stack.append((x-1, y-1))
# 下右
if frame[x, y+1] != 255 and frame[x-1, y+1] == 255:
stack.append((x-1, y+1))
return_value = 1
return return_value
for x in range(frame.shape[0]):
for y in range(frame.shape[1]):
if scan_line_seed(x, y) == 1:
contour_index = contour_index + 1
print contour_index
cv2.namedWindow('frame', cv2.WINDOW_NORMAL)
cv2.imshow('frame', frame)
cv2.imwrite('numbered_intel.pgm', frame)
cv2.waitKey(0)