Vue.js で axios await/async を使って Azure Face API にWebカメラの画像データを application/octet-stream で送るメモ

Vue.js で axios await/async を使って Azure Face API にWebカメラの画像データを application/octet-stream で送るメモです。

Vue.js とWebカメラの画像データの連携

Vue.js とWebカメラの画像データの連携については以下の記事がとても役立ちました!ありがとうございます。

Vue.jsでWebカメラで撮影した画像を取得する – Qiita

バイナリで取得して Array Buffer で変換する処理

カメラの画像は Canvas に書き込まれているので、実際には Canvas の相手をします。

canvas.toDataURL でBase64を取得するサンプルって結構多いのですが、たしかに、バイナリで取得して Array Buffer で変換するものって見かけなかったので、以前の知見をベースにやってみます。

Vue.js で axios await/async を使って Azure Face API にファイルアップロードのデータを application/octet-stream で送るメモ

具体的には

いやー、FileReaderさまさまですね。一瞬、自分の力で頑張るのかなと不安になりましたが、準備されてました。すてき。

JPEG フォーマットの画像を取得して送るファイルを抑えるのが便利

canvas.toBlob を、そのまま実行するとPNG画像でファイルサイズが大きくなり、データを送るときに重くなります。

canvas.toBlob( function , "image/jpeg", 0.8);

HTMLCanvasElement.toBlob() – Web API | MDN

こちらの指定にすると、JPEGの指定と、0.8 のところで画質が決めれます。これでかなりファイルサイズが抑えられました。

ソースコード

実際に動くコードはこちらです。BootstrapVue を使って見た目をサッと作りやすいので、ブログ用にこうしています。

  • Windows 10
  • Google Chrome

で検証しています。

'Ocp-Apim-Subscription-Key':'Key' の部分の 'Key' を自分のキーに置き換えてください。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue.js FaceAPI as Camera</title>

  <!-- Load required Bootstrap and BootstrapVue CSS -->
  <link rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />

  <!-- Load polyfills to support older browsers -->
  <script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver"
    crossorigin="anonymous"></script>

  <!-- Load Vue followed by BootstrapVue -->
  <script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
  <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>

  <!-- Load the following for BootstrapVueIcons support -->
  <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue-icons.min.js"></script>

</head>

<body>
  <div class="container">

    <div id="appFaceAPI-Camera">

      <div class="row">
        <div class="col">
            <h1>Vue.js FaceAPI as Camera</h1>
        </div>
      </div>

      <div class="row">
        <div class="col">
          <video ref="video" id="video" width="320" height="240" autoplay></video>
          <div>
            <b-button v-on:click="hanlderCapture">Snap Photo</button>
          </div>
          <h2>Captured Data</h2>
          <canvas ref="canvas" id="canvas" width="320" height="240"></canvas>
        </div>
      </div>
      
      <div class="row">
        <div class="col">
          <h2>response JSON Data : </h2>
          <pre><code>{{ response }}</code></pre>
        </div>
      </div>

    </div>

  </div>

  <!-- axios -->
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

  <script>
    const app = new Vue({
      el: '#appFaceAPI-Camera',
      data: {
        response: '',
        video: {},
        canvas: {},
        captures: []
      },
      methods: {
        
        // キャプチャボタンの挙動
        hanlderCapture: async function () {
          this.canvas = this.$refs.canvas
          this.canvas.getContext('2d').drawImage(this.video, 0, 0, 320, 240);
          
          // まず、CanvasからBlogデータを取得
          const blob = await this.getBlogData(this.canvas);
          // BlogデータをArrayBufferに変換
          const contentBuffer = await this.readBlobToArrayBuffTo(blob);
          // console.log(contentBuffer);

          this.sendCognitiveAsFile(contentBuffer);

        },

        getBlogData: function (canvas) {
          return new Promise((resolve, reject) => {
            try {
              // 標準はPNG 読み込みなので、ファイルサイズが重くなりがち
              /*
              canvas.toBlob(function(blob){
                resolve(blob);
              });
              */
              // JPEG画質も指定できる
              canvas.toBlob(function(blob){
                resolve(blob);
              },"image/jpeg", 0.8);
            } catch( e ){
              reject(e);
            }
          })
        },

        // Blob の中から ArrayBuffer としてデータを取り出す
        // Blob : Binary Large Object バイナリデータを格納する場合のデータ型
        // 
        // 本来 onnload で取り出すが await / async で呼び出せるようにしている
        readBlobToArrayBuffTo: function (file) {
          return new Promise((resolve, reject) => {
            let reader = new FileReader();
            reader.onload = () => {
              resolve(reader.result);
            };
            reader.onerror = reject;
            reader.readAsArrayBuffer(file);
          })
        },

        // このあたりは以下の記事を参考に。
        // https://www.1ft-seabass.jp/memo/2020/05/07/azure-face-api-application-octet-stream-using-axios/

        sendCognitiveAsFile: async function(contentBuffer) {
          // エンドポイント
          // 米国西部2の場合は West US 2 なので、 westus2.api.cognitive.microsoft.com
          //
          // 設定
          // returnFaceId true
          // recognitionModel detection_02
          // detectionModel detection_02
          const FACE_API_ENDPOINT_URL = 'https://westus2.api.cognitive.microsoft.com/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=false&recognitionModel=recognition_02&returnRecognitionModel=false&detectionModel=detection_02';

          // サブスクリプションをOcp-Apim-Subscription-Keyヘッダーに
          // JSONで送るのでContent-typeヘッダーにapplication/octet-stream指定
        
          const config = {
            url: FACE_API_ENDPOINT_URL,
            method: 'post',
            headers: {
              'Content-type': 'application/octet-stream',
              'Ocp-Apim-Subscription-Key':'Key'
            },
            data: contentBuffer
          };
 
          // axios
          try {
              // POSTリクエストで送る
              const responseAzure = await axios.request(config);
              console.log('post OK');
              // データ送信が成功するとレスポンスが来る
              console.log(responseAzure.data);
              //
              this.response = responseAzure.data;
          } catch (error) {
              console.log('post Error');
              // ダメなときはエラー
              console.error(error);
          }

        }

      }
      ,
      mounted() {
        console.log('mounted');

        // ビデオのリアルタイムキャプチャを canvas に送る
        this.video = this.$refs.video;
        if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
          navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
            this.video.srcObject = stream
            this.video.play()
          })
        }
      }
    })
  </script>
</body>

</html>

動かしてみる

実際に動かしてみます。

image

このように、カメラがキャプチャされていて、Snap Photo ボタンをクリックします。

image

Captured Data に撮影されて、無事、私と息子の2人の顔が判定されました。