CCCMKホールディングス TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

店頭での商品オススメ企画 Mediapipeの活用(1)

はじめまして、データベースマーケティング研究所の井上と申します。

現在お店の来店者に向けて商品をオススメする企画を検討しております。 企画概要としては来店者の画像を機械学習にかけ商品をオススメするというものになります。

f:id:ccc_ino:20220222112818p:plain
企画概要の流れ

課題

普通にカメラとディスプレイを設置していても素通りされてしまいそうなので、まずは認知してもらうのにどうしようという部分を考えました。 実際に自分自身がスーパーやドラッグでサイネージやディスプレイに気づかないか、気づいて素通りするかのどちらかです。 とはいえ実験で予算もリソースも限られてるので手っ取り早く、人目を引いてくれそうなものということでMediapipeを使うことにしました。

MediaPipe

Mediapipe

MediapipeはGoogleが提供しているライブストリーミングメディアに対してのMLソリューションのフレームワークです。 かなり平たく言うとカメラ映像から人物の顔、手、体を検出してくれます。(他にも色々できます)
実際に使ってみるとこうなります。

f:id:ccc_ino:20220222130250p:plain
Mediapipeでの顔検出

顔がメッシュで覆われてサイバー感(ホラー感?)が出ますね。 お店の中でディスプレイの前を通った時に異質なものが自分と同じような動きを追従してたらちょっと気になってしまいますよね。
この異質感でお客さんに認知をしてもらえることを期待してます。 下記がコードです。(ほぼサンプルのままです)

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"
        crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
        crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js" crossorigin="anonymous"></script>
</head>

<body>
    <div class="container">
        <video class="input_video" style="display:none;"></video>
        <canvas class="output_canvas" width="360px" height="360px"></canvas>
    </div>
    <script>

        const videoElement = document.getElementsByClassName('input_video')[0];
        const canvasElement = document.getElementsByClassName('output_canvas')[0];
        const canvasCtx = canvasElement.getContext('2d');

        function onResults(results) {
            canvasCtx.save();
            canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
            canvasCtx.drawImage(
                results.image, 0, 0, canvasElement.width, canvasElement.height);
            if (results.multiFaceLandmarks) {
                for (const landmarks of results.multiFaceLandmarks) {
                    drawConnectors(canvasCtx, landmarks, FACEMESH_TESSELATION,
                        { color: '#C0C0C070', lineWidth: 1 });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_EYE, { color: '#FF3030' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_EYEBROW, { color: '#FF3030' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_RIGHT_IRIS, { color: '#FF3030' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_EYE, { color: '#30FF30' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_EYEBROW, { color: '#30FF30' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_LEFT_IRIS, { color: '#30FF30' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_FACE_OVAL, { color: '#E0E0E0' });
                    drawConnectors(canvasCtx, landmarks, FACEMESH_LIPS, { color: '#E0E0E0' });
                }
            }
            canvasCtx.restore();
        }

        const faceMesh = new FaceMesh({
            locateFile: (file) => {
                return `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/${file}`;
            }
        });
        faceMesh.setOptions({
            maxNumFaces: 1,
            refineLandmarks: true,
            minDetectionConfidence: 0.5,
            minTrackingConfidence: 0.5
        });
        faceMesh.onResults(onResults);

        const camera = new Camera(videoElement, {
            onFrame: async () => {
                await faceMesh.send({ image: videoElement });
            },
            width: 360,
            height: 360
        });
        camera.start();

    </script>
</body>

</html>

MediapipeはAPIがPython、Javascript、Android/iOS、C++で提供されてます。 まずはPython+Flaskで軽くデモを作ってみたのですが問題が出てきました。
今回は画面数を3~5画面で想定してるのですが、画面が遷移する(≒URLが変わる)と映像領域が少しの間白くなり、表示まで4,5秒かかるのです。 これは画面が遷移してページを読み込むとVideoオブジェクトを新たに生成するためです。

        const camera = new Camera(videoElement, {
            onFrame: async () => {
                await faceMesh.send({ image: videoElement });
            },
            width: 360,
            height: 360
        });
        camera.start();

しかしその間を忙しいお客さんは待ってくれません。 カメラの前からいなくなったり、素通りしたりしてしまいます。

ということで画面遷移を発生させずに一画面(1つのHTML)で動くように変更しました。
その場合、一画面上でVideoオブジェクトと画面の要素の表示状態をコントロールすることが必要になり、 Flaskでは難しいのでJavascriptでの実装に変更することにしました。

Javascriptで実装しながらおおまかな流れは実現できることは確認できたのですが、細かい要件で対応できないところが出てきたとこと、 1つのHTMLで実現しようとする作りがカオスになりそうだったのでElectronを使うことにしました。


終わりに

記事のタイトルがMediapipeなのにElectronで締まってしまいましたが、次回もう少し細かい要件と実装した内容等を書きたいと思います。 普段はUI/UXの部分を実装する機会がないのですがやはり目に見える部分の実装は楽しいなと思いました。 またMediapipeを使うのも初めてでしたが、すごい技術が手軽に使えるようになったんだな、と改めて思いました。