Showcase Gig Blog

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上に設定しておきます。
20160711_speechtotext_6

3. iOSアプリの実装

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

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

20160711_speechtotext_1

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

20160711_speechtotext_2

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

20160711_speechtotext_3

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

20160711_speechtotext_4

4. コーディング

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

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) { }
    }
}

5. 動作確認

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

6. 参考

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

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中