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

広告

Pythonで画像処理カテゴリの投稿