姿勢の悪さを検知して画面を暗くするアプリをPythonで作る(OpenCV)

技術ネタ

僕はデスクワーク中とても姿勢が悪くなってしまうのですが、前々から直したいと思っていました。

姿勢が悪いことは見た目が悪いだけでなく、ストレートネックや血流の悪化による肩こりなどの実害につながるため治せるに越したことはありません。ただ、そうわかっていて姿勢をよくしようとするものの、すぐに忘れてしまい、姿勢が悪くなってしまいます。

そこで姿勢が悪いことを検知して、逐一直させるシステムを作ろうと思います。

ソースコードはこちら

スポンサーリンク

システムの概要

このシステムには以下の機能が求められます。

  • 姿勢の良し悪しを検知する
  • 姿勢が悪くなったことをユーザがリアルタイムで認識し直せる
  • 姿勢が悪くなったことをユーザが無視できない

特に3つめはシステムによる指示の実効性を高めるために必要な機能だと考えています。一方で、無視させないためにポップアップを出し続けるなどは不快感が強く、イライラさせるかもしれません。

今回は「姿勢が悪いことが検知されたら画面を暗くして作業しにくくし、姿勢が良くなったら画面を明るくし作業しやすくなる」という実装をしたいと思います。

「姿勢の良し悪しの検知」ですが、これは軽く先行研究を調べてみると、「カメラで顔の位置を検出し、その高さが変わったら姿勢が変わったと判断する」方法で実装しているものがありました。今回はなるべくPCだけで完結させたいのでこの方法で簡単に実装したいと思います。

スポンサーリンク

ライブラリのインストール

カメラから顔位置を検出するためにOpenCVを使用します。

画面の明るさの変更はscreen-brightness-controlというライブラリで行います(Windows, Linuxに対応)。

pip install opencv-python
pip install screen-brightness-control # 画面の明るさを変えるのに必要(Windows, Linuxに対応)
スポンサーリンク

ソースコード

OpenCVで顔認識し、顔位置を取得します。キャリブレーションした位置から指定したピクセル数顔の位置が下がったら悪い姿勢判定をして、画面を暗くします。画面が暗くなった後、姿勢を良くして顔の位置が上がったら画面を明るくします。

import cv2
import screen_brightness_control as sbc
import numpy as np
import time
import csv

def set_brightness(brightness):
    try:
        sbc.set_brightness(brightness)
    except sbc.ScreenBrightnessError as error:
        print(error)

# モデルを指定(haarcascade_frontalface_alt2.xmlを使用)
model_path = './haarcascade_frontalface_alt2.xml'

# 姿勢の記録先
log_path = './posture_log.csv'
f = open(log_path, 'a', newline='')
writer = csv.writer(f)
writer.writerow(['timestamp', 'x', 'y', 'y_th'])

# 設定パラメータ
monitor_interval = 100
th_posture = 30 # カメラ内で何ピクセル頭の位置が下がったら姿勢が悪いと判定するか(小さいほど自分に厳しい)
th_count = 10 # 何回姿勢が良い・悪いと判定されたら画面の明るさを変えるか
brightness_dark = 20
brightness_bright = 100

calib_flag = False
postures = []
good_posture = None
bad_posture_count = 0
good_posture_count = 0
bad_posture_flag = False

capture = cv2.VideoCapture(0) # VideoCapture オブジェクトを取得
capture_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
capture_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
cascade = cv2.CascadeClassifier(model_path) #学習データの取り込み

print('Push "s" to start calibration')
while(True):
    ret, frame = capture.read()
    frame = cv2.flip(frame, 1) # 反転
    image_gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #グレイスケール化
    # 顔を抽出
    facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(50,50))

    face_position=(0,0,0,0)
    
    if len(facerect) > 0:
        for rect in facerect:
            if(face_position[2]<rect[2]):
                face_position = rect
        (x, y, w, h) = face_position

        if calib_flag:
            postures.append((x, y, w, h))
        if good_posture is not None:
            line_y = int(good_posture[1]+good_posture[3]/2) + th_posture
            writer.writerow([time.time(), x+w//2, capture_height - (y+h//2), capture_height - line_y]) # 解釈しやすい座標系にして記録
            cv2.line(frame, (0, line_y), (capture_width, line_y), (0, 255, 0), thickness=2)
            # 姿勢が悪くなったか判定
            if (y+h//2) - (good_posture[1]+good_posture[3]//2) > th_posture:
                bad_posture_count += 1
            else:
                good_posture_count += 1

        cv2.circle(frame,(x+w//2, y+h//2), 3, (0, 255, 0),thickness = 5)
    cv2.imshow('posture monitor', frame)

    key = cv2.waitKey(monitor_interval)
    if key == ord('q'): # 停止
        break
    elif key == ord('s'):
        print('Calibration started')
        print('Push "e" to end calibration')
        calib_flag = True
        good_posture = None
    elif key == ord('e') and calib_flag:
        print('Calibration ended')
        print('Keep good posture!')
        good_posture = np.array(postures).mean(axis=0).astype(np.int64)
        calib_flag = False
        postures = []

    if bad_posture_count >= th_count:
        set_brightness(brightness_dark) # 明るさを20に
        bad_posture_count = 0
        good_posture_count = 0
        bad_posture_flag = True
    
    if bad_posture_flag and good_posture_count >= th_count:
        set_brightness(brightness_bright) # 明るさを100に
        bad_posture_count = 0
        good_posture_count = 0
        bad_posture_flag = False

f.close()
capture.release()
cv2.destroyAllWindows()
スポンサーリンク

1時間使用してみた結果

このシステムをバックグラウンドで動かして論文執筆作業を1時間やった時の顔の中心位置のカメラ内のy座標の推移です。

一度17:42ごろに姿勢が悪くなったのですが、システムに監視されているという意識がずっとあったため、基本的にはずっと姿勢が良い状態でした。

姿勢をずっと意識して作業をすると背中の上側が張る感じがしました。

ずっと監視されていることを意識していたため、常に気を張って作業しており、twitterやyoutubeを開いて一息つくみたいなことが気軽にはできなくなった気がします。常にこれを使うのは疲れそうなのでたまに姿勢を意識したい時はこれを起動するのが良いかもしれません。

マスクを着けていると顔認識が難しいことがあったので、マスクをしなくてよい環境専用になりそうです。

コメント

タイトルとURLをコピーしました