PCの前に座ると「おかえりなさい」と言ってくれる python+OpenCV

# 企画
- カメラで顔を検知する
- 認識が成功したら優しく声をかけてくれる
- 時間によってかけてくれるセリフが違う「おはよう」とか「お疲れ様とか」
- 機械学習とかは全く使わない。
- 制約：執筆込みで5時間以内。

# 環境
+ windows10
+ Python 3.5.2 |Anaconda 4.2.0 (64-bit)

# やってみよう！
## カメラで顔を検知する
大前提。PCのカメラで顔を認識することから始めてみる。

```python:sample1.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2

cap = cv2.VideoCapture(0)
if cap.isOpened() is False:
    raise ("IO Error")

while True:
    ret, frame = cap.read()

    cv2.imshow("camera", frame)

    k = cv2.waitKey(10)
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()
```

これでPCカメラの映像を表示し続けることができる。止めたい場合はEscキーを押せばいい。

いい具合のサンプルが見当たらなかったので、画像から顔を認識するプログラム（おまけ参照）を元に、カメラから顔を認識するプログラムを書いた。

```python:sample2.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
from datetime import datetime

cap = cv2.VideoCapture(0)
if cap.isOpened() is False:
    raise ("IO Error")

while True:
    ret, frame = cap.read()

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # gray変換

    face_cascade = cv2.CascadeClassifier("haarcascades/haarcascade_frontalface_default.xml")

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    color = (0, 0, 255)  # 囲む色を指定
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)

    cv2.imshow("camera", frame)

    # Escで終了/Spaceで画像保存
    k = cv2.waitKey(10)  # 10msec
    if k == 27:
        break
    if k == 32:
        now = datetime.now().strftime('%Y%m%d%H%M%S')
        savepic = now + '.jpg'
        cv2.imwrite("savepic", frame)

cap.release()
cv2.destroyAllWindows()
```

whileの中にそのまま記述するとキャプチャがガクガクすることがあるらしい。
私はこれで問題なかったのだが、もし上手くいかなかった場合にはThreadを用いると改善されるそうだ。

認識の内容についてはおまけで触れているため省略、それ以外のwaitKeyなどについてはコメントを読んでいただきたい。

## 認識したら音を再生する
優しく声をかけて欲しいのだが、それにはまず優しい声を録音するところから始めないといけない。とりあえず、顔を認識したら音楽を再生できるようにしてみよう。

次の記述をsample2に追加する。

```python:sample3.py
# 認識したら5秒間musicを鳴らして終了
    if len(faces) > 0:
        pygame.mixer.init()
        pygame.mixer.music.load("mp3/ここにファイル名を入れてね.mp3")
        pygame.mixer.music.play(1)
        time.sleep(5)
        pygame.mixer.music.stop()
        break
```

facesとは認識したときの座標を指している。つまり認識したらmp3を読み込んで再生する。ただし再生してから5秒間（sleepの引数）は何もできず、5秒後breakによってwhileループを抜けてプログラムは終了する。

ただしmp3は日本語名だとエラーが起きた。
もしかすると文字化けによって「そんなファイルねーよ」と怒られたのかもしれない。
また、breakしない場合はカメラがsleepによって止まっているため、次のフレームのループが開始する。つまり連続して何度かmp3が流れることになることに注意。

## 時間によって音楽を変更する
拙いやり方。if文でちまちまセリフを変えることにする。

2018/7/15追記。コメント頂いたのでプログラム修正しました。誰かがスマートな方法提案してくれることを待ってます:bow:

```python:sample4.py
    todaydetail = datetime.today()
    hour = todaydetail.hour

    # 認識したら5秒間musicを鳴らして終了
    if len(faces) > 0:
        pygame.mixer.init()
        if 5 <= hour <= 9:
            print("おはようございます")
            pygame.mixer.music.load("mp3/morning.mp3")
        elif 10 <= hour <= 12:
            print("今日も一日頑張ってね！")
            pygame.mixer.music.load("mp3/goodluck.mp3")
        elif 13 <= hour <= 18:
            print("もうひと踏ん張りだよ！")
            pygame.mixer.music.load("mp3/goforit.mp3")
        elif 19 <= hour <= 22:
            print("おかえりなさい。今日も一日お疲れ様です")
            pygame.mixer.music.load("mp3/goodjob.mp3")
        elif 23 <= hour <= 4:
            print("おやすみなさい")
            pygame.mixer.music.load("mp3/goodnight.mp3")
```

とりあえず最初に挙げたやりたいことが最低限実現したため完成。あとはmp3を用意すればいい。好きな子に頼み込んでもいいしセリフを吹き込んでもいいし、二次元の世界から切り取って一人で喜んでもいい。
~~好きな子に頼み込める度胸と根性があるのであれば、わざわざこんなプログラムを書く必要などないかもしれないが…。~~

コードについては、あえて要らない部分は消さずにそのままこのページの最後に貼っておく。ここまでお疲れ様でした。

# やってみたかったこと
- 自分の顔だけを認識する
- 自動で起動＆終了
- 他のカメラを用いての検証

# 参考資料
- [OpenCVを使ってWebカメラの映像の顔認識を行う【python】](http://famirror.hateblo.jp/entry/2015/12/20/180000)
<注意>このサイトのサンプルプログラムを動かしてみたところ、私の環境ではいくつかのエラーを吐いた。そのため私の書いたサンプルを推奨する。

- [pythonで音楽再生する方法](http://qiita.com/Nyanpy/items/cb4ea8dc4dc01fe56918)
- [日付・時間](http://www.python-izm.com/contents/basis/date.shtml)


# おまけ : 三次元と二次元の画像を認識してコラ画像を作成する
前に書いた画像から顔を検知するやつ。
三次元と二次元を選べる。ただ検知するのかコラ画像にするのかも任意。
~~内容については以下参照。[以前書いた記事](http://mgame.hatenablog.com/entry/2017/06/10/113000)~~
ブログ削除癖が禍してリンクが切れてた。すまぬーん。新しく解説を書いたりは…しない。

```python:face_rec.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 顔認識
import os
import cv2
import sys
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image


# リサイズするための関数
def resize_image(image, height, width):

    # 元々のサイズを取得
    org_height, org_width = image.shape[:2]

    # 大きい方のサイズに合わせて縮小
    if float(height)/org_height > float(width)/org_width:
        ratio = float(height)/org_height
    else:
        ratio = float(width)/org_width

    # リサイズ
    resized = cv2.resize(image, (int(org_height*ratio), int(org_width*ratio)))

    return resized


#  顔認識のための関数 認識のみver
def faced(image, x):
    # 普通の写真を検知するなら上、アニメ顔なら下
    if x == 3:
        face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
    elif x == 2:
        face_cascade = cv2.CascadeClassifier('lbpcascade_animeface-master/lbpcascade_animeface.xml')
    else:
        print("強制終了します")
        sys.exit()
    img = cv2.imread(image)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # gray変換
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)  # 顔認識(img, scaleFactor, minNeighbors)

    color = (0, 0, 255)  # 囲む色を指定
    for (x, y, w, h) in faces:
        cv2.rectangle(img, (x, y), (x+w, y+h), color, 2)

    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    plt.show()


#  顔認識のための関数 リサイズ使用ver
def overlay_face(image, x):
    # 普通の写真を検知するなら上、アニメ顔なら下
    if x == 3:
        face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
    elif x == 2:
        face_cascade = cv2.CascadeClassifier('lbpcascade_animeface-master/lbpcascade_animeface.xml')
    else:
        print("強制終了します")
        sys.exit()

    img = cv2.imread(image)  # 認識対象の画像

    # 上書きする画像
    # アルファチャンネルを維持
    ol_image_path = "pic2.png"
    ol_image = cv2.imread(ol_image_path, cv2.IMREAD_UNCHANGED)

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # gray変換
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)  # 顔認識(img, scaleFactor, minNeighbors)

    if len(faces) > 0:
        for rect in faces:
            resized_ol_image = resize_image(ol_image, rect[2], rect[3])
            image = overlayOnPart(img, resized_ol_image, rect[0], rect[1])

    # 認識結果の出力
    cv2.imwrite("result2.png", image)


#  認識した顔の部分に画像を張り付ける関数
def overlayOnPart(src_image, overlay_image, posX, posY):

    # オーバレイ画像のサイズを取得
    # ol_height, ol_width = overlay_image.shape[:2]

    # OpenCVの画像データをPILに変換
    # BGRAからRGBAへ変換
    src_image_RGBA = cv2.cvtColor(src_image, cv2.COLOR_BGR2RGB)
    overlay_image_RGBA = cv2.cvtColor(overlay_image, cv2.COLOR_BGRA2RGBA)

    # PILに変換
    src_image_PIL = Image.fromarray(src_image_RGBA)
    overlay_image_PIL = Image.fromarray(overlay_image_RGBA)

    # 合成のため、RGBAモードに変更
    src_image_PIL = src_image_PIL.convert('RGBA')
    overlay_image_PIL = overlay_image_PIL.convert('RGBA')

    # 同じ大きさの透過キャンパスを用意
    # 用意したキャンパスに上書き
    # オリジナルとキャンパスを合成して保存
    tmp = Image.new('RGBA', src_image_PIL.size, (255, 255, 255, 0))
    tmp.paste(overlay_image_PIL, (posX, posY), overlay_image_PIL)
    result = Image.alpha_composite(src_image_PIL, tmp)

    return cv2.cvtColor(np.asarray(result), cv2.COLOR_RGBA2BGRA)


#  メイン関数(開始)
if __name__ == '__main__':
    # 画像認識開始
    pic = "pic3.jpg"  # 認識対象の画像
    if os.path.exists(pic):  # imageが存在すれば実行開始
        inp_dim = int(input("3次元 or 2次元 / 3 or 2 :"))

    print("モード選択...(1)顔認識 / (2)コラ画像")
    inp_mode = int(input("1 or 2 : "))

    if inp_mode == 1:
        faced(pic, inp_dim)

    elif inp_mode == 2:
        overlay_face(pic, inp_dim)

    else:
        print("終了します")
        sys.exit()
```
*実行例*
3次元 or 2次元 / 3 or 2 :3
モード選択...(1)顔認識 / (2)コラ画像
1 or 2 : 1

# 今回書いたプログラム
```python:camera_maid.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cv2
import pygame
import time
from datetime import datetime

cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # gray変換

    face_cascade = cv2.CascadeClassifier("haarcascades/haarcascade_frontalface_default.xml")

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    color = (0, 0, 255)  # 囲む色を指定
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)

    todaydetail = datetime.today()
    hour = todaydetail.hour

    # 認識したら5秒間musicを鳴らして終了
    if len(faces) > 0:
        pygame.mixer.init()
        if 5 <= hour <= 9:
            print("おはようございます")
            pygame.mixer.music.load("mp3/Redo.mp3")
        elif 10 <= hour <= 12:
            print("今日も一日頑張ってね！")
            pygame.mixer.music.load("mp3/Redo.mp3")
        elif 13 <= hour <= 18:
            print("もうひと踏ん張りだよ！")
            pygame.mixer.music.load("mp3/Redo.mp3")
        elif 19 <= hour <= 22:
            print("お疲れ様でした")
            pygame.mixer.music.load("mp3/Redo.mp3")
        elif 23 <= hour <= 4:
            print("おやすみなさい")
            pygame.mixer.music.load("mp3/Redo.mp3")
        pygame.mixer.music.play(1)
        time.sleep(5)
        pygame.mixer.music.stop()
        break

    faces = 0

    cv2.imshow("camera", frame)

    # Escで終了/Spaceで画像保存(savepicフォルダに投入)
    k = cv2.waitKey(10)  # 10msec
    if k == 27:
        break
    if k == 32:
        now = datetime.now().strftime("%Y%m%d%H%M%S")
        path = "savepic/" + now + ".jpg"
        cv2.imwrite(path, frame)

cap.release()
cv2.destroyAllWindows()
```