Pythonで記号の輪郭を検出する(OpenCV編)

PythonのOpenCV(cv2)モジュールを使って、画像中の記号の輪郭を検出してみます。

目次

  1. 輪郭を検出する方法
  2. 輪郭を表示する方法
  3. 輪郭検出の実施例

輪郭を検出する方法

PythonのOpenCV(cv2)モジュールのfindContoursメソッドで、画像の輪郭を検出できます。ただし、輪郭を検出する前に準備が必要です。

  1. カラー画像をグレースケールに変換

  2. グレースケール画像を2値画像に変換

  3. 2値画像から輪郭を検出

findContoursの使い方は次の通りです。

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

変数

内容

image

ndarray

解析する画像のデータ。

mode

輪郭情報の出力モード。

method

輪郭の表現の仕方。

contours

list

検出した輪郭のリスト。

hierarchy

ndarray

検出した輪郭の階層情報。

modeに指定できる項目は下表です。輪郭の階層関係を使わないのであれば、RETR_EXTERNALとRETR_LISTが使い勝手良さそうですね。

RETR_EXTERNAL

一番外側の輪郭を出力する。

RETR_LIST

全ての輪郭を出力する。

RETR_CCOMP

全ての輪郭を2レベルの階層に分けて出力する。

RETR_TREE

全ての輪郭を全体の階層構造で出力する。

RETR_FLOODFILL

methodに指定できる項目は下表です。CHAIN_APPROX_NONEよりもCHAIN_APPROX_SIMPLEの方が、消費するメモリ量が少なくなります。

CHAIN_APPROX_NONE

輪郭の出力に、全ての検出点を含めます。

CHAIN_APPROX_SIMPLE

輪郭を出力するときに、省略できる検出点は省略します。

CHAIN_APPROX_TC89_L1

CHAIN_APPROX_TC89_KCOS

輪郭を表示する方法

findContoursをいろいろ試してみるのですが、その前に検出した輪郭を表示できるようにしましょう。

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

変数

内容

image

ndarray

輪郭を書き込む画像のデータ。

contours

list

輪郭のリスト。

contourIdx

輪郭のインデックス。-1にすると全ての輪郭を表す。

color

tuple

輪郭線の色。

thickness

int

省略可。既定値は1。輪郭線の太さ。

lineType

省略可。既定値はLINE_8。輪郭線の線種。

imageに輪郭が書き込まれますので注意してください。

輪郭検出の実施例

この画像の輪郭を検出してみます。

元画像

コードはこんな感じです。

import cv2

im = cv2.imread('test.png')
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
retval, im_bw = cv2.threshold(im_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 輪郭の検出
contours, hierarchy = cv2.findContours(im_bw, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

# 全ての輪郭を書き込んで出力
im_con = im.copy()
cv2.drawContours(im_con, contours, -1, (0,255,0), 2)
cv2.imwrite('result.png', im_con)

こういう出力になります。

反転せず

画像全体の外側に輪郭が出来てしまっていますね。これはちょっとよろしくありません。

背景色が黒で、前景色が白なんですね。

cv2.THRESH_BINARYをcv2.THRESH_BINARY_INVに書き換えて白黒反転させると、こういう出力になります。

反転

輪郭を検出できました。

では、輪郭を1つずつ出力してみてみましょう。

import cv2

im = cv2.imread('test.png')
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
retval, im_bw = cv2.threshold(im_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 輪郭の検出
contours, hierarchy = cv2.findContours(im_bw, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

# 輪郭を1つずつ書き込んで出力
for i in range(len(contours)):
    im_con = im.copy()
    cv2.drawContours(im_con, contours, i, (0,255,0), 2)
    cv2.imwrite('result' + str(i) + '.png', im_con)
全部1 全部2 全部3 全部4 全部5 全部6 全部7 全部8 全部9 全部10

概ね期待通りですね。大きさなどでフィルターをするのが良さそうです。

では、一番外側の輪郭だけを取りだしてみましょう。cv2.RETR_LISTをcv2.RETR_EXTERNALに変更します。

import cv2

im = cv2.imread('test.png')
im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
retval, im_bw = cv2.threshold(im_gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# 輪郭の検出
contours, hierarchy = cv2.findContours(im_bw, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 全ての輪郭を書き込んで出力
im_con = im.copy()
cv2.drawContours(im_con, contours, -1, (0,255,0), 2)
cv2.imwrite('result.png', im_con)

# 輪郭を1つずつ書き込んで出力
for i in range(len(contours)):
    im_con = im.copy()
    cv2.drawContours(im_con, contours, i, (0,255,0), 2)
    cv2.imwrite('result' + str(i) + '.png', im_con)

検出した輪郭を全て書き込んだものがこちらです。

外形0

輪郭を1つずつ取り出すとこうなります。

外形1 外形2

公開日

広告