FunAudioLLM/SenseVoiceSmallを使って音声の感情を判定する

開発環境

環境構築

以下のライブラリをインストールします

uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
uv pip install funasr

実行

以下のコードを実行します

import argparse
import os
from funasr import AutoModel

def analyze_emotion_with_funasr(audio_file_path: str):
    """
    funasrライブラリとSenseVoiceモデルを使用して音声ファイルの感情を分析する。

    Args:
        audio_file_path (str): 分析したい音声ファイルのパス
    """
    if not os.path.exists(audio_file_path):
        print(f"エラー: 指定されたファイルが見つかりません。")
        print(f"パスを確認してください: {audio_file_path}")
        return

    print("--- 環境・モデル情報 ---")
    print(f"ライブラリ: funasr")
    
    # --- 1. モデルのロード ---
    # funasr の公式ドキュメントに沿ったモデルのロード方法
    model_dir = "iic/SenseVoiceSmall"
    print(f"モデル: {model_dir}")
    print("モデルをロードしています... (初回は時間がかかります)")
    
    try:
        model = AutoModel(
            model=model_dir,
            vad_model="fsmn-vad",
            vad_kwargs={"max_single_segment_time": 30000},
            device="cuda:0",
            # trust_remote_code=True は funasr のバージョンによっては不要な場合があります
        )
    except Exception as e:
        print(f"モデルのロード中にエラーが発生しました: {e}")
        return

    print("モデルのロードが完了しました。")
    print("\n音声の感情を判定しています...")
    
    # --- 2. 感情認識の実行 ---
    try:
        # ★★★ ここが重要 ★★★
        # task="ser" を指定して、感情認識タスクを実行する
        res = model.generate(
            input=audio_file_path,
            task="ser",  # Speech Emotion Recognition
        )
        
        # --- 3. 結果の表示 ---
        print("\n" + "="*30)
        print("  感情判定結果")
        print("="*30)
        
        # funasrのSERタスクの出力形式はリスト形式で返ってくることが多い
        if res and "emotion" in res[0]:
            emotion_label = res[0]["emotion"]
            print(f"  ファイル: {os.path.basename(audio_file_path)}")
            print(f"  判定された感情: {emotion_label}")
        else:
            print("  感情を判定できませんでした。")
            print("  モデルからの生データ:", res) # デバッグ用に生データを表示
        
        print("="*30)

    except Exception as e:
        print(f"\n推論中に予期せぬエラーが発生しました: {e}")


if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description="funasr と SenseVoiceモデルを使用して、音声ファイルの感情を分析します。"
    )
    parser.add_argument(
        "audio_file",
        type=str,
        help="分析したい音声ファイルのパス。"
    )
    
    args = parser.parse_args()
    
    analyze_emotion_with_funasr(args.audio_file)

これを以下で実行します

python .\emotion_recognition.py .\VOICEACTRESS100_090.wav

判定結果は以下のようになります

音声の感情を判定しています...
rtf_avg: 0.038: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  4.29it/s] 
rtf_avg: 0.046: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  3.54it/s] 
rtf_avg: 0.047, time_speech:  6.062, time_escape: 0.286: 100%|████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00,  3.38it/s] 

==============================
  感情判定結果
==============================
  感情を判定できませんでした。
  モデルからの生データ: [{'key': 'VOICEACTRESS100_090', 'text': '<|ja|><|EMO_UNKNOWN|><|Speech|><|woitn|>戦闘服は両腕を露出し両足がアンダースーツで覆われている'}]
==============================

ニュートラルの場合は、上記のように判定をします。ただしほぼ?テキストから判定をしているみたいなのでボイス側のニュアンスはあまり反映されていない感じがしています