MicrosoftTranslatorAPI
GoogleCloudVisionAPI
けものフレンズ

Google Cloud Vision APIとMicrosoft Translator APIを使って、ラッキービーストに物体認識させるようにしたよ

More than 1 year has passed since last update.

iPhoneのSFSpeechRecognizerとAVSpeechSynthesizerと発泡スチロールでボスっぽいなにかを作るの続き


概要

前回作ったボスっぽいなにかに、Google Cloud Vision APIによるラベリングを組み込んで、目の前にある物体について説明してくれるようにしたよ

system2.png

前回は、「○○ってなんですか?」と聞くと「○○」についてWikipediaで調べてサマリーを話してくれるだけでしたが、今回はフロントカメラとGoogle Cloud Vision APIを使って、目の前にある物体について説明してくれるようにします。


プログラムの流れ

プログラムとしての流れは下記のような感じになります。


  1. SFSpeechRecognizerで、音声認識をする

  2. 「これなんですか?」的なフレーズが入力されたら、フロントカメラで撮影する

  3. Google Cloud Vision APIを使って、撮影した画像にラベリングをする(写真に写っている物体の名前を取得する)

  4. 検出された物体の名前をMicrosoft Translator APIを使って日本語に変換する

  5. 変換された単語をWikipediaで調べて、サマリーを取得する

  6. サマリーをボスっぽい口調に変換する

  7. AVSpeechSynthesizerで音声合成して喋らせる

プログラム全体で見ると、初期化や設定、エラーハンドリング、状態管理などの分量が増え複雑になってきていますが、各ステップの処理自体は極めてシンプルなので、ここでは各ステップの処理の部分だけ切り出して説明します。

プログラム全体は、週末あたりにリファクタリングしてからGitHubで公開しようと思ってます。


Google Cloud Vision APIによるラベリング

フロントカメラに写った物体を認識する部分については、Google Cloud Vision APIを使います。レイテンシーや値段の問題はありますが、プロトタイプを作る上ではTensorFlowなどを自前で組み込むより圧倒的に楽です。


画像を送ってラベリングさせる

Google Cloud Vision APIの使い方は至って簡単です。

を指定して、POSTでリクエストをするだけです。

func detectObjects(in image: UIImage) {

let request: Parameters = [
"requests": [
"image": [
"content": image.base64String
],
"features": [
[
"type": "LABEL_DETECTION",
"maxResults": 10
]
]
]
]

let httpHeader: HTTPHeaders = [
"Content-Type": "application/json",
"X-Ios-Bundle-Identifier": Bundle.main.bundleIdentifier ?? ""
]

Alamofire.request("https://vision.googleapis.com/v1/images:annotate?key=\(googleAPIKey)", method: .post, parameters: request, encoding: JSONEncoding.default, headers: httpHeader).validate(statusCode: 200..<300).responseJSON { response in
switch response.result {
case .success(let json):
if let dictionary = json as? [AnyHashable: Any], let response0 = (dictionary["responses"] as?[[AnyHashable: Any]])?.first, let labelAnnotations = response0["labelAnnotations"] as? [[AnyHashable: Any]], let firstDescription = labelAnnotations[0]["description"] as? String {
debugPrint(labelAnnotations)
} else {
debugPrint("Error. No respo")
}
case .failure(let error):
debugPrint(error)
}
}
}

extension UIImage {

var base64String: String {
var imagedata = UIImagePNGRepresentation(self)
// 必要に応じて、リサイズする...
return imagedata!.base64EncodedString(options: .endLineWithCarriageReturn)
}
}

するとこんな感じでJSONでレスポンスが返ってきます。最大でmaxResultsで指定した数だけ、画像内に含まれる物体のラベルが返ってきます。

{

"responses": [
{
"labelAnnotations": [
{
"mid": "/m/0bt9lr",
"description": "dog",
"score": 0.97346616
},
{
"mid": "/m/09686",
"description": "vertebrate",
"score": 0.85700572
},
{
"mid": "/m/01pm38",
"description": "clumber spaniel",
"score": 0.84881884
},
{
"mid": "/m/04rky",
"description": "mammal",
"score": 0.847575
},
{
"mid": "/m/02wbgd",
"description": "english cocker spaniel",
"score": 0.75829375
}
]
}
]
}

問題は、返ってきた複数のラベルのうち、どれを使うかということなのですが、今回は最初のプロトタイプなので一番最初の要素(一番確度の高い要素)を使っています。

ただ、そうするとなかなか狙った通りの物体が選ばれないので、汎用的なワードをフィルタリングしたり、画像の中央からの距離などで重み付けをするなど、「どのラベルを選ぶか」のチューニングが今後一つ肝になりそうです。


撮影のタイミング

なお、前回作ったSFSpeechRecognizerによる音声認識で「これ〜なに?」みたいなパターンを検出した場合に、撮影+API呼び出しが行われるようにしています。


Microsoft Translator APIで翻訳する

Google Cloud Vision APIでラベリングされた結果は英語ですが、ボスは日本語で喋らせたいので、日本語に変換する必要があります。今回は一定量まで無料で使えるMicrosoft Translator APIを使いました。


Microsoft Translator APIの概要と流れ


  • Microsoft Translator APIを使うためには、Azureに登録をする必要があります。公式の手順にしたがって、サブスクリプションを追加します。


トークンの取得


  • 翻訳のAPIを叩くためにはトークンが必要。

  • トークンは、アプリごとに割り当てられるSubscription Keyを使って取得できる。(POSTのURLを叩くと、レスポンスでトークンが返ってくる)

  • トークンは10分で失効してしまうので、10分以上間が空いてしまう場合は取得し直す必要がある。


トークンを使って翻訳


  • 取得したトークンをヘッダーにつけてAPIを叩くと、翻訳結果が返ってくる(XMLで)

  • XMLをパースして、翻訳結果を取得する。


実装

HTTP通信用にAlamofireを、レスポンスのXMLのパース用にSWXMLHashを使っています。

本当は、これに加えてトークン取得失敗時や失効時のハンドリングが必要になります。

import Alamofire

import SWXMLHash

let text = inputTextField.text!
let headers = ["Ocp-Apim-Subscription-Key": "YOUR APP KEY"]

Alamofire.request("https://api.cognitive.microsoft.com/sts/v1.0/issueToken", method: .post, headers: headers).responseString { (response) in
switch response.result {
case .success(let str):
Alamofire.request("https://api.microsofttranslator.com/v2/Http.svc/Translate", method: .get, parameters: ["text": text, "to": "ja"], headers: ["Authorization": "Bearer \(str)"]).responseString(completionHandler: { (response) in
switch response.result {
case .success(let str):
let xml = SWXMLHash.parse(str)
self.outputLabel.text = xml["string"].element?.text
case .failure(let error):
debugPrint(error)
}
})
case .failure(let error):
debugPrint(error)
}
}


Wikipediaで調べて口調変換して、発声させる

ここから先は前回の実装と同じなので、省略します。iPhoneのSFSpeechRecognizerとAVSpeechSynthesizerと発泡スチロールでボスっぽいなにかを作る を読んでね!


出来上がったもの


ラッキービーストをアップデートした。Google Cloud Vision APIを使って、見せたものについて説明できるようになった。まだ精度は低いけれども。 #けものフレンズ pic.twitter.com/RrNULicbON

— Sousai (@croquette0212) 2017年4月16日


まとめと今後の展望

物体認識を初めて使って見て、何かと勉強になりました。


  • まず、Google Cloud Vision APIを使うと、導入が非常に簡単であること。

  • 当然ながら、期待したようなワードで検出されるわけではないこと。

  • 物体名は英語でラベリングされるため、日本語に翻訳する必要があるが、そこでさらに情報量が失われてしまうこと。

ボス(ラッキービースト)は、本来はパークガイドとして動物のことを説明するロボットですが、これを高い精度で実現するためには、最終的には「日本語で動物をラベリングできる」学習済みデータセットが必要になってくるな、ということが見えてきました。

ラベリングの処理自体は、TensorFlowを使ってiPhone上で行うのは難しくなさそうですが、学習データを作るのは流石に難易度が高すぎるので、どうしようか悩ましいところです。

まあ、まずは今回のプロトタイプを元に、色々調整をしてクオリティーを上げていきたいと思います。ガワもなんとかしたいし、やっぱり耳を動かしたり歩かせたりしたいなー。


参考資料