Zenn
Closed6

オフラインで使える音声認識ツールキット「VOSK」を試す

kun432kun432

たまたま見つけた

https://alphacephei.com/vosk/

Voskは音声認識ツールキットです。 Voskの優れた点は以下の通りです。

  1. 20以上の言語と方言に対応しています: 英語、インド英語、ドイツ語、フランス語、スペイン語、ポルトガル語、中国語、ロシア語、トルコ語、ベトナム語、イタリア語、オランダ語、カタロニア語、アラビア語、ギリシャ語、ペルシア語、フィリピノ語、ウクライナ語、カザフ語、スウェーデン語、日本語、エスペラント語、ヒンディー語、チェコ語、ポーランド語、ウズベク語、韓国語、ブルトン語、グジャラート語、タジク語、テルグ語。今後もさらに追加予定です。
  2. 軽量デバイスでもオフラインで動作します: Raspberry Pi、Android、iOS
  3. pip3 install voskで簡単にインストールできます
  4. 各言語のポータブルモデルはそれぞれ50MBですが、より大規模なサーバーモデルも利用可能です。
  5. 最高のユーザー体験を実現するストリーミングAPIを提供します(一般的な音声認識Pythonパッケージとは異なります)。
  6. さまざまなプログラミング言語用のバインディングもあります: Java/C#/JavaScriptなど
  7. 最高の精度を実現するために、語彙の素早い再構成が可能です。
  8. 単純な音声認識のほか、話者識別にも対応しています。

オフラインでポータブル、日本語にも対応、ストリーミングあり、ってところかな。

kun432kun432

モデルのチューニングとか辞書の修正など

https://alphacephei.com/vosk/adaptation

できる方法は4つ。ChatGPTにまとめてもらった。

1. ランタイムでの認識語彙の更新

  • 対象: 小規模モデル(動的グラフを持つモデル)
  • 概要: 実行中に認識すべき単語リストをオンラインで変更できます。これにより、必要な単語のみを取り込むことで精度向上が図れます。
  • 注意点:
    • 大規模モデルや静的グラフのモデルはこの手法には対応していません。

2. オフラインでの言語モデルの更新

  • 対象: 小規模モデル
  • 概要: VOSKのKaldiモデルは「辞書」「音響モデル」「言語モデル」の3要素から構成されています。認識精度の改善には、単語の出現確率を調整するために、テキストから新しい言語モデル(文法)を再コンパイルする手法が有効です。
  • 手順:
    1. テキスト準備: 対象となるスピーチに合致するテキストを用意し、句読点を除去しすべて小文字に変換する(Pythonスクリプトなどで処理)。
    2. ツールの準備: Kaldi内で openfstopengrm をビルドする。
    3. 文法の構築:
      • fstsymbols コマンドでシンボルリスト(words.txt)を生成
      • farcompilestringsngramcountngrammakefstconvert をパイプで繋ぎ、新しい文法(Gr.new.fst)を作成
      • 既存のGr.fstと置換する
  • 注意点:
    • この手法では新しい単語は追加できず、あくまで既存単語の確率調整となります。

3. 大規模モデルの語彙・言語モデルの更新

  • 対象: Aspire EN、Daanzu En、Russian、German、French など、一部の大規模モデル
  • 概要: 大規模モデルは内部に固定されたグラフを持っていますが、更新可能なモデルについては、辞書と統合された言語モデル(グラフ)を再構築できます。
  • 手順:
    1. Lexiconの準備: Kaldi形式で辞書データを用意する。
    2. 言語モデルの作成: 汎用の言語モデルと、ドメイン固有の言語モデルを補間したものを準備する。
    3. コンパイル: 辞書と文法(グラフ)をコンパイルし、新たなグラフを生成する。
    4. 置換: 生成したグラフをモデル内の既存グラフと置き換える。
  • 注意点:
    モデルによっては必要なファイルが提供されていない場合もあり(例: Indian English)、更新ができない場合があります。

4. 音響モデルのファインチューニング

  • 対象: 既存の音響モデル全般
  • 概要: 約1時間分の音声データを用いて、音響モデル自体を特定の環境や条件に合わせて微調整する方法です。
  • 手順:
    1. データ収集: 対象となる環境や話者の音声データを収集する。
    2. フォーマット変換: Kaldi形式にデータを変換する。
    3. スクリプト実行: Kaldiのファインチューニング用スクリプトを実行してモデルを微調整する。
  • 注意点:
    詳細なドキュメントは未整備であり、関連のIssueで追跡されています。

全体としてKaldiに依存してるっぽい。

言語モデルについては以下にもドキュメントがある

https://alphacephei.com/vosk/lm

kun432kun432

インストール。

https://alphacephei.com/vosk/install

AndroidやiOS、ソースからのビルドについて明記があるが、自分はPythonで。ローカルのMac(M2)でやる。

作業ディレクトリ&Python仮想環境作成

mkdir vosk-work && cd vosk-work
uv venv -p 3.12.9

パッケージインストール

uv pip install vosk
出力
 + vosk==0.3.44

日本語のモデルをダウンロードする。一応、コードの中からもダウンロードはできるようなのだけど、あらかじめダウンロードしたほうがわかりやすい。

mkdir -p models

# small
wget https://alphacephei.com/vosk/models/vosk-model-small-ja-0.22.zip
unzip vosk-model-small-ja-0.22.zip -d models

# large
wget https://alphacephei.com/vosk/models/vosk-model-ja-0.22.zip
unzip vosk-model-ja-0.22.zip -d models

Pythonで使う場合のサンプルコードが以下にある。

https://github.com/alphacep/vosk-api/tree/master/python/example

ここではマイクを使ったサンプルをClaude-3.7-Sonnetに修正させた。マイクからの入力はsoundeviceを使っているのでパッケージの追加が必要。

uv pip install sounddevice
sample.py
import queue
import sys
import os
import sounddevice as sd
from vosk import Model, KaldiRecognizer

samplerate = 16000  # サンプリングレート
model_base_dir = "models"
model = "vosk-model-small-ja-0.22"

model_path = f"{model_base_dir}/{model}"

# モデルパスが見つからない場合の警告
if not os.path.exists(model_base_dir):
    print(f"モデルディレクトリが見つかりません。'{model_base_dir}'ディレクトリにモデルを配置してください。")
    print("モデルは https://alphacephei.com/vosk/models からダウンロードできます。")
    sys.exit(1)

if not os.path.exists(model_path):
    print(f"モデルが見つかりません。'{model_base_dir}'ディレクトリにモデル'{models}'のディレクトリがあることを確認してください。")
    print("モデルは https://alphacephei.com/vosk/models からダウンロードできます。")
    sys.exit(1)

# キューの設定
q = queue.Queue()

# コールバック関数
def callback(indata, frames, time, status):
    """音声データが入力されるたびに呼び出される関数"""
    if status:
        print(status, file=sys.stderr)
    q.put(bytes(indata))

try:
    # モデルの読み込み
    model = Model(model_path)
    print(f"モデルを読み込みました: {model_path}")

    # 音声ストリームの開始
    with sd.RawInputStream(samplerate=samplerate, blocksize=8000, dtype="int16",
                          channels=1, callback=callback):
        print("#" * 80)
        print("音声認識を開始しました。話しかけてください。")
        print("終了するには Ctrl+C を押してください。")
        print("#" * 80)

        # 認識器の初期化
        rec = KaldiRecognizer(model, samplerate)

        # メインループ
        while True:
            data = q.get()
            if rec.AcceptWaveform(data):
                result = rec.Result()
                print(f"認識結果: {result}")
            else:
                partial = rec.PartialResult()
                if not '"partial" : ""' in partial:
                    print(f"部分結果: {partial}")

except KeyboardInterrupt:
    print("\n終了しました")
    sys.exit(0)
except Exception as e:
    print(f"エラーが発生しました: {type(e).__name__}: {str(e)}")
    sys.exit(1)

では実行

uv run sample.py

以下のように表示されたら発話する。

出力
LOG (VoskAPI:ReadDataFiles():model.cc:213) Decoding params beam=13 max-active=7000 lattice-beam=4
LOG (VoskAPI:ReadDataFiles():model.cc:216) Silence phones 1:2:3:4:5:6:7:8:9:10
LOG (VoskAPI:RemoveOrphanNodes():nnet-nnet.cc:948) Removed 0 orphan nodes.
LOG (VoskAPI:RemoveOrphanComponents():nnet-nnet.cc:847) Removing 0 orphan components.
LOG (VoskAPI:ReadDataFiles():model.cc:248) Loading i-vector extractor from models/vosk-model-small-ja-0.22/ivector/final.ie
LOG (VoskAPI:ComputeDerivedVars():ivector-extractor.cc:183) Computing derived variables for iVector extractor
LOG (VoskAPI:ComputeDerivedVars():ivector-extractor.cc:204) Done.
LOG (VoskAPI:ReadDataFiles():model.cc:282) Loading HCL and G from models/vosk-model-small-ja-0.22/graph/HCLr.fst models/vosk-model-small-ja-0.22/graph/Gr.fst
LOG (VoskAPI:ReadDataFiles():model.cc:303) Loading winfo models/vosk-model-small-ja-0.22/graph/phones/word_boundary.int
モデルを読み込みました: models/vosk-model-small-ja-0.22
################################################################################
音声認識を開始しました。話しかけてください。
終了するには Ctrl+C を押してください。
################################################################################

新幹線のアナウンスの冒頭を読み上げてみた。

https://choimitena.com/Text/Sample#google_vignette

small

出力
部分結果: {
  "partial" : "今日 も"
}
部分結果: {
  "partial" : "今日 も"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
認識結果: {
  "text" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
部分結果: {
  "partial" : "この"
}
部分結果: {
  "partial" : "この 電車 は"
}
部分結果: {
  "partial" : "この 電車 は 希"
}
部分結果: {
  "partial" : "この 電車 は 希 御"
}
部分結果: {
  "partial" : "この 電車 は のぞみ 号 東京"
}
部分結果: {
  "partial" : "この 電車 は のぞみ 号 東京 行き です"
}
部分結果: {
  "partial" : "この 電車 は のぞみ 号 東京 行き です"
}
認識結果: {
  "text" : "この 電車 は のぞみ 号 東京 行き です"
}
部分結果: {
  "partial" : "途中 の"
}
部分結果: {
  "partial" : "途中 の 停車 駅"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 神 様"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 線 横浜"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新横浜 品川"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新横浜 品川 です"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新横浜 品川 です"
}
認識結果: {
  "text" : "途中 の 停車 駅 は 京都 名古屋 新横浜 品川 です"
}

big

部分結果: {
  "partial" : "今日 も"
}
部分結果: {
  "partial" : "今日 も 心"
}
部分結果: {
  "partial" : "今日 も 新 幹線"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
部分結果: {
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
認識結果: {
  "text" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
部分結果: {
  "partial" : "この"
}
部分結果: {
  "partial" : "この 電車 は"
}
部分結果: {
  "partial" : "この 電車 は"
}
部分結果: {
  "partial" : "この 電車 は 希 御"
}
部分結果: {
  "partial" : "この 電車 は のぞみ 号 東京"
}
部分結果: {
  "partial" : "この 電車 は のぞみ 号 東京 駅 で"
}
部分結果: {
  "partial" : "この 電車 は のぞみ 号 東京 駅 で"
}
認識結果: {
  "text" : "この 電車 は のぞみ 号 東京 行き です"
}
部分結果: {
  "partial" : "途中 の"
}
部分結果: {
  "partial" : "途中 の 停車 駅"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 線"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 市"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川 です"
}
部分結果: {
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川 です"
}
認識結果: {
  "text" : "途中 の 停車 駅 は 京都 名古屋 新横浜 品川 です"
}

これぐらいだとあまり違いは感じられないようだけど、bigのほうがやや反応が敏感な感があって、ちょっとしたノイズでも何かしら文字にする感があった。

kun432kun432

もう一つのやり方としてVOSKにはサーバ版もある。

https://github.com/alphacep/vosk-server

MQTT・gRPC・WebRTC・WebSocketに対応しているらしく、Dockerイメージも提供されている。

https://github.com/alphacep/vosk-server/tree/master/docker

Dockerで提供されている日本語版はbigモデルをWebSocket経由でアクセスできる様子。

https://github.com/alphacep/vosk-server/blob/master/docker/Dockerfile.kaldi-ja

試してみる。

docker run -d -p 2700:2700 alphacep/kaldi-ja:latest

ふむ、x86_64のイメージしかないみたい。とりあえず続ける。

出力
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested

ではWebSocketクライントで試してみる。パッケージの追加が必要。

uv pip install websocket-client
出力
 + websocket-client==1.8.0

コード。これもでもコードをClaude-3.7-Sonnetに書き換えてもらった。

ws_sample.py
#!/usr/bin/env python3

import sys
import traceback
import json
import queue
import sounddevice as sd
from websocket import create_connection

# WebSocketサーバーのURL
server_url = "ws://localhost:2700"

# オーディオ設定
samplerate = 16000  # サンプリングレート
blocksize = int(samplerate * 0.2)  # 0.2秒のオーディオブロック

# キューを設定
q = queue.Queue()

# オーディオコールバック関数
def audio_callback(indata, frames, time, status):
    """マイクからのオーディオブロックごとに呼び出される関数"""
    if status:
        print(status, file=sys.stderr)
    q.put(bytes(indata))

try:
    # WebSocket接続を作成
    print(f"サーバー {server_url} に接続しています...")
    ws = create_connection(server_url)
    print("接続しました")
    
    # サンプルレート設定をサーバーに送信
    ws.send(json.dumps({"config": {"sample_rate": samplerate}}))
    
    # マイク入力ストリームを開始
    with sd.RawInputStream(samplerate=samplerate, blocksize=blocksize, 
                          dtype='int16', channels=1, 
                          callback=audio_callback):
        
        print("#" * 80)
        print("マイク入力を開始しました。話しかけてください。")
        print("終了するには Ctrl+C を押してください。")
        print("#" * 80)
        
        # メインループ - マイクからのデータを読み取ってWebSocketに送信
        while True:
            # キューからデータを取得
            data = q.get()
            
            # バイナリデータとしてWebSocketに送信
            ws.send_binary(data)
            
            # サーバーからの応答を受信して表示
            result = ws.recv()
            
            # 空の部分結果は表示しない
            parsed = json.loads(result)
            if "partial" in parsed and parsed["partial"] == "":
                continue
                
            print(result)
            
except KeyboardInterrupt:
    print("\n終了しました")
    # 終了通知を送信
    try:
        ws.send(json.dumps({"eof": 1}))
        print(ws.recv())
        ws.close()
    except:
        pass
except Exception as err:
    print(''.join(traceback.format_exception(type(err), err, err.__traceback__)))
    try:
        ws.close()
    except:
        pass

実行

uv run ws_sample.py

こんな感じで。

出力
サーバー ws://localhost:2700 に接続しています...
接続しました
################################################################################
マイク入力を開始しました。話しかけてください。
終了するには Ctrl+C を押してください。
################################################################################
{
  "partial" : "今日 も"
}
{
  "partial" : "今日 も"
}
{
  "partial" : "今日 も"
}
{
  "partial" : "今日 も 心"
}
{
  "partial" : "今日 も 心"
}
{
  "partial" : "今日 も 新"
}
{
  "partial" : "今日 も 新 幹線"
}
{
  "partial" : "今日 も 新 幹線 を ご"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
{
  "partial" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
{
  "result" : [{
      "conf" : 0.977624,
      "end" : 3.150000,
      "start" : 2.820000,
      "word" : "今日"
    }, {
      "conf" : 1.000000,
      "end" : 3.420000,
      "start" : 3.150000,
      "word" : "も"
    }, {
      "conf" : 1.000000,
      "end" : 3.930000,
      "start" : 3.600000,
      "word" : "新"
    }, {
      "conf" : 1.000000,
      "end" : 4.440000,
      "start" : 3.930000,
      "word" : "幹線"
    }, {
      "conf" : 1.000000,
      "end" : 4.560000,
      "start" : 4.440000,
      "word" : "を"
    }, {
      "conf" : 0.998848,
      "end" : 4.710000,
      "start" : 4.560000,
      "word" : "ご"
    }, {
      "conf" : 1.000000,
      "end" : 5.040000,
      "start" : 4.710000,
      "word" : "利用"
    }, {
      "conf" : 0.752697,
      "end" : 5.520000,
      "start" : 5.040000,
      "word" : "ください"
    }, {
      "conf" : 1.000000,
      "end" : 5.790000,
      "start" : 5.520000,
      "word" : "まし"
    }, {
      "conf" : 1.000000,
      "end" : 6.000000,
      "start" : 5.790000,
      "word" : "て"
    }, {
      "conf" : 0.932386,
      "end" : 6.840000,
      "start" : 6.210000,
      "word" : "ありがとう"
    }, {
      "conf" : 0.989276,
      "end" : 7.170000,
      "start" : 6.840000,
      "word" : "ござい"
    }, {
      "conf" : 1.000000,
      "end" : 7.410000,
      "start" : 7.170000,
      "word" : "ます"
    }],
  "text" : "今日 も 新 幹線 を ご 利用 ください まし て ありがとう ござい ます"
}
{
  "partial" : "この"
}
{
  "partial" : "この"
}
{
  "partial" : "この"
}
{
  "partial" : "この 電車 は"
}
{
  "partial" : "この 電車 は"
}
{
  "partial" : "この 電車 は"
}
{
  "partial" : "この 電車 は 希"
}
{
  "partial" : "この 電車 は 希 御"
}
{
  "partial" : "この 電車 は 希 御"
}
{
  "partial" : "この 電車 は 希 御"
}
{
  "partial" : "この 電車 は のぞみ 号 東京"
}
{
  "partial" : "この 電車 は のぞみ 号 東京"
}
{
  "partial" : "この 電車 は のぞみ 号 東京 行き"
}
{
  "partial" : "この 電車 は のぞみ 号 東京 駅 です"
}
{
  "partial" : "この 電車 は のぞみ 号 東京 駅 です"
}
{
  "partial" : "この 電車 は のぞみ 号 東京 駅 です"
}
{
  "result" : [{
      "conf" : 1.000000,
      "end" : 9.720000,
      "start" : 9.390000,
      "word" : "この"
    }, {
      "conf" : 1.000000,
      "end" : 10.140000,
      "start" : 9.720000,
      "word" : "電車"
    }, {
      "conf" : 1.000000,
      "end" : 10.410000,
      "start" : 10.140000,
      "word" : "は"
    }, {
      "conf" : 0.992665,
      "end" : 11.010000,
      "start" : 10.560000,
      "word" : "のぞみ"
    }, {
      "conf" : 0.998073,
      "end" : 11.340000,
      "start" : 11.040000,
      "word" : "号"
    }, {
      "conf" : 1.000000,
      "end" : 11.880000,
      "start" : 11.370000,
      "word" : "東京"
    }, {
      "conf" : 0.998851,
      "end" : 12.090000,
      "start" : 11.880000,
      "word" : "行き"
    }, {
      "conf" : 0.997337,
      "end" : 12.450000,
      "start" : 12.090000,
      "word" : "です"
    }],
  "text" : "この 電車 は のぞみ 号 東京 行き です"
}
{
  "partial" : "途中"
}
{
  "partial" : "途中 の"
}
{
  "partial" : "途中 の 停車"
}
{
  "partial" : "途中 の 停車 駅"
}
{
  "partial" : "途中 の 停車 駅 は"
}
{
  "partial" : "途中 の 停車 駅 は"
}
{
  "partial" : "途中 の 停車 駅 は"
}
{
  "partial" : "途中 の 停車 駅 は"
}
{
  "partial" : "途中 の 停車 駅 は"
}
{
  "partial" : "途中 の 停車 駅 は"
}
{
  "partial" : "途中 の 停車 駅 は 京都"
}
{
  "partial" : "途中 の 停車 駅 は 京都"
}
{
  "partial" : "途中 の 停車 駅 は 京都"
}
{
  "partial" : "途中 の 停車 駅 は 京都"
}
{
  "partial" : "途中 の 停車 駅 は 京都"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 市"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 市 長"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川 です"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川 です"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川 です"
}
{
  "partial" : "途中 の 停車 駅 は 京都 名古屋 新 横浜 品川 です"
}
{
  "result" : [{
      "conf" : 1.000000,
      "end" : 15.120000,
      "start" : 14.730000,
      "word" : "途中"
    }, {
      "conf" : 1.000000,
      "end" : 15.270000,
      "start" : 15.150000,
      "word" : "の"
    }, {
      "conf" : 1.000000,
      "end" : 15.630000,
      "start" : 15.270000,
      "word" : "停車"
    }, {
      "conf" : 1.000000,
      "end" : 15.840000,
      "start" : 15.630000,
      "word" : "駅"
    }, {
      "conf" : 0.999512,
      "end" : 16.080000,
      "start" : 15.840000,
      "word" : "は"
    }, {
      "conf" : 1.000000,
      "end" : 17.340000,
      "start" : 16.680000,
      "word" : "京都"
    }, {
      "conf" : 1.000000,
      "end" : 18.360000,
      "start" : 17.700000,
      "word" : "名古屋"
    }, {
      "conf" : 0.990113,
      "end" : 19.680000,
      "start" : 18.600000,
      "word" : "新横浜"
    }, {
      "conf" : 0.999990,
      "end" : 20.550000,
      "start" : 19.860000,
      "word" : "品川"
    }, {
      "conf" : 0.982475,
      "end" : 20.940000,
      "start" : 20.550000,
      "word" : "です"
    }],
  "text" : "途中 の 停車 駅 は 京都 名古屋 新横浜 品川 です"
}

環境や接続方式にあわせてDockerファイルをカスタマイズしてビルドするのが良さそう。

kun432kun432

まとめ

VOSK、キーワードとしては聞いたことがあったんだけども、ストリーミングでASRできるなんて知らなかった。しかも結構前からある・・・

日本語モデルの認識性能等についてはきちんと確認できていないけども、ストリーミングで使えるのはとても良い。オフラインで使えるのも良いんだけど、その場合はリソース使用状況とかも気になるところ。もうちょっと試してみるつもり。

あと気になったところ。

  • ドキュメントは必要最低限な印象。少なくはないけど多くもない。
  • サンプルコードは色々豊富に用意されていると思う。
  • なので、レポジトリのコード読むのが良さそうに思う。
  • 学習する、となると、Kaldiのハードルがなかなかに高そうな気がする・・・そこまで触ってはいないのだけども。

そういえばこの辺のライブラリでも対応してる。
https://zenn.dev/kun432/scraps/83be11ca302d6c

このスクラップは27日前にクローズされました
ログインするとコメントできます