我一直在努力寻找一种有效的填充算法。在许多算法中,我只尝试过“递归线填充”,其中一个行为完全符合它应该的主要警告,它偶尔会打击堆栈。 :(
我已经尝试了很多我发现的非递归实现,并且它们都非常温和:要么在奇怪的地方留下空白,要么泛滥整个区域(当它们被封闭时)。
任何人都有一个非递归填充工作源代码用C语言编写(或者c ++不是太大的OOP而且我可以很容易地解开)?
答案 0 :(得分:25)
只需实现一堆int对,其中包含一些固定大小的数组(例如,像素的大小或像素的平方根),并使用int跟踪顶部。
以下是一些非递归实现floodfill的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));
}
}
}
答案 1 :(得分:11)
这是一些可以满足您需求的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;
}
答案 2 :(得分:9)
快速谷歌搜索会显示Wikipedia article on 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 */
...
}
}
}
答案 3 :(得分:7)
通过使用本地数据来模拟堆栈,是否存在所有递归函数都可以作为迭代函数实现的证明?您可以使用std :: vector来创建算法的类似堆栈的行为而不会破坏堆栈,因为它将使用堆。
编辑:我注意到你正在使用C,所以你可以通过realloc实现类似的行为,而不是std :: vector,因为你需要在你所使用的任何数据结构的本地“堆栈”中添加更多的元素。 / p>答案 4 :(得分:3)
您可以通过创建显式堆栈或队列并将工作加载到其上/将其拉出来将任何递归算法转换为迭代算法。
您所需要的只是选择一个漂亮,紧凑的工作表示。最坏的情况:创建一个struct
,保存您通常会传递给递归版本的参数...
答案 5 :(得分:2)
我们注意到我们在3d卷上的Floodfill实现消耗了很多内存;所以我们通过以下方式修改了代码(有了很大的改进):
在起点周围创建一个半径= 10 voxs的球体,并将该半径内的所有体素标记为“要被访问”
如果当前体素>阈值,插入1.
转到邻居[+ 1,-1,0](也检查一个没有重新访问任何体素),如果neighbor.getVoxVal = 0(目标卷的初始化值),则它落在球体的边界,将坐标插入不同的堆栈中。 (这将成为我们下一个领域的起点)
radius = radius + 10(体素)
所以,一次,我们的洪水填充是在同心球上进行填充,这是整个卷的一部分,正如我所说,这大大减少了内存消耗,但我仍在寻找实施/想法会更好。
答案 6 :(得分:2)
我不知道我的回答是否与您提出的问题完全相关,但此后我提出了我的C版Flood-Fill算法,它不使用递归调用。
1-11-2017:NEW-VERSION;成功测试了两个BITMAPS。
它仅使用新点的偏移队列,它在窗口上工作:双缓冲区的WinnOffs-(WinDimX,WinDimY):* VBuffer(屏幕或图像的副本),以及可选的,它写一个泛洪填充结果的掩码(* ExtraVBuff)。 在调用之前必须用0填充ExtraVBuff(如果你不需要掩码,你可以设置ExtraVBuff = NULL);在通话后使用它你可以做渐变式填充或其他绘画效果。 NewFloodFill的每像素32位工作,它是一个C函数。我在1991年重新编写了这个算法(我用他的Pascal写的),但现在它在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);
}
我已经使用了,对于所显示的测试程序的输入,遵循Windows未压缩.BMP图像(ffill1.bmp):
通过所示的测试程序填写如下(ffill2.bmp):
使用“mask”而不是NULL,输出位图为(ffill3.bmp):
答案 7 :(得分:1)
我有一个非递归填充,但我不会发布它,因为它是家庭作业的解决方案。但这里有一个提示:深度优先搜索,这是一种自然算法,使用远比广度优先搜索更多的辅助空间。这是我当时写的(适当删除):
我不敢通过简单的递归尝试深度优先搜索;递归的深度仅受删除限制,我的实验表明,问题删除可能需要超过一百万的堆栈深度。所以我把堆栈放在一个辅助数据结构中。使用显式堆栈实际上也可以轻松尝试广度优先搜索,事实证明广度优先搜索可以比深度优先搜索使用少40倍的空间。
对于我的数据结构,我使用了Dave Hanson C Interfaces and Implementations的Seq_T
;从深度优先到广度优先改变只需要改变一个函数调用。
答案 8 :(得分:1)
您可以将递归泛洪填充快速转换为超高性能的伪递归...不要编辑这些行,只需添加新行即可: 将递归函数放在XY循环中以增加结构。 将找到的邻居记录到“找到的邻居数组”中 而不是内存,因此您将开始将递归的4-16-64样式树打包到XY数组中。内存使用量从1吉字节到2兆字节。 还可以使用称为“填充的邻居数组”的2D数组...对于标记为“填充的邻居数组”中已填充的任何像素,中止该函数,此操作对每个重复项使用2条指令,对每个泛洪操作使用20条指令,然后迭代填充像多米诺骨牌一样向左和向上快速疯狂地移动。
1024x1024使用大约1百万个* 20条指令,对于单个内核来说这是0.1秒。
这样,我在i7上达到了每秒900万个填充像素,我有一个视频作为证明,并且在博客页面上提供了代码和理论解释:
www.youtube.com/watch?v=4hQ1wA4Sl4c
这是我尝试解释其工作原理的页面。 http://unity3dmc.blogspot.com/2017/02/ultimate-3d-floodfill-scanline.html?m=1
和代码。
如果能够保持井井有条,则递归将是最快的。
如果通过数据(图像)网格进行递归,则也可以网格格式存储递归的处理,因为处理的步骤代表网格中的像素,而不是将结果分解为树格式。
答案 9 :(得分:0)
以下是基于BFS的洪水填充问题的迭代c ++方法:
// 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});
}
}
}
答案 10 :(得分:0)
这里是非递归例程的指南,该例程每秒完成1000万像素:it's called marching-floodfills,如果您将以前的递归例程前进到X-Y循环中会发生什么情况。
写自己的内存,一个2D数组记录已验证的空间,另一个数组记录完整的填充图像,并使用此循环系统对其进行读写...平均每个像素20条指令。我使用上述视频逻辑以每秒500万个体素处理了20亿个体素图。
答案 11 :(得分:0)
我发现Paul Heckbert所做的填充是最简单的非递归C实现:
use std::sync::Arc;
use parking_lot::{Mutex, MutexGuard, MappedMutexGuard};
use std::collections::HashMap;
pub struct MyStruct {
data: Arc<Mutex<HashMap<i32, Vec<i32>>>>,
}
impl MyStruct {
pub fn get_data_for(&self, i: &i32) -> MappedMutexGuard<Vec<i32>> {
MutexGuard::map(self.data.lock(), |d| d.get_mut(i).unwrap())
}
}
来源:https://github.com/erich666/GraphicsGems/blob/master/gems/SeedFill.c