做语音机器人项目时,除了"听得懂内容"(ASR),还有一个非常关键但经常被忽略的能力:
听得出"情绪"。
同一句"我没事",愤怒、委屈、平静、开心,语气一变完全是不同的含义。
所以在我的 AI 机器人项目里,我希望在 ASR 识别文本之后,再通过语音情绪模型判断"当下说话人的情绪状态"。
本文就是把这个需求完整走一遍,最终产出一个:
-
✅ 基于 FunASR 的
emotion2vec_plus_large模型 -
✅ 支持 本地单条 wav 音频 的 9 类情绪识别
-
✅ 自动保存原始 JSON 结果,方便后续集成到 FastAPI / 机器人系统
-
✅ 输出 Top-1 情绪和各类别概率,一眼看到模型"怎么想的"

【🚀🚀🚀如果你对人工智能的学习有兴趣可以看看我的其他博客,对新手很友好!🚀🚀🚀】
【🚀🚀🚀本猿定期无偿分享学习成果,欢迎关注一起学习!🚀🚀🚀】
功能目标 & 情绪类别说明
我们要实现的脚本功能非常简单:
-
读取
./data/input.wav(一段人说话的音频) -
调用 FunASR 的
iic/emotion2vec_plus_large模型做推理 -
输出 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:只要情绪标签 + 分数,不需要特征向量
-
整体流程:
-
load_emotion_model()负责模型加载和设备选择(CPU / GPU) -
infer_emotion()负责对一个wav_path做推理 -
pretty_print()对推理结果做友好打印 -
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()