рабочий нерекурсивный алгоритм заполнения, написанный на C? - PullRequest
17 голосов
/ 11 августа 2009

Я пытался найти работающий алгоритм заливки. Из многих алгоритмов, которые я пробовал, только «рекурсивное заполнение строки» ведет себя именно так, как и должно быть, с основным предупреждением о том, что время от времени происходит перебор стека. (

Я перепробовал много нерекурсивных реализаций, которые нашел, и все они были исключительно временными: либо оставляли пробелы в незнакомых местах, либо заливали всю область (когда они должны быть заключены).

У кого-нибудь есть нерекурсивный рабочий исходный код для заливки, написанный на C (или C ++, который не слишком сильно ООП, и я могу достаточно легко разобраться)?

Ответы [ 10 ]

25 голосов
/ 11 августа 2009

Просто реализуйте стек пар int с массивом некоторого фиксированного размера (например, размер изображения в пикселях или квадратный корень из него) для стека и отслеживайте вершину с помощью целого числа.

Вот некоторый код C #, который реализует флудинг нерекурсивно:

private static void Floodfill(byte[,] vals, Point q, byte SEED_COLOR, byte COLOR)
{
    int h = vals.GetLength(0);
    int w = vals.GetLength(1);

    if (q.Y < 0 || q.Y > h - 1 || q.X < 0 || q.X > w - 1)
        return;

    Stack<Point> stack = new Stack<Point>();
    stack.Push(q);
    while (stack.Count > 0)
    {
        Point p = stack.Pop();
        int x = p.X;
        int y = p.Y;
        if (y < 0 || y > h - 1 || x < 0 || x > w - 1)
            continue;
        byte val = vals[y, x];
        if (val == SEED_COLOR)
        {
            vals[y, x] = COLOR;
            stack.Push(new Point(x + 1, y));
            stack.Push(new Point(x - 1, y));
            stack.Push(new Point(x, y + 1));
            stack.Push(new Point(x, y - 1));
        }
    }
}
11 голосов
/ 11 августа 2009

Вот код C ++, который делает то, что вы хотите. Он использует очередь и более эффективен в отношении вставок в очередь.

connectedRegion(const Point& source, RegionType& region, const Color target)
{
    Color src_color = color_of(source, region);
    if (region.count(source) == 0 || src_color == target)
        return;
    std::queue<Point> analyze_queue;
    analyze_queue.push(source);

    while (!analyze_queue.empty())
    {
        if (color_of(analyze_queue.front()) != src_color)
        {
            analyze_queue.pop();
            continue;
        }
        Point leftmost_pt = analyze_queue.front();
            leftmost_pt.col -= 1;
        analyze_queue.pop();
        Point rightmost_pt = leftmost_pt;
            rightmost_pt.col += 2;
        while (color_of(leftmost_pt, region) == src_color)
            --leftmost_pt.col;

        while (color_of(rightmost_pt, region) == src_color)
            ++rightmost_pt.col;

        bool check_above = true;
        bool check_below = true;
            Point pt = leftmost_pt;
            ++pt.col;
        for (; pt.col < rightmost_pt.col; ++pt.col)
        {
            set_color(pt, region, target);

            Point pt_above = pt;
                    --pt_above.row;
            if (check_above)
            {
                if (color_of(pt_above, region) == src_color)
                {
                    analyze_queue.push(pt_above);
                    check_above = false;
                }
            }
            else // !check_above
            {
                check_above = (color_of(pt_above, region) != src_color);
            }

            Point pt_below = pt;
                    ++pt_below.row;
            if (check_below)
            {
                if (color_of(pt_below, region) == src_color)
                {
                    analyze_queue.push(pt_below);
                    check_below = false;
                }
            }
            else // !check_below
            {
                check_below = (color_of(pt_below, region) != src_color);
            }
        } // for 
    } // while queue not empty
    return connected;
}
9 голосов
/ 11 августа 2009

Быстрое поиск в Google вызывает статью Wikipedia о Flood Fill , которая включает в себя реализации псевдокода, которые не являются рекурсивными. Ниже приведен код, который может помочь вам начать, базовая реализация очереди в C:

typedef struct queue_ { struct queue_ *next; } queue_t;
typedef struct ffnode_ { queue_t node; int x, y; } ffnode_t;

/* returns the new head of the queue after adding node to the queue */
queue_t* enqueue(queue_t *queue, queue_t *node) {
    if (node) {
        node->next = queue;
        return node;
    }
    return NULL;
}

/* returns the head of the queue and modifies queue to be the new head */
queue_t* dequeue(queue_t **queue) {
    if (queue) {
        queue_t *node = (*queue);
        (*queue) = node->next;
        node->next = NULL;
        return node;
    }
    return NULL;
}

ffnode_t* new_ffnode(int x, int y) {
    ffnode_t *node = (ffnode_t*)malloc(sizeof(ffnode_t));
    node->x = x; node->y = y;
    node->node.next = NULL;
    return node;
}

void flood_fill(image_t *image, int startx, int starty, 
                color_t target, color_t replacement) {
    queue_t *head = NULL;
    ffnode_t *node = NULL;

    if (!is_color(image, startx, starty, target)) return;

    node = new_ffnode(startx, starty);
    for ( ; node != NULL; node = (ffnode_t*)dequeue(&head)) {
        if (is_color(image, node->x, node->y, target)) {
            ffnode_t *west = node, *east = node;

            recolor(image, node->x, node->y, replacement);
            /* 1. move w to the west until the color of the node to the west
               no longer matches target */
            ...
        }
    }
}
7 голосов
/ 11 августа 2009

Нет ли где-нибудь доказательства того, что все рекурсивные функции могут быть реализованы как итеративная функция с использованием локальных данных для имитации стека? Вероятно, вы могли бы использовать std :: vector для создания подобного стеку поведения алгоритма без перегрузки стека, поскольку он будет использовать кучу.

РЕДАКТИРОВАТЬ: я заметил, что вы используете C, поэтому вместо std :: vector вы можете просто реализовать подобное поведение через realloc, так как вам нужно добавить больше элементов в ваш локальный «стек» любой структуры данных, которую вы будете использовать.

3 голосов
/ 11 августа 2009

Вы можете преобразовать любой рекурсивный алгоритм в итеративный, создав явный стек или очередь и загрузив в него работу / выполнив ее.

Все, что вам нужно, это выбрать красивое, компактное представление работы, которую нужно сделать. В худшем случае: создайте struct, содержащий аргументы, которые вы обычно передаете рекурсивной версии ...

2 голосов
/ 13 октября 2017

Я не знаю, является ли мой ответ совершенно подходящим для поставленного вами вопроса, но в дальнейшем я предлагаю свою версию C алгоритма Flood-Fill, который не использует рекурсивные вызовы.

1-11-2017: НОВАЯ ВЕРСИЯ; УСПЕШНО ПРОВЕРЕНО С ДВУМЯ БИТМАЗАМИ.

Он использует только очередь смещений новых точек, он работает в окне: WinnOffs- (WinDimX, WinDimY) двойного буфера: * VBuffer (копия экрана или изображения) и, при необходимости, он написать маску результата заливки (* ExtraVBuff). ExtraVBuff должен быть заполнен 0 перед вызовом (если вам не нужна маска, вы можете установить ExtraVBuff = NULL); используя его после вызова, вы можете делать градиентные заливки или другие эффекты рисования. NewFloodFill работает с 32 битами на пиксель и является функцией C. Я заново изобрел этот алгоритм в 1991 году (я написал его на Паскале), но теперь он работает в C с 32-битной на пиксель; также не использует вызовы каких-либо функций, делает только деление после каждого «всплывающего» сообщения из очереди и никогда не переполняет очередь, что, если он имеет правильный размер (около 1/4 пикселей изображения), позволяет всегда правильно заполнять любую область; Я показываю перед функцией c (FFILL.C), после тестовой программы (TEST.C):

#define IMAGE_WIDTH 1024
#define IMAGE_HEIGHT 768
#define IMAGE_SIZE IMAGE_WIDTH*IMAGE_HEIGHT
#define QUEUE_MAX IMAGE_SIZE/4

typedef int T_Queue[QUEUE_MAX];
typedef int T_Image[IMAGE_SIZE];

void NewFloodFill(int X,
                  int Y,
                  int Color,
                  int BuffDimX,
                  int WinOffS,
                  int WinDimX,
                  int WinDimY,
                  T_Image VBuffer,
                  T_Image ExtraVBuff,
                  T_Queue MyQueue)

/* Replaces all pixels adjacent to the first pixel and equal to this;   */
/* if ExtraVBuff == NULL writes to *VBuffer (eg BUFFER of 786432 Pixel),*/
/* otherwise prepare a mask by writing on *ExtraVBuff (such BUFFER must */
/* always have the same size as *VBuffer (it must be initialized to 0)).*/

/*         X,Y: Point coordinates' of origin of the flood-fill.         */
/*     WinOffS: Writing start offset on *VBuffer and *ExtraVBuff.       */
/*    BuffDimX: Width, in number of Pixel (int), of each buffer.        */
/*     WinDimX: Width, in number of Pixel (int), of the window.         */
/*       Color: New color that replace all_Pixel == origin's_point.     */
/*     WinDimY: Height, in number of Pixel (int), of the window.        */
/*     VBuffer: Pointer to the primary buffer.                          */
/*  ExtraVBuff: Pointer to the mask buffer (can be = NULL).             */
/*     MyQueue: Pointer to the queue, containing the new-points' offsets*/

{
 int VBuffCurrOffs=WinOffS+X+Y*BuffDimX;
 int PixelIn=VBuffer[VBuffCurrOffs];
 int QueuePnt=0;
 int *TempAddr=((ExtraVBuff) ? ExtraVBuff : VBuffer);
 int TempOffs1;
 int TempX1;
 int TempX2;
 char FLAG;

 if (0<=X && X<WinDimX && 0<=Y && Y<WinDimY) do
  {
   /* Fill to left the current line */
   TempX2=X;
   while (X>=0 && PixelIn==VBuffer[VBuffCurrOffs])
    {
     TempAddr[VBuffCurrOffs--]=Color;
     --X;
    }
   TempOffs1=VBuffCurrOffs+1;
   TempX1=X+1;

   /* Fill to right the current line */
   VBuffCurrOffs+=TempX2-X;
   X=TempX2;
   while (X+1<WinDimX && PixelIn==VBuffer[VBuffCurrOffs+1])
    {
     ++X;
     TempAddr[++VBuffCurrOffs]=Color;
    }
   TempX2=X;

   /* Backward scan of the previous line; puts new points offset in Queue[] */
   if (Y>0)
    {
     FLAG=1;
     VBuffCurrOffs-=BuffDimX;
     while (X-->=TempX1)
      {
       if (PixelIn!=VBuffer[VBuffCurrOffs] ||
           ExtraVBuff && Color==ExtraVBuff[VBuffCurrOffs])
        FLAG=1;
       else
       if (FLAG)
        {
         FLAG=0;
         if (QueuePnt<QUEUE_MAX)
          MyQueue[QueuePnt++]=VBuffCurrOffs;
        } 
       --VBuffCurrOffs;
      }
    }

   /* Forward scan of the next line; puts new points offset in Queue[] */
   if (Y<WinDimY-1)
    {
     FLAG=1;
     VBuffCurrOffs=TempOffs1+BuffDimX;
     X=TempX1;
     while (X++<=TempX2)
      {
       if (PixelIn!=VBuffer[VBuffCurrOffs] ||
           ExtraVBuff && Color==ExtraVBuff[VBuffCurrOffs])
        FLAG=1;
       else
       if (FLAG)
        {
         FLAG=0;
         if (QueuePnt<QUEUE_MAX)
          MyQueue[QueuePnt++]=VBuffCurrOffs;
        }
       ++VBuffCurrOffs;
      }
    }

   /* Gets a new point offset from Queue[] */ 
   if (--QueuePnt>=0)
    {
     VBuffCurrOffs=MyQueue[QueuePnt];
     TempOffs1=VBuffCurrOffs-WinOffS;
     X=TempOffs1%BuffDimX;
     Y=TempOffs1/BuffDimX;
    }

  /* Repeat the main cycle until the Queue[] is not empty */
  } while (QueuePnt>=0);
}

Здесь есть тестовая программа:

#include <stdio.h>
#include <malloc.h>
#include "ffill.c"

#define RED_COL 0xFFFF0000
#define WIN_LEFT 52
#define WIN_TOP 48
#define WIN_WIDTH 920
#define WIN_HEIGHT 672
#define START_LEFT 0
#define START_TOP 671

#define BMP_HEADER_SIZE 54

typedef char T_Image_Header[BMP_HEADER_SIZE];
void main(void)
{

 T_Image_Header bmpheader;
 T_Image *image;
 T_Image *mask;
 T_Queue *MyQueue;

 FILE *stream;
 char *filename1="ffill1.bmp";
 char *filename2="ffill2.bmp";
 char *filename3="ffill3.bmp";
 int bwritten;
 int bread;

 image=malloc(sizeof(*image));
 mask=malloc(sizeof(*mask));
 MyQueue=malloc(sizeof(*MyQueue));

 stream=fopen(filename1,"rb");
 bread=fread(&bmpheader, 1, BMP_HEADER_SIZE, stream);
 bread=fread((char *)image, 1, IMAGE_SIZE<<2, stream);
 fclose(stream);

 memset(mask,0,IMAGE_SIZE<<2);

 NewFloodFill(START_LEFT,
              START_TOP,
              RED_COL,
              IMAGE_WIDTH,
              IMAGE_WIDTH*WIN_TOP+WIN_LEFT,
              WIN_WIDTH,
              WIN_HEIGHT,
              *image,
              NULL,
              *MyQueue);

 stream=fopen(filename2,"wb+");
 bwritten=fwrite(&bmpheader, 1, BMP_HEADER_SIZE, stream);
 bwritten=fwrite((char *)image, 1, IMAGE_SIZE<<2, stream);
 fclose(stream);

 stream=fopen(filename3,"wb+");
 bwritten=fwrite(&bmpheader, 1, BMP_HEADER_SIZE, stream);
 bwritten=fwrite((char *)mask, 1, IMAGE_SIZE<<2, stream);
 fclose(stream);

 free(MyQueue);
 free(mask);
 free(image);
}

Я использовал для ввода показанной тестовой программы следующее несжатое изображение .BMP для Windows (ffill1.bmp):

enter image description here

Заполняется показанной тестовой программой следующим образом (ffill2.bmp):

enter image description here

Используя «маску» вместо NULL, выходной битовый массив выглядит так (ffill3.bmp):

enter image description here

2 голосов
/ 15 июля 2010

Мы заметили, что наша реализация заливки на трехмерных томах потребляла много памяти; поэтому мы изменили код следующими способами (было значительное улучшение):

  1. Создайте сферу радиуса = 10 вокс вокруг начальной точки и отметьте все вокселы в пределах этого радиуса как «для посещения»

  2. Если текущий воксел> порог, вставить 1.

  3. Перейдите к соседям [+1, -1, 0] (также убедитесь, что никто не пересматривает воксел), если neighbour.getVoxVal = 0 (значение инициализации для целевого тома), тогда он падает на границе сферы, вставьте координаты в другой стек. (это будет отправной точкой для нашей следующей сферы)

  4. радиус = радиус + 10 (вокселей)

Таким образом, наш флуд одновременно работает с концентрической сферой и заполняет все, что является частью всего тома, и, как я уже сказал, это резко сократило потребление памяти, но я все еще ищу реализация / идея, которая была бы лучше.

1 голос
/ 08 августа 2018

Вы можете быстро преобразовать рекурсивную заливку в ультраэффективную псевдорекурсивную ... Не редактируйте строки, просто добавьте новые строки: поместите рекурсивную функцию в цикл XY для добавленной структуры. записать найденных соседей в "массив найденных соседей" вместо памяти вы начнете упаковывать дерево стилей 4-16-64 рекурсии в массив XY. использование памяти идет от 1 гигабайта до 2 мегабайт. Также используйте двумерный массив с именем «заполненный массив соседей» ... прервите функцию для любых пикселей, помеченных как заполненные в «заполненном массиве соседей», при этом используются 2 инструкции для каждого дубликата, 20 команд для каждой операции заливки и выполняется многократное заполнение влево и вверх, как домино, безумно быстро.

1024x1024 использует около 1 миллиона * 20 инструкций, что составляет 0,1 секунды для одного ядра.

Таким образом, на i7 достигается 9 миллионов заполненных пикселей в секунду, у меня есть видео в качестве доказательства и страница блога с пояснениями по коду и теории:

www.youtube.com / смотреть? V = 4hQ1wA4Sl4c

и вот страница, где я попытался объяснить, как это работает. http://unity3dmc.blogspot.com/2017/02/ultimate-3d-floodfill-scanline.html?m=1

И код.

Рекурсии были бы самыми быстрыми, если бы они могли оставаться организованными.

Если вы выполняете рекурсию по сетке данных (изображения), вы можете хранить обработку рекурсий также в формате сетки, потому что обработанные шаги представляют пиксели из сетки, а не разбивают результаты в древовидный формат.

1 голос
/ 11 августа 2009

У меня нерекурсивная заливка, но я не буду публиковать ее, потому что это решение домашнего задания. Но вот подсказка: поиск в глубину, который является естественным алгоритмом, использует намного больше вспомогательного пространства, чем поиск в ширину. Вот то, что я написал в то время (соответственно вычеркнуто):

Я не смею пробовать поиск в глубину с помощью простой рекурсии; глубина рекурсии ограничена только УДАЛЕНО, и мои эксперименты показывают, что УДАЛЕННАЯ ПРОБЛЕМА может, тем не менее, потребовать глубину стека более миллиона. Поэтому я поместил стек во вспомогательную структуру данных. Использование явного стека на самом деле облегчает также поиск в ширину, и оказывается, что поиск в ширину может использовать в 40 раз меньше места, чем поиск в глубину.

Для своей структуры данных я использовал Seq_T из C Дейва Хансона "Интерфейсы и реализации" ; переход от глубины к ширине требует изменения только одного вызова функции.

0 голосов
/ 16 мая 2017

Ниже приведен мой итерационный метод C ++ на основе BFS для задачи заливки:

// M is the input matrix, with every entry(pixel) have a color
// represented with an integer value.
// (x, y) row and column of seed point respectively
// k: The new color to fill the seed and its adjacent pixels
void floodFill(vector<vector<int>> &M, int x, int y, int k) {
    queue<pair<int, int>> nodeQ;
    nodeQ.push({x, y});
    int oldCol = M[x][y];
    while(!nodeQ.empty()) {
        pair<int, int> currNode = nodeQ.front();
        nodeQ.pop();
        if(M[currNode.first][currNode.second] == oldCol) {
            M[currNode.first][currNode.second] = k;
            if(currNode.first > 0) nodeQ.push({currNode.first-1, currNode.second});
            if(currNode.first < (M.size()-1)) nodeQ.push({currNode.first+1, currNode.second});
            if(currNode.second > 0) nodeQ.push({currNode.first, currNode.second-1});
            if(currNode.second < (M[0].size()-1)) nodeQ.push({currNode.first, currNode.second+1});
        }
    }
}
...