2D 맵에서 연결된 (논리적으로 구별되는) 수역을 어떻게 감지 할 수 있습니까?


17

2D 육각형 그리드 맵이 있습니다. 각 육각 셀에는 물인지 바다인지를 결정하는 데 사용되는 높이 값이 있습니다. 나는 수역을 결정하고 표시하는 좋은 방법을 생각하고 있습니다. 대양과 내해는 쉽습니다 (홍수 채우기 알고리즘 사용).

그러나 지중해 와 같은 수역은 어떻습니까? 더 큰 물에 붙어있는 물의 몸체 ( "바다"와 "굴뚝"은 개구부의 크기에 의해서만 다름)?

다음은 내가 감지하려고하는 예입니다 (이미지의 중간에있는 푸른 물이 기술적으로 연결되어 있음에도 불구하고 왼쪽의 큰 바다와 다르게 표시되어야 함). 세계지도

어떤 아이디어?

답변:


10

당신이 설명하는 것은 분할 문제 입니다. 실제로 해결되지 않은 문제라고 말해서 죄송합니다. 그러나 내가 추천하는 한 가지 방법은 Graph-Cut 기반 알고리즘입니다. Graph-Cut은 이미지를 로컬로 연결된 노드의 그래프로 나타냅니다. Max-flow-min-cut 정리Ford Fulkerson 알고리즘을 사용하여 두 하위 구성 요소 사이의 경계가 최소 길이가되도록 그래프의 연결된 구성 요소를 재귀 적으로 세분화 합니다.

기본적으로 모든 워터 타일을 그래프로 연결합니다. 인접한 워터 타일 사이의 차이에 해당하는 그래프의 가장자리에 가중치를 지정하십시오. 나는 당신의 경우에 모든 가중치가 1 일 수 있다고 생각합니다. 바람직한 결과를 얻으려면 다른 가중치 체계를 사용해야합니다. 예를 들어 해안에 인접성을 포함하는 무게를 추가해야 할 수도 있습니다.

그런 다음 그래프의 연결된 모든 구성 요소를 찾으십시오. 이것들은 명백한 바다 / 호수 등입니다.

마지막으로, 연결된 각 구성 요소에 대해 두 개의 새로운 하위 구성 요소를 연결하는 모서리의 가중치최소 가되도록 구성 요소를 재귀 적으로 세분화합니다 . 모든 하위 구성 요소가 최소 크기 (예 : 바다의 최대 크기)에 도달 할 때까지 또는 두 구성 요소를 절단하는 모서리의 무게가 너무 큰 경우 재귀 적으로 세분화하십시오. 마지막으로 남아있는 연결된 모든 구성 요소에 레이블을 지정하십시오.

이것이 실제로 할 일은 채널에서 서로 바다를 잘라내지만 큰 바다를 가로 지르는 것은 아닙니다.

여기 의사 코드가 있습니다.

function SegmentGraphCut(Map worldMap, int minimumSeaSize, int maximumCutSize)
    Graph graph = new Graph();
    // First, build the graph from the world map.
    foreach Cell cell in worldMap:
        // The graph only contains water nodes
        if not cell.IsWater():
            continue;

        graph.AddNode(cell);

        // Connect every water node to its neighbors
        foreach Cell neighbor in cell.neighbors:
            if not neighbor.IsWater():
                continue;
            else:  
                // The weight of an edge between water nodes should be related 
                // to how "similar" the waters are. What that means is up to you. 
                // The point is to avoid dividing bodies of water that are "similar"
                graph.AddEdge(cell, neighbor, ComputeWeight(cell, neighbor));

   // Now, subdivide all of the connected components recursively:
   List<Graph> components = graph.GetConnectedComponents();

   // The seas will be added to this list
   List<Graph> seas = new List<Graph>();
   foreach Graph component in components:
       GraphCutRecursive(component, minimumSeaSize, maximumCutSize, seas);


// Recursively subdivides a component using graph cut until all subcomponents are smaller 
// than a minimum size, or all cuts are greater than a maximum cut size
function GraphCutRecursive(Graph component, int minimumSeaSize, int maximumCutSize, List<Graph> seas):
    // If the component is too small, we're done. This corresponds to a small lake,
    // or a small sea or bay
    if(component.size() <= minimumSeaSize):
        seas.Add(component);
        return;

    // Divide the component into two subgraphs with a minimum border cut between them
    // probably using the Ford-Fulkerson algorithm
    [Graph subpartA, Graph subpartB, List<Edge> cut] = GetMinimumCut(component);

    // If the cut is too large, we're done. This corresponds to a huge, bulky ocean
    // that can't be further subdivided
    if (GetTotalWeight(cut) > maximumCutSize):
        seas.Add(component);
        return;
    else:
        // Subdivide each of the new subcomponents
        GraphCutRecursive(subpartA, minimumSeaSize, maximumCutSize);
        GraphCutRecursive(subpartB, minimumSeaSize, maximumCutSize);

편집하다 : 그런데, 모든 가장자리 무게가 1 인 경우 최소 해수 크기가 약 40으로 설정되고 최대 절단 크기가 1 인 예제에서 알고리즘이 수행하는 작업은 다음과 같습니다.

임 구르

파라미터를 사용하여 다른 결과를 얻을 수 있습니다. 예를 들어, 최대 절단 크기가 3이면 주요 바다에서 더 많은 베이가 조각되고 바다 # 1이 북쪽과 남쪽의 절반으로 세분화됩니다. 최소 바다 크기가 20이면 중앙 바다도 절반으로 나뉩니다.


강력 해 보인다. 확실히 유도 생각했다.
v.oddou

이 게시물에 정말 감사합니다. 나는 당신의 예에서 합리적인 것을 얻었습니다
Kaelan Cooter

6

분리되었지만 연결된 수역을 식별하는 빠르고 더러운 방법은 모든 수역을 축소하고 간격이 나타나는지 확인하는 것입니다.

위의 예에서 2 개 이하의 워터 타일이 연결된 워터 타일 (빨간색으로 표시)을 모두 제거하면 원하는 결과와 약간의 에지 노이즈가 발생한다고 생각합니다. 본체에 레이블을 지정한 후에는 물을 원래 상태로 "흐르고"분리 된 타일을 다시 분리 된 본체에 다시 사용할 수 있습니다.

여기에 이미지 설명을 입력하십시오

다시 말하지만, 이것은 빠르고 더러운 솔루션입니다. 나중의 프로덕션 단계에서는 충분하지 않을 수 있지만 "지금이 작업을 수행"하고 다른 기능으로 넘어가는 것으로 충분합니다.


5

다음은 좋은 결과를 도출해야하는 완벽한 알고리즘입니다.

  1. 수역에서 형태 학적 침식 을 수행하십시오. 즉, 각 타일이 해당 타일 과 모든 이웃 (또는 하나의 타일보다 넓은 강을 가진 경우 더 큰 영역) 인 경우 에만 물로 간주되는 맵의 사본을 만듭니다. . 이로 인해 모든 강이 완전히 사라질 것입니다.

    (이것은 내해의 왼쪽에있는 섬의 물이 강이라고 생각할 것입니다. 이것이 문제라면, 대신에 하나의 vrinek의 대답이 제안 하는 것과 같은 다른 규칙을 사용할 수 있습니다 . 여기에 "강 삭제"단계가 있습니다.)

  2. 침식 된지도의 연결된 물 성분을 찾아 각각 고유 한 레이블을 지정하십시오. (나는 당신이 이미 이것을하는 방법을 알고 있다고 가정한다.) 이것은 이제 강과 해안의 물 (침식이 영향을 미쳤던 곳)을 제외한 모든 것을 표시한다.

  3. 원래 맵 의 각 워터 타일 에 대해 침식 된 맵에서 인접한 워터 타일에있는 레이블을 찾은 후 다음을 수행하십시오.

    • 타일 ​​자체가 침식 된지도에 라벨을 가지고 있다면, 그것은 바닷물입니다. 원래지도에서 해당 레이블을 제공하십시오.
    • 인접 레이블이 하나만 발견되면 해안 또는 강 입구입니다. 그 라벨을 줘.
    • 레이블이 없으면 강입니다. 그냥 내버려둬
    • 여러 레이블을 찾으면 두 개의 큰 몸체 사이에 짧은 병목 현상이 발생합니다. 강처럼 생각하거나 두 개의 몸체를 하나의 레이블로 결합하는 것이 좋습니다.

    (이 단계에서는 침식 된 맵 (읽은 위치)과 원본 (쓰기 한 위치)에 대해 별도의 레이블 그리드를 유지하거나 하나의 구조에 두 개의 레이블 필드가 있어야합니다. 그렇지 않으면 반복됩니다. 주문에 따라 다릅니다.)

  4. 개별 하천에도 고유하게 라벨을 지정하려면 위의 단계 후에 레이블이없는 물로 연결된 나머지 구성 요소를 모두 찾아 레이블을 지정하십시오.


1

vrinek의 아이디어에 따라 토지를 재배하거나 물을 줄이면 원래 연결되었던 부분이 토지가 자란 후에 연결이 끊어집니다.

이것은 다음과 같이 수행 될 수 있습니다 :

  1. 땅을 얼마나 성장시킬 것인지 정의하십시오 : 1 진수? 2 헥스? 이 값은n

  2. 모든 랜드 노드를 방문하고 모든 인접 n노드를 랜드 노드 까지 깊이 설정하십시오 (무한 루프를 얻지 않도록 사본에 쓰기)

  3. 현재 플러드 필 알고리즘을 다시 실행하여 현재 연결된 것과 그렇지 않은 것을 판별하십시오.


0

걸프의 위치를 ​​대략적으로 알고 있습니까? 그렇다면 플러드 필을 수정하여 인접하지만 탐색되지 않은 셀 수 (방문한 셀 목록과 함께)를 추적 할 수 있습니다. 16 진 맵에서 6으로 시작하고 해당 값이 특정 지점 아래로 떨어질 때마다 "개방"을 누르는 것을 알 수 있습니다.

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