Заливка с использованием стека

Я использую рекурсивный алгоритм заливки Flood в Java для заполнения некоторых областей изображения. С очень маленькими изображениями он работает нормально, но когда изображение становится больше, JVM выдает мне ошибку Stack Over Flow.

Вот почему я должен переопределить метод, используя 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;

}

Спасибо!


person dafero    schedule 06.05.2010    source источник
comment
Википедия: алгоритм заполнения флудом   -  person bitc    schedule 06.05.2010
comment
Если бы вы показали свой существующий код (или его краткое изложение), кто-то мог бы рассказать вам, как написать его таким образом, чтобы он не был рекурсивным, если это то, что вам нужно.   -  person Bill K    schedule 06.05.2010
comment
Я предлагаю выбрать один из представленных там алгоритмов на основе очередей, на мой взгляд, их проще реализовать, чем решение на основе стека.   -  person IVlad    schedule 06.05.2010
comment
Спасибо за ответ, я пытался реализовать с объяснением из Википедии, но я не знал, как это сделать. Вот почему я задаю вопрос здесь. Если кто-нибудь может объяснить это мне другими словами, я буду очень благодарен.   -  person dafero    schedule 06.05.2010


Ответы (4)


Вы можете использовать Queue для удаления рекурсии из алгоритма заполнения. Вот несколько основных идей:

  1. Иметь способ отмечать посещенные точки
  2. В начале поставьте в очередь начальную точку.
  3. While the queue is not empty, continue dequeuing its element. And with each element
    1. Fill its color and mark just-dequeued point as visited
    2. Ставить в очередь непосещенные соседние точки того же цвета

Ниже приведен мой Java-код для решения похожей, но другой проблемы обнаружения больших двоичных объектов. Я надеюсь, что вы можете извлечь из этого некоторые идеи и адаптировать их к проблеме. Однако код не очень хорошо продуман.

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();
        }

    }

}

Тестовый ввод:

alt text

person Gant    schedule 06.05.2010
comment
Мммм, понятно... но как я могу адаптировать ваш код? Мне нужно покрасить в зеленый цвет некоторые области, например: все черные области изображения. Спасибо за все! - person dafero; 06.05.2010
comment
В примере кода программа увеличивает значение переменной pixelCount. Может быть, вы хотите вместо этого setRGB в этот момент? Кроме того, он сканирует всю область изображения для каждого пятна. Вы можете захотеть заполнить только одну каплю, начните заполнять в определенный момент. - person Gant; 06.05.2010
comment
Ага! Я понимаю! Без вашей помощи было бы невозможно!! Просто мне нужно изменить строку pixelCount++ на img.setRGB(p.x, p.y, Color.GREEN.getRGB()); и это все! Я очень благодарен!! Большое спасибо! - person dafero; 06.05.2010
comment
Красиво, не знаю, как я не подумал об этом решении. Работал безупречно в моем коде! - person FelipeKunzler; 15.11.2015

вот моя база реализации на основе информации с этой страницы и других, собранных в Интернете (проверено и работает)

развлекайся ;-)

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;
}
person fdsfdsfdsfds    schedule 04.11.2016

Вы должны вернуть последний операторfloodFill, превратив его в хвостовой вызов. Это сэкономит вам место в стеке.

person Puppy    schedule 06.05.2010
comment
Хвостовую рекурсию всегда можно перевести в итеративный формат. Это позволит сэкономить еще больше места в стеке. - person Jon W; 06.05.2010

Важным моментом в заливке заливки является то, обрабатываете ли вы точки сначала в глубину или в ширину. Сначала в глубину — это исходное решение, которое вы рассматривали с использованием стека, а в ширину — алгоритмы, показанные ниже, использующие очередь для хранения точки. Разница существенна при заполнении больших выпуклых пространств. Метод «сначала в ширину» сохраняет идеально выпуклую область примерно на краю круга (или краю заливки). Если вы используете метод «сначала в глубину», вы можете в худшем случае сохранить каждый пиксель в конксекс-области, это означает, что в худшем случае для заливки изображения размером 1000x1000 может потребоваться 1000000 кадров стека.

person RolfS    schedule 25.07.2014
comment
Чтобы пояснить (cmiiw), основанная на стеке структура данных (очередь filo) будет идти в ширину, заполняясь от выпуклого края внутрь, в то время как основанная на стеке рекурсия будет сначала в глубину. Для сравнения, очередь fifo будет работать от источника наружу, поскольку каждая точка обрабатывается в том порядке, в котором она отправлена. Очереди FIFO, как правило, имеют больше накладных расходов, если вы не можете увеличить кольцевой буфер без его копирования. - person ; 05.01.2017