Quello che stai descrivendo è il problema di segmentazione . Mi dispiace dire che in realtà è un problema irrisolto. Ma un metodo che consiglierei per questo è un algoritmo basato sul taglio grafico . Graph-Cut rappresenta l'immagine come un grafico di nodi connessi localmente. Suddivide ricorsivamente i componenti collegati del grafico in modo tale che il bordo tra i due sottocomponenti sia di lunghezza minima utilizzando il teorema Max-flow-min-cut e l' algoritmo Ford Fulkerson .
In sostanza, si collegano tutte le tessere dell'acqua in un grafico. Assegnare pesi ai bordi nel grafico che corrispondono alle differenze tra le piastrelle adiacenti dell'acqua. Penso che nel tuo caso, tutti i pesi potrebbero essere 1. Dovrai giocare con diversi schemi di ponderazione per ottenere un risultato desiderabile. Ad esempio, potresti dover aggiungere un po 'di peso che include l'adiacenza alle coste.
Quindi, trova tutti i componenti collegati del grafico. Questi sono ovvi mari / laghi e così via.
Infine, per ogni componente collegato, suddividere ricorsivamente il componente in modo tale che i bordi che collegano i due nuovi sottocomponenti abbiano un peso minimo . Continua a suddividere in modo ricorsivo fino a quando tutti i sottocomponenti raggiungono una dimensione minima (cioè come la dimensione massima di un mare) o se i bordi che tagliano i due componenti hanno un peso troppo elevato. Infine, etichettare tutti i componenti collegati che rimangono.
In pratica ciò che farà è tagliare i mari gli uni dagli altri ai canali, ma non attraversare grandi sponde di oceani.
Eccolo in pseudocodice:
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 : A proposito, ecco cosa farebbe l'algoritmo con il tuo esempio con una dimensione del mare minima impostata su circa 40, con una dimensione di taglio massima di 1, se tutti i pesi del bordo sono 1:
Giocando con i parametri, è possibile ottenere risultati diversi. Una dimensione massima di taglio di 3, ad esempio, comporterebbe molte più baie scavate dai mari principali e il mare n. 1 sarebbe suddiviso in metà nord e sud. Una dimensione minima del mare di 20 comporterebbe anche la divisione del mare centrale a metà.