구현하기 가장 쉬운 보로 노이 다이어그램 알고리즘? [닫은]


88

Voronoi 다이어그램을 구현하는 쉬운 알고리즘은 무엇입니까?

특별히 의사 형태의 알고리즘을 찾을 수 없었습니다. Voronoi 다이어그램 알고리즘, 튜토리얼 등의 링크를 공유하십시오.


답변:


32

포인트 세트의 들로네 삼각 분할을 계산하는 쉬운 알고리즘은 가장자리를 뒤집는 것입니다 . 들로네 삼각 분할은 보로 노이 다이어그램의 이중 그래프이므로 선형 시간의 삼각 분할에서 다이어그램을 구성 할 수 있습니다.

불행히도, 뒤집기 접근법의 최악의 실행 시간은 O (n ^ 2)입니다. Fortune의 라인 스윕과 같은 더 나은 알고리즘이 존재하며 O (n log n) 시간이 걸립니다. 그러나 이것은 구현하기가 다소 까다 롭습니다. 당신이 게으르다면 (나처럼) Delaunay 삼각 측량의 기존 구현을 찾아서 사용한 다음 이중 그래프를 계산하는 것이 좋습니다.

일반적으로 주제에 대한 좋은 책은 de Berg et al.의 Computational Geometry 입니다.


19

가장 쉬울까요? 이것이 무차별 대입 방식입니다. 출력의 각 픽셀에 대해 모든 점을 반복하고 거리를 계산하고 가장 가까운 것을 사용합니다. 가능한 한 느리지 만 매우 간단합니다. 성능이 중요하지 않으면 제대로 작동합니다. 나는 흥미로운 개선 작업을하고 있지만, 다른 사람이 동일한 (아주 분명한) 아이디어를 가지고 있는지 여전히 찾고 있습니다.


14

Bowyer-Watson 알고리즘은 이해하기 매우 쉽습니다. 다음은 구현입니다. http://paulbourke.net/papers/triangulate/ . 점 집합에 대한 delaunay 삼각 분할이지만 delaunay의 이중, 즉 voronoi 다이어그램을 구하는 데 사용할 수 있습니다. BTW. 최소 스패닝 트리는 들로네 삼각 분할의 하위 집합입니다.


12

보로 노이 다이어그램을 구성하는 가장 효율적인 알고리즘은 Fortune의 알고리즘 입니다. O (n log n)에서 실행됩니다.

다음은 C에서 그의 참조 구현에 대한 링크 입니다.

개인적으로 저는 Bill Simons와 Carson Farmer 의 파이썬 구현이 정말 마음에 듭니다 . 확장이 더 쉽다는 것을 알았 기 때문입니다.


c 구현에 대한 링크가 더 이상 작동하지 않는 것 같습니다 :(
FutureCake

1
구조에 @FutureCake 인터넷 아카이브 : web.archive.org/web/20181018224943/http://ect.bell-labs.com/who/...
polettix


9

Stephan Fortune / Shane O'Sullivan의 C 및 C ++의 2 차원 그래프에 대해 무료로 사용할 수있는 voronoi 구현이 있습니다.

VoronoiDiagramGenerator.cpp 

VoronoiDiagramGenerator.h 

여러 곳에서 찾을 수 있습니다. 예 : http://www.skynet.ie/~sos/masters/


4
광범위하게 참조되고 문서화되지 않았으며이 코드를 기반으로 본 거의 모든 재 구현은 잘못되었습니다 (다른 언어에서는 많은 사람들이 보로 노이가 필요하며 올바르게 이식하기에 충분히 이해할 수있는 사람은 거의 없습니다). 내가 본 유일한 작업 포트는 과학 / 학계 커뮤니티에서 온 것이며 함수 시그니처가 엄청나게 복잡하거나 대규모로 최적화되어 (대부분의 목적으로 사용할 수 없도록) 일반 프로그래머가 사용할 수 없게됩니다.
Adam

VoronoiDiagramGenerator.cpp는 기능이 제한되어 있습니다. 정렬되지 않은 모서리 세트를 출력합니다. 이것에서 실제 다각형을 추출하는 것은 중요하지 않습니다. 플러스 측에서는 경계 사각형에 대한 클립이 있으므로 무한대 포인트가 생성되지 않습니다.
Bram


6

원래 질문은 Voronoi를 구현하는 방법에 대해 묻지 만이 주제에 대한 정보를 검색 할 때 다음과 같은 게시물을 찾았다면 많은 시간을 절약 할 수 있었을 것입니다.

Voronoi 다이어그램을 구현하기위한 "거의 정확한"C ++ 코드가 인터넷에 많이 있습니다. 대부분은 시드 포인트가 매우 밀집되어있을 때 거의 실패를 유발하지 않았습니다. 너무 많은 시간을 낭비하기 전에 완성 된 프로젝트에서 사용할 것으로 예상되는 포인트 수로 온라인에서 찾은 코드를 광범위하게 테스트하는 것이 좋습니다.

내가 온라인에서 찾은 최고의 구현은 여기에서 링크 된 MapManager 프로그램의 일부였습니다. http://www.skynet.ie/~sos/mapviewer/voronoi.php 대부분 작동하지만 다룰 때 간헐적 인 다이어그램 손상이 발생합니다 10 ^ 6 포인트를 주문하십시오. 나는 부패가 어떻게 들어오고 있는지 정확히 알아낼 수 없었습니다.

어젯밤 나는 이것을 발견했다 : http://www.boost.org/doc/libs/1_53_0_beta1/libs/polygon/doc/voronoi_main.htm "The Boost.Polygon Voronoi library". 매우 유망 해 보입니다. 여기에는 정확성과 뛰어난 성능을 입증하기위한 벤치 마크 테스트가 함께 제공됩니다. 라이브러리에는 적절한 인터페이스와 문서가 있습니다. 지금까지이 라이브러리를 찾지 못해서 놀랐습니다. 그래서 여기에 글을 씁니다. (저는 연구 초기에이 게시물을 읽었습니다.)


4

실제로 https://rosettacode.org/wiki/Voronoi_diagram에서 사용 가능한 25 가지 언어에 대한 구현이 있습니다.

예 : Java의 경우 :

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class Voronoi extends JFrame {
    static double p = 3;
    static BufferedImage I;
    static int px[], py[], color[], cells = 100, size = 1000;

    public Voronoi() {
        super("Voronoi Diagram");
        setBounds(0, 0, size, size);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        int n = 0;
        Random rand = new Random();
        I = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
        px = new int[cells];
        py = new int[cells];
        color = new int[cells];
        for (int i = 0; i < cells; i++) {
            px[i] = rand.nextInt(size);
            py[i] = rand.nextInt(size);
            color[i] = rand.nextInt(16777215);

        }
        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {
                n = 0;
                for (byte i = 0; i < cells; i++) {
                    if (distance(px[i], x, py[i], y) < distance(px[n], x, py[n], y)) {
                        n = i;

                    }
                }
                I.setRGB(x, y, color[n]);

            }
        }

        Graphics2D g = I.createGraphics();
        g.setColor(Color.BLACK);
        for (int i = 0; i < cells; i++) {
            g.fill(new Ellipse2D .Double(px[i] - 2.5, py[i] - 2.5, 5, 5));
        }

        try {
            ImageIO.write(I, "png", new File("voronoi.png"));
        } catch (IOException e) {

        }

    }

    public void paint(Graphics g) {
        g.drawImage(I, 0, 0, this);
    }

    static double distance(int x1, int x2, int y1, int y2) {
        double d;
        d = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); // Euclidian
    //  d = Math.abs(x1 - x2) + Math.abs(y1 - y2); // Manhattan
    //  d = Math.pow(Math.pow(Math.abs(x1 - x2), p) + Math.pow(Math.abs(y1 - y2), p), (1 / p)); // Minkovski
        return d;
    }

    public static void main(String[] args) {
        new Voronoi().setVisible(true);
    }
}

3

가장 간단한 알고리즘은 voronoi 다이어그램의 정의에서 비롯됩니다. "각 다각형이 정확히 하나의 생성 지점을 포함 하고 주어진 다각형의 모든 지점이 다른 어떤 지점보다 생성 지점에 더 가깝도록 n 개의 지점이있는 평면을 볼록 다각형으로 분할하는 것입니다. . "wolfram의 정의.

여기서 중요한 부분은 모든 지점이 다른 지점보다 생성 지점에 더 가깝다는 것입니다. 여기에서 알고리즘은 매우 간단합니다.

  1. 생성 지점의 배열이 있습니다.
  2. 캔버스의 모든 픽셀을 반복합니다.
  3. 모든 픽셀에 대해 가장 가까운 생성 지점을 찾으십시오.
  4. 픽셀 색상을 얻으려는 다이어그램에 따라. 테두리로 구분 된 다이어그램을 원하는 경우 두 번째에서 가장 가까운 지점을 확인한 다음 일부 값보다 작 으면 테두리 색상으로 차이와 색상을 확인합니다.

색상 다이어그램을 원한다면 모든 생성 포인트와 관련된 색상을 가지고 모든 픽셀에 가장 가까운 생성 포인트 관련 색상으로 색상을 지정하십시오. 그게 다입니다. 효율적이지는 않지만 구현하기가 매우 쉽습니다.


3

이것은 가장 빠른 방법입니다. 단순한 보로 노이지 만 멋지게 보입니다. 공간을 그리드로 나누고 무작위로 배치 된 각 그리드 셀에 점을 배치하고 그리드를 따라 이동하여 3x3 셀을 확인하여 인접 셀과의 관계를 찾습니다.

그라디언트가 없으면 더 빠릅니다.

가장 쉬운 3D 보로 노이가 무엇인지 물어볼 수 있습니다. 아는 것은 매혹적 일 것입니다. 아마 3x3x3 셀과 그라디언트 확인.

http://www.iquilezles.org/www/articles/smoothvoronoi/smoothvoronoi.htm

float voronoi( in vec2 x )
{
    ivec2 p = floor( x );
    vec2  f = fract( x );

    float res = 8.0;
    for( int j=-1; j<=1; j++ )
    for( int i=-1; i<=1; i++ )
    {
        ivec2 b = ivec2( i, j );
        vec2  r = vec2( b ) - f + random2f( p + b );
        float d = dot( r, r );

        res = min( res, d );
    }
    return sqrt( res );
}

체비 체프 거리도 마찬가지입니다. 여기에서 random2f 2d 플로트 노이즈를 사용할 수 있습니다.

https://www.shadertoy.com/view/Msl3DM

편집 : 나는 이것을 코드와 같은 C로 변환했습니다.

이것은 얼마 전이었는데, 사람들을 위해이게 멋지다고 생각합니다.

 function rndng ( n: float ): float
 {//random number -1, 1
     var e = ( n *321.9)%1;
     return  (e*e*111.0)%2-1;
 }

 function voronoi(  vtx: Vector3  )
 {
     var px = Mathf.Floor( vtx.x );
     var pz = Mathf.Floor( vtx.z );
     var fx = Mathf.Abs(vtx.x%1);
     var fz = Mathf.Abs(vtx.z%1);

     var res = 8.0;
     for( var j=-1; j<=1; j++ )
     for( var i=-1; i<=1; i++ )
     {
         var rx = i - fx + nz2d(px+i ,pz + j ) ;
         var rz = j - fz + nz2d(px+i ,pz + j ) ;
         var d = Vector2.Dot(Vector2(rx,rz),Vector2(rx,rz));
         res = Mathf.Min( res, d );
     }
     return Mathf.Sqrt( res );
 }

자명하지 않은 하나의 문자 변수를 사용하는 이유는 무엇입니까? 그리고 무엇 ivec2입니까? 또는 vec2? 읽을 수 없습니다.
shinzou

좋은 지적,
나도


0

Fortune의 알고리즘 / Sweep line 알고리즘을 기반으로 한 Google 코드에서이 우수한 C # 라이브러리를 찾았습니다.

https://code.google.com/p/fortune-voronoi/

목록을 생성하기 만하면됩니다. 벡터는 두 개의 숫자 (좌표)를 float로 전달하여 만들 수 있습니다. 그런 다음 목록을 Fortune.ComputeVoronoiGraph ()에 전달합니다.

다음 위키 백과 페이지에서 알고리즘의 개념을 조금 더 이해할 수 있습니다.

http://en.wikipedia.org/wiki/Fortune%27s_algorithm

http://en.wikipedia.org/wiki/Sweep_line_algorithm

내가 이해할 수 없었던 한 가지는 Partially Infinite 가장자리에 대한 선을 만드는 방법입니다 (좌표 기하학에 대해 많이 알지 못합니다 :-)). 누군가가 알고 있다면 저에게도 알려주십시오.


2
이러한 링크가 질문에 답할 수 있지만 여기에 답변의 필수 부분을 포함하고 참조 용 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 무효화 될 수 있습니다.
Kmeixner 2015 년

0

이미지에 그리려는 경우 대기열 기반 플러드 채우기 알고리즘을 사용할 수 있습니다.

Voronoi::draw(){
    // define colors for each point in the diagram;
    // make a structure to hold {pixelCoords,sourcePoint} queue objects
    // initialize a struct of two closest points for each pixel on the map
    // initialize an empty queue;

    // for each point in diagram:
        // for the push object, first set the pixelCoords to pixel coordinates of point;
        // set the sourcePoint of the push object to the current point;
        // push the queue object;

    // while queue is not empty:
        // dequeue a queue object;
        // step through cardinal neighbors n,s,e,w:
            // if the current dequeued source point is closer to the neighboring pixel than either of the two closest:
                // set a boolean doSortAndPush to false;
                // if only one close neighbor is set:
                    // add sourcePoint to closestNeighbors for pixel;
                    // set doSortAndPush to true;
                // elif sourcePoint is closer to pixel than it's current close neighbor points:
                    // replace the furthest neighbor point with sourcePoint;
                    // set doSortAndPush to true;
                // if flag doSortAndPush is true:
                    // re-sort closest neighbors; 
                    // enqueue object made of neighbor pixel coordinates and sourcePoint;

    // for each pixel location:
        // if distance to closest point within a radius for point drawing:
            // color pixel the point color;
        // elif distances to the two closest neighbors are roughly equal:
            // color the pixel to your border color;
        // else 
            // color the pixel the color of the point's region; 

}

대기열을 사용하면 영역이 병렬로 분산되어 총 픽셀 방문 수를 최소화 할 수 있습니다. 스택을 사용하는 경우 첫 번째 포인트는 전체 이미지를 채우고 두 번째 포인트는 첫 번째 포인트보다 더 가까운 픽셀을 채 웁니다. 이것은 계속 될 것이며 방문 수를 크게 증가시킬 것입니다. FIFO 대기열을 사용하면 푸시 된 순서대로 픽셀을 처리합니다. 결과 이미지는 스택을 사용하든 대기열을 사용하든 거의 동일하지만 대기열에 대한 big-O는 스택 알고리즘의 big-O보다 선형에 훨씬 더 가깝습니다 (이미지 픽셀 수와 관련하여). 일반적인 아이디어는 영역이 동일한 속도로 확산되고 충돌은 일반적으로 영역 경계에 해당하는 지점에서 정확히 발생한다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.