用 C 语言填充整数数组的简单泛洪填充算法

Simple flood fill algorithm in C to fill an array of integers

提问人:YeO 提问时间:10/26/2023 最后编辑:YeO 更新时间:10/27/2023 访问量:107

问:

在尝试了在 Fortran 90 中实现它之后,我转向了 C。我不确定这是我的实现还是实际的,但它没有按预期工作,并且倾向于填充整个数组,无论我以何种方式放置任何边框。我的目标是创建一个扩展。queueflood fill algorithmPython

下面是代码,它基于 的实现这个算法。
最初的目的是将 实现为动态数组并从 rosettacode 借用代码,但我也无法使其工作,所以我试图让它更像 的实现,尽管进行了细微的调整。
scikit-imagequeuescikit-imageQueueWithHistory

queue.h

#ifndef QUEUE_H
#define QUEUE_H

typedef size_t DATA; /* type of data to store in queue */
typedef struct {
    DATA *buf;
    size_t head;
    size_t tail;
    size_t alloc;
} queue_t, *queue;

extern queue init_queue();
extern void free_queue(queue q);
extern void push_queue(queue q, DATA *n);
extern unsigned char pop_queue(queue q, DATA *n);

#endif /* QUEUE_H */

queue.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "queue.h"


#define QUEUE_INIT_SIZE 64
#define QUEUE_THRESHOLD 512


static void _handle_memory_allocation_failure(const char *message, queue q) {
    fprintf(stderr, "Error: Failed to (re)allocate memory for %s\n", message);
    free_queue(q);  // Ensure proper cleanup
    exit(EXIT_FAILURE);
}

queue init_queue() {
    queue q = malloc(sizeof(queue_t));
    if (!q) {
        _handle_memory_allocation_failure("queue structure", NULL);
    }
    q->buf = malloc(sizeof(DATA) * (q->alloc = QUEUE_INIT_SIZE));
    if (!q->buf) {
        _handle_memory_allocation_failure("queue buffer", q);
    }
    q->head = q->tail = 0;
    return q;
}

void free_queue(queue q) {
    if (!q) return;
    free(q->buf);
    free(q);
}

static void _grow_queue(queue q) {
    q->alloc *= 2;
    DATA *new_buf = realloc(q->buf, sizeof(DATA) * q->alloc);
    if (!new_buf) {
        _handle_memory_allocation_failure("growing queue buffer", q);
    }
    q->buf = new_buf;
}

void push_queue(queue q, DATA *n) {
    if (q->alloc <= q->head) {
        _grow_queue(q);
    }
    q->buf[q->head++] = *n;
}

unsigned char pop_queue(queue q, DATA *n) {
    if (q->tail < q->head) {
        *n = q->buf[q->tail++];
        return 1;
    }
    return 0;
}

floodfill.h

#ifndef FLOODFILL_H
#define FLOODFILL_H

// Function declarations
int flood_fill(int* image, int* offsets, size_t n_offsets,
               size_t start_index, int new_value, size_t image_size);

#endif /* FLOODFILL_H */

floodfill.c

#include <stdlib.h>
#include <stdio.h>
#include "floodfill.h"
#include "queue.h"


// C implementation of floodfill algorithm

int flood_fill(int* image, int* offsets, size_t n_offsets,
               size_t start_index, int new_value, size_t image_size) {
    
    size_t current_index, neighbor_index;

    // Get the seed value (image value at start location)
    int seed_value = image[start_index];
    // Short circuit to avoid infinite loop
    if (seed_value == new_value) {
        return 0;
    }
    image[start_index] = new_value;
    // Initialize the queue and push start position
    queue q = init_queue();
    push_queue(q, &start_index);
    
    // Break loop if all queued positions were evaluated
    while (pop_queue(q, &current_index)) {
        // Look at all neighbors
        for (int i = 0; i < n_offsets; ++i) {
            neighbor_index = current_index + offsets[i];
            if (neighbor_index < image_size) {
                if (image[neighbor_index] == seed_value) {
                    // Process neighbor and push to queue
                    image[neighbor_index] = new_value;
                    push_queue(q, &neighbor_index);
                }
            }
        }
    }
    // Ensure memory is released
    free_queue(q);
    return 0;
}

上面的 C 代码封装在一个 Python 模块中,下面是调用 C 代码的 main 函数。它在模块 API 中调用。flood_fill

#define RAVEL_INDEX(tuple, h) ( \
    (int)PyLong_AsLong(PyTuple_GetItem(tuple, 0)) + \
    h * (int)PyLong_AsLong(PyTuple_GetItem(tuple, 1)) \
)
#define N_OFFSETS 8

static PyObject *algorithms_flood_fill(PyObject *self, PyObject *args) {
    PyObject *image_obj;
    PyObject *start_obj;
    int seed_value;
    if (!PyArg_ParseTuple(args, "O!O!i", &PyArray_Type, &image_obj,
                                         &PyTuple_Type, &start_obj,
                                         &seed_value)) {
        PyErr_SetString(PyExc_TypeError, "Error parsing input");
        return NULL;
    }
    PyArrayObject *image_array;
    image_array = (PyArrayObject*)PyArray_FROM_OTF(image_obj,
                                                   NPY_INT,
                                                   NPY_ARRAY_IN_FARRAY);
    if (image_array == NULL) {
        // Py_XDECREF(image_array);
        PyErr_SetString(PyExc_TypeError, "Couldn't parse the input array");
        return NULL;
    }
    int h = (int)PyArray_DIM(image_array, 0);
    int w = (int)PyArray_DIM(image_array, 1);
    intptr_t start_index = (intptr_t)RAVEL_INDEX(start_obj, h);
    // it should be safe to use PyArray_DATA as we ensured aligned contiguous Fortran array
    int *image = (int *)PyArray_DATA(image_array);
    size_t image_size = h * w;
    int offsets[N_OFFSETS] = {-(h+1), -h, -(h-1), -1, +1, h-1, h, h+1};
    flood_fill(image, offsets, N_OFFSETS, start_index, seed_value, image_size);
    Py_DECREF(image_array);
    Py_RETURN_NONE;
}

如果我给它一个初始化为 0 的 9x9 整数数组,中间有一行 1(行索引 4)、、,数组中的所有 0 都会被 3 替换,这不是预期的行为。int offsets[] = {-10, -9, -8, -1, 1, 8, 9, 10}size_t n_offsets = 8size_t start_index = 0int new_value = 3size_t image_size = 81

import numpy as np
from mymodule import flood_fill

a = np.zeros((9, 9), order='F', dtype=np.int32)
a[4, :] = 1

flood_fill(a, (0, 0), 3)

# output :
# array([[3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [1, 1, 1, 1, 1, 1, 1, 1, 1],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3],
#        [3, 3, 3, 3, 3, 3, 3, 3, 3]])

实现或逻辑本身是否有问题,或者 Python 包装器调用它的方式是否有问题可能导致此问题?queueflood fill algorithm

python c 算法 队列 泛洪填充

评论

0赞 wLui155 10/26/2023
您应该检查您正在访问的指数的下限。' if (neighbor_index < image_size) {' 在 floodfill.c 中是不够的,因为某些偏移量可能是负的。neighbor_index = current_index + offsets[i];
0赞 trincot 10/26/2023
为什么要再次发帖?
1赞 YeO 10/26/2023
@wLui155我设置为这样我就没有必要检查它是否为阳性,我确实实施了完整性测试,但它没有改变任何东西neighbor_indexsize_t
0赞 trincot 10/26/2023
但是你读过那里的评论吗?他们指出了这个错误。将最右边的单元格标识为下一行左侧单元格的邻居。肯定不对。如果您使用调试器和小输入调试代码,则会出现这种事情。
1赞 trincot 10/26/2023
类型是一回事,但我的评论是关于其他事情的。假设您有一个 4x4 矩阵,因此单元格编号为 0 到 15:第二行的单元格为 4 到 7。问问自己:4号牢房的邻居是哪家?单元格 3 是单元格 4 的邻居吗?答案是否定的,因此您不应将其视为添加到此单元格 4 的有效偏移量。size_t-1

答:

1赞 Matija Špeletić 10/26/2023 #1

使用存储为一维数组的图像时,需要更复杂的逻辑来检查邻居是否有效。

尝试这样的事情:

    // C implementation of floodfill algorithm
    
    //offset indexes are [upleft,up,upright,left,right,downleft,down,downright]
    const int UP_LEFT=0,UP=1,UP_RIGHT=2,LEFT=3,RIGHT=4,DOWN_LEFT=5,DOWN=6,DOWN_RIGHT=7;
    
    int valid_neighbour(int current_index, int dim, int offset_index){
        
        // first column
        if(current_index%dim==0
            && (offset_index==UP_LEFT
                ||offset_index==LEFT
                ||offset_index==DOWN_LEFT)){
            return 0;
        }
        
        //last column
        if(current_index%dim==dim-1
            && (offset_index==UP_RIGHT
                ||offset_index==RIGHT
                ||offset_index==DOWN_RIGHT)){
            return 0;
        }
        
        //first row
        if(current_index/dim==0
            && (offset_index==UP_LEFT
                ||offset_index==UP
                ||offset_index==UP_RIGHT)){
            return 0;
        }
        
        //last row
        if(current_index/dim==dim-1
            && (offset_index==DOWN_LEFT
                ||offset_index==DOWN
                ||offset_index==DOWN_RIGHT)){
            return 0;
        }
        return 1;
    }
    
    int flood_fill(int* image, int* offsets, int n_offsets,
                   int start_index, int new_value, int image_size,int image_dim) {
        
        int current_index, neighbor_index;
    
        // Get the seed value (image value at start location)
        int seed_value = image[start_index];
        // Short circuit to avoid infinite loop
        if (seed_value == new_value) {
            return 0;
        }
        //image[start_index] = new_value;
        // Initialize the queue and push start position
        queue q = init_queue();
        push_queue(q, &start_index);
        
        // Break loop if all queued positions were evaluated
        while (pop_queue(q, &current_index)) {
            //Don't process already processed pixels
           if(image[current_index]==new_value){
               continue;
           }
           image[current_index]=new_value;
           printf("curr index: %d\n",current_index);
           
            // Look at all neighbors
            for (int i = 0; i < n_offsets; ++i) {
                neighbor_index = current_index + offsets[i];
                if (neighbor_index < image_size && neighbor_index>=0) {
                    
                    if (image[neighbor_index] == seed_value && valid_neighbour(current_index,image_dim,i)) {
                        // Process neighbor and push to queue
                        //image[neighbor_index] = new_value;
                        push_queue(q, &neighbor_index);
                    }
                }
            }
        }
        // Ensure memory is released
        free_queue(q);
        return 0;
    }
    
    int main()
    {
        int image[] = {
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 1, 0, 0, 0, 0
        };
        int offsets[] = {-10, -9, -8, -1, 1, 8, 9, 10};
        int n_offsets = 8;
        int start_index = 0;
        int seed_value = 3;
        int image_size = 81;
        int image_dim=9;
    
        flood_fill(image, offsets, n_offsets, start_index, seed_value, image_size,image_dim);
        
        for(int i=0;i<9;i++){
            for(int j=0;j<9;j++){
                printf("%d ",image[i*9+j]);
        }
        printf("\n");
        }
    }

注意:我用了代替,稍后更改它。intsize_t

评论

0赞 YeO 10/26/2023
谢谢,关于优化,我实际上认为在将邻居推送到队列之前将其设置为实际上可以防止将大量单元多次推送到队列,因为它们会被测试拒绝,而您的实现可能会多次推送相同的单元(但是,当然, 第一次更新后会短路)...再一次,我可能是错的new_value== seed_value
0赞 YeO 10/27/2023
我接受了你的回答,但就其价值而言,我坚持我对优化的评论,在推送到队列之前将邻居设置为新的是要走的路。在提供的 9x9 数组示例中,我的代码(归功于及其实现)推送了 36 个单元格,这是需要填充的单元格的确切数量,而您的代码将 108 个单元格推送到队列(并正确丢弃它们),这是很多重复项!scikit-image
0赞 Matija Špeletić 10/27/2023
好。我匆匆忙忙,多次编辑和运行代码,可能是我的误判。我编辑了答案。