Як я можу виявити пов'язані (але логічно відмінні) водойми на 2D карті?


17

У мене є двовимірна шестикутна карта сітки. Кожна шестигранна клітина має значення висоти, яке використовується для визначення, чи це вода чи океан. Я намагаюся придумати хороший спосіб визначити та позначити водойми. Океани та внутрішні моря прості (за допомогою алгоритму заповнення заплав).

А як щодо водойм, як Середземномор’я ? Водні об'єкти, які прикріплюються до більших (де "моря" та "затоки" відрізняються лише розміром отвору)?

Ось приклад того, що я намагаюся виявити (блакитне водойма в середині зображення, яке повинно бути позначене інакше, ніж велике океанське ліворуч, незважаючи на технічне з'єднання): карта світу

Будь-які ідеї?

Відповіді:


10

Те, що ви описуєте, - це проблема сегментації . Пробачте, кажу, що це насправді невирішена проблема. Але один із способів я рекомендував би для цього алгоритм на основі Graph-Cut . Graph-Cut представляє зображення як графік локально пов'язаних вузлів. Він підрозділяє пов'язані компоненти графіка рекурсивно таким чином, що межа між двома підкомпонентами має мінімальну довжину, використовуючи теорему Макс-потоку-хв-різання та Форд Фулкерсон алгоритм .

По суті, ви з'єднуєте всі водяні плитки у графіку. На грані призначте ваги, які відповідають різниці між сусідніми плитками води. Я думаю, що у вашому випадку всі ваги можуть бути 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);

EDIT : До речі, ось що алгоритм зробив би з вашим прикладом з мінімальним розміром моря, рівним приблизно 40, з максимальним розміром зрізу 1, якщо всі крайові ваги дорівнюють 1:

Імгур

Граючи з параметрами, ви можете отримати різні результати. Наприклад, максимальний розмір зрізу, наприклад, призведе до того, що багато інших заток витягнуться з головних морів, а море №1 стане розділеним на північ і південь. Мінімальний розмір моря 20 приведе до того, що центральне море поділиться і навпіл.


здається потужним. безумовно, спонукання до думки.
v.oddou

Дуже дякую за цю публікацію. Мені вдалося отримати щось розумне з вашого прикладу
Kaelan Cooter

6

Швидкий і брудний спосіб визначити окремий, але з’єднаний водойму, буде зменшити всі водойми і побачити, чи з’являються прогалини.

У наведеному вище прикладі я думаю, що видалення всіх плиток з водою, які мають 2 або менше водяних плиток, підключених до них (позначено червоним кольором), забезпечить вам бажаний результат плюс трохи крайового шуму. Після того, як ви помітили тіла, ви можете "перетікати" воду до початкового стану і повернути вилучені плитки для відокремлених нині тіл.

введіть тут опис зображення

Знову ж таки, це швидке і брудне рішення, воно може бути недостатньо хорошим для наступних етапів виробництва, але для цього буде достатньо "зайнятися цим" і перейти до якоїсь іншої функції.


5

Ось повний алгоритм, який, на мою думку, повинен дати хороші результати.

  1. Виконати морфологічну ерозію на акваторії - тобто зробіть копію карти, на якій кожна плитка вважається лише водою якщо вона та всі її сусіди (або більша площа, якщо у вас є річки ширші, ніж одна плитка) - вода . Це призведе до того, що всі річки повністю зникнуть.

    (Це вважатиме річку на острові в лівій частині вашого внутрішнього моря річками. Якщо це проблема, ви можете використовувати інше правило, наприклад пропонується відповідь одного вринека ; наступні кроки будуть працювати, поки ви тут є якийсь крок "видалити річки".)

  2. Знайдіть з'єднані водні компоненти еродованої карти і дайте кожному унікальний мітку. (Я припускаю, що ви вже знаєте, як це зробити.) Це тепер позначає все, крім річок і берегової води (там, де ерозія мала вплив).

  3. На кожній водяній плитці в оригінальній карті знайдіть мітки, присутні на сусідніх водоймах на еродованій карті, а потім:

    • Якщо сама плитка має етикетку на еродованій карті, то це вода середнього моря; дайте йому таку мітку на оригінальній карті.
    • Якщо ви знайдете лише одну чітку сусідню етикетку, то це берег чи гирло річки; дайте йому таку етикетку.
    • Якщо ви не знайдете ярликів, то це річка; Облиш це.
    • Якщо ви знайдете кілька міток, то це коротке вузьке місце між двома більшими тілами; ви можете або розглянути його як річку, або об'єднати два тіла під однією міткою.

    (Зверніть увагу, що для цього кроку ви повинні зберігати окремі сітки міток (або мати два поля мітки в одній структурі) для еродованої карти (що ви читаєте з) та оригіналу (до якого ви пишете), інакше буде ітерація- помилки, що залежать від порядку.)

  4. Якщо ви також хочете однозначно позначити окремі річки, то, виконуючи вищезазначені дії, знайдіть усі сполучені компоненти немеченої води та позначте їх.


1

Слідуючи ідеї vrinek, як щодо вирощування землі (або зменшення води), щоб частини, які ви спочатку з'єднали, будуть від'єднані після вирощування землі?

Це можна зробити так:

  1. Визначте, скільки ви хочете вирощувати землю: 1 шестигранник? 2 шестигранники? Це значення єn

  2. Відвідайте всі наземні вузли та встановіть усіх своїх сусідів на глибину, nщоб заземлити вузли (написати копію, щоб не отримати нескінченну петлю)

  3. Запустіть свій оригінальний алгоритм заливки, щоб визначити, що зараз підключено, а що ні


0

Чи маєте ви грубе уявлення про те, де знаходиться затока? Якщо так, ви можете змінити заливку, щоб відстежувати кількість сусідніх, але не досліджених осередків (разом зі списком відвідуваних осередків). Він починається з шестигранної карти 6, і коли це значення опускається нижче певної точки, то ви знаєте, що ви потрапляєте в "отвір".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.