OpenCVを使ったPythonでの画像処理について、顔の検出を扱っていきます。ここではHaar Cascadesという分類器を使って顔の検出を行います。
ここで行うことは、顔認識ではなく、顔検出です。画像の中に顔が写っているかどうかをを検出する方法ですので、誰の顔であるのかといった顔認識まで行う検出ではありません。顔認識についてはディープラーニングで詳しく扱う分野となりますので、間違わないように注意してください。
Haar Cascadesの基本的な考え方
Haar Cascadesの数学的な細かい説明は専門的な論文を読む必要があると思いますが、簡単に考え方について触れておこうと思います。
Haar Cascadesは、以下示す画像のような、Haar特徴量を使います。
黒い四角形の領域に含まれる画素値の総和から白い四角形の領域に含まれる画素値の総和を引いた値がこの特徴量になります。明るい部分の平均と暗い部分の平均の差をとって、これを顔の特徴と比べていくというものです。
本来は細かく画像を分析して特徴を捉えるのにかず多くの計算が必要になるのですが、顔の大部分の画素に関しては無視してこの特徴量を当てはめることで計算量を減らす単純な形にすることができます。
例えば、顔の目の部分は上の端の特徴の横の画像のように、顔の中に目の位置の特徴を考えることができます。また、目は線の特徴の縦の画像のように、目と目の間に感覚があると考えることができます。
こういった考えから分析するのがHaar Cascadesによる検出です。
Haar Cascadesの検出器
ここではHaar Cascadesを使っての検出を扱います。OpenCVには、次のようなあらかじめいくつかの事前学習を済ませた検出器を提供しています。例えば顔や目、笑顔といったもを検出のためのものです。
- haarcascade_eye.xml
- haarcascade_frontalcatface.xml
- haarcascade_frontalcatface_extended.xml
- haarcascade_frontalface_alt.xml
- haarcascade_frontalface_alt2.xml
- haarcascade_frontalface_alt_tree.xml
- haarcascade_frontalface_default.xml
- haarcascade_fullbody.xml
- haarcascade_lefteye_2splits.xml
- haarcascade_licence_plate_rus_16stages.xml
- haarcascade_lowerbody.xml
- haarcascade_profileface.xml
- haarcascade_righteye_2splits.xml
- haarcascade_russian_plate_number.xml
- haarcascade_russian_plate_number.xml
- haarcascade_smile.xml
- haarcascade_upperbody.xml
これらの検出器はこちらのリンクの、https://github.com/opencv/opencv/tree/master/data/haarcascades にXMLファイルで保存されています。
これをダウンロードして利用することになります。
ここでは、このファイル群をhaarcascadesフォルダを作業ディレクトリに作ってその中に保存して利用していきます。
それでは実際に検出を行っていきましょう。
Haar Cascadesを使った顔検出
それではHaar Cascadesを使った顔検出をやって行きましょう。
jupyter notebookを使って準備していきます。まずは各種ライブラリのインポートから。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
次にここで利用する画像を読み込みます。3つの画像を用意しました。
face1 = cv2.imread('images/face1.jpg', 0)
face2 = cv2.imread('images/face2.jpg', 0)
faces = cv2.imread('images/faces.jpg', 0)
それぞれimread()で読み込んでいます。
それぞれをimshow()で表示します。
plt.imshow(face1, cmap='gray')
plt.imshow(face2, cmap='gray')
plt.imshow(faces, cmap='gray')
描画してみましょう。
それぞれ画像に特徴を持たせました。明るめと暗めの顔の画像と複数の人が写っている画像です。
顔の検出
それぞれの画像の顔の検出からやっていきましょう。
まず、CascadeClassifier()を使って検出器を読み込んでおきます。
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
次に、顔検出の処理を関数で処理できるように定義していきます。
次のようにコードを書いてみました。
def detect_face(img):
face_img = img.copy()
face_rects = face_cascade.detectMultiScale(face_img)
for (x,y,w,h) in face_rects:
cv2.rectangle(face_img, (x,y), (x+w,y+h), (255,255,255), 10)
return face_img
関数名をdetect_face()として、引数に読み込む画像をimgとしてとります。
処理としては、読み込んだ顔の画像をまずcopy()で複製します。これをdetectMultiScale()を使って顔検出を行います。その結果をfor-in文を使って座標と幅と高さをそれぞれタプルとして取り出し、rectangle()を使って顔画像に四角形を描いていきます。画像を渡し、2点の座標、色、線の太さを指定しています。これで顔の検出された画像を返しています。
それぞれの画像をdetect_face()で処理して描画していきます。
result = detect_face(face1)
plt.imshow(result,cmap='gray')
result = detect_face(face2)
plt.imshow(result,cmap='gray')
result = detect_face(faces)
plt.imshow(result,cmap='gray')
処理結果をresultに格納して、グレースケールで表示しています。
描画してみましょう。
顔の部分が白い四角で囲まれて検出されているのがわかります。
ただし、一部、顔では無いところが検出されているものもあります。
3つ目の画像は左から2番目の男の人のネクタイも検出されてしまっています。
この部分を調整した関数を定義してみましょう。
def adj_detect_face(img):
face_img = img.copy()
face_rects = face_cascade.detectMultiScale(face_img,scaleFactor=1.2, minNeighbors=5)
for (x,y,w,h) in face_rects:
cv2.rectangle(face_img, (x,y), (x+w,y+h), (255,255,255), 10)
return face_img
処理は先ほどの関数とほぼ同じです。detectMultiScale()にscaleFactorとminNeighborsをパラメータとして与えています。
scaleFactorは各画像スケールにおける縮小量を表し、1より大きくしていくことで細かい領域をそれだけ飛ばして計算することになります。計算も早くなりますが、大きくしすぎると見逃しも多くなります。ここでは1.2にしてみました。
minNeighborsは物体の検出候補となる矩形が、最低でもこの数だけの近傍矩形を含むものとしています。物体と認識する部分がたくさん重なっているものに対して検出するというイメージで、大きくすると誤った検出は少なくなりますが、見逃しも多くなります。
result = adj_detect_face(faces)
plt.imshow(result,cmap='gray')
これであらためて先ほどの3つ目の画像を処理してみます。
余計な検出が無くなったのがわかります。検出数も少なくなり、正面から顔とわかる部分のみが検出されています。
目の検出
次は目の部分を検出してみようと思います。
CascadeClassifier()で今度は目の検出器を読み込みます。
eye_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_eye.xml')
検出処理の関数を先ほどと同じように定義します。
def detect_eyes(img):
face_img = img.copy()
eyes = eye_cascade.detectMultiScale(face_img)
for (x,y,w,h) in eyes:
cv2.rectangle(face_img, (x,y), (x+w,y+h), (255,255,255), 10)
return face_img
やっている処理は関数名などが違うだけで全く同じです。
result = detect_eyes(face1)
plt.imshow(result,cmap='gray')
result = detect_eyes(face2)
plt.imshow(result,cmap='gray')
顔画像の2つから目を検出して表示してみます。
それぞれ表示してみます。
両目が検出されているのがわかります。一箇所ご検出していますが、このあたりも先ほどと同じようにパラメータを調整するといいでしょう。
こちらの画像は目の検出に失敗しています。これは目の周りの部分に明るい部分と暗い分の差が少ない為に、この検出器では特徴を捉えることができなかったということになります。
以上、2つのカスケードファイルを使って顔検出と目の検出をやってきました。他のファイルも試してみるといいでしょう。ファイル名を見ればどういう検出を目的としているのかも連想できると思います。
webカメラと接続しての顔検出
次はwebカメラ(私の環境ではMacBook Pro内臓のカメラ)に接続して、撮影された人物の顔の検出をやっていこうと思います。
webカメラの接続の方法についてはこちらでもやっています。
こちらを参考にコードを書いていきましょう。
jupyter notebookだとカーネルが止まる可能性があるので、テキストエディタでコードを書いてターミナルから実行してみることにします。(ここでのファイル名はvideo_face_detection.pyにしてみました。)
import cv2
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
def detect_face(img):
face_img = img.copy()
face_rects = face_cascade.detectMultiScale(face_img)
for (x,y,w,h) in face_rects:
cv2.rectangle(face_img, (x,y), (x+w,y+h), (255,255,255), 10)
return face_img
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read(0)
frame = detect_face(frame)
cv2.imshow('Video Face Detection', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
コードの前半は、これまでと行った処理と同じです。OpenCVをインポートし、カスケードファイルを読み込み、顔検出の関数を定義しています。上のものと全く同じコードです。
cv2.VideoCapture(0)でカメラと接続してオブジェクトを生成します。カメラが1台だけ接続された環境なのでデフォルトの0と指定しています。
while文を使って、カメラでのキャプチャーを実行して行きます。read()で読み込みます。retで読み込みの可否のture/false、frameで映像のパラメータを取得しています。
映像のパラメータframeをdetect_face()を使って顔の検出をします。これを、cv2.imshow()を使って表示しています。第1引数はタイトルです。
if文を使って、cv2.waitKey(1) & 0xFF == 27のとき、つまり1ミリ秒キーイベントを待ち、[esc]キーが押されたらbreakして終了させます。この27はASCII制御文字列で[esc]キーを意味することは、これまでも出てきたのでおなじみですね。
while文の処理はここまで。
最後に、release()でデバイスを解放して終了させ、destroyAllWindows()でウインドウを破棄して操作は終了です。
このPythonスクリプトをターミナルから実行します。
カメラが起動しビューワーに撮影されたものが表示されます。カメラで顔を表示すると次のように、顔の周りを四角で囲まれた映像が映し出されるはずです。
カメラの前で動いて見ると、四角の枠も顔に追従して動くはずです。[esc]キーを押して終了させましょう。
最後に
ここでは、OpenCVを使ったPythonでの画像処理について、画像からの顔の検出を扱ってきました。
OpenCVで予め用意されているHaar Cascadesという分類器を使いました。
ここで触れた方法は、画像の中に顔が写っているかどうかをを検出するものですので、誰の顔であるのかといった顔認識まで行う検出ではありません。顔認識についてはディープラーニングで扱う分野なので、顔検出とは意味が違うということを理解しておきましょう。