teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

1

音声認識のサンプルを追加

2020/03/22 23:25

投稿

TsukubaDepot
TsukubaDepot

スコア5086

answer CHANGED
@@ -10,4 +10,229 @@
10
10
 
11
11
  いろいろ試した感じ、一区切りの発話で認識させる方法(一旦認識を終了させる方法)は不可能ではなさそうです。
12
12
 
13
- ただ、運悪く認識できない場合は現在の結果がその後の認識結果に影響を与えてしまうので、それをどのようにして防ぐか考える必要がありそうです。
13
+ ただ、運悪く認識できない場合は現在の結果がその後の認識結果に影響を与えてしまうので、それをどのようにして防ぐか考える必要がありそうです。
14
+
15
+
16
+ ---
17
+
18
+ 決して褒められた例ではないと思いますが、ひとまとまりの発話で認識を完了させるようなサンプルコードをつくってみました。
19
+
20
+ [Qiitaの記事](https://qiita.com/chino_tweet/items/027c432cfb983f95679a)を参考にしています。
21
+
22
+ [Qiitaの記事](https://qiita.com/chino_tweet/items/027c432cfb983f95679a)を含め、多くの例題が認識完了後の処理をクロージャで行っていますが、それだとうまくいかない処理もあるので、この例では delegate で処理させています。
23
+
24
+ 「ひとまとまりで処理を完了する」というメソッドはないので、かわりに「もっともらしさ(confidence)」が0.94を超えた場合には認識を中断するようにしています。
25
+
26
+ これでもそこそこ認識してくれますが、**問題は「もっともらしさ(confidence)が0.94以下の場合には認識を中断せず、認識を継続する」点**にあります。
27
+
28
+ なにが問題かと言うと、「こんにちは」と発話しても認識に成功せず、その後「おはようございます」と続けて発話した場合、2回目に発話した「おはようございます」ではなく、**1回目の発話である「こんにちは」も含め、「こんにちはおはようございます」という一つの発話として認識を行なってしまう**点です。
29
+
30
+ 当然このような場合、「もっともらしさ(confidence)」はどんどん下がる一方なので、まずその後認識に成功することはありません。
31
+
32
+ なので、ある一定時間認識に成功しなかった場合には、強制的に認識を中断する(Amazon Alexaであれば
33
+ ごめんなさい。認識できませんでした」の状態)方法を実装する必要があります(`SFSpeechRecognizer`は無音性が1分続くと認識を中断しますが、それ以外の方法が必要だと言う意味です)。
34
+
35
+ ```swift
36
+ import UIKit
37
+ import Speech
38
+
39
+ class ViewController: UIViewController,SFSpeechRecognizerDelegate, SFSpeechRecognitionTaskDelegate {
40
+
41
+ //UIラベルの変数
42
+ @IBOutlet weak var textView: UITextView!
43
+ @IBOutlet weak var recordButton: UIButton!
44
+ //UIボタンの変数
45
+ @IBAction func recordButton(_ sender: UIButton) {
46
+ if audioEngine.isRunning {
47
+ // 音声エンジン動作中なら停止
48
+ audioEngine.stop()
49
+ recognitionRequest?.endAudio()
50
+ recordButton.isEnabled = false
51
+ recordButton.setTitle("Stopping", for: .disabled)
52
+ recordButton.backgroundColor = UIColor.lightGray
53
+ return
54
+ }
55
+ // 録音を開始する
56
+ try! startRecording()
57
+ recordButton.setTitle("認識を完了する", for: [])
58
+ recordButton.backgroundColor = UIColor.red
59
+ }
60
+ //メンバプロパティでタスクのオブジェクトを宣言
61
+ private var recognitionTask: SFSpeechRecognitionTask?
62
+ //認識リクエストのインスタンスを宣言
63
+ private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
64
+
65
+ //SFSpeechRecognizerインスタンスを生成
66
+ //日本語に指定
67
+ private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
68
+
69
+ //端末のマイクを使う準備
70
+ private let audioEngine = AVAudioEngine()
71
+
72
+ override func viewDidLoad() {
73
+ super.viewDidLoad()
74
+ // Do any additional setup after loading the view.
75
+
76
+ //マイクの許可をデフォルトでは無効にしておく
77
+ recordButton.isEnabled = false
78
+ }
79
+
80
+ // 画面に表示される直前に呼ばれます。
81
+ override func viewWillAppear(_ animated: Bool) {
82
+ speechRecognizer.delegate = self // デリゲート先になる
83
+ SFSpeechRecognizer.requestAuthorization { (status) in
84
+ OperationQueue.main.addOperation {
85
+ switch status {
86
+ case .authorized: // 許可OK
87
+ self.recordButton.isEnabled = true
88
+ self.recordButton.backgroundColor = UIColor.blue
89
+ case .denied: // 拒否
90
+ self.recordButton.isEnabled = false
91
+ self.recordButton.setTitle("録音許可なし", for: .disabled)
92
+ case .restricted: // 限定
93
+ self.recordButton.isEnabled = false
94
+ self.recordButton.setTitle("このデバイスでは無効", for: .disabled)
95
+ case .notDetermined:// 不明
96
+ fallthrough
97
+ @unknown default:
98
+ self.recordButton.isEnabled = false
99
+ self.recordButton.setTitle("録音機能が無効", for: .disabled)
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ private func startRecording() throws {
106
+ //ここに録音する処理を記述
107
+ //オプショナルバインディング
108
+ if let recognitionTask = recognitionTask {
109
+ // 既存タスクがあればキャンセルしてリセット
110
+ recognitionTask.cancel()
111
+ self.recognitionTask = nil
112
+ }
113
+
114
+ let audioSession = AVAudioSession.sharedInstance()
115
+ try audioSession.setCategory(AVAudioSession.Category.record)
116
+ try audioSession.setMode(AVAudioSession.Mode.measurement)
117
+ try audioSession.setActive(true)
118
+
119
+ //認識開始の前に認識リクエストを初期化
120
+ recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
121
+ guard let recognitionRequest = recognitionRequest else { fatalError("リクエスト生成エラー") }
122
+
123
+ //録音完了前に途中の結果を報告してくれる
124
+ recognitionRequest.shouldReportPartialResults = true
125
+
126
+ //audioEngineインスタンスのinputNodeプロパティを取得する
127
+ let inputNode = audioEngine.inputNode
128
+
129
+ recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, delegate: self)
130
+
131
+
132
+ //マイクからの録音フォーマット
133
+ let recordingFormat = inputNode.outputFormat(forBus: 0)
134
+
135
+ inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
136
+ self.recognitionRequest?.append(buffer)
137
+ }
138
+
139
+ //オーディオエンジンで録音を開始して、テキスト表示を変更する
140
+ audioEngine.prepare() // オーディオエンジン準備
141
+ try audioEngine.start() // オーディオエンジン開始
142
+
143
+ textView.text = "Hello World"
144
+
145
+ }
146
+
147
+ // delegate
148
+ //音声認識機能の状態が変化するタイミングで呼ばれる
149
+ //録音ボタンの有効と無効を切り替える
150
+ public func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
151
+ if available {
152
+ // 利用可能になったら、録音ボタンを有効にする
153
+ recordButton.isEnabled = true
154
+ recordButton.setTitle("Start Recording", for: [])
155
+ recordButton.backgroundColor = UIColor.blue
156
+ } else {
157
+ // 利用できないなら、録音ボタンは無効にする
158
+ recordButton.isEnabled = false
159
+ recordButton.setTitle("現在、使用不可", for: .disabled)
160
+ }
161
+ }
162
+
163
+ // delegate
164
+ func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishSuccessfully successfully: Bool) {
165
+ print(#function)
166
+ print("isCanceled: (task.isCancelled)")
167
+ // 認識された場合には successfullt は true になる
168
+ // 1分経って認識がキャンセルされた場合には、successfully は false になる
169
+ print("successfully: (successfully)")
170
+
171
+ audioEngine.stop()
172
+ audioEngine.inputNode.removeTap(onBus: 0)
173
+
174
+ recognitionRequest = nil
175
+ recognitionTask = nil
176
+
177
+ recordButton.isEnabled = true
178
+ recordButton.setTitle("Start Recording", for: [])
179
+ recordButton.backgroundColor = UIColor.blue
180
+
181
+ // 強制的に再開させるのであれば、ここにコードを記述する
182
+ // 録音を開始する
183
+ try! startRecording()
184
+ recordButton.setTitle("認識を完了する", for: [])
185
+ recordButton.backgroundColor = UIColor.red
186
+ }
187
+
188
+ func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishRecognition recognitionResult: SFSpeechRecognitionResult) {
189
+ print(#function)
190
+ }
191
+
192
+ func speechRecognitionDidDetectSpeech(_ task: SFSpeechRecognitionTask) {
193
+ print(#function)
194
+ }
195
+
196
+ func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didHypothesizeTranscription transcription: SFTranscription) {
197
+ print(#function)
198
+
199
+ switch task.state {
200
+ case .completed:
201
+ // 完全に終了
202
+ print("完全に終了")
203
+ case .finishing:
204
+ // 録音は完了したが、認識は継続している
205
+ print("認識継続中")
206
+ case .canceling:
207
+ // キャンセル中
208
+ print("キャンセル中")
209
+ case .running:
210
+ // 認識中
211
+ print("認識中")
212
+ case .starting:
213
+ // 開始
214
+ print("開始")
215
+ @unknown default:
216
+ // 想定外
217
+ fatalError()
218
+ }
219
+
220
+ // 認識中に呼ばれる
221
+ if transcription.segments[0].confidence > 0.94 {
222
+ // This property reflects the overall confidence in the recognition of the entire phrase. The value is 0 if there was no recognition, and it is closer to 1 when there is a high certainty that a transcription matches the user's speech exactly. For example, a confidence value of 0.94 represents a very high confidence level, and is more likely to be correct than a transcription with a confidence value of 0.72.
223
+ if audioEngine.isRunning {
224
+ dump(transcription)
225
+ print(transcription.formattedString)
226
+ textView.text = transcription.formattedString
227
+
228
+ // 音声エンジン動作中なら停止
229
+ audioEngine.stop()
230
+ recognitionRequest?.endAudio()
231
+ recordButton.isEnabled = false
232
+ recordButton.setTitle("Stopping", for: .disabled)
233
+ recordButton.backgroundColor = UIColor.lightGray
234
+ }
235
+ }
236
+ }
237
+ }
238
+ ```