概要
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枚の写真に対して輪郭抽出を行う。
前者は床が格子状に、後者は後ろにチェック柄がある画像。
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))
当該画像がそのまま表示される。
続いて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')
やはり犬と床との間の輪郭が一部なくなっている。また輪郭ではなく毛並みも抽出されている。
引数を変えて再確認してみよう。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')
毛並み等のノイズっぽい箇所は減ったようだ。
次にksizeを変えて再実行する。5×5, 7×7だとどうなるか。
5×5の場合
7×7の場合
もう1枚の写真でも実行。ksize=7で。
犬の輪郭とソファーの輪郭、それからチェック柄がくっきり出る結果になっている。
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')
なかなかキレイなエッジが取れた。
もう1枚についても同様の処理を行ってみる。
チェック柄がうざいことになってしまっているが、まずまずの結果。
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')
下記URLによるとmedian使えば自動でそれなりのthresholdが登録できるとのこと。
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%')
改定履歴
Author: Masato Watanabe, Date: 2019-1-14, 記事投稿