【Python】OpenCVで画像から物体の輪郭を検出する

今回はOpenCVを使った輪郭検出についてです。この手法を使うと、画像内の物体の輪郭を検出でき、画像内での位置等を特定できます。

輪郭検出は画像前景(特定の物体)の抽出や検出、認識を行うアプリケーションの基礎となるステップです。

今回は、物体の輪郭を検出する流れと、Pythonでの実装方法を見ていこうと思います。

輪郭とは

輪郭とは、画像内の物体の境界上の全ての点を結んだものです。画像処理における輪郭は、同じ色味をもった境界ピクセルのことを指します。ざっくりと、色が急激に変化する点を線で結んだものが物体の境界とみなされます。

オリジナルの画像と輪郭検出を行った画像の比較

上の図はOpenCVで輪郭検出をした例です。パラメータがアバウトなこともありきれいとは言えませんが、鉛筆とコーヒーカップを検出できていることがわかります。

OpenCVではこのような輪郭検出ができる関数が用意されています。以下では、実際にOpenCVを使ってい上のような画像を出力するまでの過程を見ていきます。

輪郭検出の手順

OpenCVには輪郭検出のための関数が用意されていると言いましたが、オリジナルの画像をそのまま関数にぶち込んでも望んだ結果はおそらく返ってきません。

これまでの記事でも何度か取り上げているように、画像処理には前処理が必要な場合が多いです。今回も例に漏れず、いくつか前処理が必要です。

それでは、前処理を含めた輪郭検出の手順をまとめます。

1. 入力画像のグレースケール化

まずインプットとなる画像を読み込んだら、その画像をグレースケールに変換します。この処理は、次のステップで行う閾値処理のための準備です。

2. 閾値処理

続いて閾値処理を行い、物体の輪郭を明らかにします。このステップではバイナリ閾値処理やCannyのエッジ検出手法等が用いられます。

今回はバイナリ閾値処理を適用します。

閾値処理をすることで、検出したい物体が強調されて表示されるようになります。この処理によって、検出したい物体は白のピクセルに、背景は黒のピクセルに変換されます(二値化)。

バイナリ閾値処理については以下の記事にまとめています。

Cannyでのエッジ検出についてはこちら

3. 輪郭を検出する

ここでOpenCVのfindContours()を用いて、閾値処理をした画像から輪郭を検出します。

4. 元の画像に輪郭を描画する

最後は、インプットの画像に輪郭を重ねて表示します。

この時はdrawContours()関数を使用します。

Pythonで実装

ではPythonで実際にコードを書いてみます。

今回使用する画像は冒頭でも示したコーヒーカップとペンの画像です。

インプットの画像

画像読込~閾値処理

まずは前処理の部分のコードをまとめて載せます。

import cv2

#入力画像
image = cv2.imread('input/cup.jpg')

#画像のサイズ縮小
height = image.shape[0]
width = image.shape[1]
image = cv2.resize(image,(round(width/4), round(height/4)))

image_copy1 = image.copy()
#グレースケール化
image_copy1 = cv2.cvtColor(image_copy1,cv2.COLOR_BGR2GRAY)

#閾値処理
ret,thresh = cv2.threshold(image_copy1,95,255,cv2.THRESH_BINARY)

cv2.cvtColor()でオリジナルの画像をグレースケール化しています。

cv2.threshold()で閾値処理を実行します。今回は、値が95より大きいピクセルは255(白)に設定され、物体とみなされます。一方、それ以外のピクセルは0(黒)に設定され、背景とみなされます。

輪郭検出

輪郭検出はcv2.findContours()で行います。引数は以下の通りです。

contours, hierarchy = cv2.findContours(入力画像、抽出モード、近似手法)

抽出モードでは、輪郭検出を行うアルゴリズムを指定します。種類については色々ありますが、今回は割愛します。例ではcv.RETR_TREEを使用します。

詳しくは以下記事を参考にしてみてください。

https://www.learning-nao.com/?p=2020

近似手法には輪郭近似のアルゴリズムを指定します。こちらについても詳細は割愛します。例ではcv2.CHAIN_APPROX_SIMPLEを使用します。

実際のコードは以下のようになります。

#輪郭検出 (cv2.ChAIN_APPROX_SIMPLE)
contours1, hierarchy1 = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

contours1に輪郭情報が保持されます。

輪郭描画

最後に入力画像に検出した輪郭を重ね合わせます。このステップではcv2.drawContours()を用います。

cv2.drawContours(入力画像、輪郭情報、輪郭のインデックス、線の色、線の種類)

輪郭情報には、輪郭検出で得られた輪郭のリストを渡します。

輪郭のインデックスは、輪郭情報の中で表示する輪郭を指定します。-1とすると、すべての輪郭が描画されます。

線の色はタプルで指定します(R, G, B)。線の種類は省略可能です。

実際のコードは以下のようになります。

#輪郭の描画
cv2.drawContours(image, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)

ここまでの処理で以下のような画像が得られます。

輪郭を検出した画像

コード全体

ここまでのコード全体は以下のようになります。

import cv2

#入力画像
image = cv2.imread('input/cup.jpg')

#画像のサイズ縮小
height = image.shape[0]
width = image.shape[1]
image = cv2.resize(image,(round(width/4), round(height/4)))

image_copy1 = image.copy()
#グレースケール化
image_copy1 = cv2.cvtColor(image_copy1,cv2.COLOR_BGR2GRAY)

#閾値処理
ret,thresh = cv2.threshold(image_copy1,95,255,cv2.THRESH_BINARY)
#輪郭検出 (cv2.ChAIN_APPROX_SIMPLE)
contours1, hierarchy1 = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

#輪郭の描画
cv2.drawContours(image, contours1, -1, (0, 255, 0), 2, cv2.LINE_AA)

#実行結果
cv2.imshow('Drawn contours', image)
cv2.imshow('Original', image_copy1)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCVの学習におすすめ書籍

PythonでOpenCV始めてみようという方におすすめなのが以下書籍です。実装例も豊富なので、1からコードを書かずとも学習を進めることができます。

オライリーの1冊は読み物というより辞書としての利用におすすめです。お値段結構しますが、細かい情報までしっかりと詰め込まれています。

まとめ

OpenCVで物体の輪郭を検出する方法を紹介しました。

今回の処理は、画像処理を用いるアプリケーションの多くで活用されているはずです。これを活用、応用していくことで、面白いアプリケーションが作れるかもしれませんね^^

前処理の手法や閾値の値を変えることによって、精度をテストすることができます。お目当ての画像で輪郭が検出できるよう、いろいろ試してみてください。

OpenCVの基礎を身につけるためのロードマップは以下を参考にしてください。

ではでは👋