回答:
あなたが説明しているのはセグメンテーション問題です。実際には未解決の問題だと言ってすみません。しかし、これに推奨する方法の1つは、Graph-Cutベースのアルゴリズムです。Graph-Cutは、ローカルに接続されたノードのグラフとして画像を表します。Max-flow-min-cut定理とFord Fulkersonアルゴリズムを使用して、2つのサブコンポーネント間の境界の長さが最小になるように、グラフの接続されたコンポーネントを再帰的に分割します。
基本的に、すべての水タイルをグラフに接続します。隣接する水タイル間の差に対応するグラフのエッジに重みを割り当てます。あなたの場合、すべての重みが1になる可能性があると思います。望ましい結果を得るには、異なる重みスキームで遊ぶ必要があります。たとえば、海岸への隣接を含む重みを追加する必要がある場合があります。
次に、グラフのすべての接続コンポーネントを見つけます。これらは明らかな海/湖などです。
最後に、接続されたコンポーネントごとに、2つの新しいサブコンポーネントを接続するエッジの重みが最小になるように、コンポーネントを再帰的に細分割します。すべてのサブコンポーネントが最小サイズ(海の最大サイズなど)に達するまで、または2つのコンポーネントを切断するエッジの重量が大きすぎる場合は、再帰的に分割し続けます。最後に、残っているすべての接続コンポーネントにラベルを付けます。
これが実際に行うことは、チャネルで海を切り離すことですが、大規模な海を横切ることはありません。
ここに擬似コードがあります:
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の場合、中央の海も半分に分割されます。
良い結果が得られると思う完全なアルゴリズムを次に示します。
実行形態学的浸食、各タイルは水を考慮した上でマップのコピーを作成する-水域での唯一の(あなたが1枚のタイルより広い川を持っている場合は、より大きな面積、)それとその近隣諸国のすべてが水である場合に。これにより、すべての川が完全に消えます。
(これにより、内海の左側の島水は川と見なされます。これが問題である場合は、代わりにvrinekの答えが提案するような別のルールを使用できます。以下の手順は、ここに何らかの「川の削除」ステップがあります。)
侵食されたマップの接続された水成分を見つけ、それぞれに一意のラベルを付けます。(これを行う方法を既に知っていると思います。)これで、川と海岸水(侵食が影響した場所)以外のすべてにラベルが付けられました。
元のマップの各水タイルについて、侵食されたマップの隣接する水タイルに存在するラベルを見つけてから、次の操作を行います。
(このステップでは、侵食されたマップ(読み取り元)と元のマップ(書き込み先)のラベルのグリッドを別々に保持する(または1つの構造内に2つのラベルフィールドを持つ)必要があります。順序依存エラー。)
個々の川にも一意にラベルを付けたい場合は、上記の手順の後、ラベル付けされていない水の残りの接続コンポーネントをすべて見つけて、ラベル付けします。