MENU

face-apiを使って、ゲームを作って、たくさん失敗した記録。

URLをコピーする
URLをコピーしました!

こんにちは。自称フロントエンドエンジニアの森です。

先日、face-apiを使ったミニゲームをローンチしたのですが、制作にあたって色々と躓き、勉強になったので、ここにログとして残したいと思います。(制作時の感情が甦り、若干口調がおかしくなる箇所がありますが、お目溢しください)

目次

作ったゲーム

「ふたりでガッチャンコ!」
Webカメラを使い、2人の顔を絵の通りにくっつけられたら、成功!
というミニゲームです。
ソーシャルディスタンスが叫ばれるこの時代に、家族や友達のコミュニケーションを大事にしたい。そんな想いから企画されたゲームです。

プレイ中の写真がゲーム後に表示されます!
(想像に難くないと思いますが、1人で開発・検証するのはなかなか大変でした。左手がしんどい。)

face-api.js

さて本題。
このゲームではWebカメラの映像から、人の顔を検出する face-api.js という、なんとも素敵なJavaScript APIを使用させてもらってます。

▶︎face-api.js

▶︎公式Demoページ

face-apiの機能は主に下記の5つ
★ 顔の検出
★ 目・鼻・口など、顔の68個のポイントの検出
★ 顔の類似性判断
・表情の認識
・年齢、性別の推定
(★マークはゲームで使用した機能)

Webカメラの映像に限らず、静止画(画像)や、動画でも使用できます。
なんでもできるやん…

早速やってみる

簡単に顔の検出と、landmarksを表示させてみます。

1. 公式のGithubからface-apiをダウンロード

face-api.js

2. 必要なmodelを./models/に配置

modelは、ダウンロードしたzipのweightの中にあります。

必ず必要となるモデルは “ssdMobilenetv1” です。
それ以外は用途に応じて、足していきます。今回はランドマークを表示させたいので、”faceLandmark68Net “も配置しておきます。

3. html

Webカメラ用に<video>、描画用に<canvas>をhtmlに配置します。

WebCameraに関しては、チームの山下さんが記事を書いてくれているので、そちらもどうぞ!

<!DOCTYPE html>
<head>
    <title>WebCam Test</title>
<style>
    .container {
        position: relative;
    }
    #video {
        width: 640px;
        height: 360px;
        position: absolute;
        top: 0; left: 0;
        z-index: 1;
    }
    #canvas {
        width: 640px;
        height: 360px;
        position: absolute;
        top: 0; left: 0;
        z-index: 2;
    }
</style>
</head>
<body>
    <div class="container">
        <video id="video" autoplay muted playsinline></video>
        <canvas id="canvas"></canvas>
    </div>

  <script src="./js/face-api.js"></script>
  <script src="./js/main.js"></script>
</body>
</html>

4. JavaScript

// 2で配置したモデルを読み込む。
Promise.all([
    faceapi.nets.ssdMobilenetv1.load('./models'), // 精度の高い顔検出モデル
    faceapi.nets.faceLandmark68Net.load('./models'), // 顔の68個のランドマークの検出モデル
]).catch((e) => {
    console.log(`face-apiを読み込むことができませんでした。${e}`);
});

// Webカメラの起動
const video = document.getElementById('video');
const media = navigator.mediaDevices.getUserMedia({
    audio: false,
    video: {
        width: 640,
        height: 360,
        aspectRatio: 1.77,
        facingMode: "user",
    }
}).then((stream) => {
    video.srcObject = stream;
    video.onloadeddata = () => {
        video.play();
    }
}).catch((e) => {
    console.log(e);
});

// 描画用canvasの設定
const cvs = document.getElementById('canvas');
const ctx = cvs.getContext('2d');
cvs.width = 640; cvs.height = 360;

// face-apiで顔のランドマークを取得します。
let faceData;
async function getLandMarks(){
    faceData = await faceapi.detectSingleFace(video).withFaceLandmarks();

    if(faceData == null) return;
    drawLandMarks(faceData.landmarks.positions);
}

// 取得したランドマークをcanvasに描画します。
function drawLandMarks(positions) {
    ctx.clearRect(0, 0, cvs.width, cvs.height);
    ctx.fillStyle = '#ffffff';
    for(let i = 0; i < positions.length; i++){
        ctx.fillRect(positions[i].x, positions[i].y, 3, 3)
    }
}

function render(){
    requestAnimationFrame(render);
    getLandMarks();
}

render();

※WebCameraの使用(getUserMedia)はHTTPS通信か、localhostでしか許可されません。

今回のデモは1人用なので、detectSingleFace()で、もっとも信頼度が高い顔が1つだけ検出されるようになっています。複数人を検出したい場合は、" tinyFaceDetector "のモデルを追加して、detectAllFaces()で可能です。

これだけのコードで顔を検出できます…!すごい。
口を開けたり、横向いたり、上向いたりしても…、大丈夫。すごい(2回目)

ガッチャンコよもやま話

このすごいface-apiを使って、件のゲーム「ふたりでガッチャンコ!」を作ったわけですが…

めちゃ苦労しました!
自分の知見が足りないことはもちろん、タイミングやら、状況やら…
というわけで、苦労した点を振り返りたいと思います。一部、face-apiに関係ないのもありますが、生暖かい目で見てやってください。

・iOS14 アップデート

ローンチの少し前、iOS13 からiOS14へのメジャーアップデートがありました。
先週末まで、問題なく動いていたはずのゲームが突然クラッシュし始めました。なんでぇ…?

諸々修正をした今となっては、『逆になんで動いてたんだろう』くらいなのですが、この時はショックでした…。ブラウザゲームを開発する時は、アプリやOSのアップデートには気をつけなきゃいけないですね…

結局、いろんなところを最適化することができたので、雨降って地固まったと己を納得させました。

・face-api は、初回起動が重い

この問題、山下さんも記事にしていましたが、初回起動が非常に重く、face-apiが起動した瞬間にクラッシュするという事象に見舞われました。
解決方法としては、できるだけ何も動いていないタイミングで、ダミーの真っ黒な画像をface-apiに認識させて、アイドリング的なことをさせる…、に落ち着きました。

・PIXI.js と face-api

今回、アニメーション部分にはPIXI.jsを使用しています。
face-apiは、WebGLを使用していますが、PIXI.jsもWebGLを使用しています…。2つが同時に動くと動きがもっさりしてしまい…。こちらは、pixi-legacy.jsを使用し、canvas2Dへのフォールバックで対応しました。

また、今回アニメーション部分を山下さんと分業していたのですが、開発当初「分業するなら、PIXIを2つ使えばいいか!」という単純な考えでPIXIを2つ使っていました。
はい。Containerで分ければ良いだけですね。すみません。
おそらく、【face-api.jsの初回起動が重い】の問題と、上記のもっさりする問題にも、2枚のPIXIがあったことは、大きく影響していたと思います。楽だからと言う理由で、適当なことをすると碌なことになりませんね。反省。

・FaceMatcherが重い!重い!

開発当初、右にいるプレイヤーと左にいるプレイヤーを個別に認識したくて、face-apiの" Matching Descriptors "という機能を使用していました。
これは、Matcherとして保存しておいた顔と、対象の顔がどれだけ類似しているかと0〜1の数値で出してくれるものです。「この中でAさんと一番顔が似てるのは、だーれだ!」って言うのを教えてくれる機能ですね。

ゲーム開始時に2人分のMatcherを取得して、それを元にプレイヤー1とプレイヤー2を区別していたのですが、、、(最初に右にいた人には、左に行っても右の画像しかくっついてこない仕様)
2人分の類似性判断を、描画ごとに行うとスマホではカクカクになり…
ならば、1人分ではどうだとMatcherを減らしてみたものの、スペックの低いスマホではやはりカクカクになり…

結局、この機能を使用するのは断念しました。
ですので、「2人でガッチャンコ!」では、「右にいる人に右の画像」「左にいる人に左の画像」という仕様になっています。
機能としてはおもしろいのに、残念…。PCに限定したブラウザゲームなら大丈夫なのかもしれません。

・opacityに注意

最初、何も考えずに画面を重ねて配置し、必要な画面はopacity:1;、不要な画面はopacity:0; transitionでfade-change!とやってたんですが、スマホでクラッシュが起きました。(原因特定に時間がかかった…!)
大きいDOMを大量に、opacityをかけておいておくとスマホでは耐えきれず、クラッシュするようです。基本はdisplay:none;で対応することで解決しました。

・toDataURL()はAndroidでは保存ができない

知ってました?
私は今回知りました。
toDataURL()で表示した画像は、Androidでは保存ができません…。toBlob()を使いましょう…
でもtoBlob()、重いですよね…

・toBlob()は明示的に消去をしない限り、データが削除されない

スマホで何回か繰り返しプレイをしていると、クラッシュする…
toBlob()のcreateObjectURL()で作成したURLは、ブラウザを更新するか、明示的にrevokeObjectURL()で削除してやらないといけないようです。
「ふたりでガッチャンコ!」は繰り返しプレイの際に、リロードさせていないので、メモリを食っていたようです。

・canvasはリサイズに注意

プレイ後の写真館で表示される3枚のスクリーンショットをcanvasに保存しているのですが、1番前に出ているゲームと一緒にリサイズを行ってしまっていて、保存したはずのcanvasが真っ白に…ということがありました。
リサイズしたら、コンテキストが吹っ飛ぶということを失念していました。初歩ォ…

・よく考えて、1つずつ

JavaScriptの非同期処理って、便利だけど、大変だな。と、今回本当に感じました。
今まで、「データを取ってくるまで、次の処理は待ってね」的なことは、経験してましたが、「A処理とB処理は一緒に行うと重すぎる」理由で、タイミングをずらすみたいなことを行ったのは初めてでした。今までは、あんまり考えずに「A処理とB処理が必要!クリックでどっちも呼び出し!」みたいな乱暴なことしかやってなかったのです。
ひとつずつ、着実に終わらせていくことが、結局近道…みたいな。

・まだまだある。

だがしかし、書く方も、読む方も疲れるので、この辺りで。

Special Thanks

開発に際し、いろんな地雷を踏みましたが、足を失わなかった(心が折れなかった)のは、ひとえにチームのみなさまのおかげです…。人に恵まれている…。
みなさま、ありがとうございます!

遊んでみてくださいね?

自画自賛ですが、とても良いゲームだと思います。
人と距離を取らなければならない、このご時世ですが、家族や友達とガッチャンコ!して、楽しく遊んでみてください!

「ふたりでガッチャンコ!」


コンテンツでは、この他にも様々なキッズ向けミニゲームを制作しています。ミニゲームの一覧は、【ぱおぴーのミニゲーム図鑑】で紹介しているので、ぜひ覗いていってください⭐️

最新のゲームはTwitter(@paopeee)でもチェックできます!

よかったらシェアしてね!
URLをコピーする
URLをコピーしました!
目次
閉じる