Shape Detection API + PWAでブラウザからそのまま使えるバーコードリーダーアプリを作ってみる

[ラクス Advent Calendar 2018](https://qiita.com/advent-calendar/2018/rakus) 繋ぎの3番バッターです。

今回はChrome 70から実験的に使えるようになっているShape Detection APIについてまとめてみます。使ってみると意外と実用的だったのでPWA化してブラウザだけでスマホアプリ相当のことが実現できそうなことも検証してみました。

# Shape Detection APIとは

Webの新しい技術の普及を推進する団体の [WICG](https://www.w3.org/community/wicg/) が仕様策定を進めているJavaScriptのAPIです。

[Accelerated Shape Detection in Images](https://wicg.github.io/shape-detection-api/)

まだEditor's Draft（草案レベル）ですが以下の3つの事ができます。

- Face Detection（顔検出）
- Barcode Detection（バーコードスキャン）
- Text Detection（テキスト認識）

[Shape Detection API \- Chrome Platform Status](https://www.chromestatus.com/feature/4757990523535360)

Chrome 70でこの機能がOrigin Trial（実験的に提供する機能）になりました。

[Chromium Blog: Chrome 70 beta: shape detection, web authentication, and more](https://blog.chromium.org/2018/09/chrome-70-beta-shape-detection-web.html)

Google I/O 2018でML Kitが発表されましたが、そのうち3つのMLモデルがChromeに直接組み込まれたということになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/1c627e2c-f749-f019-799c-14d85ad18d0c.png)

# 実装方法と使い方

Shape Detection APIを使えるようにするには、まず `chrome://flags` からExperimental Web Platform featuresを `Enabled` にする必要があります。アドレスバーに以下を入力して設定します。設定後、Chromeを再起動します。

`chrome://flags/#enable-experimental-web-platform-features` 

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/dce88d7a-f6af-cc6f-3e0c-30f33f4a7e8b.png)


## Face Detection

`FaceDetector` をインスタンス化して `image` オブジェクトを `detect()` に渡すことで検出結果を取得できます。

```js
const faceDetector = new FaceDetector();
const image = document.getElementById('image');
// 取得した画像をdetect()に渡す
faceDetector.detect(image)
    .then(faces =>
        {
            for (let face of faces) {
                console.log(face);
            }
        })
    .catch((e) => {
        console.error("Face Detection failed: " + e);
    });
```

取得できる情報（上記 `face` の中身）

```js
[{
boundingBox: { // 検出した顔の座標
        bottom: 204.38605880737305
        height: 49.07900619506836
        left: 177.3531951904297
        right: 226.4322052001953
        top: 155.3070526123047
        width: 49.079010009765625
        x: 177.3531951904297
        y: 155.3070526123047
    },
    landmarks: [ // 目や口や鼻の位置
        {

            locations: {
                x: 189.6961212158203,
                y: 169.10687255859375
            },
            type: "eye"
        }, {
            locations: {
                x: 198.4111328125,
                y: 185.17288208007812
            },
            type: "mouth"
        }, {
            {
                x: 202.0699462890625,
                y: 169.6812286376953
            },
            type: "nose",
        }
    ]
}]
```

APIを使えるかどうかを調べる場合は `window.FaceDetector` をチェックします。

```js
if (window.FaceDetector == undefined) {
    console.error("Face Detection not supported");
    return;
}
```

[Face Detection DEMO](https://radiocat.github.io/shape-detection-sample/examples/face/)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/8e434806-ce58-d8ef-961c-bfed0f258552.png)

## Barcode Detection

実装方法は `BarcodeDetector` を使う以外は同じなので割愛します。 [サンプルコード](https://github.com/radiocat/shape-detection-sample/blob/master/examples/barcode/index.html) でご確認ください。

取得できる情報も基本的にFaceDetectorと同じですが、 バーコードの値は `rawValue` を取得します。

[Barcode Detection DEMO](https://radiocat.github.io/shape-detection-sample/examples/barcode/)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/37991d11-bb7c-ede2-24ed-bbe0a018db70.png)

## Text Detection

テキストについても `BarcodeDetector` と同じです。[サンプルコード](https://github.com/radiocat/shape-detection-sample/blob/master/examples/text/index.html) でご確認ください。テキスト認識の結果は `rawValue` を取得しますが、Macでは座標しか取得できず、Androidはテキストの内容も取得できました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/4cb76b9d-8f9e-99b6-a2f9-4f10f0995950.png)

[Text Detection DEMO](https://radiocat.github.io/shape-detection-sample/examples/text/) 

[WICGのドキュメント](https://wicg.github.io/shape-detection-api/text.html#text-detection-api) によると実行するOSに依存しているようです。

> Example implementations of Text code detection are e.g. Google Play Services, Apple’s CIDetector (bounding box only, no OCR) or Windows 10 OCR API.

# ブラウザからカメラを操作して画像を取得する

ここからはBarcodeDetectorを使ってバーコードリーダーを作る方法を検討します。上記のサンプルコードではあらかじめ用意しておいた画像を使いましたが、実際にはカメラを使ってインタラクティブにバーコードを読み取ることが求められます。

カメラを操作して画像を取得する方法はGoogle DevelopersのWeb Fundamentalsの記事が参考になります。

[ユーザーから画像を取得する  \|  Web  \|  Google Developers](https://developers.google.com/web/fundamentals/media/capturing-images/?hl=ja&authuser=0)

## inputタグを使う

以下の指定でAndroidではファイル選択ボタンからカメラを起動させることができます。

```html
<input type="file" accept="image/*" capture>
```

[サンプルコード](https://github.com/radiocat/shape-detection-sample/blob/master/file/index.html)

残念ながらこの方法はあまり実用的ではないうえに、実用性を上げるにはいろいろと追加実装も必要になります。

- 最近のスマホの高性能なカメラの画像は大きすぎるので圧縮が必要
- カメラのローテーションを考慮する必要がある

バーコードリーダーの用途では取得した画像を再利用するユースケースもあまり考えられないので、fileプロパティで画像を取得するメリットも無く、現実的ではない選択肢と言えます。

## WebRTC APIを使う

[WebRTC](https://webrtc.org/) はビデオチャットなどの実現で使われる技術で、 `<video>` タグを使ってカメラのリアルタイムプレビューを行ったり、カメラストリームからスナップショットを取得することができるため、それを解析すればバーコードリーダーが実現できます。

[サンプルコード](https://github.com/radiocat/shape-detection-sample/blob/master/video/index.html)

```js
const video = document.getElementById('player');
// カメラにアクセスして作成されたストリームを取得
navigator.mediaDevices.getUserMedia({
        video: {
            width: 320,
            height: 240
        },
        audio: false
    })
    .then(function(stream) {
        video.srcObject = stream;
        if (window.BarcodeDetector) {
            const detector = new BarcodeDetector();
            captureTimer = setInterval(function() {
                detector.detect(video).then(
                    function(barcodes) {
                        let barcode = null;
                        for (barcode of barcodes) {
                            console.log(barcode);
                        }
                    }).catch(function(err) {
                    console.log(err);
                });
            }, 100); // 100ミリ秒ごとに解析
        } else {
            console.error(
                'BarcodeDetection is not enable!');
        }
    }).catch(function(err) {
        console.log(err);
    });
```

スタバのレシートに付いていたQRコードを読み取ることに成功しました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3324/a3c94ebe-4946-767e-b5a9-f226421c5c1d.png" width=50%>

[Barcode Detection(video) DEMO](https://radiocat.github.io/shape-detection-sample/video/)


# PWA化する

最後にこれをPWA化します。そもそもPWAとは？については [過去記事](https://qiita.com/radiocat/items/034904a094d07c389a4f) をご覧ください。

今回はPWA化のために主に2つの作業が必要です。

1. ServiceWorkerを実装してオフライン実行できるように必要なファイルをキャッシュする
2. Manifestファイルを作成してホーム画面に追加できるようにする

## WorkboxでPWA化

まずは1.の作業を行います。PWAの導入に便利なWorkboxを使います。Workboxの使い方はCodelabsの以下のガイドを読めば30分ほどで理解できるのでおすすめです。

[Build a PWA using Workbox](https://codelabs.developers.google.com/codelabs/workbox-lab/)

基本的にはCodelabsと同じことをやるだけですが、ざっと流れだけまとめておきます。

`workbox-cli` をインストール

```console
$ npm install --global workbox-cli
```
`sw.js` の作成

```js:sw.js
importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.5.0/workbox-sw.js');

if (workbox) {
  console.log(`Yay! Workbox is loaded 🎉`);

  workbox.precaching.precacheAndRoute([]);

} else {
  console.log(`Boo! Workbox didn't load 😬`);
}
```

`workbox.config.js` の生成

```console
$ workbox wizard --injectManifest
```

今回はGithubに置いたサンプルコードをそのままGithub Pagesで動作できるように `pwa/barcode.html` にサンプルコードを作り、それをリポジトリのルートにビルドさせます（普通はこんな構成にしないと思いますが）。

```js:workbox.config.js
module.exports = {
  "globDirectory": "../",  // リポジトリルートにビルド
  "globPatterns": [
    "pwa/barcode.html"    // キャッシュするファイル
  ],
  "swDest": "../sw.js",   // ServiceWorkerのビルド先
  "swSrc": "sw.js"        // ServiceWorkerのソースコード
};
```

Barcode Detectionの実行の前にServiceWorkerをロードする処理を追加

```js
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('sw.js')
            .then(registration => {
                console.log(`Service Worker registered! Scope: ${registration.scope}`);
            })
            .catch(err => {
                console.error(`Service Worker registration failed: ${err}`);
            });
    });
}
```

ServiceWorkerがロードされていることを確認

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/1a87dac5-717d-83c0-1a7f-5020d12ade55.png)

サンプルコードがキャッシュされていることを確認

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/bc56796a-d573-f079-7e82-3206e69c6eb6.png)


## Manifestファイルの作成

Manifestファイルはジェネレーターを使って自動で作成できます。

[App Manifest Generator](https://app-manifest.firebaseapp.com/)

必要項目を設定し、512x512のアイコンをアップロードすると他のサイズのアイコンファイルと `manifest.json` をzipでまとめてダウンロードできます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/e4f22060-7a1c-535f-2af9-1213dbaf6b3c.png)

ファイルを配置してhtmlからmanifestファイルをロードするように設定

```html
<link rel="manifest" href="manifest.json">
```

Manifestが設定できていることを確認

![image.png](https://qiita-image-store.s3.amazonaws.com/0/3324/36abdfcb-62fa-645f-5aa6-817df1eec369.png)

ホーム画面に追加できることを確認

<img src="https://qiita-image-store.s3.amazonaws.com/0/3324/bfbc5bd9-fdd4-0f24-d8aa-e5eff6084097.png" width=30%>

## サンプルコードとデモ

* [サンプルコード](https://github.com/radiocat/shape-detection-sample/tree/master/pwa)
* [Barcode Detection PWA DEMO](https://radiocat.github.io/shape-detection-sample/barcode.html)

# まとめ

Shape Detection　APIはまだ実験的な機能ですが、API自体はそれほど複雑なものではなく既にある程度実用可能な精度も実現されています。WebRTCやPWAのように既存技術との組み合わせによってブラウザだけで様々な用途に活用できる可能性も感じました。それにしても、ML Kitが発表された半年後にブラウザで直接これらのMLモデルが使えるようになっているスピード感には驚きです。

## 補足

別の勉強会で発表したスライドと今回のサンプルコードのGitHubリポジトリです。

* [ブラウザだけで使える機械学習モデル / Shape Detection Sample \- Speaker Deck](https://speakerdeck.com/radiocat/shape-detection-sample)
* [radiocat/shape\-detection\-sample: ShapeDetection Sample](https://github.com/radiocat/shape-detection-sample)


----

# 参考記事

* [Introduction to the Shape Detection API – Arnelle’s Blog](https://blog.arnellebalane.com/introduction-to-the-shape-detection-api-e07425396861)
* [JSで3分でできる「 顔認識 」Shape Detection API（ネット接続も不要） \- Qiita](https://qiita.com/daisu_yamazaki/items/c782f0154fcb784d4406)
* [Shape Detection APIでカメラの映像から顔認識してみた \- Qiita](https://qiita.com/sublimer/items/ff6c839f8d92735004ba)
* [workbox \+ webpackでServiceWorkerを使いPWA化してみた \- Qiita](https://qiita.com/aaaaayako/items/a4a19dba0b4c9e4b396a)