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 で送るメモ
具体的には
canvas.toBlob
で Canvas のデータをバイナリで取り出す- HTMLCanvasElement.toBlob() – Web API | MDN
- ↑ この中のサンプルソースが分かりやすい
- 上記の記事と同様 FileReader で readAsArrayBuffer を使って ArrayBuffer に変換
- FileReader.onload – Web API | MDN
- FileReader.readAsArrayBuffer() – Web API | MDN
- FileReader.onnload でリスナーを使って取り出しますが、Promiseでくるんで await / async で呼び出せるようにしています。
いやー、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>
動かしてみる
実際に動かしてみます。
このように、カメラがキャプチャされていて、Snap Photo ボタンをクリックします。
Captured Data に撮影されて、無事、私と息子の2人の顔が判定されました。