RaspberryPiでOpenCVを使って顔認識を行ってみる

RaspberryPiでOpenCVを使って顔認識を行ってみる

ようやくやりたかったOpenCVを使った顔認識を行ってみます。MicrosoftさんやIBMさんのサービスなどのWebサービスで顔認識ができるのでそれほど必須というわけでは無いのですが、ネットワークサービスが使えない場合や、回数制限などがあって頻繁にWebAPIを叩けない場合にはローカルの処理でもそこそこ似たような動作ができると助かる局面はあると思います。

例えば、顔認識はローカルのOpenCVで行って、顔が検知できたらWebAPIにアクセスすることで表情の解析を行うというような場合です。以前作成した「ヒミツのくまちゃん」 では、周期的にMicrosoftEmotionAPIを使用しているので、APIの回数制限のため長期間は動作できないことになります。そこで顔を見つけるまではローカル処理でそれなりに対応することで、WebAPIへのアクセス回数を減らすことができるようになります。

【参考】 uepon.hatenadiary.com

以前の記事でOpenCVをインストールしていることを前提にします。

uepon.hatenadiary.com

顔認識を行う。

かなり行われていることなのでネットの情報もかなり充実しています。自分も他のかたのブログなどを参考させていただきます。

自分はこちらを参考にさせていただきました。その為、今回のエントリーでは自分がやったことのメモという扱いになります。

famirror.hateblo.jp

顔認識の流れ

顔認識では大まかに以下のような処理を行います。

  1. 分類器の読み込み
  2. 画像読み込み
  3. BGR2GRAY変換(グレースケールへの変換)
  4. 顔検出処理

ここで分類器という言葉がでています。OpenCVを使った顔認識では「カスケード分類器」というものを使用して顔と非顔の分別を行う様です。

分類器とは?

カスケード型分類器 — opencv 2.2 (r4295) documentation

う~。

自分なりの解釈としては

正しいオブジェクトと間違ったオブジェクトから特徴量を学習して作った分類器から 拡大や回転などの変換をかけて一致するかしないかを検出する方法

分類器を作成するのは荷が重いのでOpenCVにデフォルトでついている分類器を使用することにします。ただし、パッケージのインストールをつかっているので分類器は別途ソースコードについているサンプルから取ってくる必要があります。

OpenCVソースコードGitHubにありますが、分類器は以下のパスにあります。

github.com

今回はhaarcascade_frontalface_default.xmlを使うことにしました。これをプログラムを置くパスにダウンロードしておきます。分類器は20あまりもあるので、ほかのものを選んでもいいのかも。

画像ファイルに含まれる顔を認識する

では、画像ファイルから顔認識をしてみます。見つかった場合には顔として認識されたエリアを赤い四角で囲みファイルとして出力をします。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2

faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
face = faceCascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=2, minSize=(10, 10))

if len(face) > 0:
    for rect in face:
        cv2.rectangle(img, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0,255), thickness=2)
else:
    print "no face"

cv2.imwrite('detected.jpg', img)

サンプルとしてはネットを探してでてきものを使ってみます。オリジナルは以下になります。

f:id:ueponx:20170406160132j:plain

ライセンス: CC0 Public DomainCreative Commons — CC0 1.0 Universal

これをプログラムに処理させると以下のような画像が出力されます。

f:id:ueponx:20170406160603j:plain

顔認識の精度は微妙ではありますが認識は行われたようです。デフォルトなんでこんなもんでしょう。ちなみに別の分類器を使えばこんな感じにもなります。(haarcascade_frontalface_alt.xml

f:id:ueponx:20170406160841j:plain

コードをみる

分類器の読み込み

faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

OpenCVのCascadeClassifier()メソッドを使用して分類器の読み込みを行います。引数に与えるXMLファイルが分類器に相当します。

画像読み込み

img = cv2.imread('image.jpg', cv2.IMREAD_COLOR)

OpenCVのimread()メソッドを使用して画像の読み込みを行います。 特定のファイルではなくプログラムの引数で与えるのであれば

args = sys.argv
img = cv2.imread(args[1], cv2.IMREAD_COLOR)

としてもいいかと思います。

BGR2GRAY変換(グレースケール変換)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

OpenCVのcvtColor()メソッドを使用して画像のグレースケール変換を行います。

顔検出処理

face = faceCascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=2, minSize=(10, 10))

ここまできてやっと顔検出のを行います。 第2引数の1.2がscaleFactorであり、分類器の精度と処理スピードに影響するパラメータとなります。以下のリンクに解説がありました。

answers.opencv.org

数値を小さくすると検出するが上がるのですが、誤検知も増え、処理時間も増えると考えていいみたいです。画像の解像度が高いと処理時間が数秒になるのでWebCameraを使うときには調整が必要そうです。(WebCameraの解像度を下げればいいのですけど。)

今回のサンプルでは処理が終わるのに5秒ぐらいかかっていました。もう少しゆるい制限にしたほうが現実的だと思います。

WebCameraの映像に含まれる顔を認識する

今度はこれをWebCameraで行ってみたいと思います。

前回エントリではcapture = cv.CaptureFromCAM(0)って感じでキャプチャデバイスの設定していましたが今回はcapture = cv2.VideoCapture(0)って感じでキャプチャデバイスを取得しています。後者のほうがより一般的なキャプチャデバイスへの対応ができるのかなと思います。(スピードはあんまり変わりません)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cv2
import time

faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_alt.xml')

capture = cv2.VideoCapture(0) # カメラセット
# 画像サイズの指定
ret = capture.set(3, 480)
ret = capture.set(4, 320)

i = 0
while True:
    start = time.clock() # 開始時刻
    ret, image = capture.read() # 画像を取得する作業
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    face = faceCascade.detectMultiScale(gray_image, scaleFactor=1.3, minNeighbors=2, minSize=(30, 30))

    if len(face) > 0:
        for rect in face:
            cv2.rectangle(image, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0,255), thickness=2)

    get_image_time = int((time.clock()-start)*1000) # 処理時間計測
    # 1フレーム取得するのにかかった時間を表示
    cv2.putText( image, str(get_image_time)+"ms", (10,10), 1, 1, (0,255,0))

    cv2.imshow("Camera Test",image)
    # キーが押されたら保存・終了
    if cv2.waitKey(10) == 32: # 32:[Space]
        cv2.imwrite(str(i)+".jpg",image)
        i+=1
        print("Save Image..."+str(i)+".jpg")
    elif cv2.waitKey(10) == 27: # 27:Esc
        capture.release()
        cv2.destroyAllWindows()
        break

処理時間の測定をしているのはパラメータの変更の効果をみるためになります。 また、スペースを押すとその時点での認識状況の保存ができるようになっています。

パラメータに関しては

scaleFactor=1.3, minNeighbors=2, minSize=(30, 30)

としました。RaspberryPi2では秒間2-3回程度の認識ができるようにしています。 パラメータで一番効果があったのはscaleFactor=1.3でした。1.2あたりでは処理時間が倍くらいかかってしまいました。また、キャプチャの解像度を下げるのも効果があります。ただし、キャプチャの解像度を下げると認識率がわるくなるのである程度にしておいたほうが無難です。デフォルトの640*480のサイズで動かすと認識に1.6秒ほどかかりました。

以下のような認識ができました。

f:id:ueponx:20170406210903j:plain

f:id:ueponx:20170406211019j:plain

おわりに

かなり昔から度々やろうと思っていたことがやっとできました。

最近ではWebAPIを使えば比較的簡単に同じことはできますが、通信制限がある場合には、OpenCVとWebAPIを組み合わせるなどのことも考えていければいいかなと思っています。

あと、RaspberryPiではやはりパワー不足は否めませんので、Xの機能をいっその事削除したり、PCを使って動かしたほうがいいかなと思います。別の処理系であってもpythonなのでそのままコードが使用できるのはいいですね。

/* -----codeの行番号----- */