スマートフォンARのUI/UXで、Voice User Interface (VUI) に言及される文脈が増えています。
参考:
- [スマホARでユーザはどう情報を操作すべきか?|タッチ操作からの脱却とVUIの可能性] (https://note.mu/ar_ojisan/n/n0322d9bbf8ef)
- なぜ、声が拡張現実インターフェイスへと進化を遂げるのか? あるいは、音声UIがなぜ主流になるのか?
- 音声インターフェイスについて思う7つのこと
スマートフォンの**「小さい面に表示された遅延した映像+AR表示」**の世界観から脱却した、より自然な操作系が出てくるのは当然の流れのように思えます。
今回、2018年においてiOSアプリで ARKit + VUI の実装を説明していきます。
先日のYahoo! HACK DAY で同僚とこんな物を作りました。喋った単語が3Dオブジェクトに。SwiftでのiOSネイティブアプリ。ARKit、SceneKit、Google Speech API、Natural Language API, Poly API pic.twitter.com/8VPrG3BGRi
— satoshi0212 (@shmdevelop) 2017年12月11日
参考: Google Speech APIとARKitとPolyと組み合わせた例(2017年)
VUIについて
VUIの概要についてはこちらの資料が詳しいです。 Voice UI Designer Meetup Tokyo 「 VUIデザインの勘所」
Leszek Zawadzki 氏の提唱する会話の6分類は要チェック。
(引用: Voice UI Designer Meetup Tokyo 「 VUIデザインの勘所」)資料内で言及のある Leszek Zawadzki 氏のエントリ。
Conversational UI Principles — Complete Process of Designing a Website Chatbot
今回はこの概念にすべて対応した実装まではせず Core
が理想の状態で渡される想定です。
追記
Super Developers Reveal Tips to Create Great Clova Skills and Three Things to Know About VUI Future
音声認識について
iOS10からApple公式のSFSpeechRecognizer
が使用可能ですが、制限もあります。
(要最新情報確認)
[iOS 10] SFSpeechRecognizerで音声認識を試してみた
- 端末ごとの音声認識回数には制限がある
- アプリごとにも認識回数に制限がある
- バッテリーとネットワークに関してハイコスト
- 一回のディクテーション時間のMaxは1分
2016年のエントリと少し古いですが、その他のiOSでの音声認識機能実装はこちらでまとめてあります。 参考までに。iOSアプリでの音声認識機能実装方法まとめ
今回は商用利用可能かつ精度が高い Googleの Cloud Speech API
を利用してみます。
iOSアプリで使うならば SFSpeechRecognizer
との2択だと思います。
登録
細かい登録手順はここでは省略します。
Cloud Speech API
を有効化してください。
iOSアプリからの呼び出しに際し API Key
が必要です。
実装
音声ファイルを生成し送信する方法とストリームで送受信する方法があります。今回は処理がシンプルな前者の方法のみ説明します。ストリーム送受信を含んだサンプルを置いてあるので参考にしてみてください。
GoogleSpeechRecognizerSample
(注意: info.plistにマイク使用許可を足さないと起動時に落ちるかも!)
音声ファイルを保存
主な部分を抜粋します。
private var audioRecorder: AVAudioRecorder?
private var soundFilePath: String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask , true)
guard let path = paths.first else { return "" }
return path.appending("/sound.caf")
}
func record() {
audioRecorder?.record(forDuration: 15)
}
音声ファイルを送信し結果取得
func soundFileToText() {
let service = "https://speech.googleapis.com/v1/speech:recognize?key=\(API_KEY)"
let data = try! Data(contentsOf: URL(fileURLWithPath: soundFilePath))
let config: [String: Any] = [
"encoding": "LINEAR16",
"sampleRateHertz": "16000",
"languageCode": "ja-JP",
"maxAlternatives": 1]
let audioRequest = ["content": data.base64EncodedString()]
let requestDictionary = ["config": config, "audio": audioRequest]
let requestData = try! JSONSerialization.data(withJSONObject: requestDictionary, options: [])
let request = NSMutableURLRequest(url: URL(string: service)!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = requestData
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data, resp, err) in
if let data = data, let json = try! JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
let results = (json["results"] as? [[String: Any]])
if let first = results?.first, let alternatives = first["alternatives"] as? [[String: Any]] {
if let alternativesFirst = alternatives.first, let str = alternativesFirst["transcript"] as? String {
DispatchQueue.main.async {
AudioRESTController.sharedInstance.delegate.doneAnalyze([str])
}
}
}
}
})
task.resume()
}
APIのレスポンスから認識された文字列を取得することができました。
ARKitと音声認識を組み合わせる
では ARKit
と音声認識を合体させてみましょう。
今回は、色の異なる3つの球体を音声で操作してみます。
オブジェクト操作コマンド
処理対象指定、命令、パラメータを含む発言をひとかたまりとして処理します。
「青いターゲットを回転」
「緑のターゲットを拡大」
「赤いターゲットを下に移動」
今回は3つの発言をそれぞれ別個に行いました。
音声認識結果から要素を取得します。
このあたりは非常に簡易的な実装をしています。
enum VUICommand {
case noMatch
case objectRotate
case objectMove
case objectScaleUp
case objectScaleDown
case sendToSlack
}
struct MoveParameter {
var isUp: Bool
var moveAmount: CGFloat {
return isUp ? 0.08 : -0.08
}
}
private func findCommand(str: String) -> VUICommand {
if str.contains("回転") {
return .objectRotate
} else if str.contains("移動") {
return .objectMove
} else if str.contains("拡大") {
return .objectScaleUp
} else if str.contains("縮小") {
return .objectScaleDown
} else if str.lowercased().contains("slack") {
return .sendToSlack
}
return .noMatch
}
private func findTarget(str: String) -> SCNNode? {
if str.contains("赤") {
return sphereNodes[0]
} else if str.contains("緑") {
return sphereNodes[1]
} else if str.contains("青") {
return sphereNodes[2]
}
return nil
}
private func findMoveParameter(str: String) -> MoveParameter {
if str.contains("下") {
return MoveParameter(isUp: false)
}
return MoveParameter(isUp: true)
}
取得した要素をもとに表示処理を実行します。
private func processCommand(str: String) {
let command = findCommand(str: str)
let target = findTarget(str: str)
switch command {
case .objectRotate:
target?.runAction(SCNAction.rotateBy(x: 0, y: .pi * 2, z: 0, duration: 3))
case .objectMove:
let parameter = findMoveParameter(str: str)
target?.runAction(SCNAction.moveBy(x: 0, y: parameter.moveAmount, z: 0, duration: 0.3))
case .objectScaleUp:
target?.runAction(SCNAction.scale(by: 2.0, duration: 0.3))
case .objectScaleDown:
target?.runAction(SCNAction.scale(by: 0.5, duration: 0.3))
case .sendToSlack:
sendToSlack(str)
case .noMatch:
break
}
}
Slack送信コマンド
Slackに送信してみます。
「Slackにテスト送信」
音声操作により、オブジェクト操作とは全く別の処理を続けて実行できることがわかると思います。
private func sendToSlack(_ sourceStr: String) {
let service = "https://hooks.slack.com/services/[web hook 対象 URL]"
let requestDictionary: [String: Any] = [
"channel": "#bots",
"username": "webhookbot",
"text": "ARKit VUI Demo: \(sourceStr)"]
let requestData = try! JSONSerialization.data(withJSONObject: requestDictionary, options: [])
let request = NSMutableURLRequest(url: URL(string: service)!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = requestData
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data, resp, err) in
if let data = data {
print(data)
}
})
task.resume()
}
実行
無事実行されました!
今回は任意に配置したオブジェクトを音声コマンドで操作しました。
画面にタップして対象を選択するのではなく、音声でターゲットと処理内容を指定し実行。
この時点で「画面の奥の方にある小さいオブジェクトを狙ってタップできない」ことや「処理を一覧から選んで行わなければならない」などいくつかの課題を解決している状態です。
現実からオブジェクト抽出し実行するようにすればより本質的な価値が出るでしょう。
今回のサンプルはこちらにアップしています。良かったら参考にしてみてください。
ARKitVUISample