【画像処理】Pythonで図形の輪郭を操作する手順【cv2.findContours】

opencv_contours_title OpenCV

オープンソースの画像処理ライブラリOpenCVを用いて、図形の輪郭を扱う方法を紹介します。

使用するライブラリと図形

はじめに使用するライブラリをimportします。

import numpy as np # おなじみのnumpy
import cv2 # メインの画像処理ライブラリ
from PIL import Image # 画像を簡易に確認するために利用

次に、この記事ないで輪郭を取得する適当な図形を用意します。

# 適当な図形を用意
campus = np.zeros((300,500),dtype=np.uint8) # 図形を出力するキャンパスを用意
campus[100:150,50:250] = 255 
campus[50:250,300:400] = 255

Image.fromarray(campus) # キャンパスを出力して確認

この図形を用いて順番に輪郭を取得する手順を追っていきます。

図形の2値化: cv2.threshold

輪郭の取得に先立って、図形を2値化します。
これは、輪郭を取得する関数cv2.findContoursが図形を2値画像として解釈するため、2値化しないと期待と結果が異なってしまうためです。

cv2.thresholdの具体例

図形を2値かするには、cv2.thresholdを用います。

_, campus = cv2.threshold(campus,127,255,cv2.THRESH_BINARY)

図形campusに対して、127以下の値を0へ、127より大きい値を255に変換しています。

cv2.thresholdの使い方

cv2.findContours

retval, dst = cv2.threshold(image, thresh, maxval, type)

  • 引数
    • image: 2値化する画像のarray。
    • thresh: 2値化する閾値。uint8の画像を中心の値で2値化する場合は127にする。
    • maxval: 2値化後の最大値。uint8の場合は255
    • type: 2値化のタイプ。単純に2値化する場合はcv2.THRESH_BINARYを指定する。それ以外の2値化タイプはリファレンスを参照
  • 返り値
    • retval: 大津の2値化を用いた場合の閾値。通常の2値化では使用しない。
    • dst: 2値化後の画像

公式リファレンス:cv2.threshold

図形の輪郭を取得: cv2.findContours

図形の輪郭を取得するには、cv2.findContoursを用います。
先ほど2値化した図形に対してcv2.findContoursの実行結果を見ていきます。

cv2.findContoursの具体例

輪郭を取得する図形として、シンプルに2つの直方体を用います。
この図形に対してcv2.findContoursで輪郭を取得した結果は次の通りです。

# findContoursでキャンパスcampusにある図形の輪郭を取得
contours, _ = cv2.findContours(campus, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

print(contours)
""" 取得した輪郭(contours)
[array([[[ 50, 100]],
 
        [[ 50, 149]],
 
        [[249, 149]],
 
        [[249, 100]]], dtype=int32),
 array([[[300,  50]],
 
        [[300, 249]],
 
        [[399, 249]],
 
        [[399,  50]]], dtype=int32)]
"""

2つのnumpy配列が取得されており、それぞれキャンパスにある四角形に対応した座標4点が取得できています。
引数に指定したcv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLEは、それぞれ輪郭の検索方法と、輪郭の近似方法を指定しています。詳細は次節で紹介します。

cv2.findContoursの使い方

cv2.findContoursは基本的に3つの引数(image, mode, method)を受け取り、2つの返り値(contours, hierarchy)を返します。

cv2.findContours

contours, hierarchy = cv2.findContours(image, mode, method)

  • 引数
    • image: 輪郭を抽出したい画像のarray。
      shape: (H,W)もしくは(H, W, 3)、dtype: np.uint8にする。
      画像は2値(0か1)のバイナリ画像と解釈される。つまり、0以外のピクセルは1と解釈される。
    • mode: 輪郭検索モード。外側の輪郭のみを取得(cv2.RETR_EXTERNAL)や、全ての輪郭を取得する(cv2.RETR_TREE)などを指定できる。より詳細はリファレンスを参照。
    • method: 輪郭近似法。cv2.CHAIN_APPROX_SIMPLEとすると簡潔に端点の輪郭が取得できる。より詳細はリファレンスを参照。
  • 返り値
    • contours: 検出した全ての輪郭をリストで保持する。各輪郭のshape: [N,1,2]. N: 輪郭の頂点数
    • hierarchy: 輪郭の階層情報。オプション。

詳細についてもっと理解したい場合は、以下の公式リファレンスも参照ください。

公式リファレンス:findContours() 

輪郭から図形を描画: cv2.drawContours

輪郭の座標から図形を描画するには、cv2.drawContoursを用います。
先の節でcv2.findContoursによって取得した輪郭を新しいキャンパス(draw_campus)に描画します。

cv2.drawContoursの具体例

draw_campus = np.zeros_like(campus, dtype=np.uint8) # 新しいキャンパスを用意
cv2.drawContours(draw_campus, contours, -1, color=255, thickness=2)
Image.fromarray(draw_campus) # キャンパスを出力して確認

輪郭の各座標を結んだ図形が描画されました。
cv2.drawContoursでは図形を塗り潰すことはできないので、塗りつぶしたい場合は後述するcv2.fillPolyを用います。

この例では、描画する輪郭をcv2.findContoursで取得しましたが、オリジナルの輪郭を描画したい場合(例えば物体検出タスクでBounding Boxが与えられていて、マスク画像を作りたい場合など)は、輪郭のshapeをOpenCVの仕様と合わせる必要があるので、注意してください。
輪郭の仕様は次節で紹介しています。

cv2.drawContoursの使い方

cv2.drawContoursでは基本的に4つの引数(image, contours, contourIdx, color)を受け取り、1つの返り値(image)を返します。ただし、返り値は入力のimageと同じアドレスなので、用途によっては省略できます。

cv2.drawContours

image = cv2.drawContours(image, contours, contourIdx, color, [thickness])

  • 引数
    • image: 描画する輪郭を出力するarray。shape: [H,W]もしくは[H,W,3]
    • contours: 描画する輪郭。各輪郭をpythonのリストとして複数の輪郭を渡す。shape: list of array([N, 1, 2])、N: 輪郭の頂点数。例: [array([[[ 50, 100]], [[ 50, 149]], [[249, 149]], [[249, 100]]])]
    • contourIdx: 描画する輪郭をindexで指定。マイナスを指定すると全ての輪郭が描画される。
    • color: 色(白黒もしくはRGB)
    • thickness: オプション。線の太さを指定。
  • 返り値
    • image: 引数として指定したimageと同じアドレスを持つarray

公式リファレンス:cv2.drawContours

図形を塗りつぶす:cv2.fillPoly

cv2.drawContoursを用いると輪郭を結んだ図形を得られましたが、図形を塗り潰したい場合はcv2.fillPolyを用います。

cv2.fillPolyの具体例

draw_campus = np.zeros_like(campus, dtype=np.uint8)
cv2.fillPoly(draw_campus, contours, 255)
Image.fromarray(draw_campus)

輪郭を塗りつぶした画像を得ることができました。

cv2.fillPolyの使い方

cv2.fillPolyでは3つの必須引数(image, contours, color)を取り、輪郭を塗りつぶした画像imageを返します。ただし、返り値はcv2.drawContours同様に引数のimageと同じなので用途によっては省略できます。

cv2.drawContours

image = cv2.fillPoly(image, contours, color)

  • 引数
    • image: 塗りつぶした図形を出力するarray。shape: [H,W]もしくは[H,W,3]
    • contours: 描画する輪郭。各輪郭をpythonのリストとして複数の輪郭を渡す。shape: list of array([N, 1, 2])、N: 輪郭の頂点数。例: [array([[[ 50, 100]], [[ 50, 149]], [[249, 149]], [[249, 100]]])]
    • color: 色(白黒もしくはRGB)
  • 返り値
    • image: 引数として指定したimageと同じアドレスを持つarray

終わりに

画像処理のライブラリOpenCVを用いた図形の輪郭操作について紹介しました。
今回は基本的な操作だけですが、輪郭の近似の仕方や、凸包を用いた平坦な輪郭を抽出する方法、外接矩形として取得する方法など発展的な内容もあるため、継続して紹介していきたいと思います。

コメント

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