Raspberry Pi CameraモジュールとOpenCVで人間の顔を認識する

# 環境

- Raspberry Pi 3B (Raspbian 9.4) + Camera Module V2
- OpenCV 3.4.3

# OpenCVのインストール

インストールはチュートリアルのとおりでいけました。
https://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html

NOTE: aptリポジトリにもpython-opencvがあるので、古いバージョンでもよければこちらを入れた方が簡単ですね。検出性能は...低いかもしれません。

```
pi@raspberrypi:~$ apt search python-opencv
ソート中... 完了
全文検索... 完了  
python-opencv/stable 2.4.9.1+dfsg1-2 armhf
  Python bindings for the computer vision library
```

## 必要パッケージのインストール

```
# apt-get install build-essential
# apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
# apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
```

## OpenCVのダウンロード

最新のソースは[こちらから](https://opencv.org/releases.html)

```
# curl -OL https://github.com/opencv/opencv/archive/3.4.3.zip
# unzip 3.4.3.zip
# cd opencv-3.4.3/
```

## ビルドとインストール

makeにはかなり時間がかかります。私の環境では一度端末がフリーズしてしまいました...

```
# # IN "opencv-3.4.3/" DIR
# mkdir build
# cd build
# cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..
# make
# make install
```

Pythonのモジュールは /usr/local/lib/PYTHON_VERSION/dist-packages/ にインストールされています。

```
pi@raspberrypi:~$ ls /usr/local/lib/{python2.7,python3.5}/dist-packages/
/usr/local/lib/python2.7/dist-packages/:
cv2.so

/usr/local/lib/python3.5/dist-packages/:
cv2.cpython-35m-arm-linux-gnueabihf.so
```

## インストールの検証

```
$ python3 -c "import cv2; print(cv2.__version__)"
3.4.3
```

# カメラの準備

## 接続とデバイスの有効化

カメラモジュールを接続したら、デバイスを有効化する必要があります。
カメラの接続方法とGUIからデバイスを有効化する方法は公式ページにステップバイステップ手順があります。
https://projects.raspberrypi.org/en/projects/getting-started-with-picamera

コマンドラインから行う場合は、カメラ接続後 raspi-config を起動して "5 Interfacing Options" ---> "P1 Camera" メニューでカメラデバイスを有効化します。設定変更後デバイスを再起動します。

```
$ sudo raspi-config
```

## カメラのテスト

```
$ raspistill -v -o test.jpg
```

# カメラで撮った画像から人間の顔を探す

公式の顔検出サンプルコードは下記URLにあります。下記コードは公式のサンプルコードを一部改変したものです。
https://docs.opencv.org/3.4.3/d7/d8b/tutorial_py_face_detection.html

## Cameraモジュールで写真を撮る

Cameraモジュールを起動してカレントディレクトリに写真を保存します。
写真を撮る前に3カウントしています。

```
pic_file = './picamera_work.jpg'
if os.path.exists(pic_file):
	os.remove(pic_file)

camera = PiCamera()
for i in range(3, 0, -1):
	print(i)
	sleep(1)
camera.capture(pic_file)
```

`camera.capture(output)`はカメラで撮った写真をoutputで指定されたパスに保存します。ファイルパスの代わりにファイルオブジェクトを渡すこともできます。
保存する画像のフォーマットはファイル名から推測してくれます (例: *.jpg=>JPEGファイル)。`format=FORMAT`で指定することもできます。
https://picamera.readthedocs.io/en/release-1.13/api_camera.html#picamera

## OpenCVの学習済分類器をロードする

OpenCVでは学習済の分類器をいくつか提供しています。
分類器は`/usr/local/share/OpenCV/haarcascades/`にインストールされています。

分類器をロードするには`CascadeClassifier`コンストラクタにパラメータとして渡すか、`load`メソッドを別途呼び出します。
下記コードサンプルでは目と顔の分類器をそれぞれロードしています。

```
face_classifier = "/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(face_classifier)
eye_classifier = "/usr/local/share/OpenCV/haarcascades/haarcascade_eye.xml"
eye_cascade = cv2.CascadeClassifier(eye_classifier)
```

## 顔を検出する

`detectMultiScale`で顔を検出します。
http://opencv.jp/opencv-2.1/cpp/object_detection.html#cv-cascadeclassifier-detectmultiscale
`detectMultiScale`は与えられた画像を縮小しながら何度も検出を試みます。パラメーター`scaleFactor`には画像の縮小量を設定します。
最終的に検出される物体は少なくとも`minNeighbors`で指定された分だけ近傍矩形を含む必要があります。`minNeighbors`に小さな値を指定すると検出できる割合は増えますが、誤検出も増えます。

```
pic = cv2.imread(pic_file)
pic_gray = cv2.cvtColor(pic, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(
	pic_gray,
	scaleFactor=1.1,
	minNeighbors=5,
	minSize=(30, 30))
```

scaleFactorの働きはこちらのQAが理解の助けになりました。
http://answers.opencv.org/question/10654/how-does-the-parameter-scalefactor-in-detectmultiscale-affect-face-detection/

## 検出したオブジェクトを表示する

検出した顔のエリアを線で囲っています。さらにそのエリア内で目を検出して、見つかった場合はそのエリアも線で囲います。

```
for (x, y, w, h) in faces:
	cv2.rectangle(pic, (x, y), (x + w, y + h), (255, 0, 0), 2)
	roi_gray = pic_gray[y:y+h, x:x+w]
	roi_color = pic[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("Raspberry pi camera detects {0} faces".format(len(faces)), pic)
cv2.waitKey(0)
cv2.destroyAllWindows()
```


## コード全体

```
from picamera import PiCamera
from time import sleep
import cv2
import os
import sys

pic_file = './picamera_work.jpg'

if os.path.exists(pic_file):
	os.remove(pic_file)

camera = PiCamera()
for i in range(3, 0, -1):
	print(i)
	sleep(1)
camera.capture(pic_file)

face_classifier = "/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(face_classifier)
eye_classifier = "/usr/local/share/OpenCV/haarcascades/haarcascade_eye.xml"
eye_cascade = cv2.CascadeClassifier(eye_classifier)

pic = cv2.imread(pic_file)
pic_gray = cv2.cvtColor(pic, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(
	pic_gray,
	scaleFactor=1.1,
	minNeighbors=5,
	minSize=(30, 30))

for (x, y, w, h) in faces:
	cv2.rectangle(pic, (x, y), (x + w, y + h), (255, 0, 0), 2)
	roi_gray = pic_gray[y:y+h, x:x+w]
	roi_color = pic[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("Raspberry pi camera detects {0} faces".format(len(faces)), pic)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

## おまけ

`imwrite`を使って切り出した顔を画像として保存することもできます。

```
count = 0;
for (x, y, w, h) in faces:
        count += 1
        cv2.rectangle(pic, (x, y), (x + w, y + h), (255, 0, 0), 2)
        roi_gray = pic_gray[y:y+h, x:x+w]
        roi_color = pic[y:y+h, x:x+w]
        cv2.imwrite("face_{0}.jpg".format(count), roi_color)
        ...
```