@kotyのブログ

PythonとかAWSとか勉強会のこととかを、田舎者SEがつづります。記事のライセンスは"CC BY"でお願いします。

テレビに近づき過ぎたら離れるよう注意する仕掛けを作った

ひととおり動くようになったので、記事にまとめます。

背景

子供はテレビが好きである。さらに、夢中になるとどんどんテレビに近づいていく。それをいちいち注意していた。 同じことを3回やったら自動化するのがプログラマーの鉄の掟であるため、 我が家のラズパイと超音波距離計か何かを使えばどうにかならんかなぁとぼんやり考えていた。

そんな中、ギーラボに置いてあった私物のスピーカーを取りに行ったときに「これも持ち帰ってくれ」と渡されたのが私物の初代kinectだった。 これには深度センサーが搭載されていることを思い出し、使ってみることを思いついたのだった。

実現方法の概要

作戦としては、以下を考えた

  1. テレビの上部にkinectを設置
  2. RGBカメラで顔を検知し顔のエリアの平均深度を計測
  3. 閾値よりも近かったら、離れるよう注意する音声を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)

顔検出と距離計測

大まかな流れは、以下の通り。

  1. RGBをskimageにてグレースケールに変換
  2. OpenCVにて顔検出
  3. 顔の領域の切り出し
  4. 顔の領域の平均深度を算出
  5. 深度をメートルに変換
  6. 閾値判定

ありものの組み合わせで実現できてしまい驚いた。顔検出ロジックも完全にブラックボックスである。。。

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 を使った。とても便利。 f:id:kkotyy:20210507171442p:plain

Kinect for Windows をRasberry Pi Zero Wで使う

いにしえのKinect for Windowsを使いたくなり、やってみた。基本的には以下の記事に沿って行った。前提として、ラズパイのパッケージ類は最新、Python3環境が入っている。

Experimenting with Kinect using opencv, python and open kinect&nbsp;(libfreenect)naman5.wordpress.com

いろいろ試行錯誤したので、この手順で良いかは分かりません。。。何か抜けているかも。

必要なパッケージをインストール

sudo apt-get install git-core cmake freeglut3-dev pkg-config build-essential libxmu-dev libxi-dev libusb-1.0-0-dev

OpenCVをインストール

sudo apt-get install python-opencv

Kinectを操作するためのライブラリをcloneする。ちなみに、歴史あるリポジトリだけどPython3に対応していた。

git clone https://github.com/OpenKinect/libfreenect.git

ビルドする。

cd libfreenect
mkdir build
cd build
cmake -L ..
make
sudo make install

Python で使うためのラッパーをインストールする。

cd ../wrappers/python
sudo python3 setup.py install

ここまでくれば、サンプルプログラムが動くはず。

python3 demo_cv2_async.py

f:id:kkotyy:20210504091540p:plain しかしスーパーもっさりである。ここから顔認識とdepth計測をしたいのだけど出来るんじゃろか。

OAuth徹底入門 読了後メモ

仕事で必要になり手に取った。

入門といえば入門なんだろうか、徹底と言っているだけあって400ページ超と(お値段もそれなり)かなりのボリュームそして情報量であった。

OAuthに関する本はほとんど出回ってなくて同人誌くらいしかなかったんだけど、これはよくまとまっていた。OAuthを手がけるのであれば読んでおくべき良書だと思う。

ドメイン駆動設計入門 読了後メモ

来年度はスクラッチ開発が予定されているので、手に取った。ちなみにいわゆる「DDD本」は挫折して本棚でほこりをかぶっております。DDD本が出てから久しい。みんなこの程度の知識は持っているのだろうか。。。

主にRailsDjangoでも同じ話だな)での開発を念頭に読んで、気になった点を五月雨に書きます。

Chapter 2. 値オブジェクト

所感

静的型な処理系であれば誤った代入も防げるし、ロジックは集約できるし、価値は理解できるんだけど、ORMと相性が悪そうだなーとは思った。どうしてんだろうな。

Chapter 4. ドメインサービス

所感

各機能に必ずドメインサービスを作りましょうみたいなSIerな考え方だと小規模サービスではかなり窮屈で開発効率が悪くなりそう。 基本的にはモデルに業務ロジックを集約しつつ、モデルをまたがるような処理はドメインサービスに書くのが良さそう。

さすがにコントローラーに長大なロジックを書くことはないんだけど、モデルは大きくなりがちだと思う。理由としては以下が挙げられる。

  • DBの正規化が不足していてフィールドが多すぎる
  • モデルにまたがった処理が書かれている

後者の場合はドメインサービスの概念を導入してやるとうまくいくんでしょう。

「業務を表現したものではなく、アプリケーションを作るために必要なのであれば、アプリケーションサービスに書くべき」とのこと。メール送信とかはどうなんだろうな。

Chapter 5. リポジトリ

データの永続化を担当。

所感

Railsだとアクティブレコードがあるから不要なようにも思った。

Chapter 6. アプリケーションサービス

ユースケースを実現するためのもの。

所感

MVCアーキテクチャのコントローラーか、バッチ処理のエントリーポイントかな。

アプリケーションサービスがドメインの振る舞いを呼び出せてしまうと、各所にロジックが散らばってしまうので、データを詰め替えるべきとの主張だった。大規模アーキテクチャにありがちなルールだなぁ。

6.4. 凝集度

すべてのインスタンス変数はすべてのメソッドで使われるべき。一部のメソッドでしか使われない変数があるのであれば、クラスとして切り出すべき。

所感

あまり考えられていなかったので、意識したい。モデルやコントローラーのクラスは凝集度が低くなりがちだとは思う。

Chapter 7. 依存関係のコントロール

本来、DBアクセスなどの低レベルモジュールに高レベルモジュールが影響を受けるべきではない。永続化方法はドメインに関係がないはずである。インターフェースを宣言し、低レベルのモジュールはそのインターフェースに合わせて実装することで、依存関係を切り離すことができる。

ここで問題になるのが実装クラスをどうやって作るかだが、DI Containerを使うと良い。

所感

例示されたケースだと、リポジトリ入れ替え可能になるのでテストが楽になるよ、とのことだったが、Railsの場合は本物のDBに向かってテストを実行して大きな問題は感じていない。。。

Chapter 9. ファクトリ

ただ漫然とnewするのではなく、ファクトリを導入すべきか検討する習慣をつけるべき。

所感

これはまあおっしゃるとおりだと思った。

Chapter 12. 集約

集約の外部から教会の内部のオブジェクトを操作してはいけない。集約を操作するための直接のインターフェースとなるオブジェクトは集約ルート(Aggregate Root)と呼ばれるオブジェクトに限定される。

所感

自分の想像していた「集約」とは違った。集約ルートの考え方は良いと思った。ただの代入であっても、それにどんな業務的な意味があるのか、あるのであればメソッドにくくることを検討する習慣を持ちたい。

おしまい

Railsだったらどうするかなーと考えながら読んだ。大前提としてドメイン駆動設計をちゃんとやらなければいけないケースでは静的型が必要で、Railsドメイン駆動設計には不向きなんだろうと思う。

RubyPythonなどの動的型付け言語に慣れ切ってしまったところにこの本を読むと、静的型付け言語を使いこなす自信がなくなってくる。ある程度静的型付き言語に慣れた後に読み直すとまた感じ方が違うんだろう。

実際の開発では何らかのWebフレームワークやORMに乗ることになるので、そのフレームワーク流儀に従ったうえでDDDを適用していくんだろう。

ミル付きコーヒーメーカー所感

ふたつほど使ったことがあるので、突然ですが所感を書きます。私としては、このふたつなら手入れが楽なパナソニックの方をおすすめします。

シロカ

1年ほど使用。ステンレスフィルター。ガラス製サーバーを落として割ってしまい、スレンレス製サーバーにかえた。

長所

  • フィルターの在庫を気にする必要がない
  • 見た目がややおしゃれ

短所

  • スレンレスフィルターをよく洗うなど、毎回の部品洗浄が面倒
  • 連続して淹れるときは、洗った部品を拭いて乾かす必要がある

パナソニック

10年近く使用。ミルが動いた時に異音が出るようになって買い替えた。

長所

  • 掃除が楽。紙フィルターを捨てた後に軽く洗うだけ
  • ミル部分は自動洗浄されるため、毎回の掃除は不要

そういえば連続して淹れたい時はどうしてたかな、、、忘れてしまった。ミル部分を乾かしたりはしていなかったと思う。

短所

  • 紙フィルターの在庫を気にする必要がある

ミル稼働時の音

シロカの方が小さいような気がするけど、パナソニックの方も今は改良されているかもしれない。どっちにしても集合住宅で早朝深夜に動かすのはちゅうちょするレベルだと思う。

味はどうなのよ

どちらもうまいです。なお参考情報↓

失敗の科学 読了後メモ

やらかし反省シリーズ。やらかしに関係する本を手に取ってみたが、「やらかしをしないようにする」ではなく「やらかすのは仕方ない。それを今後に活かすことが大事」のような内容だった。

第1章 失敗のマネジメント

妻が医療事故により亡くなってしまった事例。

ミスの再発防止のための仕組みができておらず「調査嫌い」な医療業界と、第三者機関など再発防止の仕組みができている航空業界を(ふたつの業界に環境要因はあり必ずしも医療業界を責められないとフォローしつつ)比較している。そして「間違いを教えてくれるフィードバックがなければ、訓練や経験を何年積んでも何も向上しない」「フィードバックを得る環境を作るために『ミスの報告を処罰しない』」ことを説いている。

所感

  • 環境要因もあるとはいえ、医療業界の閉鎖性には暗い気持ちになった。日本だけじゃないんだな。
  • ミスの報告を責めないのは大事。障害票に「(直接の)障害起因者」欄を作るとか、障害件数ゼロを目標にする組織にいたことがあったけど、今はどうしてるかな。

第2章 人はウソを隠すのではなく信じ込む

人は自分の過ちを認めるよりも、事実の解釈を変えてしまう。 犯罪捜査にDNA鑑定が使われるまでは、捜査は人の記憶に頼っており多くの冤罪が生まれた。

所感

  • 自分が誤っていると分かったときは素直に認められるようでありたい。そうしているつもりだけど結構難しい

第3章 「単純化の罠」から脱出せよ

瀉血(しゃけつ)療法の事例、刑務所訪問の効果の事例などを挙げ、 人は物事を単純化しがちだし単純化されて理解しやすいものを信じやすい。 しかし物事はそれほど単純では無いことが多い。RCTなど客観的な事実をみることが大事と説く。

所感

第5章 「犯人探し」バイアスとの闘い

「非難したり訴えたり裁判にかけたりすれば、相手は責任感を強く持つようになると思い込んだままでいいのか、ということだ。今のところそれで説明責任が強化されたという証拠はひとつも出ていない」

所感

まったくそのとおり。。。

第6章 究極の成果をもたらすマインドセット

「個人でも組織でも、失敗に真正面から取り組めば成長できるが、逃げれば何も学べない」

所感

まったくそのとおり。。。

終章 失敗と人類の進化

「子どもたちの心に、失敗は恥ずかしいものでも汚らわしいものでもなく、学習の支えになるものだと刻み付けなければならない。」

所感

これは日々言っていきたい。

最後に

ということで、「やらかし」を気をつけはせよ恐れずに生きたいと思ったのだった。

私って、ADHD脳!? 読了後メモ

最近公私共に大きめの「やらかし」をしていたので、手に取った。何かを解決するわけではないけれど、これまで感じていた生きづらさの原因が見える化されて納得感(?)は得られた。

「片付けられない」以外は「注意力散漫」「忘れ物、失くし物が多い」「並行作業が苦手」「決まった手順に沿って作業するのが苦手」など心当たりがかなりあり、程度は分からんけど多少なりとも注意欠陥障害に該当するのだと思う。 これまでいろいろやらかしをしてきて、どうして自分は出来ないんだと悩むことがあったが、これも個性だと思って受け入れるしかないんだろう。 もちろん、やらかさないように努力できるところはしないといけないが。。。注意書きとか予定とか入れててもそもそも注意書きや予定をを見なかったりするんだよな。

それでも、周りにご迷惑をかけつつもここまで生きてこれたから、この先も何とかなるでしょう。

漫画で分かりやすいので、似たようなお悩みの方、うっかり者のフォローばかりしている方、おすすめです。