本記事ではOpenCVを用いて画像内に含まれる物体の輪郭抽出をする方法をサンプルコードとともに紹介します。うまく輪郭を抽出するためのノイズ対策も紹介しています。画像処理の分野において輪郭抽出は必要になることが多いとと思いますので、活用できるようになることをオススメします。
画像処理での輪郭抽出の手順
今回行う輪郭抽出は、以下の手順で行います。
- ターゲット画像読み込み
- グレースケールに変換
- 2値(白黒)画像に変換
- OpenCvのfindContours関数を用いて輪郭抽出
- 結果の描画・表示
輪郭抽出を行っているのはOpenCVのfindContours関数です。
cv2.findContours関数の使いどころとしては、以下が挙げられます。
- 輪郭抽出
- 画像内の物体の数を数える
- 画像内の物体抽出(物体検知用の学習データ作成に向けたのアノテーション)
今盛んに行われている画像処理において、物体抽出関連の技術は重要視されますね。
ノイズ対策
輪郭抽出をするだけでは、細かいノイズ(の輪郭)が乗ってしまうのでその対策をします。
使用する関数は、輪郭の面積を取得できるcv2.contourArea()です。
以下のコードを追加することで、指定したサイズ以上の輪郭のみ抽出できます。
(contours:cv2.findContours()で取得される輪郭群)
contours = list(filter(lambda x:cv2.contourArea(x) > 1000, contours))
ノイズ対策有無の結果比較は以下のようになります。

対策なし画像の左上やテニスボール付近にはノイズ(細かい領域)が多数表示されていますが、対策ありは除外されています。
サンプルコード
今回輪郭抽出する対象画像は以下です。

以下のようなコードを今回作成しました。
(分析対象画像のパスは適宜変更してください)
import cv2
import matplotlib.pyplot as plt
#分析対象画像のパス
trgtimg_path = 'test_images/sample1_for_contour.jpg'
# オリジナル画像の読み込み
img_bgr = cv2.imread(trgtimg_path)
# グレースケールに変換
img_gray = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2GRAY)
# 2値化(cv2.findContours()に渡す画像はグレースケールである必要がある)
gray_th = 160
ret, img_bin = cv2.threshold(img_gray, gray_th, 255, cv2.THRESH_BINARY)
# 輪郭抽出()
contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# ノイズ対策 (小さい検出結果は除外する。ここでは大きさ1000以下を除外)
contours = list(filter(lambda x:cv2.contourArea(x) > 1000, contours))
# 領域の描画
result_img1 = cv2.drawContours(img_bgr.copy(), contours, -1, (0,255,0), 5)
cv2.imshow('result_img',result_img1)
cv2.waitKey(0)
# 個々の領域を囲む四角形をオリジナル画像に描画
for c in contours:
x,y,w,h = cv2.boundingRect(c)
result_img2 = cv2.rectangle(img_bgr, (x,y), (x+w,y+h), (255,0,0), 4)
# 以降は結果表示用(画像名と画像のリストを作成、for文で順番にsubplotに表示) -------
title_list = ['original','gray','binary(th:'+str(gray_th)+')','result1','result2']
imgs_list = [img_bgr, img_gray, img_bin, result_img1, result_img2]
fig = plt.figure(figsize=(20,10))
for i, (title,img) in enumerate(zip(title_list, imgs_list)):
ax = fig.add_subplot(2, 3, i+1)
ax.title.set_text(title)
# OpenCVはBGR順なのでRGB順に変換
ax.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
#ax.axis('off')
plt.show()
出力される結果は以下の通り。
result1は輪郭抽出結果、result2はresult1を元に輪郭を四角で囲った結果です。

当たり前ですが背景(今回は白色)と色が違うほうがうまく輪郭が抽出されています。
また、光が斜めから当たっているため、物体の輪郭と同じくらいその影にも応答してます。
二値化する際の閾値(gray_th)を変えると最終結果の輪郭も変わるので、優先的に調整するパラメータはこの閾値になります。
まとめ
OpenCVのcv2.findContours()関数を用いることで画像内の輪郭抽出ができます。
上手く輪郭抽出するには、以下のポイントがあります。
- 細かい輪郭はcv2.contourArea()を用いて排除する
グレースケール画像を二値化する際の閾値を調整する - 影にならないように物体を撮影する(ライティングを工夫)
- 物体と背景の色を似た色になるべくしない
画像処理において、ソフトウェア面で工夫すべきか、画像撮影で工夫すべきかってのはよく考えさせられます。両方やればいいんですけどね。


コメント