OpenCVで顔の位置を確認したい!

#はじめに
ゲームといった入力したものを出力するようなプログラムを動かすとき、
「顔を認識して操作ができたらいいのでは?」
と何となーく考えていました。なので、OpenCVを用いて顔の位置を反応させて、そこから顔の中心点を探り出すプログラムを作成しました。
ただし、そうは思っていても動画を処理するプログラム以前に静止画できちんと探知できないと意味がないので、写真を処理できるようなプログラムを作成してから動画で対応できるようにしました。

#作成物概要
OpenCVを用いて顔の位置を指定する

##開発環境
>MacBookPro 2019 2.4GHz corei5 メモリ16GB
>OS:macOS Mojave10.14.6
>Python(anaconda)

##内容
OpenCVのライブラリを用いて顔を捕捉し、それを写真の座標点を出力する。

#画像を読む
とりあえず、そもそも存在するライブラリを用いて何も考えずに顔を指定するものを作成した。

```Python:Face_photo.ipynb
import cv2
import matplotlib.pyplot as plt

image_name = input("File Name?")

src=cv2.imread(image_name)
image_gray=cv2.cvtColor(src,cv2.COLOR_BGR2GRAY) #グレイスケール化
color=(0,0,255) #かこむやつ(赤)
color_c=(255,0,0) #打つやつ(点,青)
cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml") #学習データの取り込み

#顔を見つける
facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(50,50))

if len(facerect) > 0:
    for rect in facerect:
        cv2.rectangle(src,tuple(rect[0:2]),tuple(rect[0:2]+rect[2:4]),color,thickness = 2) #顔を囲む線
        cv2.circle(src,tuple(rect[0:2]+rect[2:4]//2),5,color_c,thickness=-10) #中心に点を打つ
        print(rect[0:2]+rect[2:4]//2)#中心点を出力する

cv2.imwrite(image_name+"_result.jpg",src)#保存するやつ
```

これによって顔に点をプロットできます。その結果、こんな感じになります。
![baby0FTHG7355_TP_V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484148/1a50fd6b-3637-d05f-483a-a229b0aeed5e.jpeg)
(元写真)
![baby0FTHG7355_TP_V.jpg_result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484148/3c60f474-5bab-57a9-4ed5-24f919e8b316.jpeg)
(出力結果)

いい感じに捕捉できたかと思います。
##問題点
ただし、今回の方法に関しては以下のような問題点が存在しました。
![EJ0zkEnUwAACV5f.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484148/45163d22-2850-b712-b21f-88ec53b1a423.jpeg)
ぶっちゃけ鼻の穴を目と見て、鼻の下を鼻と見たら顔です(キッパリ)

なんてことも言えないので、これに関しても修正が必要です。
##一番デカい顔を判定
ということで、今回は一人を認識すればいいと言う体で考えていくとします。理由は、最終目的が顔での操作が目的なので、何人もを想定するまでの必要性がないからです。
ということで、大きい顔だけ認識できるようにします。

```Python:Face_Photo.ipynb
import cv2
import matplotlib.pyplot as plt

image_name = input("File Name?")

# image_name = "27708.jpg" #写真の取り込み

src=cv2.imread(image_name)
image_gray=cv2.cvtColor(src,cv2.COLOR_BGR2GRAY) #グレイスケール化
color=(0,0,255) #かこむやつ(赤)
color_c=(255,0,0) #打つやつ(点,青)

big_face=(0,0,0,0)

cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml") #学習データの取り込み

#なんとかする呪文
facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(50,50))

if len(facerect) > 0:
    for rect in facerect:
        print(rect)
        if(big_face[2]<rect[2]):
            big_face= rect

cv2.rectangle(src,tuple(big_face[0:2]),tuple(big_face[0:2]+big_face[2:4]),color,thickness = 2) #顔を囲む線
cv2.circle(src,tuple(big_face[0:2]+big_face[2:4]//2),5,color_c,thickness=-10) #中心に点を打つ
print("結果:%d" + (big_face[0:2]+big_face[2:4]//2))#中心点を出力する

cv2.imwrite(image_name+"_result.jpg",src)#保存するやつ
```

big_faceというものに最大の顔を保存することにしました。悪口のようでそうではありません。
もちろんですが、こうすることできちんと最大の顔を保存することができるようになりました。

#動画で認識させる
ここまでくれば仕上げ段階です。動画で動くようにしました。

```Python:Face_Video.ipynb
import cv2
import matplotlib.pyplot as plt

# image_name = input("File Name?")

# image_name = "27708.jpg" #写真の取り込み

cap=cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH,640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,480)

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

    if not ret:
        print("Couldn't detect camera.")
        break

    key=cv2.waitKey(1)&0xff
    if key==ord("q"):
        print("Given a exit command.")
        break
        
    image_gray=cv2.cvtColor(src,cv2.COLOR_BGR2GRAY) #グレイスケール化
    color=(0,0,255) #かこむやつ(赤)
    color_c=(255,0,0) #打つやつ(点,青)

    big_face=(0,0,0,0)

    cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt2.xml") #学習データの取り込み

    #なんとかする呪文
    facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(50,50))

    if len(facerect) > 0:
        for rect in facerect:
            if(big_face[2]<rect[2]):
                big_face= rect

        cv2.rectangle(src,tuple(big_face[0:2]),tuple(big_face[0:2]+big_face[2:4]),color,thickness = 2) #顔を囲む線
        cv2.circle(src,tuple(big_face[0:2]+big_face[2:4]//2),5,color_c,thickness=-10) #中心に点を打つ
        print(big_face[0:2]+big_face[2:4]//2)#中心点を出力する

    cv2.imshow("result",src)
    cv2.waitKey(1)
    
cv2.destroyAllWindows()
cap.release()
```
ここまでできると、下のような感じになります。
![名称未設定-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484148/9e699120-90a8-59ab-db21-98dc3377086d.gif)
きちんと動くようになりました。また、ここでの鼻自体の位置に関しても数値として出力できるようになっているので、これを活用すれば顔の位置で操作ができるようになります。
ちなみに、qを押したら終了というのを入れていますが、jupyterだと反応しませんでした。悲しみ。

##問題点
ただし、今回の方式での問題点がさすがに存在し、横顔だと反応しません。そもそも正面の顔を使用して学習したデータではあるので反応しないという問題点があります。なので、完全に正面にカメラと画面があり、ゲームで使用するという形であれば十分活用できるかと思います。

ということで、OpenCVでの顔捕捉は以上です。

#補足
今回使用した画像に関してはすべて[ぱくたそ](https://www.pakutaso.com/)の画像を使用しました。