【Python・OpenCV】輪郭形状の近似(cv2.approxPolyDP)

※当サイトではアフィリエイト広告を利用しています

Python プログラミング 画像処理

【Python・OpenCV】輪郭形状の近似(cv2.approxPolyDP)

2024-09-24

はじめに

cv2.approxPolyDP関数は画像中の形状を解析し、その特徴を抽出するための強力なツールです。形状の簡略化、ノイズ除去、形状分類、物体検出、特徴点抽出など、様々な画像処理のタスクに適用することができます。

本記事では、複雑な形状の輪郭をより単純な多角形で近似することができるcv2.approxPolyDP関数を紹介します。
cv2.findContours関数で検出した輪郭データを活用することができます。
cv2.findContours関数に関しては、下記の記事で紹介しています。

(広告) OpenCV関連書籍をAmazonで探す

cv2.approxPolyDP関数

cv2.approxPolyDP関数は、Douglas-Peuckerアルゴリズムと呼ばれるアルゴリズムに基づいて、与えられた曲線を近似する多角形を計算します。このアルゴリズムは、元の輪郭から指定した精度で少ない頂点を求め、それらを結び元の曲線を近似する多角形を生成します。

cv2.approxPolyDP(curve, epsilon, closed[, approxCurve])

引数

名称説明
curve(必須)・近似したい輪郭
numpy.ndarrayオブジェクトで表現され、形状は(N,1,2)または(N,2)です。Nは輪郭を構成する点の数です。
cv2.findContours関数の出力が指定可
epsilon(必須)・元の輪郭と近似輪郭の最大距離。この値が大きいほど、近似の精度は低くなり、結果として得られる多角形の頂点数は少なくなります。
・一般的に、輪郭の周囲長に対する割合として指定されます。
・float型
closed(必須)・近似される輪郭が閉じているかどうかを示すブール値。
Trueの場合、関数は閉じた輪郭として処理し、Falseの場合、開いた曲線として扱います。
・bool型
approxCurve(オプション)・近似後の輪郭を格納する出力引数。
・指定された場合、近似された輪郭がこの変数に格納されます。指定されない場合は、新しい配列が作成されて結果が格納され戻り値として取得できます。

戻り値

近似後の輪郭を表す配列が返されます。
この配列の各要素は、近似多角形の頂点の座標を表す2要素の配列です。

使い方

以下にcv2.approxPolyDP関数を使用して単純な形状の輪郭を近似する例を紹介します。

このコードでは、よれよれの四角形をcv2.approxPolyDP関数で近似しています。
引数のepsilon値を調整することで、近似の精度を制御できます。

下の画像を入力画像として使用しました。

サンプルコードは下のとおりです。

import cv2
import numpy as np
from matplotlib import pyplot as plt

# 画像読み込み(グレースケール)
image = cv2.imread("py-opencv-approxpolydp/images/image.png", cv2.IMREAD_GRAYSCALE)

# 輪郭の検出
contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 最初の(そして唯一の)輪郭を取得
contour = contours[0]

# 入力画像のサイズを取得
height, width = image.shape

# 輪郭を描画
contour_image = np.zeros((height, width, 3), dtype=np.uint8)
contour_image = cv2.drawContours(contour_image, contours, 0, (255, 0, 255), 1)

# 輪郭の周囲長を計算
epsilon = 0.03 * cv2.arcLength(contour, True)

# 輪郭の近似
approx = cv2.approxPolyDP(contour, epsilon, True)

# 輪郭の頂点の数を出力
print(f"元の輪郭の頂点数: {len(contour)}")
print(f"近似後の輪郭の頂点数: {len(approx)}")

# 結果の描画
result = np.zeros((height, width, 3), dtype=np.uint8)
cv2.drawContours(result, [approx], 0, (0, 255, 0), 1)

# 結果の可視化
plt.rcParams["figure.figsize"] = [16,6]                             # 表示領域のアスペクト比を設定
title = "cv2.approxPolyDP: codevace.com"
plt.figure(title)                                                   # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95)   # 余白を設定

plt.subplot(131)                                                    # 1行3列の1番目の領域にプロットを設定
plt.imshow(image, cmap='gray')                                      # 修復後の画像をグレースケールで表示
plt.title("Original", fontsize=20)                                  # 画像タイトルとフォントサイズを設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す

plt.subplot(132)                                                    # 1行3列の2番目の領域にプロットを設定
plt.imshow(contour_image)                                           # 修復後の画像を表示
plt.title("Contour", fontsize=20)                                   # 画像タイトルとフォントサイズを設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す

plt.subplot(133)                                                    # 1行3列の2番目の領域にプロットを設定
plt.imshow(result)                                                  # 修復後の画像を表示
plt.title("Approximated Contour", fontsize=20)                      # 画像タイトルとフォントサイズを設定
plt.axis("off")                                                     # 軸目盛、軸ラベルを消す

plt.show()                                                          # ウィンドウを表示する

このコードでは次の手順で処理しています。

  1. 6行目:入力画像をグレースケールで読み込む
  2. 9〜12行目:輪郭を検出
  3. 15〜19行目:入力画像のサイズを取得し、入力画像と同じサイズの黒い背景画像を生成し、検出した輪郭を描画
  4. 22〜25行目:輪郭の周囲からepsilonに設定する値を計算し、輪郭の近似を実行
  5. 28〜29行目:元の輪郭の頂点数と近似後の輪郭の頂点数を出力
  6. 36〜56行目:結果を画像で表示

実行した結果は、次のようになります。
頂点の数は228個から4個になりました。

元の輪郭の頂点数: 288
近似後の輪郭の頂点数: 4

結果の描画は左に入力画像、真ん中に検出した輪郭のみの画像、右に近似後の輪郭の画像です。
期待通り、四角形の輪郭を得ることができました。
また、頂点の数を大幅に減少していることから、cv2.approxPolyDP関数を使用してデータを圧縮することが可能です。これは処理速度を向上させることも意味します。

応用:簡単な形状認識

cv2.approxPolyDP関数を使用して、画像内のオブジェクトの形状を識別できます。
例えば、近似後の頂点数に基づいて、三角形、四角形、円などの形状を判定するサンプルコードは次のようになります。

import cv2

# 形状認識 関数
# 輪郭データから近似を行い、形状を認識
def identify_shape(contour):
    # 輪郭の周囲長を計算
    epsilon = 0.03 * cv2.arcLength(contour, True)

    # 輪郭の近似
    approx = cv2.approxPolyDP(contour, epsilon, True)
    
    if len(approx) == 3:
        return "三角形"
    elif len(approx) == 4:
        return "四角形"
    elif len(approx) == 5:
        return "五角形"
    elif len(approx) >= 8:
        return "円"
    else:
        return "判定できません."
    

# 画像読み込み(グレースケール)
image = cv2.imread("py-opencv-approxpolydp/images/image2.png", cv2.IMREAD_GRAYSCALE)

# 輪郭の検出
contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 検出した輪郭のごとに形状を認識
for i, contour in enumerate(contours):
    # 形状認識
    result = identify_shape(contour)

    # 結果を出力
    print(f"形状は {result} です。")

入力画像は次の画像を使用しました。
崩れた三角形と五角形が描画されています。

形状認識処理はidentify_shape関数を定義し、その中に記述しています。
近似後の輪郭の頂点の数から形状を判定します。
epsilonの計算式にある"0.03"という係数は、処理する画像によって調整が必要かもしれません。

実行結果は次のように、正しく三角形と五角形を認識できました。

形状は 五角形 です。
形状は 三角形 です。

おわりに

cv2.approxPolyDP関数は、輪郭の近似と単純化を行う強力なツールです。
適切なepsilon値の選択が重要であり、これにより近似の精度を制御できます。
この関数は、形状認識、輪郭の単純化、データ圧縮など、様々な応用分野で活用できます。

輪郭データの解析についてはcv2.approxPolyDP関数以外にも様々な機能がOpenCVに実装されています。
本ブログで次の関数について紹介していますので、参考して頂けると嬉しいです。

ご質問や取り上げて欲しい内容などがありましたら、コメントをお願いします。
最後までご覧いただきありがとうございました。

■(広告)OpenCVの参考書としてどうぞ!■

参考リンク

-Python, プログラミング, 画像処理
-