背景差分法を用いておばあちゃんを助けてみた

# 目次
* きっかけ
* この記事をおすすめする人
* システム構成
* 実装編
* 実際に使ってみた
* 最後に


# 1. きっかけ
#### おばあちゃんのストーブの灯油を入れてほしい時に入れられない...
ストーブの灯油って重たいですよね、高齢者には到底持てません。
私の家庭では、いつもおばあちゃんの灯油が切れると、電話して運んで〜と連絡がきます。
しかし！私や家族にも手が離せない時が...

オンライン面接や、テレビのいいところなど...
外出中ならなお、気付けませんよね。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/eb3667d9-cd0f-d8ea-fd4a-db68844672ea.png" width=70%>





そこで、LINEをやっていないおばあちゃんでも、石油が切れたと通知できるようなシステムを作り、おばあちゃん助けるIoTシステムを作ってみよう！となったわけですね。





# 2. この記事をおすすめする人
- 簡単なIoTシステムを手軽に作ってみたい人
- OpenCVを使い始めた人
- おばあちゃんを助けたい人

# 3. システム構成
今回は簡単に場所を取らずにできるということで、ラズパイとUSBカメラを用いて実装しました。
システム構成図を示します。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/7178a7ad-d96d-5d33-1617-ba5cd0961683.png" width=90%>

① 固定されているUSBカメラで写真を撮影します。
② 前回撮影した最新の写真をフォルダから取得します。
③ それら２点の背景差分を算出します。
④ 一定数以上の差分があれば、物体が検出された旨のメッセージと、撮影された写真を送信します。
（⑤ 撮影された写真をフォルダに保存します。）



:::note info
**これらの操作はcrontabで自動実行され、5分置きに実行されています。**
:::

# 4. 実装編
```python
import cv2
import datetime
import os
import requests

camera_index = 0

# フォルダを作成
if not os.path.exists('camera_pic'):
    os.makedirs('camera_pic')

# 最新の画像ファイルを取得
def get_latest_image():
    pic_dir = os.path.join(os.getcwd(), 'camera_pic')
    pic_list = os.listdir(pic_dir)
    if pic_list:
        latest_pic = os.path.join(pic_dir, sorted(pic_list)[-1])
        return latest_pic
    else:
        return None

# 背景差分
def diff_img(img1, img2):
    bgsub = cv2.createBackgroundSubtractorMOG2()
    img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
    img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    mean1 = cv2.mean(img1_gray)[0]
    mean2 = cv2.mean(img2_gray)[0]
    if mean1 > mean2:
        alpha = mean1 / mean2
        beta = 0
    else:
        alpha = 0
        beta = mean2 - alpha * mean1
    img2 = cv2.convertScaleAbs(img2, alpha=alpha, beta=beta)

    # 2つの画像の差分を計算
    diff = bgsub.apply(img1)
    diff = bgsub.apply(img2)
    
    # 二値化
    _, diff = cv2.threshold(diff, 50, 255, cv2.THRESH_BINARY)
    return diff

#　LINEメッセージ送信部分
def send_message(notification_message):
    line_notify_token = ''
    line_notify_api = 'https://notify-api.line.me/api/notify'
    headers = {'Authorization': f'Bearer {line_notify_token}'}
    data = {'message': f'message: {notification_message}'}
    requests.post(line_notify_api, headers = headers, data = data)

#　LINE画像送信部分
def send_image(image):
    line_notify_token = ''
    line_notify_api = 'https://notify-api.line.me/api/notify'
    payload = {'message': '撮影した写真を以下に示します。'}
    headers = {'Authorization': f'Bearer {line_notify_token}'}
    files = {'imageFile': open(image, "rb")}  
    requests.post(line_notify_api, data=payload, headers = headers, files = files)

# カメラを起動
cap = cv2.VideoCapture(camera_index)
cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25)

# 画像撮影
ret, frame = cap.read()

# 最新の画像ファイルを取得
latest_pic = get_latest_image()

# 撮影した画像を保存
now = datetime.datetime.now()
pic_name = now.strftime('%Y%m%d_%H%M%S') + '.jpg'
pic_path = os.path.join('camera_pic', pic_name)
cv2.imwrite(pic_path, frame)

# 背景差分を計算
if latest_pic is not None:
    latest_img = cv2.imread(latest_pic)
    if (frame == latest_img).all():
        diff_sum = 0
    else:
        diff = diff_img(latest_img, frame)
        diff_sum = cv2.countNonZero(diff)
    if diff_sum > 300000:  
        print("差分あり！")
        send_message('物体を検知しました。物体を確認してください。')
        image = get_latest_image()
        send_image(image)
    else:
        print("物体検出されませんでした")

else:
    print("最新の画像がありません")

cap.release()

```

# 5. 実際に使ってみた

ベースはこちらの背景で、撮影されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/e8efb74f-9710-efb1-af33-3932a2b54169.jpeg" width=50%>

こちらに何か物体が映り込めば、検出が開始されます。

灯油缶が映り込んだ時のLINE通知は以下のようになります。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1936739/81117a53-7e66-80b6-2728-d1566f6e92b8.jpeg" width=50%>

正しく検知し、LINEに送信できていることがわかりますね！

# 6. 最後に

今後もう少し精度を上げるとなれば、機械学習にて何が写っているのかの判定までしたいところですね〜！
読んでいただき、ありがとうございました！