〖FunASR情绪识别教程〗基于 emotion2vec+ large 的本地语音情绪识别实战

做语音机器人项目时,除了"听得懂内容"(ASR),还有一个非常关键但经常被忽略的能力:

听得出"情绪"。

同一句"我没事",愤怒、委屈、平静、开心,语气一变完全是不同的含义。

所以在我的 AI 机器人项目里,我希望在 ASR 识别文本之后,再通过语音情绪模型判断"当下说话人的情绪状态"。

本文就是把这个需求完整走一遍,最终产出一个:

  • ✅ 基于 FunASRemotion2vec_plus_large 模型

  • ✅ 支持 本地单条 wav 音频 的 9 类情绪识别

  • ✅ 自动保存原始 JSON 结果,方便后续集成到 FastAPI / 机器人系统

  • ✅ 输出 Top-1 情绪和各类别概率,一眼看到模型"怎么想的"

【🚀🚀🚀如果你对人工智能的学习有兴趣可以看看我的其他博客,对新手很友好!🚀🚀🚀】

【🚀🚀🚀本猿定期无偿分享学习成果,欢迎关注一起学习!🚀🚀🚀】

功能目标 & 情绪类别说明

我们要实现的脚本功能非常简单:

  1. 读取 ./data/input.wav(一段人说话的音频)

  2. 调用 FunASR 的 iic/emotion2vec_plus_large 模型做推理

  3. 输出 9 类语音情绪的概率分布,并找出 Top-1:

官方给的 9 类情绪编号是:

bash 复制代码
0: angry
1: disgusted
2: fearful
3: happy
4: neutral
5: other
6: sad
7: surprised
8: unknown

一、运行环境与目录结构

我这里单独建了一个小工程目录,比如:

bash 复制代码
Emotion/
├── main.py         # 本文的主脚本
├── data/
│   └── input.wav   # 用来测试的语音(16kHz 单声道 wav)
└── outputs/
    └── result.json # 运行后自动生成的结果

⚠️ data/input.wav 是你要做情绪识别的那段音频,建议 16kHz 单声道 wav,后面会提到怎么转换。

1.1 创建独立 conda 环境

推荐单独搞个环境,避免和其它项目的库打架:

bash 复制代码
conda create -n emo2vec python=3.10 -y
conda activate emo2vec

二、依赖回顾:这几个库要记住

这块我刻意做了一次"记忆备份",以后懒得翻文档时,直接看这几行就能回忆起来。

2.1 核心:情绪模型接口相关

  • funasr

  • modelscope

  • huggingface_hub

这三个负责"把 emotion2vec+ 模型拉下来、加载进来、跑起来"。

2.2 音频处理工具

  • soundfile

  • librosa

后面如果想做更复杂的预处理、分段、特征提取,会用到它们。本文 demo 的 main.py 里暂时还没用到,但我习惯先装上,免得后续再补。

2.3 数值计算基础

  • numpy

  • scipy

这两个基本是所有深度学习/音频项目的标配。

2.4 安装全部依赖

requirements.txt如下:

bash 复制代码
# 核心:情绪模型接口
funasr
modelscope
huggingface_hub

# 音频处理
soundfile
librosa

# 数值计算
numpy
scipy

安装依赖:

bash 复制代码
pip install -r requirements.txt

2.5 PyTorch(需要自己单独装)

  • torch(PyTorch)

这个比较特殊,需要根据你的 显卡 + CUDA 版本 从 PyTorch 官网选择对应安装命令。

例如,仅做一个示意(具体以官网为准):

bash 复制代码
# GPU 版(示例)
pip install torch --index-url https://download.pytorch.org/whl/cu121

# 或 CPU 版
pip install torch

三、核心思路:FunASR + emotion2vec+ large

这里我们直接使用 FunASR 提供的 高级封装 AutoModel

  • 通过 AutoModel(model="iic/emotion2vec_plus_large") 一行加载模型;

  • 使用 generate() 完成从 WAV 到情绪分布的完整推理流程;

  • 通过参数:

    • granularity="utterance":按「整段语音」输出一个情绪分布

    • extract_embedding=False:只要情绪标签 + 分数,不需要特征向量

整体流程:

  1. load_emotion_model() 负责模型加载和设备选择(CPU / GPU)

  2. infer_emotion() 负责对一个 wav_path 做推理

  3. pretty_print() 对推理结果做友好打印

  4. main() 里串起来:加载模型 → 推理 → 打印 → 保存 JSON

四、完整 main.py 源码(可直接复制运行)

放到项目根目录的 main.py 中即可。

python 复制代码
# main.py
# -*- coding: utf-8 -*-
"""
使用 FunASR 的 emotion2vec+ large 模型,对 ./data/input.wav 做 9 类语音情绪识别。

官方说明:
    - 模型 ID: "iic/emotion2vec_plus_large"
    - rec_result 含字段: {'feats', 'labels', 'scores'}
      - extract_embedding=False: 返回 9 类情绪 + 分数
      - extract_embedding=True : 额外返回特征
    - 9 类情绪:
        0: angry
        1: disgusted
        2: fearful
        3: happy
        4: neutral
        5: other
        6: sad
        7: surprised
        8: unknown
"""

import os
import json

import torch
from funasr import AutoModel


def load_emotion_model():
    """加载 emotion2vec_plus_large 模型。"""
    model_id = "iic/emotion2vec_plus_large"

    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"[INFO] Using device: {device}")
    if device == "cuda":
        print("[INFO] CUDA device:", torch.cuda.get_device_name(0))

    model = AutoModel(
        model=model_id,
        # 中国大陆网络建议 "ms"(ModelScope),海外可改为 "hf"(HuggingFace)
        hub="ms",
        device=device,
        disable_update=True,  # 关掉每次检查 funasr 更新的额外耗时
    )
    return model


def infer_emotion(model, wav_path: str):
    """对单个 wav 文件做情绪识别。"""
    if not os.path.exists(wav_path):
        raise FileNotFoundError(f"音频文件不存在: {wav_path}")

    os.makedirs("./outputs", exist_ok=True)

    rec_result = model.generate(
        input=wav_path,
        output_dir="./outputs",
        granularity="utterance",   # 按整段语音输出一个情绪分布
        extract_embedding=False,   # 只要 9 类情绪 + 分数
    )
    return rec_result


def pretty_print(rec_result):
    """打印详细结果 + Top1 情绪。"""
    print("\n[RAW RESULT]")
    print(json.dumps(rec_result, ensure_ascii=False, indent=2))

    if not rec_result:
        print("\n[WARN] rec_result 为空")
        return

    item = rec_result[0]
    labels = item.get("labels", [])
    scores = item.get("scores", [])

    if not labels or not scores or len(labels) != len(scores):
        print("\n[WARN] 结果中没有有效的 labels/scores")
        return

    # 打印所有类别
    print("\n[DETAIL]")
    best_label = None
    best_score = -1.0

    for label, score in zip(labels, scores):
        # 官方有时标签类似 "emo/angry",截一下最后一段
        lab_str = str(label).split("/")[-1]
        if lab_str == "<unk>":
            lab_str = "unknown"

        print(f"{lab_str:10s}: {score * 100:6.2f}%")

        if score > best_score:
            best_score = score
            best_label = lab_str

    if best_label is not None:
        print(f"\n[TOP-1] Emotion = {best_label}  (score = {best_score * 100:.2f}%)")


def main():
    wav_path = "./data/input.wav"

    print("[STEP] 加载 emotion2vec_plus_large 模型 ...")
    model = load_emotion_model()
    print("[STEP] 模型加载完成。")

    print(f"[STEP] 对音频 {wav_path} 做情绪识别 ...")
    rec_result = infer_emotion(model, wav_path)

    pretty_print(rec_result)

    # 保存原始 JSON 结果,方便后续调试或集成
    out_json = "./outputs/result.json"
    with open(out_json, "w", encoding="utf-8") as f:
        json.dump(rec_result, f, ensure_ascii=False, indent=2)

    print(f"\n[STEP] 原始结果已保存到: {out_json}")


if __name__ == "__main__":
    main()

🔮 博主整理不易,如果对你有帮助,可以点个免费的赞吗?感谢感谢!

相关推荐
新芒1 小时前
亚马逊云科技领跑“Agent”时代:揭示云与AI的下一站
人工智能·科技·microsoft
β添砖java1 小时前
机器学习----深度学习部分
人工智能·深度学习·机器学习
GMICLOUD1 小时前
GMI Cloud@AI 周报 | DeepSeek V3.2 系列震撼开源;Claude Opus 4.5 发布
人工智能·ai·ai资讯
QT 小鲜肉1 小时前
【孙子兵法之中篇】009. 孙子兵法·行军篇
人工智能·笔记·读书·孙子兵法
c#上位机1 小时前
halcon计算区域骨架
图像处理·人工智能·计算机视觉·c#·halcon
天一生水water2 小时前
储层认知→技术落地→产量优化
人工智能·算法·机器学习
华清远见成都中心2 小时前
人工智能的关键技术有哪些?
人工智能
绿蕉2 小时前
智能底盘:汽车革命的“新基石”
大数据·人工智能
GAOJ_K2 小时前
滚珠花键的使用时长与性能保持的量化关系
大数据·人工智能·科技·自动化·制造