赤子見守り用モニターをDIYする

本記事は[オプティマインド Location Tech Advent Calendar 2022](https://qiita.com/advent-calendar/2022/optimind-locationtech)の19日目の記事となります。

# はじめに
私事ですが、今年の夏頃に娘が生まれ、ありがたいことに元気にすくすくと育っているのですがここ2〜3週間ほど夜泣きが酷く、寝かしつけに苦戦している状況です。睡眠の質は日々の仕事や家族の平穏のために重要です。そこで、我が家ではネントレの一種であるファーバーメソッド[^1]に着手し始めました。ファーバーメソッドを雑に要約すると、

1. 子供を寝室に寝かせる  
2. 大人は退室
3. 一定時間毎に声をかけに部屋に戻る
4. 泣きが続いていても少し声をかけたら強制退室

という4つのステップを時間管理しつつ繰り返して子供自身の寝る力を育てる方法です。
ここで、ステップ2では子供が寝れずに泣いている声を隣室で聞きつつ待機することになりますが、急に泣き声が聞こえなくなった時に何か危険なことがあったのか、正常に睡眠に入ったのか分からずかなり怖いです。そこでモニタリングしたいよね、という話になるわけですが既製品のベビーモニターは1万円を超える製品が多く買うのを躊躇っていたところ、「そういえば家に通知用Botとして稼働しているRaspberryPiがあったな。」ということで自作しました。

# 準備物
* RaspberryPi3
    * 家で眠ってるものを流用
* 赤外線カメラ
    * [Kumanのカメラ](https://www.amazon.co.jp/Kuman-%E3%82%AB%E3%83%A1%E3%83%A9%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB-Raspberry-%E6%84%9F%E5%85%89%E3%83%81%E3%83%83%E3%83%97OV5647-%E8%B5%A4%E5%A4%96%E7%B7%9A%E3%83%95%E3%82%A3%E3%83%AB%E3%82%A4%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%88%E4%BB%98%E3%81%8D/dp/B01ICO5CFI/ref=asc_df_B01ICO5CFI/?tag=jpgo-22&linkCode=df0&hvadid=280364807675&hvpos=&hvnetw=g&hvrand=12012897309030141330&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009461&hvtargid=pla-589532087997&psc=1)を使用
* カメラ固定台
    * 100均のスマホスタンドなどを使用

# 構成図
構成は以下のようにRaspberryPiで撮影した映像をStreamlitで流して、Localネットワーク内のブラウザから参照します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2327853/cf31694e-40bd-39a4-fbae-c144d55b1e87.png)


正直、ただカメラ映像をみるだけならXを飛ばせばいいんですが、Streamlitでブラウザ経由で見れるようにしたのは、自分だけでなく奥さんでも気軽に使えるような形にしたかったためです。属人性を排除するのは大事ですね。

# 製作作業
### Raspberry Piのセットアップ
参考になるありがたいブログがたくさんあるのでそちらに譲ります。 
2022年12月時点では例えば以下のブログを参照しつつimegerを使えば15分ほどで終わります。

https://www.ingenious.jp/articles/howto/raspberry-pi-howto/raspberry-pi-basic-setup/#toc5

※一点のみ注意点です。
streamlitが依存するpyarrowは32bit OSではinstallに失敗するので、imagerで書き込むのは64bitのOSにしましょう。

https://discuss.streamlit.io/t/raspberry-pi-streamlit/2900/12?page=2

## 監視用プログラムの作成

#### 依存パッケージのインストール
RaspberryPi上で以下のコマンドを実行します。
```console
$ sudo apt install cmake libgtk2.0-dev pkg-config
$ pip install streamlit opencv-python
$ pip install mediapipe #顔検出用
```

#### 実装
pythonでopencvを使ってカメラ映像をキャプチャしstreamlitで流します。

また、それ以外に以下の内容を実装していきます。
* カメラ映像が止まっていないかどうかを識別できるようにするために、時刻描画を追加
* 寝返り検知できると面白そうなので、顔検出機能を追加(自己満足)

```python:app.py
import statistics
import time
from datetime import datetime

import cv2
import mediapipe as mp
import streamlit as st

# camera setting
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 10)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 2)


class NegaeriDetector:

    def __init__(self, window_size=5, is_draw=True):
        # face detector
        mp_face_detection = mp.solutions.face_detection
        self.face_detection = mp_face_detection.FaceDetection(
        model_selection=1, min_detection_confidence=0.5)
        self.is_draw = is_draw
        if is_draw:
            self.mp_drawing = mp.solutions.drawing_utils

        # for filtering
        self.window = [False for _ in range(window_size)]
        self.idx = 0
        
    def __del__(self):
        self.face_detection.close()
        
    def __call__(self, img):

        results = self.face_detection.process(img)

        if results.detections:
            self.window[self.idx] = True
            if self.is_draw:
                for detection in results.detections:
                    self.mp_drawing.draw_detection(img, detection)
        else:
            self.window[self.idx] = False

        self.idx += 1
        self.idx %= len(self.window)

        return statistics.mode(self.window)


negaeri_detector = NegaeriDetector()

st.title('Baby Monitor')

# place holder
baby_status = st.empty()
monitor_canvas = st.empty()

while cap.isOpened:
    ret, img = cap.read()
    time.sleep(0.01)

    if ret:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.flip(img, -1)

        if negaeri_detector(img):
            baby_status.success("Baby is here!", icon='🐥')
        else:
            baby_status.warning('Where is baby?', icon='🚨')

        cv2.putText(img,
                    text=f'{datetime.now()}',
                    org=(10, 20),
                    fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=0.5,
                    color=(0, 255, 0),
                    thickness=1,
                    lineType=cv2.LINE_4)

        monitor_canvas.image(img)
    else:
        print("missing")

    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()

```

## カメラ設置
各ご家庭の睡眠環境に応じて調整が必要ですが、参考までに我が家では布団で寝かせており、かつ子供が基本的に布団の上方向に移動していく動きを繰り出すので、足元に椅子をおいてその上にスマホスタンドに固定したRaspberryPi+カメラを設置しています。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2327853/81170583-9141-cf1b-78bb-8f371166f8e2.jpeg width=400>
## 起動設定
起動時に自動的にアプリケーションが立ち上がるようにしておきます。この設定をすることで電源を挿してカメラスタンドを置いて待てば使える状態が作れます。
まず次のような起動用スクリプトを作成します。
```shell:start-app.sh
#!/usr/bin/python3.9
/full_path/to/streamlit run /full_path/to/app.py
```
これをcronで実行するために`crontab -e`でエディタを開き末尾に以下を追記します。
```
@reboot sleep 30 && /bin/sh /full_path/to/start-app.sh
```

# 動作確認
電源を入れてしばらく待った後、設定したhost名とportでアクセスします。デフォルトでは`http://raspberrypi.local:8501`へブラウザからアクセスすれば良いでしょう。
実際にアクセスしてみた画面が以下です。
<center><img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2327853/a9a2baeb-d6d5-d028-3188-2706d525a726.png width=600></center>

 いい感じです。

### コード類
今回使ったコードはこちらにまとめてあります。

https://github.com/kashee337/baby_monitor


# 運用で感じた課題
まだ使い始めて数時間くらいですが、以下のような課題を感じたので暇な時に随時アップデートしていこうと思います。
* 寝返り検知の精度
  * 完全に自己満足でつけた寝返り検知用途の顔検出はカメラの設置角度的に少し動かれるだけで精度が全然出ませんでした。やはり真上から撮れるように設置を工夫するなどカメラのインプット条件を整えることが肝要ですね。
* 通知方法
  * たとえ寝返り検知を今より高精度に行えたとしても、通知をカメラ映像と同じ画面に出している以上あまり意味がないです。(知ってた)
  * 通知するならLineBotか何かで通知しないと結局画面から目が離せません。(知ってた)
* 機能拡張
  * 今回は寝返り検知用途に顔検出有無のみで判定させていましたが、ハイハイなどで自律的な移動が始まると特定エリアへの侵入検知もしたくなるかもしれません。(ならないかも)

# 感想
金曜の夜からファーバーメソッドにトライし始め、課題を感じて赤外線カメラを購入→土曜の夜にカメラが届き、アプリ作成→日曜の昼間にショッピングモールに家族で出かけるついでに100均でスマホスタンド購入→日曜の夜運用開始という流れでサクッと導入した割には目的を果たせて結構満足してます。これを活用しつつ子供のネントレを安全に見守り、家族全員の睡眠の質を向上させてQoLを上げていくのが直近の目標です。

  
※余談ですが、最初は「低予算で作る赤子用見守りモニター」というタイトルで書き始めたんですが、[今のラズパイの値段](https://www.amazon.co.jp/Raspberry-Pi-3-Model-B/dp/B082QN6L1N/ref=asc_df_B082QN6L1N/?tag=jpgo-22&linkCode=df0&hvadid=588952328377&hvpos=&hvnetw=g&hvrand=4698889745067850601&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009461&hvtargid=pla-1222114335088&psc=1)を調べたら衝撃を受けてそっとタイトルを差し替えました。(ラズパイを新規で購入される方の場合は普通に既製品のベビーモニターを買った方が圧倒的にコスパが良さそうです。)

# 最後に
育児の上では、仕事がフルリモート中心になって良い反面もありつつ、業務上のパフォーマンスを保つ上での悩み事も増えてくるかと思います。弊社オプティマインドでは子持ちのメンバーも多く、slack内で各種育児に役立つ情報を交換する専用のチャンネルを作って運用しています。そんな弊社に少しでも興味を持っていただけた方は、是非採用サイトをご覧いただければと思います。カジュアル面談も大歓迎ですので、気軽にお声がけください。

https://recruit.optimind.tech/

[^1]:https://nennebase.com/2022/10/26/how-to-get-self-snoothing/#:~:text=%E3%83%95%E3%82%A1%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A8%E3%81%AF%E5%AD%90%E3%81%A9%E3%82%82,%E3%82%92%E5%BA%83%E3%81%92%E3%81%A6%E3%81%84%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82