Те, що ви описуєте, - це проблема сегментації . Пробачте, кажу, що це насправді невирішена проблема. Але один із способів я рекомендував би для цього алгоритм на основі 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 приведе до того, що центральне море поділиться і навпіл.