テレビに近づき過ぎたら離れるよう注意する仕掛けを作った
ひととおり動くようになったので、記事にまとめます。
背景
子供はテレビが好きである。さらに、夢中になるとどんどんテレビに近づいていく。それをいちいち注意していた。 同じことを3回やったら自動化するのがプログラマーの鉄の掟であるため、 我が家のラズパイと超音波距離計か何かを使えばどうにかならんかなぁとぼんやり考えていた。
そんな中、ギーラボに置いてあった私物のスピーカーを取りに行ったときに「これも持ち帰ってくれ」と渡されたのが私物の初代kinectだった。 これには深度センサーが搭載されていることを思い出し、使ってみることを思いついたのだった。
実現方法の概要
作戦としては、以下を考えた
- テレビの上部にkinectを設置
- RGBカメラで顔を検知し顔のエリアの平均深度を計測
- 閾値よりも近かったら、離れるよう注意する音声をgoogle homeから流す
環境構築
そもそも手持ちのラズパイで使えるのか検証したのが以下の記事。 koty.hatenablog.com
ソースコードの解説
以下にupした。また、それぞれの要素技術に関する参考サイトはソース内のコメントに記載した。 github.com
kinectデータの取得
freenect.runloop
というメソッドのコールバックから取得できる。センサーが別なためか、depthとRGBは同時には取得できない。
この仕様は動きの激しいものを相手にする場合は問題になりそうだけど今回は問題にならないだろうと考えて、それぞれの直近のコールバック呼び出しで取得したデータを使うことにした。
また、RGBのコールバックの方が頻繁に呼び出されていたためdepthのコールバックの方でメインロジックを呼ぶことにした。
from is_too_close import is_too_close from warn_to_son import warn_to_son #import frame_convert2 import freenect #import numpy as np latest_rgb = None latest_depth = None has_warned = False keep_running = True def process_depth(dev, data, timestamp): global has_warned global latest_depth global keep_running # raise freenect.Kill if has_warned: return if latest_rgb is None: return latest_depth = data if not is_too_close(latest_rgb, latest_depth): return warn_to_son() #has_warned = True #np.save('np_depth.npy', latest_depth) #np.save('np_rgb.npy', latest_rgb) #keep_running = False def process_rgb(dev, data, timestamp): global latest_rgb latest_rgb = data def body(*args): if not keep_running: raise freenect.Kill print('Press ESC in window to stop') freenect.runloop(depth=process_depth, video=process_rgb, body=body)
顔検出と距離計測
大まかな流れは、以下の通り。
ありものの組み合わせで実現できてしまい驚いた。顔検出ロジックも完全にブラックボックスである。。。
import cv2 import numpy as np # import matplotlib.pyplot as plt import math from skimage.color import rgb2gray from skimage import io, exposure, img_as_float, img_as_ubyte import warnings # 顔検出器 face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml") THRESHOLD_METER = 1.5 def _convert2meter(raw_depth): # https://openkinect.org/wiki/Imaging_Information#Depth_Camera if raw_depth > 1050: # 2.5m以内の場合にしか適用できない数式なので、おおむねそれ以上のraw_depthの場合は一律999mと返す return 999 return 0.1236 * math.tan(raw_depth / 2842.5 + 1.1863) def _convert2gray(img): # https://qiita.com/yoya/items/dba7c40b31f832e9bc2a img = img_as_float(img) # np.array(img/255.0, dtype=np.float64) imgL = exposure.adjust_gamma(img, 2.2) # pow(img, 2.2) img_grayL = rgb2gray(imgL) img_gray = exposure.adjust_gamma(img_grayL, 1.0/2.2) # pow(img_grayL, 1.0/2.2) with warnings.catch_warnings(): warnings.simplefilter("ignore") img_gray = img_as_ubyte(img_gray) # np.array(img_gray*255, dtype=np.uint8) return img_gray def is_too_close(rgb, depth): # http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html # グレースケールに変換 gray = _convert2gray(rgb) # 顔検出 faces = face_cascade.detectMultiScale(gray) for face in faces: # array([[310, 332, 33, 33]], dtype=int32) # depth画像から顔部分を切り出し # depth[332:365, 310:343] x_start = face[1] x_end = x_start + face[3] y_start = face[0] y_end = y_start + face[2] depth_cropped = depth[x_start:x_end, y_start:y_end] # 顔領域の平均距離 distance = _convert2meter(np.average(depth_cropped)) # 閾値より近かったら too close if distance < THRESHOLD_METER: return True return False
google homeの音声出力
狙ったgoogle homeをローカルネットワークから探し出し、公開URLに置いた音声を再生する。
import os import pychromecast # https://qiita.com/rukihena/items/8af9b8baed49542c033d CHROMECAST_NAME = os.environ['CHROMECAST_NAME'] DIRECTION_MP3_URL = os.environ['DIRECTION_MP3_URL'] def warn_to_son(): chromecasts = pychromecast.get_chromecasts() google_home = [c for c in chromecasts[0] if CHROMECAST_NAME in c.device.friendly_name][0] google_home.wait() google_home.media_controller.play_media(DIRECTION_MP3_URL, 'audio/mp3') google_home.media_controller.block_until_active()
動作している動画を見せたいところですが、部屋が丸見えになるのでご容赦ください。
今後の展望
実用に向けては細々とした残タスクがある。飽きっぽいので途中で放り出しがちだけどもやっていきたい。。
- 一度注意したら5分チェックを停止するようにしたい
- 30分以内に3回注意されたらテレビを消すようにしたい。(テレビは既にnature remoで操作できるようにしてある。)
- mp3を再生しているが、AWS Pollyにしゃべらせても良いかも
ゴールデンウィークの良い自由研究になった。
おまけ
顔検出ロジックの実装を試行錯誤する際は jupyter lab を使った。とても便利。