[音声認識] Webブラウザの音声認識APIを使う

作成:2021年12月2日  最終更新:2022年1月22日

この記事はWeb API Advent Calendar 2021の2021年12月3日の記事でもあります(作成日は2日ですが23時過ぎなので……)。


【2021-12-18追記】iPhoneの挙動について別記事を作りました。
[音声認識] Webブラウザの音声認識APIを使う ~iPhoneの挙動~
https://mekiku.com/view.php?a=77

Web APIじゃなくてWebブラウザAPIの話ですがご容赦ください。

なお、アドベントカレンダーの前日はkira_pukaさんの「スプレッドシートをWeb APIにできるサービスを作ってみた」でした。
https://qiita.com/kira_puka/items/42fa53dd2fa381a742d2

さて、Windows 11で日本語の音声入力が正式に有効になり、主要OSの多くで日本語を音声入力できるようになりました。しかし、非対応クライアント(Windows 8.1/10やLinux)も含めて使える(かもしれない)のが、Web Speech API(のSpeechRecognition)です。使い方によっては、クロスプラットフォームで使える唯一の手段になる場合もあります。
(※Windows 10はInsider Buildなら使えるようですが通常版では21H2でも使えません)

SpeechRecognition - Web API | MDN
https://developer.mozilla.org/ja/docs/Web/API/SpeechRecognition

このAPIを使って2020年に話題になったのが、「Webカメラの映像に自動字幕を重ねるWebページ」です。個人的には「字幕カメラ」と呼んでます。
https://github.com/1heisuzuki/speech-to-text-webcam-overlay

例えば仮想カメラとして取り込むと、ビデオ会議に「自分の発言がリアルタイムに音声認識された」状態で参加できます。ビデオ会議での音声認識は、Microsoft Teamsが日本語に(一応)対応しつつありますが、ZoomやGoogle Meetはまだ対応していない状態です。

[随時更新]Microsoft Teamsでweb会議中にリアルタイム書き起こし(日本語)機能がリリースされたのでまとめてみた - Qiita
https://qiita.com/iwashi-kun/items/8777b48988a226f1770c

さて、前置きが長くなりましたが、実際にSpeechRecognitionを使ってみます。

SpeechRecognitionクラス

SpeechRecognitionクラスが、音声認識の基本となります。しかし……。

Speech Recognition API | Can I use... Support tables for HTML5, CSS3, etc
https://caniuse.com/speech-recognition

非常に残念なことに、現状では正規に使えるブラウザはありません。"webkit"プレフィクスをつけて、ようやくChromeとSafariが対応、という状態です。なおiOS用Chromeは本当のChromeではないこともあり、SpeechRecognitionは使えません。

※2021-01-22追記
Edgeで(日本語を含む)音声認識が動くようになってました。なお、Edgeの場合、SpeechRecognition.langプロパティを明示的に指定した方がよさそうです。少なくとも、このプロパティとHTMLページのlangを指定しない場合、たとえEdgeの第1言語が日本語になっていても、英語で音声認識しようとしました。

また、caniuseに書いてある通り、EdgeなどはAPIが存在するかのように見せかけておいて実際には動作しません。他のChromiumベースブラウザ(VivaldiやLinuxのChromiumなど)では、やはりAPIがあるように見えて使えません(以前は使えていたのですが、使えなくなりました)。

少なくともEdgeは、自社で音声認識技術を持っているのにブラウザでは使用不能という、非常に残念な状態です。


SpeechRecognitionの主要プロパティ

SpeechRecognitionで、用途にあわせて確実に設定したいプロパティは次の2点です。

また、音声の言語もlangプロパティで設定できるため、明示的に言語を切り替えたい場合は利用したいところでしょう(例えばAndroidの「音声文字変換」では言語を明示的に指定していますね)。

※2021-01-22追記

Edgeでも音声認識するつもりであれば、langプロパティを明示的に指定することをお勧めします。MDNのSpeechRecognitionページにあるlangの説明には次のようにありますが、少なくともEdgeはユーザエージェントの言語設定(第1項目)が日本語でも、HTMLのlangとSpeechRecognition.langがいずれも無指定の場合、英語で認識してしまいます。

現在の SpeechRecognition の言語を返して設定します。指定されない場合、これはデフォルトで HTML lang 属性の値になります。どちらも設定されていない場合、ユーザーエージェントの言語設定が使用されます。


(2021-01-22追記ここまで)

SpeechRecognitionの主要メソッド

SpeechRecognitionのメソッドはたった3つで、MDNを見ればわかると思います。一応、stopとabortの違いだけ簡単に。

stop()は「音声認識ここまで」で、利用シーンとしては「ユーザが明示的に音声認識を止める操作をした時」などが想定されます。

abort()は「音声認識緊急停止!全部止め!」といったところで、利用シーンとしてはエラー時やWebアプリの完全な場面転換などが想定されます。

SpeechRecognitionEventクラス

https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionEvent

SpeechRecognition.onresultイベントの引数の型です。

基本的にはresultsプロパティを使いますが、連続認識ではresultIndex(resultsの中で今回変更があった最初のインデックス)も使う場合があります。

SpeechRecognitionResultListクラス

https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionResultList

SpeechRecognitionEvent.resultsプロパティの型です。lengthプロパティを除けば、配列として使われます。配列引数は音声認識部分(認識結果が複数に分割される場合があります)です。

SpeechRecognitionResultクラス

https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionResult

SpeechRecognitionResultList[i]の型です。本体はisFinalプロパティ(この部分が確定か否かを示す)をもっています。途中結果を取得する場合、途中まで確定部分、その後は未確定部分、といった結果が得られる場合がありますが、このフラグでわかります。おそらく、SpeechRecognitionEvent.resultIndex以降が未確定部分(isFinal==false)となると思うのですが、ここは確認できていません。

なお、この型も配列としてアクセスします。引数は候補のインデックスです。0より大きなインデックスは、SpeechRecognitionクラスのmaxAlternativesプロパティに1より大きな整数を設定した場合に出てきうるものです。実用する場合、第2候補以下を考える意味は薄い場合が多いような気がします。

SpeechRecognitionAlternativeクラス

https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognitionAlternative

SpeechRecognitionResult[i]の型です。このオブジェクトに、実際の認識結果が含まれています。認識結果(transcript)とは別のプロパティ(confidence)は、後で示すように0以上1以下(0<c<1かも)の実数です。Chromeで試した限りでは、0.01以下(初期段階)か0.9弱(確定寸前)か0.9以上(確定)という感じですが、ブラウザごとに違うかもしれません。

SpeechRecognitionのイベント

プログラムとしては、SpeechRecognitionのイベントに各種ハンドラを設定することになります。

イベントそのものはMDNにある通りなのですが、実際に使うとどうなのかが知りたいところです。そこで試した結果を以下に示します。

(声が入らない場合)

onstart
onaudiostart
onaudioend
onerror (event.error=no-speech)
onend

(声が入る場合。設定はデフォルト(連続認識なし、途中結果なし)で、中身は実際に試した時のサンプル)

onstart
onaudiostart
onaudiostart
onspeechstart
onspeechend
onsoundend
onaudioend
onresult : event.resultIndex=0 event.results.length=1
event.results[0] isFinal=true results[0][0] transcript=今日は 音声認識のテストをしています confidence=0.9143685102462769
onend

(声が入る場合。設定は連続認識あり、途中結果あり)で、中身は実際に試した時のサンプル)

onstart
onaudiostart
onaudiostart
onspeechstart
onresult : event.resultIndex=0 event.results.length=1
event.results[0] isFinal=false results[0][0] transcript=今日 confidence=0.009999999776482582
onresult : event.resultIndex=0 event.results.length=1
event.results[0] isFinal=false results[0][0] transcript=今日は confidence=0.009999999776482582
(中略)
onresult : event.resultIndex=0 event.results.length=1
event.results[0] isFinal=false results[0][0] transcript=今日は音声認識のテストをしています confidence=0.009999999776482582
onresult : event.resultIndex=0 event.results.length=1
event.results[0] isFinal=false results[0][0] transcript=今日は音声認識のテストをしています confidence=0.8999999761581421
onresult : event.resultIndex=0 event.results.length=1
event.results[0] isFinal=true results[0][0] transcript=今日は音声認識のテストをしています confidence=0.9229298830032349
onspeechend
onsoundend
onaudioend
onend


※ちなみに、連続認識有効な場合、最後の認識結果から数秒経ってはじめてonspeechend以降の状態に遷移します。

1発言だけ音声認識する場合

SpeechRecognitionの continuous と interimResults を両方ともfalse(デフォルト)にしておき、onresultイベントからevent.results[i][0]を連結すれば、基本的な認識結果が得られます。

文末にマルをつけたい場合は、認識結果の末尾をチェックして、ない場合だけつける、というのが、とりあえずの対応となります。もっとも、文末に句点が来ない言語もあるし、疑問文や感嘆文はどうするか(例えばスペイン語なら文頭と文末に約物がつきます)、といった問題に直面することになりますが……。音声認識側で疑問文などの推定は行われてしまうので、結果を受けとる側でできることは限られますね。

拙作の音声入力つきチャットでは、とりあえず文末にマルがなければつけてます。チャットなので、この程度でも許されるかなと思ってます。

mk-vchat/paneInput.ts
https://github.com/sksthrs/mk-vchat/blob/main/src/paneInput.ts

連続的かつ途中結果も含めて音声認識結果を利用する場合

SpeechRecognitionの continuous と interimResults を両方ともtrueにしておき、onresultイベントからevent.results[i][0]を連結すれば、基本的な認識結果が得られます。

また、表示は確定部分と未確定部分を分けて保持しておき、新しい認識結果のisFinalを見て配分していくことで、認識結果の表示分量をコントロールしやすくなります。

(参考) 字幕が突然短くなってしまうのを改善する #53
https://github.com/1heisuzuki/speech-to-text-webcam-overlay/pull/53

また、連続的に音声認識を使い続ける場合、途中で処理が止まってしまうことがあります。こういった時は、使用中のSpeechRecognitionをabort()して、新たなSpeechRecognitionを生成するしかなさそうです。

前述の字幕カメラでは、次のような場合にSpeechRecognitionをリセット(問答無用でSpeechRecognitionオブジェクトを生成し、それまで使っていた変数に再割当て)しています。

音声認識の確定時にリセットすることや、リセットが純粋なオブジェクト再生成であることなど、結構ワイルドな感じです。

そこで、手元で確定時リセットをしないよう改変したものを動かしてみたところ、認識しづらい場面(早口だったり、複数人の声が被ったり、聞き取りづらかったりする場面)において、認識しない場合が増えるという結果になりました。つまり、ワイルドにやった方が認識が頑強ということです。もっと色々な変更を試してみたいところではあるのですが、他にもやることがあって、あまり進んでいないのが実情です。

(なお、上記の確認は同一のバラエティな音源を何回も認識させているため、1回限りの偶然ではないだろうと思っています)

おわりに

主要OSで音声入力がサポートされてきたとはいえ、途中経過は見られないものがほとんどのように思います。また、先の字幕カメラのように、たとえ不正確でも途中経過が見えた方が(ディレイが少ないという意味で)ありがたいケースもあります。さらに、ブラウザなら1回で(ブラウザを選ぶとはいえ)クロスプラットフォームになります。まだ当分はブラウザの音声認識も役立つ場面があるのではないでしょうか。


◀(前記事)[サーバ管理]さくらインターネットでDBとMLの自動バックアップ
▶(次記事)[音声認識] Webブラウザの音声認識APIを使う ~iPhoneの挙動~

(一覧)[2.技術情報 (tech)]