區域填充算法


一、區域填充概念

區域:指已經表示成點陣形式的填充圖形,是象素的集合

區域填充:將區域內的一點(常稱種子點)賦予給定顏色,然后將這種顏色擴展到整個區域內的過程。

區域填充算法要求區域是連通的,因為只有在連通區域中,才可能將種子點的顏色擴展到區域內的其它點。

1、區域有兩種表示形式

img

  1. 內點表示
    • 枚舉出區域內部的所有象素
    • 內部所有象素着同一個顏色
    • 邊界像素着與內部象素不同的顏色。
  2. 邊界表示
    • 枚舉出區域外部的所有象素
    • 邊界上的所有象素着同一個顏色
    • 內部像素着與邊界象素不同的顏色。

2、區域連通

img

  1. 四向連通區域:從區域上一點出發可通過上、下、左、右四個方向移動的組合,在不越出區域的前提下,到達區域內的任意象素
  2. 八向連通區域:從區域上一點出發可通過上、下、左、右、左上、右上、左下、右下八個方向移動的組合,在不越出區域的前提下,到達區域內的任意象素
  3. 四連通與八連通區域的區別
    • 連通性:四連通可以看作八連通的自己,但是對邊界有要求

二、簡單種子填充算法

1、基本思想

給定區域G一種子點(x, y),首先判斷該點是否是區域內的一點,如果是,則將該點填充為新的顏色,然后將該點周圍的四個點(四連通)或八個點(八連通)作為新的種子點進行同樣的處理,通過這種擴散完成對整個區域的填充。

這里給出一個四連通的種子填充算法(區域填充遞歸算法),使用【棧結構】來實現
原理算法原理如下:種子像素入棧,當【棧非空】時重復如下三步:

img

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、簡單種子填充算法的優點和缺點

優點:

  1. 該算法也可以填充有孔區域

缺點:

  1. 有些像素會多次入棧,降低算法效率,棧結構占空間
  2. 遞歸執行,算法簡單,但效率不高,區域內每一像素都要進/出棧,費時費內存
  3. 改進算法,減少遞歸次數,提高效率

三、掃描線種子填充算法

  • 目標:減少遞歸層次
  • 適用於邊界表示的4連通區間

1、基本思想

任意不間斷區間中只取一個種子像素(不間斷區間指在一條掃描線上一組相鄰元素),填充當前掃描線上的該段區間;然后確定與這一區段相鄰的上下兩條掃描線上位於區域內的區段,並依次把它們保存起來,反復進行這個過程,直到所保存的各個區段都填充完畢。

2、算法步驟

  1. 初始化:將算法設置的堆棧置為空。將給定的種子點\((x, y)\)壓入堆棧
  2. 出棧:如果堆棧為空,算法結束;否則取棧頂元素\((x, y)\)作為種子點
  3. 區段填充:從種子點\((x, y)\)開始,沿縱坐標為y的當前掃描線向左右兩個方向逐個像素用新的顏色值進行填充,直到邊界為止即象素顏色等於邊界色。設區間兩邊界的橫坐標分別為xleftxright
  4. 在與當前掃描線相鄰的上下兩條掃描線上,以區間[xleft, xright]為搜索范圍,求出需要填充的各小區間,把各小區間中最右邊的點並作為種子點壓入堆棧,轉到步驟2。

3、算法的關鍵原則

img

  1. 搜索原則:

    從前一個填充的區間(邊界之間的范圍xleft, xright)作為后一條掃描線種子點尋找的范圍。

  2. 填充原則:

    從種子點往左,右填,填到邊界

4、實例

上述算法的描述過於抽象,直接看演示

img

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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM