O que você está descrevendo é o problema de segmentação . Lamento dizer que é realmente um problema não resolvido. Mas um método que eu recomendaria para isso é um algoritmo baseado em Graph-Cut . Graph-Cut representa a imagem como um gráfico de nós conectados localmente. Ele subdivide os componentes conectados do gráfico de forma recursiva, de modo que a borda entre os dois subcomponentes seja de comprimento mínimo usando o teorema Max-flow-min-cut e o algoritmo Ford Fulkerson .
Essencialmente, você conecta todos os blocos de água em um gráfico. Atribua pesos às arestas no gráfico que correspondem às diferenças entre os ladrilhos de água adjacentes. Penso que no seu caso, todos os pesos podem ser 1. Você terá que jogar com diferentes esquemas de ponderação para obter um resultado desejável. Pode ser necessário adicionar um peso que inclua adjacência às costas, por exemplo.
Em seguida, encontre todos os componentes conectados do gráfico. São mares / lagos óbvios e assim por diante.
Finalmente, para cada componente conectado, subdividir recursivamente o componente de modo que as arestas que conectam os dois novos subcomponentes peso mínimo . Mantenha subdividir-se recursivamente até que todos os subcomponentes atinjam um tamanho mínimo (como o tamanho máximo de um mar) ou se as bordas que cortam os dois componentes tiverem um peso muito alto. Por fim, identifique todos os componentes conectados que permanecem.
O que isso fará na prática é cortar os mares um do outro nos canais, mas não em grandes extensões de oceanos.
Aqui está no pseudocódigo:
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 propósito, aqui está o que o algoritmo faria com o seu exemplo com um tamanho mínimo do mar definido em torno de 40, com um tamanho máximo de corte de 1, se todos os pesos da borda forem 1:
Ao jogar com os parâmetros, você pode obter resultados diferentes. Um tamanho máximo de corte de 3, por exemplo, resultaria em muitas outras baías sendo escavadas nos mares principais, e o mar 1 seria subdividido na metade norte e sul. Um tamanho mínimo de 20 no mar resultaria na divisão do mar ao meio também.