我的洪水填充实施有什么问题?

What's wrong with my flood fill implementation?

我在Java中写了一个flood fill实现(代码见下文)。我想用它来填充以下多边形(黄色区域)的内部:

一个点 (.) 代表一个白色像素,X 一个黑色像素和 + 当前迭代期间变量 n 的位置。

这个实现有问题,因为算法用 X 填充了整个网格,而不仅仅是多边形内的 space:

您可以在 this PDF file 中查看网格在各种迭代中的状态。第一页显示调用 floodFill 方法之前的网格,所有后续的 - 调用 visualizer.visualize(grid, n);.

时的状态

我的算法(方法 floodFill)有什么问题,我该如何解决?

代码

protected void floodFill(final FloodFillGridPoint[][] grid,
    final FloodFillGridPoint centroid, IFloodFillGridVisualizer visualizer) {
    final Queue<FloodFillGridPoint> queue = new LinkedList<>();
    queue.add(centroid);
    while (!queue.isEmpty()) {
        final FloodFillGridPoint n = queue.poll();
        if ((n.color() == COLOR_WHITE) && !n.isProcessed()) {
            n.markProcessed();
            final FloodFillGridPoint west = getWestBoundary(grid, n);
            final FloodFillGridPoint east = getEastBoundary(grid, n);
            for (int x=west.x(); x <= east.x(); x++) {
                final FloodFillGridPoint n2 = getPoint(grid, x, n.y());
                n2.setColor(COLOR_BLACK);

                final FloodFillGridPoint n2North = getPoint(grid, n2.x(),
                    n2.y()-1);
                if ((n2North != null) && (n2North.color() == COLOR_WHITE)) {
                    queue.add(n2North);
                }

                final FloodFillGridPoint n2South = getPoint(grid, n2.x(),
                    n2.y()+1);
                if ((n2South != null) && (n2South.color() == COLOR_WHITE)) {
                    queue.add(n2South);
                }
            }
            visualizer.visualize(grid, n);
        }
    }
}

private FloodFillGridPoint getEastBoundary(final FloodFillGridPoint[][] grid, final FloodFillGridPoint n) {
    FloodFillGridPoint east = n;
    while (east.color() == COLOR_WHITE) {
        final FloodFillGridPoint newNode =
            getPoint(grid, east.x()+1, east.y());
        if (newNode == null) {
            break;
        }
        east = newNode;
    }
    return east;
}

private FloodFillGridPoint getWestBoundary(final FloodFillGridPoint[][] grid, final FloodFillGridPoint n) {
    FloodFillGridPoint west = n;
    while (west.color() == COLOR_WHITE) {
        final FloodFillGridPoint newNode =
            getPoint(grid, west.x()-1, west.y());
        if (newNode == null) {
            break;
        }
        west = newNode;
    }
    return west;
}

private FloodFillGridPoint getPoint(final FloodFillGridPoint[][] grid,
    final int x, final int y) {
    if (x < 0) {
        return null;
    }
    if (y < 0) {
        return null;
    }
    if (x >= grid.length) {
        return null;
    }
    if (y >= grid[0].length) {
        return null;
    }

    return grid[x][y];
}


public class FloodFillGridPoint {
    private final int x;
    private final int y;
    private int color;
    private boolean processed = false;

    public FloodFillGridPoint(final int x, final int y, final int color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }
    public void setColor(final int ncolor) {
        this.color = ncolor;
    }
    public int color() {
        return this.color;
    }
    public void markProcessed() {
        this.processed = true;
    }
    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }
    public boolean isProcessed() {
        return this.processed;
    }

}

算法将其视为边界上的一个洞:

..........
....X.....
..XX.XXX..
..X....X..
..X....X..
..X....X..
..X....X..
..XXXXXX..
..........
..........

你看到顶部的裂缝了吗?洪水从那里溢出, 并最终覆盖整个网格。

鉴于此网格,它工作正常,并且能够从内部正方形的内部或外部正确填充,它做了正确的事情:

..........
..........
..XXXXXX..
..X....X..
..X....X..
..X....X..
..X....X..
..XXXXXX..
..........
..........

因此,如果您想将第一个示例中的正方形视为 已关闭, 并防止洪水斜向溢出, 然后检查您的实施并进行必要的更改。

不应将边界像素南边和北边的像素放入队列,因为这可能会导致 "diagonal steps",如 PDF 第 4 页所示。 IE。你可能想试试

for (int x=west.x()+1; x <= east.x()-1; x++) {

从递归中排除边界像素。

其他人关于您的对角线问题的一部分是正确的 - 您的东西边界是实际出现黑色的点,从这些点之一向北或向南可能会导致您到达白色,实际上在容器外面。我自己快速 运行 似乎也表明了其他问题 - 我注意到如果您将东西边界的定义调整为仅指向 [=22= 的 left/right ] 边界,它不会填满一些容器。这是对您的算法的调整,它既使算法更简单(通过避免明确找到 east/west 边界)又更容易看出逻辑上是正确的。对于对角角问题也是安全的:

protected void floodFill(final FloodFillGridPoint[][] grid,
        final FloodFillGridPoint centroid, IFloodFillGridVisualizer visualizer) {
        final Queue<FloodFillGridPoint> queue = new LinkedList<>();
        queue.add(centroid);
        while (!queue.isEmpty()) {
            final FloodFillGridPoint n = queue.poll();
            if ((n.color() == COLOR_WHITE) && !n.isProcessed()) {
                n.markProcessed();
                n.setColor(COLOR_BLACK);

                final FloodFillGridPoint west = getPoint(grid,n.x()-1,n.y());
                final FloodFillGridPoint east = getPoint(grid,n.x()+1,n.y());
                final FloodFillGridPoint north = getPoint(grid,n.x(),n.y()-1);
                final FloodFillGridPoint south = getPoint(grid,n.x(),n.y()+1);
                for(FloodFillGridPoint neighbor : Arrays.asList(west,east,north,south)){
                    if(neighbor!=null && neighbor.color()!=COLOR_BLACK){
                        queue.add(neighbor);
                    }
                }
                visualizer.visualize(grid, n);
            }
        }
    }

例子的结果运行:

.........    
.........    
....x....    
...x.x...    
..x.+.x..    
...x.x...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...xxx...    
..x+x.x..    
...x.x...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...xxx...    
..x.x+x..    
...xxx...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...x+x...    
..xxx.x..    
...xxx...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...xxx...    
..xxxxx..    
...x+x...    
....x....    
.........    
.........

------------------------------------------------

*此外,请注意:您已经定义了 两个 考虑点位置的位置。一个位于网格数据结构中的实际位置(由网格中的索引定义),另一个位于点本身(在其 x 和 y 字段中)。软件中有一个原则 "Don't Repeat Yourself" (DRY),意思是每条信息都应该有一个表示。在像这样的简单情况下,这可能是值得的,但我认为值得一提,因为我遇到了一个错误,其中点的内部 (x,y) 是从网格转置的——这导致了各种奇怪的情况。如果您仍然遇到问题,请自行检查。如果您的其他约束允许,并重构重复项。