getUserMedia と連携させて Computer Vision API を使ってみた

マイクロソフトの画像認識APIの Computer Vision API を試してみた。API そのものより

  • 「getUserMedia で動画を得る」->「スナップショットを取る」->「APIへアップロード」

って部分が勉強になったのでメモっておく。

Computer Vision APIとは

  • Microsoftの画像認識API (site)
  • REST で画像を投げると JSON 形式で画像を分析した結果を返してくれる
  • 画像の説明文、画像になにが写ってるか、人物の年齢や性別、顔の座標、著名人の名前、有名な建物の名前、などを返してくれる

得られるJSONデータ

例えば以下のような画像をAPIに投げると...

a boy

次のようなJSONが返ってくる

{
  "categories": [
    {
      "name": "others_",
      "score": 0.0078125
    },
    {
      "name": "outdoor_",
      "score": 0.0078125,
      "detail": {
        "landmarks": []
      }
    },
    {
      "name": "people_",
      "score": 0.30859375,
      "detail": {
        "celebrities": []
      }
    }
  ],
  "adult": {
    "isAdultContent": false,
    "isRacyContent": false,
    "adultScore": 0.0168526079505682,
    "racyScore": 0.023264266550540924
  },
  "tags": [
    {
      "name": "outdoor",
      "confidence": 0.9941964149475098
    },
    {
      "name": "road",
      "confidence": 0.9823024868965149
    },
    {
      "name": "ground",
      "confidence": 0.9675083756446838
    },
    {
      "name": "boy",
      "confidence": 0.920303225517273
    },
    {
      "name": "person",
      "confidence": 0.9178870320320129
    },
    {
      "name": "young",
      "confidence": 0.803716778755188
    }
  ],
  "description": {
    "tags": [
      "outdoor",
      "road",
      "boy",
      "person",
      "child",
      "young",
      "little",
      "small",
      "street",
      "riding",
      "sidewalk",
      "man",
      "holding",
      "standing",
      "side",
      "baby",
      "wearing",
      "walking",
      "board",
      "fire",
      "bed",
      "hydrant"
    ],
    "captions": [
      {
        "text": "a little boy that is standing on the side of a road",
        "confidence": 0.8269637487808089
      }
    ]
  },
  "requestId": "d7387e20-3e79-4b9b-b5b0-2eb967a71c7e",
  "metadata": {
    "width": 1600,
    "height": 1200,
    "format": "Jpeg"
  },
  "faces": [
    {
      "age": 6,
      "gender": "Male",
      "faceRectangle": {
        "left": 777,
        "top": 216,
        "width": 127,
        "height": 127
      }
    }
  ],
  "color": {
    "dominantColorForeground": "Grey",
    "dominantColorBackground": "Grey",
    "dominantColors": [
      "Grey",
      "White"
    ],
    "accentColor": "436737",
    "isBWImg": false
  },
  "imageType": {
    "clipArtType": 0,
    "lineDrawingType": 0
  }
}

競合サービス

MicrosoftのAPIサービス郡での位置づけ

  • Microsoftでは Cognitive Services という名のAPIサービス群を提供している
  • サービスは以下のようにカテゴライズされてる
    • Vision
    • Speech
    • Language
    • Knowledge
    • Search
  • Visionカテゴリは以下で構成される
    • Computer Vision
    • Emotion (感情を推測する)
    • Face (年齢を推測する)
    • Video

利用に際して必要なこと

  • Cognitive Servicesの利用開始登録(要クレジットカード番号)
  • 30日間の無料試用可
  • 各APIサービスの url と Subscription Key を得る(コードに記述する)

注意点

デモを作った

以下内容で動作するデモを作った

  • 画像指定パターン
    • ネットにある画像を指定
    • ローカルの画像をアップロード
    • カメラ(getUserMedia)でキャプチャした動画からスナップショットを取ってアップロード
  • 顔認識した場合は顔の位置を囲み予想年齢を表示する
  • JSONの表示
  • 説明文を Web Speech API で喋らせる
  • mobx + template stringで各UIをモジュール化(らしきことをしたがcssも含め結構ぐだぐだ)

ネットにある画像を指定

const params = [
  'visualFeatures=Categories,Tags, Description, Faces, ImageType, Color, Adult',
  'details=Celebrities, Landmarks',
  'language=en'
].join('&');

fetch('https://westcentralus.api.cognitive.microsoft.com/vision/v1.0/analyze?' + params, {
  method: 'POST',
  body: JSON.stringify({url: imgageUrl}),
  headers: {
    "Content-Type": 'application/json',
    "Ocp-Apim-Subscription-Key": '260f006a16c04453........
  }
}).then(function(response) {
  return response.json();
}).then(json => {...})
  • Content-Type は application/json を指定
  • body は JSON.stringify で文字列変換

画像のアップロード

  • Content-Type は application/octet-stream を指定
  • body には File オブジェクトを指定
const file = document.querySelector('input[type=file]').files[0];
fetch('https://westcentralus.api.cognitive.microsoft.com/vision/v1.0/analyze?' + params, {
  method: 'POST',
  body: file,
  ...
  • 画像のプレビューには FileReader を利用する
const reader = new FileReader();
const dataUrl = reader.readAsDataURL(file);
reader.onload = (file => {
  return event => {
    $img.src = event.target.result;
  };
})(file);

getUserMediaでキャプチャしてアップロード

  • Content-Type は application/octet-stream を指定
  • body は blob に変換した画像を指定

キャプチャ開始

const config = {
  video: true,
  audio: false
};
navigator.mediaDevices.getUserMedia(config).then(stream => {
  $video.src = URL.createObjectURL(stream);
  $video.play();
});

静止画のdataUrlを得る

const ctx = $canvas.getContext("2d");
ctx.drawImage($video, 0, 0, 320, 240);
const dataUrl = $canvas.toDataURL(contentType);

dataUrlをblob形式に変換

const bin = atob(dataUrl.split(',')[1]);
const imageBuffer = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) {
  imageBuffer[i] = bin.charCodeAt(i);
}
const blob = new Blob([imageBuffer.buffer], {type: contentType});

(追記)これでいけるらしい... 便利!

$canvas.toBlob(blob => {
  ...
}, contentType);