1. 程式人生 > >影象分割經典演算法--《泛洪演算法》(Flood Fill)

影象分割經典演算法--《泛洪演算法》(Flood Fill)

1.演算法介紹

泛洪演算法——Flood Fill,(也稱為種子填充——Seed Fill)是一種演算法,用於確定連線到多維陣列中給定節點的區域。 它被用在油漆程式的“桶”填充工具中,用於填充具有不同顏色的連線的,顏色相似的區域,並且在諸如圍棋(Go)和掃雷(Minesweeper)之類的遊戲中用於確定哪些塊被清除。泛洪演算法的基本原理就是從一個畫素點出發,以此向周邊的畫素點擴充著色,直到圖形的邊界。 畫圖中油漆桶的使用

2.演算法分類

泛洪填充演算法採用三個引數:起始節點(start node),目標顏色(target color)和替換顏色(replacement color)。 該演算法查詢陣列中通過目標顏色的路徑連線到起始節點的所有節點,並將它們更改為替換顏色。 可以通過多種方式構建泛洪填充演算法,但它們都明確地或隱式地使用佇列或堆疊資料結構。 根據我們是否考慮在連線角落處接觸的節點,我們有兩種變體:分別為八鄰域泛洪(八種方向)和四鄰域泛洪(四種方向)。

(1)四鄰域泛洪

四鄰域泛洪

傳統遞迴方式

傳統的四鄰域泛洪演算法的思想是對於畫素點(x,y),將其著色之後將其周圍的上下左右四個點分別進行著色。遞迴實現方式如下:

偽程式碼表示

Flood-fill (node, target-color, replacement-color):
 1. If target-color is equal to replacement-color, return.
 2. If the color of node is not equal to target-color, return.
 3. Set the color of node to replacement-color.
 4. Perform Flood-fill (one step to the south of node, target-color, replacement-color).
    Perform Flood-fill (one step to the north of node, target-color, replacement-color).
    Perform Flood-fill (one step to the west of node, target-color, replacement-color).
    Perform Flood-fill (one step to the east of node, target-color, replacement-color).
 5. Return.

函式實現

void floodFill4(int x, int y, int newColor, int oldColor)
{
  if(x >= 0 && x < w && y >= 0 && y < h && screenBuffer[y][x][y] == oldColor && screenBuffer[x] != newColor)
  {
    screenBuffer[y][x] = newColor; 
    floodFill4(x + 1, y    , newColor, oldColor);
    floodFill4(x - 1, y    , newColor, oldColor);
    floodFill4(x    , y + 1, newColor, oldColor);
    floodFill4(x    , y - 1, newColor, oldColor);
  }
}

四鄰域泛洪演算法改進

遞迴方式非常消耗記憶體,若所需著色的面積非常大,會導致溢位現象。因此,下面我們將介紹四鄰域泛洪演算法的非遞迴方式。 這裡我們使用一個棧(或佇列)來儲存未被著色的點,然後依次將存在於著色空間內的點的上下左右的點加入棧(或佇列),依次著色直到棧(或佇列)為空。基於棧(或佇列)的顯式實現(有時稱為“Forest Fire演算法”)在下面的虛擬碼中顯示。 它類似於簡單的遞迴解決方案,除了它不是進行遞迴呼叫,而是將節點推送到棧(或佇列)中以供使用:

偽程式碼表示(Queue Way)

Flood-fill (node, target-color, replacement-color):
  1. If target-color is equal to replacement-color, return.
  2. If color of node is not equal to target-color, return.
  3. Set Q to the empty Queue.
  4. Set the color of node to replacement-color.
  5. Add node to the end of Q.
  6. While Q is not empty:
  7.     Set n equal to the first element of Q.
  8.     Remove first element from Q.
  9.     If the color of the node to the west of n is target-color,
             set the color of that node to replacement-color and add that node to the end of Q.
 10.     If the color of the node to the east of n is target-color,
             set the color of that node to replacement-color and add that node to the end of Q.
 11.     If the color of the node to the north of n is target-color,
             set the color of that node to replacement-color and add that node to the end of Q.
 12.     If the color of the node to the south of n is target-color,
             set the color of that node to replacement-color and add that node to the end of Q.
 13. Continue looping until Q is exhausted.
 14. Return.

程式碼實現(Stack Way)

void floodFill4Stack(int x, int y, int newColor, int oldColor)
{
  if(newColor == oldColor) return; //avoid infinite loop
  emptyStack();
 
  static const int dx[4] = {0, 1, 0, -1}; 
  static const int dy[4] = {-1, 0, 1, 0}; 
 
  if(!push(x, y)) return;
  while(pop(x, y))
  {
    screenBuffer[y][x] = newColor;
    for(int i = 0; i < 4; i++) {
      int nx = x + dx[i];
      int ny = y + dy[i];
      if(nx > 0 && nx < w && ny > 0 && ny < h && screenBuffer[ny][nx] == oldColor) {
        if(!push(nx, ny)) return;
      }
    }
  }
}

(2)八鄰域泛洪

八鄰域泛洪 八鄰域演算法是將一個畫素點的上下左右,左上,左下,右上,右下都進行著色。

遞迴方式

void floodFill8(int x, int y, int newColor, int oldColor)
{
  if(x >= 0 && x < w && y >= 0 && y < h && screenBuffer[y][x][y] == oldColor && screenBuffer[x] != newColor)
  {
    screenBuffer[y][x] = newColor;
 
    floodFill8(x + 1, y    , newColor, oldColor);
    floodFill8(x - 1, y    , newColor, oldColor);
    floodFill8(x    , y + 1, newColor, oldColor);
    floodFill8(x    , y - 1, newColor, oldColor);
    floodFill8(x + 1, y + 1, newColor, oldColor);
    floodFill8(x - 1, y - 1, newColor, oldColor);
    floodFill8(x - 1, y + 1, newColor, oldColor);
    floodFill8(x + 1, y - 1, newColor, oldColor);
  }
}

非遞迴方式(借用棧的方式)

void floodFill8Stack(int x, int y, int newColor, int oldColor)
{
  if(newColor == oldColor) return; //avoid infinite loop
  emptyStack();
 
  static const int dx[8] = {0, 1, 1, 1, 0, -1, -1, -1};
  static const int dy[8] = {-1, -1, 0, 1, 1, 1, 0, -1}; 
 
  if(!push(x, y)) return;
  while(pop(x, y))
  {
    screenBuffer[y][x] = newColor;
    for(int i = 0; i < 8; i++) {
      int nx = x + dx[i];
      int ny = y + dy[i];
      if(nx > 0 && nx < w && ny > 0 && ny < h && screenBuffer[ny][nx] == oldColor) {
        if(!push(nx, ny)) return;
      }
    }
  }
}

(3)描繪線演算法(Scanline Fill)

利用填充線來加速演算法, 它不是在堆疊上推動每個潛在的未來畫素座標,而是檢查相鄰線(前一個和下一個)以找到可能在未來通過中填充的相鄰段, 線段的座標(開始或結束)被推到堆疊上。 在大多數情況下,該掃描線演算法至少比每畫素演算法快一個數量級。 描繪線演算法 該演算法的過程是:先將一條線上的畫素點進行著色,然後依次向上下擴張,直到著色完成。演算法實現如下:

遞迴方式

void floodFillScanline(int x, int y, int newColor, int oldColor){
	if(newColor==oldColor) return;
	if(screen[x][y]!=oldColor) return;
	int x1=x;
	while(x1<w&&screen[x1][y]==oldColor){
	    screen[x1][y]=newColor;
	    x1++;
	}
	x1=x-1;
	while(x1>=0&&screen[x1][y]==oldColor){
	    screen[x1][y]=newColor;
	    x1--;
	}
	x1=x;
	while(x1<w&&screen[x1][y]==newColor){
	    if(y<h-1&&screen[x1][y+1]==oldColor) floodFillScanline(x1,y+1,newColor,oldColor);
	    x1++;
	}
	x1=x-1;
	while(x1>0&&screen[x1][y]==newColor){
	    if(y>0&&screen[x1][y+1]==oldColor) floodFillScanline(x1,y+1,newColor,oldColor);
	    x1--;
	}
	x1=x;
	while(x1<w&&screen[x1][y]==newColor){
	    if(y<h-1&&screen[x1][y-1]==oldColor) floodFillScanline(x1,y+1,newColor,oldColor);
	    x1++;
	}
	x1=x-1;
	while(x1>0&&screen[x1][y]==newColor){
	    if(y>0&&screen[x1][y-1]==oldColor) floodFillScanline(x1,y+1,newColor,oldColor);
	    x1--;
	}
}

非遞迴方式

void floodFillScanline(int x, int y, int newColor, int oldColor){
	if(newColor==oldColor) return;
	if(screen[x][y]!=oldColor) return;
	emptyStack();
	int x1;
	bool spanAbove, spanBelow;
	if(!push(x,y)) return;
	while(pop(x,y)){
	    x1=x;
	    while(x1>0&&screen[x1][y]==oldColor) x1--;
	    x1++;
	    spanAbove = spanBelow = 0;
	    while(x1<w&&screen[x1][y]==oldColor){
	        screen[x1][y]=newColor;
	        if(!spanAbove&&y>0&&screen[x1][y-1]==oldColor){
	            if(!push(x1,y-1)) return;
	            spanAbove=1;
	        }
	        else if(spanAbove&&y>0&&screen[x1][y-1]!=oldColor){
	            spanAbove=1;
	        }
	        if(!spanBelow&&y<h-1&&screen[x1][y+1]==oldColor){
	            if(!push(x1,y+1)) return;
	            spanBelow=1;
	        }
	        else if(spanBelow&&y<h-1&&screen[x1][y+1]!=oldColor){
	            spanBelow=1;
	        }
	        x1++;
	    }
	}
}

(4)大規模行為(Large-scale behaviour)

用於控制洪水填充的主要技術將是以資料為中心或以流程為中心。

以資料為中心(data-centric)

以資料為中心的方法可以使用堆疊或佇列來跟蹤需要檢查的種子畫素。使用鄰接技術和佇列作為其種子畫素儲存的4向泛洪填充演算法產生擴充套件的菱形填充。 效率:每個畫素填充檢查4個畫素(8個填充為8個畫素)。 使用佇列

以流程為中心(process-centric)

以流程為中心的演算法必須使用堆疊。使用鄰接技術和堆疊作為其種子畫素儲存的4路泛洪填充演算法產生具有“後續填充的間隙”行為的線性填充。 這種方法尤其適用於較舊的8位計算機遊戲。 效率:每個畫素填充檢查4個畫素(8個填充為8個畫素)。 使用棧

3.參考

以下為本部落格的參考內容: