LoginSignup
3
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-03-24

目次

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

1. きっかけ

おばあちゃんのストーブの灯油を入れてほしい時に入れられない...

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

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

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

2. この記事をおすすめする人

  • 簡単なIoTシステムを手軽に作ってみたい人
  • OpenCVを使い始めた人
  • おばあちゃんを助けたい人

3. システム構成

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

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

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

4. 実装編

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. 実際に使ってみた

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

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

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

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

6. 最後に

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

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2