
こんな悩みを解決します。
記事の内容
- 綺麗に輪郭抽出するための処理の流れ(最初に読んでください)
- Canny法を使って手軽に輪郭を検出する方法
- エッジの特性を考慮して、自分でロジックを組む方法
記事の信頼性
この記事を書いている私は、ソフトウェア(画像処理)エンジニアとして自動車メーカーでC言語やPythonを使って製品開発をしています。
画像処理を専門としていますので、ノウハウを共有できたらと思います。
輪郭抽出のことを、画像処理の分野ではエッジ検出といいます。
画像内の物体のエッジを見つけるための技術です。
エッジは画像内の輝度が大きく変化する領域から検出され、画像解析の前処理として使われたり、白線検出や外観検査装置で位置ずれを検出するため等、さまざまなアプリケーションで使われています。
この記事を読むことで、PythonでOpenCVの機能を使いながら、綺麗に輪郭を検出することができるようになります。
綺麗に輪郭抽出するための処理の流れ(最初に読んでください)
輪郭抽出するためには、画像からエッジを検出する必要があります。エッジは微分フィルタを使うことで検出できますが、これだけでは綺麗に輪郭を抽出することは難しいです。
綺麗に輪郭を抽出するために、次の流れに沿ってロジックを組んでください。
処理の流れ
- ノイズを除去する
- 微分フィルタを使って勾配強度を計算する
- 二値化(閾値処理)で欲しいエッジを抽出する
- モルフォロジー演算でエッジを整形する(任意)
それぞれ、深堀して解説します。
ノイズを除去する
エッジ検出する際にノイズは不要な情報です。できるかぎり前段で除去しましょう。
ノイズ除去はガウシアンフィルタかメディアンフィルタを使えばよいです。
使い分けは以下のとおり。
ザラザラとした細かく弱いノイズを消したいとき
ガウシアンフィルタを使いましょう
注目画素に近いほど大きな重みがかかるので、エッジを残しつつノイズを低減できます。
ごま塩ノイズのように強いノイズを消したいとき
メディアンフィルタを使いましょう
周辺画素の中央値を注目画素に置き換えるので、エッジのボケを抑えつつも強いノイズを低減できます。
次の画像は、それぞれのフィルタを適用した場合の画像になります。
この記事で使用する画像は、白黒の小さなゴミが画像解析の邪魔になるので、メディアンフィルタを適用します。
微分フィルタを使って勾配強度を計算する
ノイズ除去のあとは、画像からエッジを検出します。
エッジを検出するには、prewittフィルタやsobelフィルタ、Laplacianフィルタなどの微分フィルタを使います。
微分とは変化の割合のことなので、画像に置き換えると、隣り合う画素の輝度値の差分を取ることになります。
つまり、微分フィルタを畳み込み演算処理すると、1画素ずつ差分値が求まります。これを勾配強度といいます。
エッジ検出処理(sobelフィルタ)をすると、エッジ画像がつくられます。
この画像は、ピクセルごとに0-255の勾配強度が入っており、値が大きいほど勾配が強い(輝度変化が大きい)領域といえます。
二値化(閾値処理)で強いエッジを抽出する
エッジ検出処理によって、各ピクセルの勾配強度が求まりました。
この記事では、大きなエッジを検出することが目的なので、勾配強度の低いピクセルを除去していきます。
そのやり方は、二値化(閾値処理)です。
検出したいエッジと検出したくないエッジを、なるべく綺麗に分けられる勾配強度の値を決めます。(=閾値決め)
以下の図は、エッジ画像のヒストグラムに対して、検出したくない強度の範囲(下限閾値と上限閾値)を決めます。
ポイントは、検出したいエッジを消さないギリギリの閾値を設定してください。
モルフォロジー演算でエッジを整形する
最後に、エッジを整形します。
モルフォロジー演算を使いますので、使い方を簡単に解説します。
モルフォロジー演算とは
- 収縮(Erosion)
境界を侵食するイメージ→小さなエッジは消える - 膨張(Dilation)
境界を膨らますイメージ→エッジは太く強調 - オープニング(Opening)
収縮のあとに膨張する処理→ノイズ除去に有効 - クロージング(Closing)
膨張のあとに収縮する処理→エッジの中の穴を埋めるのに有効
モルフォロジー演算を使って、このようにエッジを整形してください。
Pythonで手軽に輪郭を検出する方法(Canny法を使います)
二値化処理
- あ
単純な閾値処理
単純な閾値処理について、使い方を解説します。
アルゴリズムは簡単で、自分で決めた閾値より大きければある値を割り当てて、閾値より小さければ別の値を割り当てるだけです。
次の図は、適当な閾値を使って二値化した画像です。
道路の二輪車マークだけを抽出したかったのですが、うまくいきません。
狙いどおりに二値化するためには、トライアンドエラーを繰り返して閾値を自分で決めることが必要です。
使い方
単純な閾値処理は、cv2.Canny関数を使います。
返り値と引数は以下のとおり。
返り値 | 内容 |
第1返り値 | 二値化に使用した閾値 |
第2返り値 | 二値化画像 |
引数 | 内容 |
第1引数 | 二値化したいグレースケール画像 |
第2引数 | 自分で決めた閾値 |
第3引数 | 二値化によって閾値以上の画素に対して割り当てられる最大値 |
第4引数 | 二値化処理のタイプを指定(次に記載) |
サンプルコード
# sample code import cv2 import numpy as np from matplotlib import pyplot as plt # 入力画像の読み込み img = cv2.imread("threshold_origin.png", cv2.IMREAD_GRAYSCALE) cv2.imshow('org_img', img) # 適応的な閾値処理 th = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 7, 10) cv2.imshow('th_img', th) cv2.waitKey(0) cv2.destroyAllWindows()
それぞれの二値化処理のメリット、デメリット解説
単純な閾値処理、適応的な閾値処理、大津の二値化の3つのアルゴリズムを紹介しましたが、どれをどんなときに使えばよいのかわかりづらいですよね。
それぞれの、メリットとデメリットを解説しますので、用途に合わせて使ってみてください。
単純な閾値処理
メリット
- 閾値を自分で決めるため、二値化をコントロールしやすい。(閾値は事前に決まるので、想定外の画像が入ってきて閾値が吹っ飛ぶとかはない)
デメリット
- 画像全体で1つの閾値しか設定できないので、領域ごとに異なる光源環境になっている場合(影など)は、期待通りに二値化できない ⇒ "適応的な閾値処理"がおすすめ。
- トライアンドエラーで閾値を決めるので、調整に時間がかかる ⇒ 特定の条件下のみだが、閾値が機械的に求まる"大津の二値化"がおすすめ。
適応的な閾値処理
メリット
- 局所的な領域ごとに異なる閾値を使うため、領域ごとに異なる光源環境になっている場合(影など)でも、期待通りの二値化ができる。
デメリット
- 画素ごとに閾値を持つため、二値化をコントロールしにくい。(想定外の画像が入ってきて閾値が予期せぬ値になることがある) ⇒ 安全を取るなら、"単純な閾値処理"がおすすめ。
- 制御するパラメータが増える(閾値計算のタイプ、二値化処理のタイプ、局所領域のサイズ、閾値から引く値)ため、調整がやや大変。 ⇒ 特定の条件下のみだが、閾値が機械的に求まる"大津の二値化"がおすすめ。
大津の二値化
メリット
- 閾値を決めるために、トライアンドエラーを繰り返す手間がかからない。
- 2つの山が存在するヒストグラムを持つ画像に対しては、機械的に最適な閾値が決まる。
デメリット
- 2つの山が存在しないヒストグラムを持つ画像に対しては、良い効果が得られない。 ⇒ "単純な閾値処理"か"適応的な閾値処理"がおすすめ。
- 閾値が機械的に決まってしまうため、二値化をコントロールしにくい。(4つの山を持つヒストグラムとか、想定外の画像が入ってきて閾値が予期せぬ値になることがある) ⇒ 安全を取るなら、"単純な閾値処理"がおすすめ。
以上で解説は終わりです。
これからPythonを使って画像処理を始めたい方、さらに知識を深めていきたい方は、動画コンテンツで学ぶことをおすすめします。
私もよく利用しますので、さいごに紹介だけして終わります。
画像処理の基礎:フィルタリング,パターン認識から撮像過程モデルまで
入門としてはこのあたりがおすすめです。
参考までに。