【Azure】Custom Vision Service をPythonで叩く

# はじめに

`Custom Vision Service` は Microsoft Azure の製品の一つとして提供されている画像認識サービスです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134943/069fbf6d-9d58-a908-e65c-19a1e2a6735f.png)

こんな感じで画像の種類を示すタグと、それに紐づく画像を何枚かアップロードして学習させておくことで、ある画像がどのタグにどれだけ近いかを教えてくれるサービスです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134943/e8a1e4cc-fe37-e20d-1b56-4bf414faa665.png)

上の例では、バイオリン、オーボエ、チューバを学習した上で少し構図の違うバイオリンの写真をアップロードした結果、99.9%の確率でバイオリンの写真であることが認識されています。

今回は、このサービスのAPI（のうちのひとつ）を、Pythonから実行してみようと思います。

# 全体の流れ

このサービスを利用する大まかな流れとしては、

1. 学習用の画像を、タグをつけて登録する
2. 学習を実行する
3. 判別させる画像をアップロードし、結果を取得する

という非常にシンプルなものになっていて、僕のような機械学習や画像処理を全くしらない人でも何となくで簡単に動かせる仕組みになっています。
このうち、1, 2と、3をブラウザ上で実行する方法については以下の記事が非常に参考になりましたので、そちらを参照していただければと思います。

[Custom Vision Serviceを使ってみた - Qiita](http://qiita.com/07JP27/items/55331da311544af1bb95)

この記事では、3 の部分をPythonで実行する方法について記載します。

# 本題

と言っても、基本的にはリファレンス通りにプログラムを書いて実行するだけです。

[Overview - Custom Vision Service](https://docs.microsoft.com/en-us/azure/cognitive-services/custom-vision-service/home)

こちらのページの、左メニュー "Reference" > "Custom Vision Prediction API Reference" を開くと、APIの仕様と一緒に、Testing Consoleと、各プログラミング言語によるサンプルコードが載っているページが表示されます。

今回は　Python を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134943/4af12f76-311c-10cf-5a90-23e4430ca9cc.png)

表示されるコードの上半分は 2.X のコード、下半分は 3.x のコードです。
自分の環境に合わせたものをコピーしてpythonファイルを作成します。（今回は3.x で進めます）

```py3:customvisionapi.py
import io, http.client, urllib.request, urllib.parse, urllib.error, base64

headers = {
    # Request headers
    'Content-Type': 'multipart/form-data',
    'Prediction-key': 'xxxxxxxxxxxxxxxxxxxxxxxx',
}

params = urllib.parse.urlencode({
    # Request parameters
    'iterationId': 'xxxxxxxxxxxxxxxxxxxxxxxx',
})

try:
    conn = http.client.HTTPSConnection('southcentralus.api.cognitive.microsoft.com')
    f = open("violin_a.jpg", "rb", buffering=0)
    conn.request("POST", "/customvision/v1.0/Prediction/xxxxxxxxxxxxxxxxxxxxxxxx/image?%s" % params, f.readall(), headers)
    response = conn.getresponse()
    data = response.read()
    print(data)
    conn.close()
except Exception as e:
    # print("[Errno {0}] {1}".format(e.errno, e.strerror))
    print(e)
```

ここで、`xxxxxxxxxxxxxxxxxxxxxxxx`で記載した各パラメータは以下より確認できます。

* __Prediction-key__
    * Custom Vision Service コンソールの設定画面内の "Prediction Key:" 欄
* __iterationId__
    * コンソールの "PERFORMANCE" 画面内、左上の "Prediction URL" をクリックして表示されるURLの末尾、 "iterationId" パラメータの後ろの文字列
* __ProjectID__
    * コンソール画面自体のURLの "projects/" より後ろの部分

もしかしたらもっと良い調べ方があるかもしれませんが、ぱっと見つかったのが上記でした。

それぞれ値をセットしたら、あとは比較用のファイルをPythonファイルと同じディレクトリに置いて実行するだけです。

```bash
$ python customvisionapi.py
b'{"Id":"bxxxxxxxxxxxxxxxxxxxxxxxx","Project":"xxxxxxxxxxxxxxxxxxxxxxxx","Iteration":"xxxxxxxxxxxxxxxxxxxxxxxx","Created":"2017-06-06T13:59:23.5590916Z","Predictions":[{"TagId":"8fe34be4-eeff-495b-a83f-2a74bd25f035","Tag":"instrument","Probability":0.9999934},{"TagId":"bd2281d4-e4ff-48f1-a9ab-d525277479f9","Tag":"violin","Probability":0.9987756},{"TagId":"f33cdfdd-7eb2-47a2-8a30-2162a8f9e7fa","Tag":"oboe","Probability":3.22442238E-22},{"TagId":"b1490919-c0ab-4771-9564-13752bcfb96c","Tag":"tuba","Probability":7.720858E-24}]}'
```
結果がでましたね。
そのままでは若干分かりづらいですが、タグごとに `Probability` データが返却され、 `Violin` タグの `Probability` が　`0.9999934`　と高い数値になっているのが分かるかと思います。

これで、プログラムから画像認識をさせることができるようになりました！
IoTデバイスに組み込んだり、`Django` などのWebアプリケーションと合わせたりと、夢が広がりますね。

# `BadRequestImageFormat` が出る！

さて、以上で手順は終わりではあるのですが、僕がこれを試した際、`BadRequestImageFormat` が出て結果が取得できない現象に1, 2時間ほど悩まされましたので、解決策をメモします（原因ははっきり分かっていません、、、）

```bash
$ python customvisionapi.py
b'{"Code":"BadRequestImageFormat","Message":""}'
```

ちなみにこれをそのままGoogle先生に聞いても、なにそれ知らないと言われるばかりでした。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/134943/f031e4f4-1f50-72e3-bf1a-c9819206db43.png)

というわけで以下、解決方法です。

先ほどのサンプルコードですが、実はドキュメント上では画像ファイルをリクエストボディに貼り付ける部分のコードが `{body}` と書かれるのみで省略されています。

```py3
conn.request("POST", "/customvision/v1.0/Prediction/{projectId}/image?%s" % params, "{body}", headers)
```

最初はこの部分を 

```py3
imagefile = open("./violin_a.jpg", "rb")
conn.request("POST", "/customvision/v1.0/Prediction/{projectId}/image?%s" % params, imagefile, headers)
```

というように `open()` メソッドを使ってやってみていたのですが、どうしてもダメでした。

で、[ioモジュールのレファレンス](https://docs.python.jp/3/library/io.html#module-io)なんかを見ながら試行錯誤しているうちに、以下を気をつけることでエラーなくAPIを叩けることが分かりました。

* `open()` の第３引数に `buffering=0` を入れること
* ファイルオブジェクトの `readall()` メソッドの戻り値をリクエストボディに渡すこと
* ~~`png` ではなく `jpg` 画像を使うこと（！）~~

__（2017.06.25 修正）
PNG画像でうまく動作していなかったのは、私が動作確認に利用していたPNGファイル自体の問題でした。
同じソースコードで、PNG画像でも問題なく結果を取得できることが確認できましたので、修正いたします。
（/修正）__

どれも意味は分かっていないのですが、とりあえずこれで動いた、ということで、ひとまず記載しておきます。
（なお、Python2.xの場合は、3つ目jpg以外は関係なく動いていました。なんのこっちゃ。）

結果、動作したプログラムは本文の方で掲載した通りです。

# まとめ

以上です。
Azureの各サービスは、まだまだ情報が少ない不便はあるものの、何かに活用できそうなAPIがいくつも提供されている印象です。2万円分の無料枠もありますので、今後もいろいろと試してみようと思います。