LoginSignup
23
7

More than 3 years have passed since last update.

バーチャルブーブークッションを作りたかった

Last updated at Posted at 2019-12-21

どうも、フリーランスエンジニアの黒神(@kokushing)と申します。
クソアプリ Advent Calendar 2019 22日目の投稿です。

作ったもの

顔を認識すると屁を放つクソアプリです。

booboo-app

動作デモ

※Mac版Chromeにて動作確認

経緯

どんなクソアプリを作ってやろうかなとトイレで考えてたとき、ふとひらめいたんですよね。
「バーチャルブーブークッションを作ろう」と。

ブーブークッションとは、空気の入った袋のようなものの上に座ることで、「ブゥーッ」っと妙にリアルな屁の音が鳴るあのジョークグッズです。

実装に関して、IoT的に物体を用意するのは難易度高そうだし、なにか手軽に実装できそうな手段は無いか考えた結果、「PCの前に着席したときに顔認識して音出せば良いんじゃね?」という天才的アイデアを思いつきました。

実装

顔認識にはface-api.jsというtensorflow.jsを用いたJavaScriptライブラリを利用しました。

顔認識といえばPythonとかOpenCVとかのイメージが強いんですが、なんとこのJavaScriptライブラリ、単体でリアルタイムな顔認識を行うことができるんですねー。
精度も高いしAPIも豊富なのでかなり使い勝手の良いライブラリだと思います。

ただ、モデルデータが20MBくらいあるので軽量とは言えませんけどね。
(同系統のJSライブラリ「pico.js」は200行程度の超軽量ですが精度がイマイチでした)

フロントのフレームワークにはReact.jsを使用しました。
最近Hooks APIで書くことが多いので、その練習も兼ねています。
(クソアプリアドベントカレンダーは新しい技術を試すのにもってこいの神イベントですね)

ソースコードはこんな感じです。

import React, { useState, useEffect, useRef } from "react";
import * as faceapi from "face-api.js";
import "./App.css";

function App() {
  const [audio, setAudio] = useState(null);
  const [isCompleted, setIsCompleted] = useState(false);
  const videoEl = useRef(null);

  const onPlay = () => {
    const options = new faceapi.TinyFaceDetectorOptions();
    const loop = setInterval(async () => {
      const result = await faceapi
        .detectSingleFace(videoEl.current, options)
        .withFaceLandmarks()
        .withFaceExpressions();

      if (result) {
        audio.play();
        setIsCompleted(true);
        clearInterval(loop);
      }
    }, 500);
  };

  useEffect(() => {
    (async () => {
      await faceapi.nets.tinyFaceDetector.loadFromUri(
        "https://kokushin.github.io/booboo-app/weights"
      );
      await faceapi.nets.faceLandmark68Net.loadFromUri(
        "https://kokushin.github.io/booboo-app/weights"
      );
      await faceapi.nets.faceExpressionNet.loadFromUri(
        "https://kokushin.github.io/booboo-app/weights"
      );

      navigator.mediaDevices
        .getUserMedia({
          audio: false,
          video: { width: 320 }
        })
        .then(stream => {
          videoEl.current.srcObject = stream;
          videoEl.current.play();

          setAudio(
            new Audio("https://kokushin.github.io/booboo-app/assets/audio.mp3")
          );
        });
    })();
  }, []);

  return (
    <div className="App">
      <h1>booboo-app</h1>
      <p>カメラに写った顔を認識すると音が出ます</p>
      {isCompleted ? (
        <>
          <p>放屁完了</p>
          <p>
            <img
              src="https://kokushin.github.io/booboo-app/assets/image.png"
              width="320"
              alt=""
            />
          </p>
          <button onClick={() => window.location.reload()}>Retry</button>
        </>
      ) : (
        <p>認識中...</p>
      )}
      <video ref={videoEl} onPlay={onPlay} style={{ display: "none" }} />
    </div>
  );
}

export default App;

最初にface-api.jsのモデルデータを読み込み初期化、その後Webカメラを起動、受け取ったvideoのオブジェクトをface-api.jsに渡し、setIntervalで500ms毎にvideo内に顔が検知されるまで監視を続けます。

顔が検知できたらresultがtrueになるので屁を放ち監視を終了します。

至ってシンプルな流れですね。

残念な点

Webカメラの起動から顔の認識まで若干時間がかかってしまうので、椅子に着席したタイミングの1秒後くらいに音が鳴ってしまいます。本当は着席と同時に出したかったんですけど己の技術力不足で厳しそうでした😇

クソースコード

コード自体はMITライセンスです。

kokushin/booboo-app

使用されている画像に関しては いらすとや
妙にリアルなオナラの音に関しては Onara Sound MP3 様 を利用しています。

実を出したい勢の方々、ご拡張ください。

まとめ

face-api.js、初めて使ったんですが手軽でいい感じでした。
次はもっと多くのAPIを用いてクソじゃないアプリを開発したいですね。

参考リンク

23
7
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
23
7