ラズパイを使って居眠り運転を検知してみた

# 目次
* きっかけ
* この記事をおすすめする人
* 事前準備
* システム構成
* 目のアスペクト比の計算
* 実装編
* 実際に使ってみた
* 最後に


# 1. きっかけ
#### 居眠り運転危ない！！
居眠り運転で大切な人を失うのは嫌ですよね...
どうにか予防できないか...
でも睡眠を検知するドラレコとかあったとしてもお高い...

ラズパイを使って簡単にできないか！？！？
ということで、作成していきます！！


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/08b76cad-4bc6-df01-0a72-5521653c1958.png" width=70%>


今回は、車のシガーソケットを給電にし、ラズパイに繋いでおきます。
車のエンジンがかかった時にプログラムを始動させるように設定しておきましょう。

また、居眠りを検知すると、アラートを鳴らすようにしておきましょう。

また、今回dlibを使って顔を検出していきます。
今回は`shape_predictor_68_face_landmarks.dat`を使用します。


これはdlibライブラリに含まれる顔ランドマーク検出器の学習済みモデルファイルです。
詳しい計算手法は`第５章 目のアスペクト比の計算`をご覧ください。




# 2. この記事をおすすめする人
- 簡単なIoTシステムを手軽に作ってみたい人
- dlibライブラリを使って顔の検出をしてみたい人
- opencvを使ってみたい人



# 3. 事前準備
まず、pythonの仮想環境を作りましょう！(任意)
自分はローカル環境を極力汚したくない人なので、絶対やります()

```:ターミナル
$ mkdir EyeTrackingActivity
$ cd EyeTrackingActivity
$ python3 -m venv venv
$ source venv/bin/activate
```

これで完了です。

次に今回必要なライブラリをインストールしていきます。


```:ターミナル
$ pip install opencv-python
$ pip install cmake
$ pip install dlib
$ pip install scipy
$ pip install pygame
```

次に、先ほど紹介した`shape_predictor_68_face_landmarks.dat`を用意します。
[こちら](http://dlib.net/files/)から`shape_predictor_68_face_landmarks.dat.bz2`
をダウンロードして展開し、EyeTrackingActivityフォルダの直下におきましょう。

次に居眠りを検知したときのアラートを用意します。
自分は[こちら](https://pixabay.com/ja/sound-effects/search/)からダウンロードしました。

ここまで来たら準備完了です。(ラズパイにカメラを繋いておきましょう)
現在のディレクトリ構成はこのような感じです。

```
$ tree

EyeTrackingActivity
├── alert.mp3
├── app.py
├── shape_predictor_68_face_landmarks.dat
└── venv
```




# 4. システム構成

フローチャートを以下に示します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/b7e12040-324b-2f55-bfcb-5cf0cefa0c1e.png" width=100%>



① 各箇所の初期化
② カメラの情報の読み込み
③ 顔の検出
④ 目のアスペクト比の計算(どれだけ開いているか)
⑤ 閉じていればカウントダウン開始
⑥ 1秒以上閉じている場合はアラート再生
⑦ 目を開いている場合はアラート停止
(以上、②〜⑦を繰り返す)

# 5. 目のアスペクト比の計算手法

今回は`shape_predictor_68_face_landmarks.dat`を使わせていただきました。
顔の68個のランドマークポイントを識別します。これには目、鼻、口、あごの輪郭などが含まれます。詳しくは[こちら](https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/)

![スクリーンショット 2023-10-22 21.26.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/491179ed-5219-7e59-0a7c-dedc73774861.png)

今回は右目を37~42, 左目を43~48を使って抽出します。
(配列のため、プログラム内の値は0~67です)

プログラム内では、
`left_eye = landmarks[36:42]`
`right_eye = landmarks[42:48]`
と表現しています。

ここからEAR(eyes aspect ratio)を算出していきます。
今回はこの論文で紹介されている通りの実装をさせていただきました。

http://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf

![スクリーンショット 2023-10-22 21.58.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/7df4e2d3-ed20-8cd2-f70b-1dac70e39b76.png)



# 6. 実装編
```python:app.py
import cv2
import dlib
import time
from scipy.spatial import distance
import pygame

# pygame初期化
pygame.mixer.init()

# アラート音ロード
alert_sound = pygame.mixer.Sound('alert.mp3')

# dlibの顔検出器と目検出器初期化
face_detector = dlib.get_frontal_face_detector()
eye_detector = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")

# カメラ初期化
cap = cv2.VideoCapture(0)

# 目のアスペクト比を計算
def get_eye_aspect_ratio(eye):
    A = distance.euclidean(eye[1], eye[5])
    B = distance.euclidean(eye[2], eye[4])
    C = distance.euclidean(eye[0], eye[3])
    ear = (A + B) / (2.0 * C)
    return ear

def get_landmarks(shape):
    return [(shape.part(i).x, shape.part(i).y) for i in range(shape.num_parts)]

eye_closed_time = None  # 目を閉じた時刻を追跡

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

    # 顔と目を検出
    faces = face_detector(frame, 1)
    for face in faces:
        shape = eye_detector(frame, face)
        landmarks = get_landmarks(shape)
        left_eye = landmarks[36:42]
        right_eye = landmarks[42:48]

        # 目が閉じているかどうか
        left_eye_aspect_ratio = get_eye_aspect_ratio(left_eye)
        right_eye_aspect_ratio = get_eye_aspect_ratio(right_eye)

        # 両目が閉じていると判断された場合
        if left_eye_aspect_ratio < 0.25 and right_eye_aspect_ratio < 0.25:
            if eye_closed_time is None:
                eye_closed_time = time.time()  # 目を閉じた時刻を記録
            elif time.time() - eye_closed_time >= 1:
                print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
                print("@@@@  運転手が眠っている可能性があります！ @@@@")
                print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n\n\n")
                if not pygame.mixer.get_busy():  # 他の音が再生されていない場合にのみ音を再生
                    alert_sound.play()
        else:
            eye_closed_time = None  # 目を開けたのでリセット
            alert_sound.stop()  # アラート音を停止

    # ウィンドウにフレームを表示
    cv2.imshow('Frame', frame)

    # 'q'キーを押すとループを抜ける
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

```

# 7. 実際に使ってみた


設置完了！
常に監視されている気分です...


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/3342be2b-9772-ae75-0295-431c6c9b6997.jpeg" width=50%>

実際の動作しているシーンはTwitterの動画を見てください。
※ 音に注意！
※ 動画撮影の関係で、動画の実行環境はMacbookです。

https://twitter.com/paupau_engineer/status/1716083259229319313?s=46&t=j3w6NdCebqL8ss67UL7ETg






# 8. 最後に
どうしても画像での処理のため、夜間になると正しく認識しないようになるのが画像のボトルネックですね...
読んでいただき、ありがとうございました！

(先月の記事はエンベデッドシステムスペシャリスト受験のためお休みしました...)