SHOWCASE GIG BLOG

LIST

2016.07.11

Watson Speech to textをiOSで実装する

こんにちは、ディレクターの永田です。
前回はWatsonのiOS SDKの設定方法の発表をさせていただきました。
それを利用した実装例としてSpeech to textのアプリを作ってみます。
皆様の参考となれば幸いです。

1. Speech to textとは

読んで字のごとくですが、音声データをテキスト化してくれるサービスです。
Google Speech APIやAzure Speechなど他プラットフォームでも同じようなサービスがあるのですが、Watsonの場合下記の特徴があると思います。

  • リアルタイム変換のI/Fがある
    • RESTだけでなく、WebSocketのI/Fもある。
  • 長い時間のコネクションを許してくれる
    • 通常は無音 、またはaudioデータ30秒なしでタイムアウトする。
    • inactivity_timeoutに-1を設定することで、タイムアウトしないようにできる。
      • ※タイムアウトにしないことによる料金の発生はあると思いますので注意してください

2. 準備

アンバインド状態で、Speech to textのサービスをBluemix上に設定しておきます。

3. iOSアプリの実装

Objective-C民にはツラい現実ですが、Swiftで組んでいきます。
これも時代ということで受け入れたいと思います。

(1) まずは適当なプロジェクトを用意してください。

(2) StoryBoardでボタンとTextViewを配置しておきます。Autolayoutも適当に設定してください。

(3) textViewを参照できるようにしておきます。

(4) ボタン動作のコールバックを受けれるようにしておきます。

4. コーディング

IBMさんがGitHubにあげてくれているサンプルソースを少し修正して作ります。

[code lang=text]
import UIKit
import AVFoundation
import SpeechToTextV1

class ViewController: UIViewController {

@IBOutlet weak var streamButto: UIButton!
@IBOutlet weak var transcriptionField: UITextView!

var stt: SpeechToText? = nil
var isStreamingCustom = false
var stopStreamingCustom: (Void -> Void)? = nil
var captureSession: AVCaptureSession? = nil

override func viewDidLoad() {
super.viewDidLoad()
// Bluemix上のSpeech to textのユーザーID、パスワードを設定
self.stt = SpeechToText.init(username: “ユーザーID”, password: “パスワード”)
transcriptionField.endEditing(true)
// Do any additional setup after loading the view, typically from a nib.
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

@IBAction func pushStream(sender: AnyObject) {
// Speech to textにすでに接続済みと時
if (isStreamingCustom) {
captureSession?.stopRunning() // Optional Chainingのアンラップ
stopStreamingCustom?() // Optional Chainingのアンラップ
streamButto.setTitle(“Start Streaming (Custom)”, forState: .Normal)
isStreamingCustom = false
return
}

// Speech to textに接続したかどうかのフラグ
isStreamingCustom = true

// UIButtonの名称を変更
streamButto.setTitle(“Stop Streaming (Custom)”, forState: .Normal)

// Speech to textへ接続した時、nilだったらエラーを表示して終了する。
guard let stt = stt else {
failure(“STT Streaming (Custom)”, message: “SpeechToText not properly set up.”)
return
}

// シミュレータなどマイクが有効でない時はエラーを表示して終了する。
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeAudio)
guard !devices.isEmpty else {
let domain = “swift.ViewController”
let code = -1
let description = “Unable to access the microphone.”
let userInfo = [NSLocalizedDescriptionKey: description]
let error = NSError(domain: domain, code: code, userInfo: userInfo)
failureCustom(error)
return
}

// キャプチャの入出力セッションの作成。エラーだった終了する(多分ほぼない)
captureSession = AVCaptureSession()
guard let captureSession = captureSession else {
return
}

// 入力セッションをマイクと設定する
let microphoneDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)
let microphoneInput = try? AVCaptureDeviceInput(device: microphoneDevice)
if captureSession.canAddInput(microphoneInput) {
captureSession.addInput(microphoneInput)
}

// Speech to textへの出力設定を行う
var settings = TranscriptionSettings(contentType: .L16(rate: 44100, channels: 1))
settings.model = “ja-JP_BroadbandModel”
settings.continuous = true // 無発声0.5秒で終わりとする
settings.inactivityTimeout = -1; // 無制限に接続する
settings.interimResults = true // 変換してすぐに結果を受け取る?フラグ(多分変換が早いがその分不正確)

// Speech to textのオブジェクトからAVCaptureAudioDataOutputを作る
let outputOpt = stt.createTranscriptionOutput(settings, failure: failureCustom) {
results in
self.showResults(results)
}

// access tuple elements
guard let output = outputOpt else {
isStreamingCustom = false
streamButto.setTitle(“Start Streaming (Custom)”, forState: .Normal)
return
}
let transcriptionOutput = output.0
stopStreamingCustom = output.1

// add transcription output to capture session
if captureSession.canAddOutput(transcriptionOutput) {
captureSession.addOutput(transcriptionOutput)
}

// start streaming
captureSession.startRunning()

}

// textviewにテキストを流し込む関数
func showResults(results: [TranscriptionResult]) {
var text = “”

for result in results {
//文章が終わった時の「。」処理
if let transcript = result.alternatives.last?.transcript where result.final == true {
let title = titleCase(transcript)
text += String(title.characters.dropLast()) + “。”
}
}

if results.last?.final == false {
if let transcript = results.last?.alternatives.last?.transcript {
text += titleCase(transcript)
}
}
// テキストを設定
self.transcriptionField.text = text
}

// 英文用に文頭は大文字にする関数
func titleCase(s: String) -> String {
let first = String(s.characters.prefix(1)).uppercaseString
return first + String(s.characters.dropFirst())
}

// エラー時のダイアログ処理
func failure(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let ok = UIAlertAction(title: “OK”, style: .Default) { action in }
alert.addAction(ok)
presentViewController(alert, animated: true) { }
}

// エラー時のダイアログ処理
func failureCustom(error: NSError) {
let title = “Speech to Text Error:\nStreaming (Custom)”
let message = error.localizedDescription
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let ok = UIAlertAction(title: “OK”, style: .Default) { action in
self.streamButto.enabled = true
self.streamButto.setTitle(“Start Streaming (Custom)”,
forState: .Normal)
self.isStreamingCustom = false
}
alert.addAction(ok)
presentViewController(alert, animated: true) { }
}
}
[/code]

5. 動作確認

上記ソースを実行したのが下のgifアニメです。
私の「桃太郎」の記憶が曖昧なせいもあると思いますが、バッチリ変換というわけにはいかないようです。
英語だとまだ精度はいいのかもしれません。
今後の精度アップに期待しましょう。

6. 参考

Speech to text API Reference
watson-developer-cloud/ios-sdk

カテゴリー: GIGの日常BLOG /