【Python・OpenCV】Cannyエッジ検出器による輪郭抽出(cv2.Canny)

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

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

【Python・OpenCV】Cannyエッジ検出器による輪郭抽出(cv2.Canny)

2023-09-19

はじめに

エッジ検出はコンピュータビジョンの重要な処理の一つで、画像内の物体の輪郭を検出するのに役立ちます。OpenCVは、エッジ検出を行うための強力なツールを提供しています。この記事ではソーベル フィルタによるエッジ検出を紹介しましたが、今回紹介するCannyエッジ検出器は最も一般的に使用される方法の一つです。本記事では、OpenCV のcv2.Canny関数を使ってエッジ検出を行う方法についてサンプルコードやソーベル フィルタによるエッジ検出の結果との比較を含め解説します。

Cannyエッジ検出器とは

Cannyエッジ検出器は、画像内のエッジ(物体や特徴の輪郭)を検出するためのアルゴリズムです。このアルゴリズムは、以下の主要なステップから成り立っています。

  1. スムージング(Smoothing)
    • 最初のステップでは、、画像のノイズを軽減するために入力画像に対してガウシアンフィルタなどのスムージング処理を適用します。
    • スムージングにより、画像内の高周波ノイズが減少し、滑らかで連続的な画像が生成されます。(スムージングの参考記事
  2. 勾配の計算(Gradient Calculation)
    • スムージングされた画像に対して、Sobelフィルタなどの微分フィルタを適用して、各ピクセルの勾配(輝度の変化率)を計算します。
    • 勾配の計算により、各ピクセルのエッジの方向と強度が得られます。
  3. 非最大値抑制(Non-Maximum Suppression)
    • 勾配情報を使用して、エッジの候補となるピクセルを選択します。
    • 各ピクセルにおける勾配方向を基に、その方向に沿った近隣ピクセルと比較し、局所的な最大勾配を持つピクセルを残し、それ以外のピクセルを抑制します。これにより、エッジが細くなります。
  4. ヒステリシス閾値処理(Hysteresis Thresholding)
    • 閾値処理を使用して、強いエッジと弱いエッジを区別します。
    • 2つの閾値、高い閾値(強いエッジを示す)と低い閾値(弱いエッジを示す)が使用されます。
    • 強いエッジは確実にエッジとして認識され、弱いエッジは後で検討されます。
  5. エッジトラッキング(Edge Tracking by Hysteresis)
    • 強いエッジピクセルから出発して、隣接する弱いエッジピクセルを連結し、連続的なエッジを形成します。
      これにより、エッジが中断されずに継続的な線になります。

Cannyエッジ検出器は、エッジを正確に検出し、細かいノイズを抑制する能力で知られています。これは、画像処理、コンピュータビジョン、ロボティクスなどのさまざまなアプリケーションで使用されます。エッジ検出において、ガウシアンスムージングとヒステリシス閾値処理は特に重要な役割を果たし、エッジの品質に大きな影響を与えます。

cv2.Canny関数

OpenCVにおけるCannyエッジ検出器はcv2.Canny関数として提供されています。

cv2.Canny(入力画像, threshold1, threshold2[, apertureSize[, L2gradient]])

引数

名称説明
入力画像(必須)入力画像
threshold1(必須)エッジ候補の閾値の低い値です。この閾値よりも低い勾配値を持つピクセルは弱いエッジの候補として保持されます。整数値を指定します。
threshold2(必須)エッジ候補の閾値の高い値です。この閾値よりも高い勾配値を持つピクセルは強いエッジの候補として保持されます。整数値を指定します。
apertureSize(オプション)Sobel演算子に使用するカーネルサイズです。デフォルト値は3ですが、他の値を指定することができます。一般的に、3、5、7のいずれかの奇数を使用します。(デフォルト:3)
L2gradient(オプション)勾配の計算方法を指定します。Trueの場合、ユークリッド距離を使用して勾配を計算し、Falseの場合はL1ノルムを使用します。(デフォルト:False)

戻り値

戻り値は検出されたエッジを表すマップ(画像)。

使い方

下はサンプルコードです。カラー画像ではなくグレースケール画像を対象としています。

入力画像として、この画像を使用しています。cv2.imread関数でグレースケールに変換しました。

スムージングにはガウシアンフィルターを使用しました。画像によっては他のスムージング フィルターの方が効果的かもしれませんが、今回のの入力画像の場合、ガウシアンフィルターがCannyエッジ検出器で良い結果を得る事ができました。

import cv2
import matplotlib.pyplot as plt

# 画像の読み込み
image = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE)

# ガウシアンフィルターを適用してノイズを削除
kernel_size = 5                                                         # カーネルサイズの設定
sigma = 0                                                               # sigmaの設定
blurred = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma)    # ガウシアンフィルターの適用

# Cannyエッジ検出を適用
canny = cv2.Canny(blurred, threshold1=30, threshold2=100)

# 画像とエッジ画像を表示
plt.rcParams["figure.figsize"] = [12,3.5]                               # ウィンドウサイズを設定
title = "cv2.Canny: codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 2行2列の1番目の領域にプロットを設定
plt.imshow(image, cmap='gray')                                          # 入力画像をグレースケールで表示
plt.title('Original Image')                                             # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 2行2列の2番目の領域にプロットを設定
plt.imshow(canny, cmap='gray')                                          # X方向のエッジ検出結果画像をグレースケールで表示
plt.title('Canny')                                                      # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

このサンプルコードの結果が下の図です。

ソーベル フィルタによるエッジ検出の記事を投稿しましたが、その時と同じ入力画像を使用した結果、ソーベル フィルタより、より細部に渡りエッジを検出しています。
比較した結果は下になります。
左がCannyエッジ検出器の結果、右がソーベル フィルタの結果です。
Cannyエッジ検出器は建物の壁の凹凸もしっかりとエッジとして検出できていることがわかります。一方でソーベル フィルターは建物と空の境目や、窓や出入り口などの形状を強く検出できています。どちらが優れているかでは無く、どの様なエッジを検出するかで使い分ける方が良さそうです。

また、この結果は、それぞれの方式のパラメーター(関数の引数)の最適化ができていないため、それらの最適化することも必要かもしれませんので、参考程度としてお考え下さい。

Cannyエッジ検出器とソーベル フィルターの比較のサンプルコードは下になります。

import cv2
import matplotlib.pyplot as plt

# 画像の読み込み
image = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE)

# ガウシアンフィルターを適用してノイズを削除
kernel_size = 5                                                         # カーネルサイズの設定
sigma = 0                                                               # sigmaの設定
blurred = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma)    # ガウシアンフィルターの適用

# Cannyエッジ検出を適用
canny = cv2.Canny(blurred, threshold1=30, threshold2=100)


# Sobelフィルタを適用
sobel_x = cv2.Sobel(blurred, cv2.CV_32F, 1, 0, ksize=3)                 # 水平方向の勾配
sobel_y = cv2.Sobel(blurred, cv2.CV_32F, 0, 1, ksize=3)                 # 垂直方向の勾配

# 勾配の絶対値を計算
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)

# 水平方向と垂直方向の勾配を組み合わせて合成勾配を計算
sobel_combined = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)


# 画像とエッジ画像を表示
plt.rcParams["figure.figsize"] = [12,3.5]                               # ウィンドウサイズを設定
title = "cv2.Canny: codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(121)                                                        # 2行2列の1番目の領域にプロットを設定
plt.imshow(canny, cmap='gray')                                          # 入力画像をグレースケールで表示
plt.title('cv2.Canny')                                                  # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(122)                                                        # 2行2列の2番目の領域にプロットを設定
plt.imshow(sobel_combined, cmap='gray')                                 # X方向のエッジ検出結果画像をグレースケールで表示
plt.title('cv2.Sobel')                                                  # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

おわりに

OpenCVにおけるCannyエッジ検出器の cv2.Canny関数の使い方と、ソーベル フィルター(cv2.Sobel関数)との比較について解説しました。

また、本ブログでは他のエッジ検出の方法も紹介しています。

エッジ検出は画像処理において基本的なプロセスの一つです。この記事が皆さんが関わるプロジェクトのお役に立てることができればとても嬉しいです。

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

参考リンク

(広告)あなたに合ったプログラミング スクールが見つかるかも

プログラミングの学習が、新しい世界を開きます。副業からキャリアの転換まで、目標に向かって一歩を踏み出しましょう。

これらのプログラミング スクールは、あなたの目的に合わせて選択できるさまざまなプログラムを提供しています。どのスクールを選ぶにせよ、プログラミングは未来への扉を開き、新しい機会を切り開く手段として素晴らしい選択です。あなたの夢や目標を実現する第一歩を踏み出す準備はできていますか?

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