iMind Developers Blog

iMind開発者ブログ

OpenCVのSobel, CannyでのEdge Dtection

概要

OpenCVを使ってedge detection(輪郭検出)を行う。有名ドコロのSobel operatorとCanny edge detectorを利用。

導入及び環境

インストールはcondaにて実行。

$ conda install -c conda-forge opencv

バージョン情報。

$ python --version
Python 3.6.5 :: Anaconda, Inc.

$  pip freeze | grep -i opencv
opencv-python==3.4.2.17

対象データ

例として下記の世界一かわいい犬が写った2枚の写真に対して輪郭抽出を行う。

f:id:imind:20181119141853j:plain

f:id:imind:20181119141859j:plain

前者は床が格子状に、後者は後ろにチェック柄がある画像。

Sobel operator

Sobelフィルタは垂直方向と水平方向の画素の変化から輪郭を検出する。

まずは画像を読み込んで何の変換もせずにそのまま表示。

%matplotlib inline
import cv2
import numpy as np
from matplotlib import pylab as plt

img = cv2.imread('data/edge_detct_sobel/dog1_s.jpeg')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))

f:id:imind:20181119142359p:plain

当該画像がそのまま表示される。

続いてcv2.Sobel()を用いて輪郭検出した結果を表示する。床と犬の色が近いがさてどうなるか。ksizeは3を指定している。この場合、3×3ピクセルの変化から輪郭を取ることになる。

# X方向とY方向でそれぞれ検出して足す
x = cv2.Sobel(img, cv2.CV_8U, 0, 1, ksize=3)
y = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=3)
img_sobel = x + y

# 画像の表示
plt.figure(figsize=(9, 9))
plt.imshow(img_sobel)
plt.title('cv2.CV_8U, ksize=3')

f:id:imind:20181119142510p:plain

やはり犬と床との間の輪郭が一部なくなっている。また輪郭ではなく毛並みも抽出されている。

引数を変えて再確認してみよう。Sobelの引数は下記の通り。

cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]] 

ddepthは出力イメージの深さ。8U(8bit unit)だと勾配が取れないケースがあるのでCV_64F(64bit float)で確認してみる。

# X方向とY方向でそれぞれ検出して平均を取る
x = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
y = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
img_sobel = np.sqrt(x ** 2 + y ** 2)

# 0〜1.0の間に変換する
img_sobel = np.abs(img_sobel) / np.max(img_sobel)

# 画像の表示
plt.figure(figsize=(9, 9))
plt.imshow(img_sobel)
plt.title('cv2.CV_64F, 1, 0, ksize=3')

f:id:imind:20181119143048p:plain

毛並み等のノイズっぽい箇所は減ったようだ。

次にksizeを変えて再実行する。5×5, 7×7だとどうなるか。

5×5の場合

f:id:imind:20181119143726p:plain

7×7の場合

f:id:imind:20181119143622p:plain

もう1枚の写真でも実行。ksize=7で。

f:id:imind:20181119150501p:plain

犬の輪郭とソファーの輪郭、それからチェック柄がくっきり出る結果になっている。

Canny Edge Detector

Canny Edge Detectorは事前にガウシアンフィルタとかでノイズを削って、Sobelとかで勾配を見つけて、関係ない画素を削って、抽出されたエッジからしきい値を使ってエッジなのかそうでないのかを判定する、といった多段の処理が行われる。

Sobel単体でやるより優秀な結果が出るはず。

img = cv2.imread('data/edge_detct_sobel/dog1_s.jpeg')
edges = cv2.Canny(img, 100, 200)
plt.figure(figsize=(20, 20))
plt.imshow(edges,cmap = 'gray')
plt.title('Canny')

f:id:imind:20181119151116p:plain

なかなかキレイなエッジが取れた。

もう1枚についても同様の処理を行ってみる。

f:id:imind:20181119151901p:plain

チェック柄がうざいことになってしまっているが、まずまずの結果。

cv2.Cannyにはimageの他に2つの引数(サンプルコードでは100と200)を取っているが、これはlowerとupperのthreshold。

これを230〜255のような無茶な値にしてしまうと、結果はこうなる。

img = cv2.imread('data/edge_detct_sobel/dog1_s.jpeg')
edges = cv2.Canny(img, 230, 255)
plt.figure(figsize=(9, 9))
plt.imshow(edges,cmap = 'gray')
plt.title('Canny : 230〜255')

f:id:imind:20181119153139p:plain

下記URLによるとmedian使えば自動でそれなりのthresholdが登録できるとのこと。

https://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/

medianの上下30%くらいがいいという噂を聞いたので指定してみる。

img = cv2.imread('data/edge_detct_sobel/dog1_s.jpeg')
med = np.median(img)
edges = cv2.Canny(img, med * 0.7, med * 1.3)
plt.figure(figsize=(20, 20))
plt.imshow(edges,cmap = 'gray')
plt.title('Canny : median-30%〜mdian+30%')

f:id:imind:20181119153411p:plain

f:id:imind:20181126215329p:plain

改定履歴

Author: Masato Watanabe, Date: 2019-1-14, 記事投稿