1. 程式人生 > >漫水填充演算法

漫水填充演算法

所謂漫水填充演算法,是給定一個聯通域內的一個點,以此為起點找到這個聯通域的其餘所有點並將其填充為指定顏色的一種演算法。 
之所以稱之為漫水填充,是因為這種演算法就是模擬了漲水的過程,從一點開始,水流慢慢加大,直到漫過了全部區域。 
這個演算法的詳細介紹可以參考下面的連結。 
https://en.wikipedia.org/wiki/Flood_fill

這個演算法在我們尋找一片指定區域時非常有用。因此,我就花了點時間寫了個程式。我所實現的演算法類似於下面這張圖片中的方法,不過我是每次填充一行。

這裡寫圖片描述

下面這個 floodFill_Gray 函式用於填充灰度影象,講一個聯通域的灰度填充為 newVal。

static inline bool fillPoint_Gray( QImage &image, QPoint p, uchar val, uchar newVal, QStack<QPoint> &stack)
{
    int x = p.rx();
    int y = p.ry();
    uchar *line = image.scanLine(y);
    if( line[x] != val )
    {
        return false;
    }
    line[x] = newVal;
    if( y > 0 )
    {
        uchar *last = image.scanLine(y - 1
); if( last[x] == val ) { stack.push(QPoint(x, y - 1)); } } if( y < image.height() - 1 ) { uchar *next = image.scanLine(y + 1); if( next[x] == val ) { stack.push(QPoint(x, y + 1)); } } return true; } bool
floodFill_Gray(QImage &image, QPoint seedPoint, uchar newVal ) { if(image.format() != QImage::Format_Indexed8) { return false; } int width = image.width(); int height = image.height(); int x = seedPoint.rx(); int y = seedPoint.ry(); if(x < 0 || x >= width || y < 0 || y >= height) { return false; } int value = image.scanLine(y)[x]; QStack<QPoint> stack; stack.push(seedPoint); while(!stack.isEmpty()) { QPoint p = stack.pop(); bool ret = fillPoint_Gray(image, p, value, newVal, stack); bool ret2 = ret; y = p.ry(); x = p.rx(); x--; while(x >= 0 && ret) { ret = fillPoint_Gray(image, QPoint(x, y), value, newVal, stack); x--; } x = p.rx(); x ++; while(x < width && ret2) { ret2 = fillPoint_Gray(image, QPoint(x, y), value, newVal, stack); x++; } } return true; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

有時,由於有噪聲等影響,我們的待填充區域內的原始顏色並不完全一樣,這時可以採用模糊填充。所謂模糊填充就是我們設定一個顏色範圍,將這個顏色範圍內的點都認為是區域的內點。 
下面的程式碼就實現了這個功能。

static inline bool fillPoint_Gray( QImage &image, QPoint p, uchar low, uchar high, uchar newVal, QStack<QPoint> &stack)
{
    int x = p.rx();
    int y = p.ry();
    uchar *line = image.scanLine(y);
    if( line[x] < low || line[x] > high )
    {
        return false;
    }
    line[x] = newVal;
    if( y > 0 )
    {
        uchar *last = image.scanLine(y - 1);
        if( last[x] >= low && last[x] <= high )
        {
            stack.push(QPoint(x, y - 1));
        }
    }
    if( y < image.height() - 1 )
    {
        uchar *next = image.scanLine(y + 1);
        if( next[x] >= low && next[x] <= high )
        {
            stack.push(QPoint(x, y + 1));
        }
    }
    return true;
}

bool floodFill_Gray(QImage &image, QPoint seedPoint, int low, int high, uchar newVal )
{
    if(image.format() != QImage::Format_Indexed8)
    {
        return false;
    }
    low = qBound(0, low, 255);
    high = qBound(0, high, 255);
    if(low > high)
    {
        return false;
    }

    QStack<QPoint> stack;
    stack.push(seedPoint);

    int width = image.width();
    while(!stack.isEmpty())
    {
        QPoint p = stack.pop();
        bool ret = fillPoint_Gray(image, p, low, high, newVal, stack);
        bool ret2 = ret;
        int y = p.ry();
        int x = p.rx();
        x--;
        while(x >= 0 && ret)
        {
            ret = fillPoint_Gray(image, QPoint(x, y), low, high, newVal, stack);
            x--;
        }

        x = p.rx();
        x ++;
        while(x < width && ret2)
        {
            ret2 = fillPoint_Gray(image, QPoint(x, y), low, high, newVal, stack);
            x++;
        }
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

我還寫了一個用來填充彩色影象的。這個程式碼還比較簡單,暫時還沒實現模糊顏色的判別。之所以沒有寫,主要還是在RGB顏色空間中,不太方便度量兩個顏色值之間的距離。如果簡單的採用歐氏距離,則與我們眼睛對色彩的感受會有些出入。可能我們覺得顏色差別很大的兩個顏色的距離並不遠,而看起來差不多的兩個顏色可能距離挺大的。

static inline bool fillPoint_RGB32( QImage &image, QPoint p, QRgb val, QRgb newVal, QStack<QPoint> &stack)
{
    int x = p.rx();
    int y = p.ry();
    QRgb *line = (QRgb*)image.scanLine(y);
    if( line[x] != val )
    {
        return false;
    }
    line[x] = newVal;
    if( y > 0 )
    {
        QRgb *last = (QRgb*)image.scanLine(y - 1);
        if( last[x] == val )
        {
            stack.push(QPoint(x, y - 1));
        }
    }
    if( y < image.height() - 1 )
    {
        QRgb *next = (QRgb*)image.scanLine(y + 1);
        if( next[x] == val )
        {
            stack.push(QPoint(x, y + 1));
        }
    }
    return true;
}

bool floodFill_RGB32(QImage &image, QPoint seedPoint, QRgb newVal )
{
    qDebug() << image.format();
    if(image.format() != QImage::Format_RGBA8888 && image.format() != QImage::Format_ARGB32 )
    {
        return false;
    }
    int width = image.width();
    int height = image.height();
    int x = seedPoint.rx();
    int y = seedPoint.ry();
    if(x < 0 || x >= width || y < 0 || y >= height)
    {
        return false;
    }
    QRgb value = image.pixel(x, y);
    QStack<QPoint> stack;
    stack.push(seedPoint);

    while(!stack.isEmpty())
    {
        QPoint p = stack.pop();
        bool ret = fillPoint_RGB32(image, p, value, newVal, stack);
        bool ret2 = ret;
        y = p.ry();
        x = p.rx();
        x--;
        while(x >= 0 && ret)
        {
            ret = fillPoint_RGB32(image, QPoint(x, y), value, newVal, stack);
            x--;
        }

        x = p.rx();
        x ++;
        while(x < width && ret2)
        {
            ret2 = fillPoint_RGB32(image, QPoint(x, y), value, newVal, stack);
            x++;
        }
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

下面是一個測試影象。

這裡寫圖片描述

我要將其中最大的紅色的聯通區域填充為藍色。下面是填充後的結果。 
這裡寫圖片描述

但是這個演算法有一個注意事項,就是它只將上下左右相鄰的畫素視為連同,斜向相鄰的畫素認為是不連通的。之所以這樣設計是為了處理下面這種情況。如果斜向也認為是連同的話那麼下圖中所有的白色區域就都是聯通的了,這和我們直觀的感覺是不相同的。

這裡寫圖片描述

當然,這樣處理後,黑色區域中有兩個孤立點就被程式認為不與其他黑色區域聯通了。這個與我們直觀感覺也是有差異的。總之,這兩種情況無法兼顧。