Pythonで画像を取り扱う時にしばしば画像の領域を扱う時があります。最近ですと Segment Anything を使うプロジェクトでよくやります。
領域を取得するとよく次のような二次元座標のリストが得られます。この座標に頂点を持つ多角形というやつです。
polygon = [(168, 509), (174, 27), (223, 28), (216, 510)] polygon_list = [ [(168, 509), (174, 27), (223, 28), (216, 510)], [(323, 509), (326, 307), (371, 308), (368, 510)], [(551, 297), (553, 33), (593, 34), (592, 298)], ]
この領域を合成する方法を紹介します。
使うのはShapelyというライブラリです。これはpip install shapelyでインストール可能なライブラリで、本来は地理情報の処理のために用いるライブラリです。JavaScriptのTurf.jsのようなものです。ただの二次元座標をShapelyに使える形に変換し、Shapely上で領域を処理し、最後にShapelyで扱う形式から元の二次元座標に戻すという流れで領域を合成します。
shapely/shapely: Manipulation and analysis of geometric objects
The Shapely User Manual — Shapely 0 documentation
実際のコードが次です。コード本体はunion_polygonsとmulti_polygon_to_tuple_listの2つの関数のみで、他の部分はデモ用のコードです。
import random # randomモジュールをインポート
from shapely import unary_union, MultiPolygon # shapelyからunary_unionとMultiPolygonをインポート
from shapely.geometry import Polygon # shapely.geometryからPolygonをインポート
import matplotlib.pyplot as plt # matplotlib.pyplotをpltとしてインポート
import japanize_matplotlib # matplotlibで日本語を扱えるようにするためのモジュールをインポート
# 複数のポリゴンをタプルのリストに変換する関数
def multi_polygon_to_tuple_list(polygons: MultiPolygon) -> list[list[tuple[int, int]]]:
polygons_list = [] # 変換されたポリゴンのリストを初期化
for polygon in polygons.geoms: # 各ポリゴンについて
exterior_coords = list(polygon.exterior.coords) # ポリゴンの外側の座標を取得
polygons_list.append([(int(coord[0]), int(coord[1])) for coord in exterior_coords]) # 座標を整数に変換してリストに追加
return polygons_list # 変換されたポリゴンのリストを返す
# ポリゴンのリストを結合する関数
def union_polygons(polygons: list[list[tuple[int, int]]]) -> list[list[tuple[int, int]]]:
polygons = [Polygon(p) for p in polygons] # リスト内の各タプルリストをPolygonオブジェクトに変換
combined_polygons: MultiPolygon = unary_union(polygons) # すべてのポリゴンを結合
return multi_polygon_to_tuple_list(combined_polygons) # 結合されたポリゴンをタプルのリストに変換して返す
# ポリゴンをプロットする関数
def plot_polygons(ax, polygons: list[list[tuple[int, int]]], title: str, labels: list[str]):
for i, polygon in enumerate(polygons): # 各ポリゴンについて
x, y = zip(*polygon) # x座標とy座標を分ける
ax.fill(x, y, color="#" + "".join([random.choice('0123456789ABCDEF') for _ in range(6)]), alpha=0.5,
label=labels[i]) # ポリゴンを塗りつぶしてプロット
ax.set_title(title) # タイトルを設定
ax.legend() # 凡例を表示
if __name__ == '__main__':
# 既存の領域リスト
polygon_list = [
[(472, 508), (479, 31), (523, 32), (516, 509)],
[(472, 509), (476, 310), (518, 311), (515, 510)], # ↑と重複している領域
[(547, 508), (553, 33), (595, 34), (589, 509)],
]
combined_polygon_coords = union_polygons(polygon_list) # ポリゴンを結合
print(" 元の多角形の座標:", polygon_list) # 元の多角形の座標を表示
print("結合された多角形の座標:", combined_polygon_coords) # 結合された多角形の座標を表示
original_labels = [f"元の多角形-{i + 1}" for i in range(len(polygon_list))] # 元の多角形のラベル
combined_labels = [f"結合された多角形-{i + 1}" for i in range(len(combined_polygon_coords))] # 結合された多角形のラベル
fig, axs = plt.subplots(1, 2, figsize=(12, 6)) # 2つのサブプロットを持つ図を作成
plot_polygons(axs[0], polygon_list, "元の多角形", original_labels) # 元の多角形をプロット
plot_polygons(axs[1], combined_polygon_coords, "結合された多角形", combined_labels) # 結合された多角形をプロット
plt.tight_layout() # レイアウトを整える
plt.show() # 図を表示
実際にこれを実行した結果が次です。元々の多角形で重複していた部分が統合され、二つの多角形になりました。

この記事では多角形の合成にのみ焦点をあてましたがShapelyはより多彩なことができます。図形レベルでの複雑な処理が必要な場合は一旦Shapelyで扱うのがおすすめです。