圖像填充算法


封閉連通域的圖像填充是個常見的算法,最近有機會接觸到大圖像的例子,做一下總結。

    這類問題最基本的算法是種子填充。即先給出封閉區域內的一點,從這點出發搜索鄰域,只要不到邊界,就把相鄰點納入連通域,賦予填充色。邊界的判斷比較靈活,可以使用固定顏色,也可以用一定閾值的色彩容差,類似photoshop中的魔棒。其他更復雜的計算自然也可以。

    鄰域的搜索是填充的重點。最簡單的算法就是遞歸,寫出來也就幾行代碼,像下面的樣子:

FillShape(int   x,   int   y)
{
    if( !IsBrim(x,y) )
    {
        SetPixel(x,y);
        FillShape(x+1,   y);
        FillShape(x-1,   y);
        FillShape(x,   y+1);
        FillShape(x,   y-1);
     }
}

只要當前點不是邊緣,就填充,然后遞歸相鄰的點。這個算法是4連域,改成8連域也是很簡單的事。

上述算法簡單直觀,但存在一些問題。首先它需要逐點搜索,效率不高。其次,在實際的編程中,遞歸運算是很消耗資源的事情。函數的遞歸需要壓棧,即把當前函數的地址和狀態量放到系統的堆棧中,進入下一個狀態。實際操作系統的資源總是有限的,如果遞歸的層數太多,系統的堆棧會溢出,這個是用戶控制不了的。這樣當我們處理稍大一些的圖像時,往往會出現堆棧溢出的錯誤,使程序無法運行。

    針對這些問題,我們先做修改,把填充目標由點改為線。也即使用線掃描的方式搜索連通域。進入一個種子點后,在X方向從左向右逐點搜索連通點,填充,直到遇到中斷點停止,然后從這些連通點開始,取上下點遞歸,偽代碼見下:

FillShape(int   x,   int   y)
{
    if( !IsBrim(x,y) )
    {
// 向左填充,FillToLeft(x,y);
        nleft = x;
        while(!IsBrim(nleft,y))
       {
            SetPixel(nleft,y);
            nleft--;
        }
// 向右填充,FillToRight(x,y);
        nright = x;
        while(!IsBrim(nright,y))
       {
            SetPixel(nright,y);
            nright++;
        }
// 上下層的點遞歸
        for(i=nleft+1;i<right;i++)
        {
            FillShape(x,y+1);
            FillShape(x,y-1);
        }
     }
}

這樣做減少了遞歸次數,提高了效率。但是還沒有解決遞歸的根本缺陷,如果遇到大區域填充,仍然可能出現堆棧溢出。根本的解決之道是放棄遞歸。研究表明,任何遞歸算法都是可以修改為使用循環的非遞歸算法。修改的關鍵是兩步:一、設計並實現一個自己的棧,保存原來遞歸出現的中間狀態量,這樣資源的利用率大大提高。二、在循環處理代碼中,至少實現起始層和下一層遞歸的功能,這樣才能把中間狀態壓入自己的棧中以備處理。
    經過修改的非遞歸算法如下:(為了簡潔起見,我把具體的代碼用函數名代替):

// 構建堆棧代碼
PushStack();// 壓棧
PopStack();// 出棧
SetStackEmpty();// 清空棧
int IsStackEmpty();// 判斷棧是否為空
 
FillShape(int   x,   int   y)
{
    FillToLeft(x,y);
    FillToRight(x,y);
    SetStackEmpty();
    PushStack();
    while(!IsStackEmpty())
    {
        PopStack();
        xLeft = getstack_left();
        xRight = getstack_right();
        // 處理上邊
        y=y-1;
        FillToLeft(xLeft,y);
        i=xLeft;
        while( i <= xRight)
        {
            FillToRight(i,y);
            PushStack();
        }
        // 處理下邊
        y=y+2;
        FillToLeft(xLeft,y);
        i=xLeft;
        while( i <= xRight)
        {
            FillToRight(i,y);
            PushStack();
        }
    }
}

這段代碼的思路是,仍然使用掃描線進行鄰域填充,使用自己的堆棧來記錄中間狀態。完成一條掃描線的填充后,把前一個掃描線狀態壓入棧,彈出時,向上下搜索相鄰的掃描線段,填充,壓棧。直到棧中狀態都被彈出處理為止。

    至此,填充算法提高了效率,也避免了系統堆棧溢出。而遞歸算法,更適合用於原理說明和較少層次的運算,對圖像的處理應當慎用並修改之。

http://blog.sina.com.cn/s/blog_6fa6b8fe0100r4gn.html


免責聲明!

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



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