【Python・OpenCV】ラプラシアン フィルターによるエッジ検出(cv2.Laplacian)

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

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

【Python・OpenCV】ラプラシアン フィルターによるエッジ検出(cv2.Laplacian)

2023-10-19

はじめに

エッジ検出は画像処理の基本的なタスクで、物体の輪郭や特徴を強調するために利用されます。
エッジ検出は複数のアルゴリズムが提案されていて、本サイトでもCannyエッジ検出器、ソーベルフィルターによるエッジ検出を紹介しました。
この記事では、OpenCVを使用したラプラシアン フィルター(Laplacian Filter)を用いたエッジ検出の原理と実装方法を詳しく説明します。初心者にも分かりやすく、サンプルコードを交えながら、ラプラシアン フィルターの特性を確認します。さらに、Cannyエッジ検出器ソーベルフィルタープリウィット フィルターとの比較を通じて、それぞれの特徴、利点、制約についても詳しく紹介します。

ラプラシアン フィルターとは

ラプラシアン フィルターは、画像中の急激な輝度変化(エッジ)を検出するために使用されるフィルターです。このフィルターは画像中の輝度勾配を計算し、勾配が急激な場所をエッジとして検出します。ラプラシアン フィルターは、画像中の二次微分を計算することによってエッジ情報を抽出します。

特徴

  • エッジが急激な部分を強調するのに優れています。
  • シンプルで計算が高速です。
  • パラメータ調整が比較的簡単です。
  • ノイズに対して敏感なため、ノイズを強調することがあります。したがって、前処理が必要となる場合があります。
  • 画像中の小さなエッジを検出することが苦手です。

ラプラシアン フィルターは、以下の手順で計算されます。

  1. 画像のグレースケール変換
    まず、ラプラシアン フィルターは通常、カラー画像ではなく、グレースケール画像に適用されます。これは、色情報を無視し、輝度情報だけを考慮することでエッジを抽出しやすくするためです。
  1. 離散ラプラシアン演算子
    ラプラシアン フィルターは、ラプラシアン演算子(Δ)を使用します。ラプラシアン演算子は、以下の数式で表されます。
     $$\nabla^2I(x,y) = \frac{\partial^2}{\partial{x}^2}I+ \frac{\partial^2}{\partial{y}^2}I$$
    ここで、\(I(x,y) \)は画像の輝度値です。この演算子は画像の各ピクセルに対して、x軸とy軸の両方の方向に対する二次微分を計算します。これにより、画像中の急激な変化を強調し、エッジが浮かび上がります。
  2. 離散化
    コンピュータ上で画像を処理するために、連続的な微分演算子を離散的なフィルターカーネルに変換する必要があります。ラプラシアン演算子を離散化すると、以下のようになります.
    $$\Delta I(x,y)=I(x+1,y)+I(x−1,y)+I(x,y+1)+I(x,y−1)−4I(x,y)$$
    この式では、中心ピクセル (x, y) の輝度値から周囲の4つのピクセルの輝度値を引いています。これにより、中心ピクセルとその周囲の輝度の違いが強調され、急峻な輝度変化(エッジ)が抽出されます。
  3. フィルターカーネルを使用
    ラプラシアン フィルターは、上記の離散化されたラプラシアン演算子をフィルターカーネルとして使用して画像に畳み込むことで適用されます。畳み込み操作は、画像の各ピクセルにフィルターカーネルを適用し、中心ピクセルの値を計算します。
  4. 絶対値の取得
    ラプラシアン フィルターの出力は負の値を含むことがあります。通常、絶対値を取得してエッジの輪郭を強調し、正の値のエッジも検出します。

ラプラシアン フィルターは、急激な輝度変化を持つエッジを抽出するのに非常に効果的ですが、ノイズに対しても敏感であるため、スムージングなどの前処理が必要なことに注意してください。

cv2.Laplacian関数

OpenCVでラプラシアン フィルターを適用する場合はcv2.Laplacian関数を使用します。

cv2.Laplacian(入力画像, ddepth[, ksize[, scale[, delta[, borderType]]]])

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

引数

名称説明
入力画像(必須)入力画像データ。グレースケール画像を対象とします。カラー画像を使用する場合、事前にグレースケールに変換する必要があります。
ddepth(必須)出力画像のデータ型を指定します。出力画像のデータ型を指定することで、演算の精度を調整できます。指定できるものは、こちらで確認できます。
ksize(オプション)ラプラシアン カーネルのサイズを指定します。カーネルサイズが大きいほど、エッジが滑らかになります。(デフォルト:1)
scale(オプションラプラシアン演算の結果に掛けるスケールファクターを指定します。通常、1を指定しますが、値を調整してエッジの強調度を変更することができます。(デフォルト:1)
delta(オプション)処理結果に追加するオフセットを指定します。通常、0を指定しますが、値を変更することで画像の輝度を調整できます。(デフォルト:0)
borderType(オプション)画像の境界条件を指定します。(デフォルト:cv2.BORDER_DEFAULT

ksizeを選択する際のポイント

  1. エッジのサイズと方向
    • 画像中のエッジのサイズや方向に注意を払いましょう。ラプラシアン フィルターはエッジに対して検出性能が高いため、エッジの幅や方向に合わせて ksize を調整することが役立ちます。
    • エッジが細い場合、小さな ksize が適しています。一方、太いエッジを強調したい場合、大きな ksize を検討しましょう。
  2. ノイズの影響
    • ノイズが画像に存在する場合、ラプラシアン フィルターはノイズに対して敏感で、ノイズを強調することがあります。この場合、小さな ksize を選ぶと、ノイズの影響を減少させることができます。
  3. 処理速度
    • カーネルサイズが大きいほど、処理に時間がかかります。したがって、処理速度の制約がある場合、適切な ksize を選び、処理時間を最適化しましょう。
  4. 試行と調整
    • 最適な ksize を見つけるために、異なるサイズのカーネルで実験を行い、その結果を比較しましょう。画像の内容や処理の目的によって最適な ksize が異なることがあります。

総合的に、一般的な画像処理の場面では、ksize に3x3または5x5のカーネルを選択することが多いです。これらのサイズは多くのエッジ検出タスクに対して適しており、一般的に良好な結果をもたらします。ただし、具体的なアプリケーションや画像の特性に合わせて調整が必要な場合もあります。

最終的に、選択した ksize に対して実際の画像でテストを行い、目的に合ったエッジ検出結果が得られるかどうか確認することが重要です。

戻り値

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

使い方

ラプラシアン フィルターのサンプルコードは、カラー画像ではなくグレースケール画像を対象としています。

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

引用元

https://pixabay.com/ja/photos/%E5%BB%BA%E7%89%A9-%E6%A9%8B-%E5%B7%A5%E4%BA%8B-%E5%AF%BE%E7%A7%B0-5506574/

下がサンプルコードです。
このサンプルコードでは、引数のddepthをcv2.CV_8Uとcv2.CV_32F、ksizeを3と5の場合についてエッジ検出し、結果を比較しています。
また、入力画像のスムージングにはガウシアンフィルターを使用しています。

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

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

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

# ラプラシアン フィルターを適用
laplacian_8U_k3 = cv2.Laplacian(blurred, cv2.CV_8U, ksize=3)
laplacian_8U_k5 = cv2.Laplacian(blurred, cv2.CV_8U, ksize=5)
laplacian_32F_k3 = cv2.Laplacian(blurred, cv2.CV_32F, ksize=3)
laplacian_32F_k5 = cv2.Laplacian(blurred, cv2.CV_32F, ksize=5)
# データ型をcv2.CV_32F(float32)からcv2.CV_8U(uint8)に変換
laplacian_32F_k3 = np.clip(laplacian_32F_k3 * 255, a_min = 0, a_max = 255).astype(np.uint8)
laplacian_32F_k5 = np.clip(laplacian_32F_k5 * 255, a_min = 0, a_max = 255).astype(np.uint8)

# 絶対値を取得
laplacian_8U_k3 = cv2.convertScaleAbs(laplacian_8U_k3)
laplacian_8U_k5 = cv2.convertScaleAbs(laplacian_8U_k5)
laplacian_32F_k3 = cv2.convertScaleAbs(laplacian_32F_k3)
laplacian_32F_k5 = cv2.convertScaleAbs(laplacian_32F_k5)

# 画像とエッジ画像を表示
plt.rcParams["figure.figsize"] = [9,9]                                  # ウィンドウサイズを設定
title = "cv2.Laplacian: codevace.com"
plt.figure(title)                                                       # ウィンドウタイトルを設定
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.03, top=0.95)       # 余白を設定
plt.subplot(321)                                                        # 3行2列の1番目の領域にプロットを設定
plt.imshow(image, cmap='gray')                                          # 入力画像をグレースケールで表示
plt.title('Original Image')                                             # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(323)                                                        # 3行2列の3番目の領域にプロットを設定
plt.imshow(laplacian_8U_k3, cmap='gray')                                # ddepth=cv2.CV_8U, ksize=3の結果
plt.title('Laplacian ddepth=cv2.CV_8U, ksize=3')                        # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(324)                                                        # 3行2列の4番目の領域にプロットを設定
plt.imshow(laplacian_8U_k5, cmap='gray')                                # ddepth=cv2.CV_8U, ksize=5の結果
plt.title('Laplacian ddepth=cv2.CV_8U, ksize=5')                        # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(325)                                                        # 3行2列の5番目の領域にプロットを設定
plt.imshow(laplacian_32F_k3, cmap='gray')                               # ddepth=cv2.CV_32F, ksize=3の結果
plt.title('Laplacian ddepth=cv2.CV_32F, ksize=3')                       # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.subplot(326)                                                        # 3行2列の6番目の領域にプロットを設定
plt.imshow(laplacian_32F_k5, cmap='gray')                               # ddepth=cv2.CV_32F, ksize=5の結果
plt.title('Laplacian ddepth=cv2.CV_32F, ksize=5')                       # 画像タイトル設定
plt.axis("off")                                                         # 軸目盛、軸ラベルを消す
plt.show()

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

引数のddepthとksizeを変えた場合、精度が高くカーネルサイズが大きい程、エッジの検出感度が上がっていて、変化の幅も大きい結果となりました。

  • 建物の出入口の窪んで影になっている部分でも、cv2.CV_32Fでは壁の模様の横縞を検出できている。
  • cv2.CV_32Fでは空の雲の形状まで検出してしまっているのため、建物だけを検出したい場合は工夫が必要。


cv2.Laplacian関数の引数の決定のために、前処理のスムージング フィルターのカーネル サイズなどと合わせて検討することが重要かもしれません。

他のエッジ検出アルゴリズムとの比較

  • ラプラシアンフィルター:「ラプラシアン フィルターとは」で示した通り、エッジが急激な部分を強調するのに優れていて、計算コストが低いです。パラメータ設定が比較的簡単ですが、感度が高いため、小さなエッジの検出が苦手です。
  • ソーベルフィルター:計算量が少なく、ノイズに対する耐性があります。
  • Cannyエッジ検出器:ラプラシアンよりも高度なエッジ検出アルゴリズムです。パラメータ設定が難しく、計算コストが高いですが、エッジの検出精度が高いことが特徴です。
  • プリウィット フィルター:ソーベルフィルターと同様にエッジ検出に使用されます。計算コストが低く、必要な方向のエッジだけを検出することができることが大きな特徴と言えます。

おわりに

ラプラシアン フィルター(cv2.Laplacian関数)について解説しました。
引数に設定する値を決めることが簡単ではないかもしれませんが、ラプラシアン フィルターは感度の高いエッジ検出アルゴリズムであることが確認できました。
他のエッジ検出アルゴリズムの結果と比較しながらアルゴリズムの選定を行うことで、より高品質なエッジ検出の結果を得るできると思われます。

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

参考リンク

(広告)おすすめ転職エージェント

転職はあなたの可能性を広げる鍵です。その鍵を手に入れるため、おすすめ転職エージェントを紹介します。

新しいキャリアの扉を開くために、転職エージェント活用して、妥協のない転職活動を行ってください。

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