回答編集履歴

1

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

2020/03/22 23:25

投稿

TsukubaDepot
TsukubaDepot

スコア5086

test CHANGED
@@ -23,3 +23,453 @@
23
23
 
24
24
 
25
25
  ただ、運悪く認識できない場合は現在の結果がその後の認識結果に影響を与えてしまうので、それをどのようにして防ぐか考える必要がありそうです。
26
+
27
+
28
+
29
+
30
+
31
+ ---
32
+
33
+
34
+
35
+ 決して褒められた例ではないと思いますが、ひとまとまりの発話で認識を完了させるようなサンプルコードをつくってみました。
36
+
37
+
38
+
39
+ [Qiitaの記事](https://qiita.com/chino_tweet/items/027c432cfb983f95679a)を参考にしています。
40
+
41
+
42
+
43
+ [Qiitaの記事](https://qiita.com/chino_tweet/items/027c432cfb983f95679a)を含め、多くの例題が認識完了後の処理をクロージャで行っていますが、それだとうまくいかない処理もあるので、この例では delegate で処理させています。
44
+
45
+
46
+
47
+ 「ひとまとまりで処理を完了する」というメソッドはないので、かわりに「もっともらしさ(confidence)」が0.94を超えた場合には認識を中断するようにしています。
48
+
49
+
50
+
51
+ これでもそこそこ認識してくれますが、**問題は「もっともらしさ(confidence)が0.94以下の場合には認識を中断せず、認識を継続する」点**にあります。
52
+
53
+
54
+
55
+ なにが問題かと言うと、「こんにちは」と発話しても認識に成功せず、その後「おはようございます」と続けて発話した場合、2回目に発話した「おはようございます」ではなく、**1回目の発話である「こんにちは」も含め、「こんにちはおはようございます」という一つの発話として認識を行なってしまう**点です。
56
+
57
+
58
+
59
+ 当然このような場合、「もっともらしさ(confidence)」はどんどん下がる一方なので、まずその後認識に成功することはありません。
60
+
61
+
62
+
63
+ なので、ある一定時間認識に成功しなかった場合には、強制的に認識を中断する(Amazon Alexaであれば
64
+
65
+ ごめんなさい。認識できませんでした」の状態)方法を実装する必要があります(`SFSpeechRecognizer`は無音性が1分続くと認識を中断しますが、それ以外の方法が必要だと言う意味です)。
66
+
67
+
68
+
69
+ ```swift
70
+
71
+ import UIKit
72
+
73
+ import Speech
74
+
75
+
76
+
77
+ class ViewController: UIViewController,SFSpeechRecognizerDelegate, SFSpeechRecognitionTaskDelegate {
78
+
79
+
80
+
81
+ //UIラベルの変数
82
+
83
+ @IBOutlet weak var textView: UITextView!
84
+
85
+ @IBOutlet weak var recordButton: UIButton!
86
+
87
+ //UIボタンの変数
88
+
89
+ @IBAction func recordButton(_ sender: UIButton) {
90
+
91
+ if audioEngine.isRunning {
92
+
93
+ // 音声エンジン動作中なら停止
94
+
95
+ audioEngine.stop()
96
+
97
+ recognitionRequest?.endAudio()
98
+
99
+ recordButton.isEnabled = false
100
+
101
+ recordButton.setTitle("Stopping", for: .disabled)
102
+
103
+ recordButton.backgroundColor = UIColor.lightGray
104
+
105
+ return
106
+
107
+ }
108
+
109
+ // 録音を開始する
110
+
111
+ try! startRecording()
112
+
113
+ recordButton.setTitle("認識を完了する", for: [])
114
+
115
+ recordButton.backgroundColor = UIColor.red
116
+
117
+ }
118
+
119
+ //メンバプロパティでタスクのオブジェクトを宣言
120
+
121
+ private var recognitionTask: SFSpeechRecognitionTask?
122
+
123
+ //認識リクエストのインスタンスを宣言
124
+
125
+ private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
126
+
127
+
128
+
129
+ //SFSpeechRecognizerインスタンスを生成
130
+
131
+ //日本語に指定
132
+
133
+ private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))!
134
+
135
+
136
+
137
+ //端末のマイクを使う準備
138
+
139
+ private let audioEngine = AVAudioEngine()
140
+
141
+
142
+
143
+ override func viewDidLoad() {
144
+
145
+ super.viewDidLoad()
146
+
147
+ // Do any additional setup after loading the view.
148
+
149
+
150
+
151
+ //マイクの許可をデフォルトでは無効にしておく
152
+
153
+ recordButton.isEnabled = false
154
+
155
+ }
156
+
157
+
158
+
159
+ // 画面に表示される直前に呼ばれます。
160
+
161
+ override func viewWillAppear(_ animated: Bool) {
162
+
163
+ speechRecognizer.delegate = self // デリゲート先になる
164
+
165
+ SFSpeechRecognizer.requestAuthorization { (status) in
166
+
167
+ OperationQueue.main.addOperation {
168
+
169
+ switch status {
170
+
171
+ case .authorized: // 許可OK
172
+
173
+ self.recordButton.isEnabled = true
174
+
175
+ self.recordButton.backgroundColor = UIColor.blue
176
+
177
+ case .denied: // 拒否
178
+
179
+ self.recordButton.isEnabled = false
180
+
181
+ self.recordButton.setTitle("録音許可なし", for: .disabled)
182
+
183
+ case .restricted: // 限定
184
+
185
+ self.recordButton.isEnabled = false
186
+
187
+ self.recordButton.setTitle("このデバイスでは無効", for: .disabled)
188
+
189
+ case .notDetermined:// 不明
190
+
191
+ fallthrough
192
+
193
+ @unknown default:
194
+
195
+ self.recordButton.isEnabled = false
196
+
197
+ self.recordButton.setTitle("録音機能が無効", for: .disabled)
198
+
199
+ }
200
+
201
+ }
202
+
203
+ }
204
+
205
+ }
206
+
207
+
208
+
209
+ private func startRecording() throws {
210
+
211
+ //ここに録音する処理を記述
212
+
213
+ //オプショナルバインディング
214
+
215
+ if let recognitionTask = recognitionTask {
216
+
217
+ // 既存タスクがあればキャンセルしてリセット
218
+
219
+ recognitionTask.cancel()
220
+
221
+ self.recognitionTask = nil
222
+
223
+ }
224
+
225
+
226
+
227
+ let audioSession = AVAudioSession.sharedInstance()
228
+
229
+ try audioSession.setCategory(AVAudioSession.Category.record)
230
+
231
+ try audioSession.setMode(AVAudioSession.Mode.measurement)
232
+
233
+ try audioSession.setActive(true)
234
+
235
+
236
+
237
+ //認識開始の前に認識リクエストを初期化
238
+
239
+ recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
240
+
241
+ guard let recognitionRequest = recognitionRequest else { fatalError("リクエスト生成エラー") }
242
+
243
+
244
+
245
+ //録音完了前に途中の結果を報告してくれる
246
+
247
+ recognitionRequest.shouldReportPartialResults = true
248
+
249
+
250
+
251
+ //audioEngineインスタンスのinputNodeプロパティを取得する
252
+
253
+ let inputNode = audioEngine.inputNode
254
+
255
+
256
+
257
+ recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest, delegate: self)
258
+
259
+
260
+
261
+
262
+
263
+ //マイクからの録音フォーマット
264
+
265
+ let recordingFormat = inputNode.outputFormat(forBus: 0)
266
+
267
+
268
+
269
+ inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
270
+
271
+ self.recognitionRequest?.append(buffer)
272
+
273
+ }
274
+
275
+
276
+
277
+ //オーディオエンジンで録音を開始して、テキスト表示を変更する
278
+
279
+ audioEngine.prepare() // オーディオエンジン準備
280
+
281
+ try audioEngine.start() // オーディオエンジン開始
282
+
283
+
284
+
285
+ textView.text = "Hello World"
286
+
287
+
288
+
289
+ }
290
+
291
+
292
+
293
+ // delegate
294
+
295
+ //音声認識機能の状態が変化するタイミングで呼ばれる
296
+
297
+ //録音ボタンの有効と無効を切り替える
298
+
299
+ public func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
300
+
301
+ if available {
302
+
303
+ // 利用可能になったら、録音ボタンを有効にする
304
+
305
+ recordButton.isEnabled = true
306
+
307
+ recordButton.setTitle("Start Recording", for: [])
308
+
309
+ recordButton.backgroundColor = UIColor.blue
310
+
311
+ } else {
312
+
313
+ // 利用できないなら、録音ボタンは無効にする
314
+
315
+ recordButton.isEnabled = false
316
+
317
+ recordButton.setTitle("現在、使用不可", for: .disabled)
318
+
319
+ }
320
+
321
+ }
322
+
323
+
324
+
325
+ // delegate
326
+
327
+ func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishSuccessfully successfully: Bool) {
328
+
329
+ print(#function)
330
+
331
+ print("isCanceled: (task.isCancelled)")
332
+
333
+ // 認識された場合には successfullt は true になる
334
+
335
+ // 1分経って認識がキャンセルされた場合には、successfully は false になる
336
+
337
+ print("successfully: (successfully)")
338
+
339
+
340
+
341
+ audioEngine.stop()
342
+
343
+ audioEngine.inputNode.removeTap(onBus: 0)
344
+
345
+
346
+
347
+ recognitionRequest = nil
348
+
349
+ recognitionTask = nil
350
+
351
+
352
+
353
+ recordButton.isEnabled = true
354
+
355
+ recordButton.setTitle("Start Recording", for: [])
356
+
357
+ recordButton.backgroundColor = UIColor.blue
358
+
359
+
360
+
361
+ // 強制的に再開させるのであれば、ここにコードを記述する
362
+
363
+ // 録音を開始する
364
+
365
+ try! startRecording()
366
+
367
+ recordButton.setTitle("認識を完了する", for: [])
368
+
369
+ recordButton.backgroundColor = UIColor.red
370
+
371
+ }
372
+
373
+
374
+
375
+ func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishRecognition recognitionResult: SFSpeechRecognitionResult) {
376
+
377
+ print(#function)
378
+
379
+ }
380
+
381
+
382
+
383
+ func speechRecognitionDidDetectSpeech(_ task: SFSpeechRecognitionTask) {
384
+
385
+ print(#function)
386
+
387
+ }
388
+
389
+
390
+
391
+ func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didHypothesizeTranscription transcription: SFTranscription) {
392
+
393
+ print(#function)
394
+
395
+
396
+
397
+ switch task.state {
398
+
399
+ case .completed:
400
+
401
+ // 完全に終了
402
+
403
+ print("完全に終了")
404
+
405
+ case .finishing:
406
+
407
+ // 録音は完了したが、認識は継続している
408
+
409
+ print("認識継続中")
410
+
411
+ case .canceling:
412
+
413
+ // キャンセル中
414
+
415
+ print("キャンセル中")
416
+
417
+ case .running:
418
+
419
+ // 認識中
420
+
421
+ print("認識中")
422
+
423
+ case .starting:
424
+
425
+ // 開始
426
+
427
+ print("開始")
428
+
429
+ @unknown default:
430
+
431
+ // 想定外
432
+
433
+ fatalError()
434
+
435
+ }
436
+
437
+
438
+
439
+ // 認識中に呼ばれる
440
+
441
+ if transcription.segments[0].confidence > 0.94 {
442
+
443
+ // 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.
444
+
445
+ if audioEngine.isRunning {
446
+
447
+ dump(transcription)
448
+
449
+ print(transcription.formattedString)
450
+
451
+ textView.text = transcription.formattedString
452
+
453
+
454
+
455
+ // 音声エンジン動作中なら停止
456
+
457
+ audioEngine.stop()
458
+
459
+ recognitionRequest?.endAudio()
460
+
461
+ recordButton.isEnabled = false
462
+
463
+ recordButton.setTitle("Stopping", for: .disabled)
464
+
465
+ recordButton.backgroundColor = UIColor.lightGray
466
+
467
+ }
468
+
469
+ }
470
+
471
+ }
472
+
473
+ }
474
+
475
+ ```