kansiho's memo

ruby, python, javascript. Rails, wordpress, OpenCV, heroku...

Pythonで初めてのOpenCV【キャニーエッジ検出, 円形の検出, 顔の検出】

以下の文章は 

kansiho.hatenablog.com

の記事の続きで、

Canny Edge Detection — OpenCV-Python Tutorials 1 documentation の和訳が中心です。

画像の勾配について

エッジ検出のために、輝度の勾配を使います。これについては、

qiita.com

の「edgeの検出」の項目が非常にわかりやすいです。

キャニーエッジ検出はもっともベーシックなエッジ検出アルゴリズムです。John F. Cannyによって1986年に開発されました。このアルゴリズムには以下の複数ステップがあります。

step1. ノイズを減らす

画像をなめらかにする必要があります。なので、5x5 Gaussian filterによってノイズを減らします。

step2. 画像の輝度勾配を見つける

なめらかにされた画像から、Sobelフィルタを使って縦方向(G_y)と横方向(G_x)の1次微分を取得し、エッジの勾配と方向を求めます。

step3. 非極大値の抑制

各画素に対してその画素が勾配方向に対して極大値であるかどうかを確認します。

[http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/images/nms.jpg:image=http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/images/nms.jpg]

step4. ヒステリシス(Hysteresis)を使ったしきい値処理

前処理で検出されたエッジの内,正しいエッジとそうでないものを区別します。

OpenCVでcannyエッジ検出

上記の処理を行う、cv2.Canny() という関数が用意されています。 第1引数は入力画像を指定します.第2,3引数はヒステリシスを使ったしきい値処理に使う minVal と maxVal です。第4引数は画像の勾配を計算するためのSobelフィルタのサイズ aperture_size で、デフォルト値は3です。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('sample.jpg',0)
edges = cv2.Canny(img,100,200)

plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

plt.show()

f:id:serendipity4u:20170723101359p:plain

エッジ検出できました。

円形を見つける

OpenCVはハフ変換による円検出を行います。

ハフ変換とは

高校では直線はy=ax+b(a,bは定数) というふうに教わるものですが、ハフ変換の考え方においては以下のように、

f:id:serendipity4u:20170723104909p:plain

p とΘという2つのパラメータで直線を考えます。

画像の解析初級編 (傾きを検出してみる) | スターフィールド株式会社

がとてもわかりやすいです(画像引用元)。

円形は、(x-p)2+(y-q)2 = r2 という、(p,q,r)の3つのパラメータによって表現することができます。これはすなわち、3次元上であらゆる円を表現できるということです。直線でやったことを、3次元で行えば、円の式が求まります。

では、OpenCV上でハフ変換関数、cv2.HoughCircles()を使います。

import cv2
import numpy as np

img = cv2.imread('sample.jpg',0)
img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)

circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,
                            param1=50,param2=30,minRadius=0,maxRadius=0) #エラーになる場合はHOUGH_GRADIENTをcv.CV_HOUGH_GRADIENT  にする

circles = np.uint16(np.around(circles))
for i in circles[0,:]:
    # draw the outer circle
    cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
    # draw the center of the circle
    cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)

cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()

なんか事故った。パラメータを調整しないとですね。

f:id:serendipity4u:20170723105505p:plain

顔の検出

OpenCVは学習機と検出器の両方を提供しています。ここでは検出の部分を扱います。OpenCVはあらかじめいくつかの事前に学習を済ませた学習機を提供しており,例えば顔,目,笑顔検出のための検出器があります。これらは opencv/data/haarcascades/ フォルダ内に保存されているXMLファイルに保存されています。自分の場合は、brew install opencv OpenCVをインストールしたので、以下のようなパスになりました。

import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('/usr/local/Cellar/opencv/2.4.13.2/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('/usr/local/Cellar/opencv/2.4.13.2/share/OpenCV/haarcascades/haarcascade_eye.xml')

img = cv2.imread('sample.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, 1.3, 3) #もし顔が検出されれば検出された顔の位置が Rect(x,y,w,h) として出力
for (x,y,w,h) in faces:
    img2 = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = img[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

できました! f:id:serendipity4u:20170723134816p:plain