【Python】OpenCVで輪郭の検出 – findContours(), drawContours()

Pythonの応用
スポンサーリンク

OpenCVを使ったPythonでの画像処理について、ここでは輪郭の検出(Contour Detection)について扱っていきます。

輪郭とは境界に沿って同色や強度を持った部分を連続的に繋げた線といっていいでしょう。

この輪郭の検出は画像処理での物体の認識や検出にとても有益な処理です。

ここではOpenCVに備わっている輪郭検出メソッドを使って、画像に含まれる物体の外側の輪郭と内側の輪郭の検出の違いを見ていきます。

スポンサーリンク

輪郭の検出 – findContours()

では、OpenCVを使った輪郭の検出をやっていきましょう。jupyter notebookを使ってやっていきます。

まずは、お決まりのライブラリのインポートです。

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

imread()を使って準備しておいた画像を読み込みます。

img = cv2.imread('images/black_white.png',0)

第2引数を0としているのはグレースケール画像として読み込むためです。

imread()についてはこちらでも扱っています。

【Python】OpenCVを使ったimageファイルの画像処理の基礎
Pythonで画像や動画を処理する際に、ライブラリのOpenCVが利用できます。OpenCVは画像や動画の処理に特化した外部ライブラリです。画像認証の機械学習などにも利用することになります。ここでは画像処理の基礎的な操作を行ってみます。

画像の型もshapeで確認してみます。

img.shape

実行するとこうなります。

この画像をimshow()で表示します。

plt.imshow(img,cmap='gray')

表示するとこうなります。

白い星型、白い丸に回った矢印、白い丸に時計の表示の画像となっています。

この画像を、findContours()を使って輪郭の検出をやっていきます。

image, contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

findContours()には第1引数に対象の画像を渡し、第2引数に輪郭の抽出モード、第3引数に輪郭を近似する方法(検出する方法)を指定します。出力は輪郭画像、輪郭、輪郭の階層情報が返されるので、image、contours、hierarchyの変数に格納します。(OpenCV4と使っている場合は、contours, hierarchy = cv2.findContours()の2つが返されると言う違いがあります)

この関数は第2引数に指定したRETRメソッドに基づいて、外部、内部、またはその両方の画像の輪郭(contours)を返します。次の4つのモードがあります。

  • cv2.RETR_EXTERNAL:外部輪郭のみを抽出
  • cv2.RETR_CCOMP:2レベルの階層構造で整理された内部輪郭と外部輪郭の両方を抽出
  • cv2.RETR_TREE:ツリーグラフで構成された内部と外部の両方の輪郭を抽出
  • cv2.RETR_LIST:内部も外部も関係なしにすべての輪郭を抽出

第3引数の近似方法のフラグは次の2つがあります。

  • cv2.CHAIN_APPROX_NONE
  • cv2.CHAIN_APPROX_SIMPLE

cv2.CHAIN_APPROX_NONEを指定すると輪郭上の全点の情報を保持するのですが、検出した線を表現する境界上の全ての点の情報を保持してしまうので輪郭の抽出の為には不要な情報も含まれてしまいます。直線の端点のみを保持するだけで十分です。cv2.CHAIN_APPROX_SIMPLEを指定すると輪郭を圧縮して冗長な点の情報を削除し、メモリの使用を抑えられますので、ここではこちらのフラグを使っています。

type(contours)
len(contours)
type(hierarchy)
hierarchy.shape

contoursの型をtype()で取得し、len()で数を取得します。hierarchyも型をtype()で取得し、shapeで配列のサイズを取得してみます。

これらを実行するとこうなります。

抽出された輪郭contoursがN個であった場合、hierarchyは(1, N, 4)のnumpy配列で、輪郭contours[i]の階層情報はhierarchy[0, i]に入っています。

contoursはリスト型で18のデータが入っています。hierarchyのサイズも(1,18, 4)であるのがわかりました。

hierarchy

hierarchyの中身はここでは次のようになっています。

4つの要素は、[次のインデックス、前のインデックス、最初の子のインデックス、親のインデックス] として輪郭の階層を示していて、存在しない場合は-1が入っています。

スポンサーリンク

検出した輪郭の描画 – drawContours()

輪郭を抽出したデータを描画していこうと思います。輪郭の描画にはdrawContours()を使います。

このcv2.drawContours()関数は第1引数は入力画像、第2引数は輪郭のデータ(list)、第3引数は描画したい輪郭のインデックスを指定します。この第3引数は、第2引数で与えた輪郭listから一つの描画したい輪郭を指定するのに使います。全輪郭を描画する時はー1を指定します。残りの引数で輪郭を描画する時の色や線の太さを指定します。

外側の輪郭の描画

まずは外側の輪郭を描画してみましょう。次のコードで描画します。

external_contours = np.zeros(image.shape)

for i in range(len(contours)):
    
    if hierarchy[0][i][3] == -1:
        
        cv2.drawContours(external_contours, contours, i, 255, -1)

plt.imshow(external_contours,cmap='gray')

np.zeros()を使って元の画像と同じ大きさの黒い下地の画像を作っています。これに検出した輪郭を描画していく形になります。

for-in文を使って輪郭をひとつずつ取り出し、階層のインデックスをそれぞれ調べて行きます。一番外側の輪郭とはそれの親となるような階層をもたないので、hierarchy[0][i][3] が -1の場合の輪郭データを描画して行けば良いということになります。これらをdrawContours()に渡して、255で色を白、-1で塗りつぶしの指定をして、imshow()で表示しています。

実行すると次のようになります。

外側の輪郭を基準にした形が表示されました。

内側の輪郭の描画

次は内側の輪郭を描画していこうと思います。コードは次のように先ほどとほぼ同じです。

image_internal = np.zeros(image.shape)

for i in range(len(contours)):

    if hierarchy[0][i][3] != -1:
        
        cv2.drawContours(image_internal, contours, i, 255, -1)

plt.imshow(image_internal,cmap='gray')

コードの違いは、hierarchy[0][i][3] != -1とすることで、外側の輪郭ではなく内側のものであることを示しています。

実行するとこうなります。

元の画像で表示されている形の外側が消え、内側の輪郭の形だけが表示されています。三角形は内側の輪郭が無かったので、表示自体が消えています。

階層構造については深く触れませんでしたが、hierarchy[0][i][3] == を、ここで0,1,15のそれぞれの場合で描画してみると、0だと真っ黒の画像、1だと時計の内側だけの画像、15だと曲がった矢印の画像が表示されます。これがここでの輪郭の階層の位置関係になります。

スポンサーリンク

最後に

ここでは、OpenCVを使ったPythonでの画像処理について、輪郭の検出(Contour Detection)について扱いました。この輪郭の検出は画像処理での物体の認識や検出に有益な処理となります。

http://lang.sist.chukyo-u.ac.jp/classes/OpenCV/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.html

輪郭検出メソッドfindContours()を使って、画像に含まれる物体の外側の輪郭と内側の輪郭の検出を行います。輪郭検出のモードは4つあり、ここではcv2.RETR_CCOMPを使いました。輪郭の近似方法は、メモリ消費の少ないCHAIN_APPROX_SIMPLEを使います。

輪郭の描画にはdrawContours()を使いますが、輪郭の階層構造を意識して、外側の輪郭、内側輪郭を考える必要があります。

タイトルとURLをコピーしました