19

我希望以计算速度快的方式创建一个“blob”。此处的 blob 被定义为可以是任何形状但全部连接的像素的集合。例子:

.ooo....  
..oooo..  
....oo..  
.oooooo.
..o..o..  

...ooooooooooooooooooo...  
..........oooo.......oo..  
.....ooooooo..........o..  
.....oo..................  


......ooooooo....  
...ooooooooooo...  
..oooooooooooooo.  
..ooooooooooooooo  
..oooooooooooo...  
...ooooooo.......  
....oooooooo.....  
.....ooooo.......  
.......oo........  

在哪里 。是死区,o 是标记像素。我只关心“二进制”生成 - 一个像素是 ON 还是 OFF。因此,例如,这些看起来像一些虚构的番茄酱或虚构的细菌或任何有机物质。

什么样的算法可以做到这一点?我真的很茫然

4

3 回答 3

24

David Thonley 的评论是正确的,但我假设您想要一个具有“有机”形状和平滑边缘的斑点。为此,您可以使用元球。Metaballs 是一个在标量场上工作的幂函数。使用行进立方体算法可以有效地渲染标量场。可以通过改变球的数量、位置和半径来制作不同的形状。

有关 2D 元球的介绍,请参见此处:https ://web.archive.org/web/20161018194403/https://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html

在这里介绍行进立方体算法:https ://web.archive.org/web/20120329000652/http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/

请注意,3D 中交叉点的 256 种组合在 2D 中只有 16 种组合。这很容易实现。

编辑:

我用 GLSL 着色器编写了一个快速示例。这是使用 50 个 blob 的结果,以及来自 hkankaan 主页的能量函数。 替代文字

这是实际的 GLSL 代码,尽管我对每个片段进行评估。我没有使用行进立方体算法。您需要渲染一个全屏四边形才能使其工作(两个三角形)。vec3 统一数组只是通过glUniform3fv.

/* Trivial bare-bone vertex shader */
#version 150
in vec2 vertex;
void main()
{
    gl_Position = vec4(vertex.x, vertex.y, 0.0, 1.0);
}

/* Fragment shader */
#version 150
#define NUM_BALLS 50
out vec4 color_out;
uniform vec3 balls[NUM_BALLS]; //.xy is position .z is radius

bool energyField(in vec2 p, in float gooeyness, in float iso)
{
    float en = 0.0;
    bool result = false;
    for(int i=0; i<NUM_BALLS; ++i)
    {
        float radius = balls[i].z;
        float denom =  max(0.0001, pow(length(vec2(balls[i].xy - p)), gooeyness));
        en += (radius / denom);
    }
    if(en > iso)
        result = true;
    return result;
}
void main()
{
    bool outside;
    /* gl_FragCoord.xy is in screen space / fragment coordinates */
    outside = energyField(gl_FragCoord.xy, 1.0, 40.0);
    if(outside == true)
        color_out = vec4(1.0, 0.0, 0.0, 1.0);
    else
        discard;
}
于 2010-08-27T20:42:36.627 回答
13

这是一种方法,我们首先生成一个分段仿射马铃薯,然后通过插值对其进行平滑处理。插值思想基于采用 DFT,然后将低频保持原样,在高频处用零填充,并采用逆 DFT。

这是只需要标准 Python 库的代码:

import cmath
from math import atan2
from random import random

def convexHull(pts):    #Graham's scan.
    xleftmost, yleftmost = min(pts)
    by_theta = [(atan2(x-xleftmost, y-yleftmost), x, y) for x, y in pts]
    by_theta.sort()
    as_complex = [complex(x, y) for _, x, y in by_theta]
    chull = as_complex[:2]
    for pt in as_complex[2:]:
        #Perp product.
        while ((pt - chull[-1]).conjugate() * (chull[-1] - chull[-2])).imag < 0:
            chull.pop()
        chull.append(pt)
    return [(pt.real, pt.imag) for pt in chull]


def dft(xs):
    pi = 3.14
    return [sum(x * cmath.exp(2j*pi*i*k/len(xs)) 
                for i, x in enumerate(xs))
            for k in range(len(xs))]

def interpolateSmoothly(xs, N):
    """For each point, add N points."""
    fs = dft(xs)
    half = (len(xs) + 1) // 2
    fs2 = fs[:half] + [0]*(len(fs)*N) + fs[half:]
    return [x.real / len(xs) for x in dft(fs2)[::-1]]

pts = convexHull([(random(), random()) for _ in range(10)])
xs, ys = [interpolateSmoothly(zs, 100) for zs in zip(*pts)]   #Unzip.

这会产生类似这样的东西(初始点和插值):

分段仿射马铃薯和平滑版本

这是另一个尝试:

pts = [(random() + 0.8) * cmath.exp(2j*pi*i/7) for i in range(7)]
pts = convexHull([(pt.real, pt.imag ) for pt in pts])
xs, ys = [interpolateSmoothly(zs, 30) for zs in zip(*pts)]

例子

这些偶尔会有扭结和凹陷。这就是这个 blob 家族的性质。

请注意,SciPy 具有凸包和 FFT,因此上述函数可以用它们代替。

于 2016-09-24T08:19:27.297 回答
1

您可能可以设计算法来执行此操作,这些算法是一系列随机迷宫生成算法的次要变体。我会根据union-find方法推荐一个。

union-find 的基本思想是,给定一组被划分为不相交(非重叠)子集的项目,以快速识别特定项目属于哪个分区。“联合”是将两个不相交的集合组合在一起以形成一个更大的集合,“查找”是确定特定成员属于哪个分区。这个想法是集合的每个分区都可以由集合的特定成员标识,因此您可以形成树结构,其中指针从一个成员指向一个成员指向根。您可以通过首先找到每个分区的根,然后修改一个根的(以前为空的)指针以指向另一个,来合并两个分区(为每个分区指定一个任意成员)。

您可以将问题表述为不相交的联合问题。最初,每个单独的单元格都是它自己的一个分区。您想要的是合并分区,直到您获得连接单元的少量分区(不一定是两个)。然后,您只需选择一个(可能是最大的)分区并绘制它。

对于每个单元格,您都需要一个指针(最初为 null)来进行联合。您可能需要一个位向量来充当一组相邻单元格。最初,每个单元将有一组四个(或八个)相邻单元。

对于每次迭代,您随机选择一个单元格,然后按照指针链查找其根。在根的详细信息中,您可以找到它的邻居集。从中选择一个随机成员,然后找到它的根,以识别相邻区域。执行联合(将一个根指向另一个根等)以合并两个区域。重复直到您对其中一个区域感到满意为止。

合并分区时,新根的新邻居集将是前两个根的邻居集的集合对称差(异或)。

您可能希望在每个根元素中增加分区(例如大小)时维护其他数据。您可以使用它来更有选择性地继续使用特定的工会,并帮助决定何时停止。分区中细胞散射的一些测量可能是相关的——例如,小的偏差或标准偏差(相对于大的细胞计数)可能表示密集的大致圆形斑点。

完成后,您只需扫描所有单元格以测试每个单元格是否是您选择的分区的一部分以构建单独的位图。

在这种方法中,当您在迭代开始时随机选择一个单元格时,会强烈倾向于选择较大的分区。当您选择一个邻居时,也有选择更大的相邻分区的偏见。这意味着您往往会很快获得一个明显占主导地位的 blob。

于 2010-08-27T20:41:32.497 回答