Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 3 years have 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上で行うのは難しくなさそうですが、学習データを作るのは流石に難易度が高すぎるので、どうしようか悩ましいところです。

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

参考資料

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away