【问题标题】:Flood fill using a stack使用堆栈进行洪水填充
【发布时间】:2010-05-06 17:43:43
【问题描述】:

我正在使用 Java 中的递归 Flood 填充算法来填充图像的某些区域。 对于非常小的图像,它可以正常工作,但是当图像变大时,JVM 会给我一个堆栈溢出错误。

这就是为什么我必须用我自己的堆栈使用 Flood Fill 重新实现该方法的原因。 (我读到这是在这种情况下最好的方法)

谁能解释一下如何编码? (如果手头没有代码,有算法的伪代码就可以了)

我在网上看了很多,但不是很了解。

编辑:我添加了递归代码

public void floodFill(int x, int y, Color targetColor,Color replacementColor) {

    if (img.getRGB(x, y) != targetColor.getRGB()) return;

    img.setRGB(x, y, replacementColor.getRGB());
    floodFill(x - 1, y, targetColor, replacementColor);
    floodFill(x + 1, y, targetColor, replacementColor);
    floodFill(x, y - 1, targetColor, replacementColor);
    floodFill(x, y + 1, targetColor, replacementColor);

    return;

}

谢谢!

【问题讨论】:

  • 如果您要展示现有代码(或其高级摘要),有人可能会告诉您如何以非递归方式编写它,如果您是这样的话之后。
  • 我建议选择其中一种基于队列的算法,在我看来,它们比基于堆栈的解决方案更容易实现。
  • 感谢您的回答,我尝试使用维基百科的解释来实现,但我不知道如何。这就是我在这里提出问题的原因。如果有人能用其他语言向我解释一下,我将不胜感激。

标签: java image-manipulation flood-fill


【解决方案1】:

您可以使用 Queue 从 Floodfill 算法中移除递归。以下是一些基本的想法:

  1. 有办法标记访问点
  2. 开始时,将起点排队。
  3. 当队列不为空时,继续将其元素出队。并且每个元素
    1. 填充其颜色并将刚刚出列的点标记为已访问
    2. 将未访问的具有相同颜色的相邻点排入队列

以下是我的 Java 代码,用于解决 blob 检测类似但不同的问题。我希望你能从中得到一些想法,并能适应它的问题。但是代码没有很好地分解。

package blobdetector;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.management.Query;

public class Main {

    public Main() {
    }

    public static boolean isBlack(BufferedImage image, int posX, int posY) {

        int color = image.getRGB(posX, posY);

        int brightness = (color & 0xFF) + ((color >> 2) & 0xFF)
        + ((color >> 4) & 0xFF);
        brightness /= 3;

        return brightness < 128;
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("ERROR: Pass filename as argument.");
            return;
        }

        String filename = args[0];
        // String filename =
        // "C:\\Users\\Natthawut\\Desktop\\blob.jpg";
        try {
            BufferedImage bimg = ImageIO.read(new File(filename));

            boolean[][] painted = new boolean[bimg.getHeight()][bimg.getWidth()];

            for (int i = 0; i < bimg.getHeight(); i++) {
                for (int j = 0; j < bimg.getWidth(); j++) {

                    if (isBlack(bimg, j, i) && !painted[i][j]) {

                        Queue<Point> queue = new LinkedList<Point>();
                        queue.add(new Point(j, i));

                        int pixelCount = 0;
                        while (!queue.isEmpty()) {
                            Point p = queue.remove();

                            if ((p.x >= 0)
                                    && (p.x < bimg.getWidth() && (p.y >= 0) && (p.y < bimg
                                            .getHeight()))) {
                                if (!painted[p.y][p.x]
                                                  && isBlack(bimg, p.x, p.y)) {
                                    painted[p.y][p.x] = true;
                                    pixelCount++;

                                    queue.add(new Point(p.x + 1, p.y));
                                    queue.add(new Point(p.x - 1, p.y));
                                    queue.add(new Point(p.x, p.y + 1));
                                    queue.add(new Point(p.x, p.y - 1));
                                }
                            }
                        }
                        System.out.println("Blob detected : " + pixelCount
                                + " pixels");
                    }

                }
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

}

测试输入:

【讨论】:

  • 嗯,我明白了...但是我该如何调整您的代码?我需要将某些区域涂成绿色,例如:图像的所有黑色区域。谢谢大家!
  • 在代码示例中,程序所做的是递增pixelCount 变量的值。也许你想在那个时候setRGB?此外,它会扫描图像的整个区域以查找每个 blob。您可能只想填充一个斑点,从某个点开始填充。
  • 是的!我得到它!没有你的帮助是不可能的!!只是我必须将“pixelCount++”行更改为“img.setRGB(p.x, p.y, Color.GREEN.getRGB());”就是这样!我很感激!!非常感谢!
  • 漂亮,不知道我怎么没想到这个解决方案。在我的代码中完美运行!
【解决方案2】:

这是我基于此页面的信息和网络上收集的其他信息(经过测试和工作)的实现

玩得开心;-)

public static void floodFillImage(BufferedImage image,int x, int y, Color color) 
{
    int srcColor = image.getRGB(x, y);
    boolean[][] hits = new boolean[image.getHeight()][image.getWidth()];

    Queue<Point> queue = new LinkedList<Point>();
    queue.add(new Point(x, y));

    while (!queue.isEmpty()) 
    {
        Point p = queue.remove();

        if(floodFillImageDo(image,hits,p.x,p.y, srcColor, color.getRGB()))
        {     
            queue.add(new Point(p.x,p.y - 1)); 
            queue.add(new Point(p.x,p.y + 1)); 
            queue.add(new Point(p.x - 1,p.y)); 
            queue.add(new Point(p.x + 1,p.y)); 
        }
    }
}

private static boolean floodFillImageDo(BufferedImage image, boolean[][] hits,int x, int y, int srcColor, int tgtColor) 
{
    if (y < 0) return false;
    if (x < 0) return false;
    if (y > image.getHeight()-1) return false;
    if (x > image.getWidth()-1) return false;

    if (hits[y][x]) return false;

    if (image.getRGB(x, y)!=srcColor)
        return false;

    // valid, paint it

    image.setRGB(x, y, tgtColor);
    hits[y][x] = true;
    return true;
}

【讨论】:

    【解决方案3】:

    你应该返回最后一个 floodFill 语句,把它变成一个尾调用。这将节省您的堆栈空间。

    【讨论】:

    • 尾递归总是可以转换为迭代格式。这将节省更多的堆栈空间。
    【解决方案4】:

    洪水填充中的一个重要点是您是在处理点深度优先还是广度优先。深度优先是您使用堆栈查看的原始解决方案,广度优先是下面显示的使用队列存储点的算法。填充大的凸空间时差异很大。广度优先方法存储在大致圆形边缘(或填充边缘)的完美凸区域上。如果您使用深度优先方法,则在最坏的情况下,您可能会将每个像素存储在 conxex 区域中,这意味着在最坏的情况下,填充的 1000x1000 图像泛洪可能需要 1000000 个堆栈帧。

    【讨论】:

    • 为了澄清(cmiiw)基于堆栈的数据结构(filo队列)将是广度优先的,从凸边向内填充,而基于堆栈的 recursion 将是深度优先的。相比之下,fifo 队列将从原点向外工作,因为每个点都按照提交的顺序进行处理。如果您无法在不复制的情况下增长环形缓冲区,则 Fifo 队列往往会产生更多开销
    猜你喜欢
    • 1970-01-01
    • 2022-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    相关资源
    最近更新 更多