本文最后更新于:2023年12月5日 下午

获取原始语音文件后,需要筛选出某个角色的音频用于下一步训练,本文记录实现过程。

困难

数据庞大:原神中语音场景很多,角色也很多,要从几万条语音中找到目标角色的那几百条语音类似于海里捞一堆针了。

解决思路

有问题就想办法解决,这里记录我的实践路线:

  1. 选择目标角色(神里绫华云堇

  2. 网上抓出目标角色的一些经典台词(几十条就行)

    我们能够轻易获取的角色语音内容信息

  3. 识别所有语音内容

    1. 重采样语音 到 16k 采样率
    2. 识别语音
  4. 将识别结果与经典台词进行比对,找到角色的部分语音文件作为种子文件

  5. 提取语音的声纹特征

  6. 对特征进行降维

  7. 对降维特征进行聚类,根据聚类结果分类语音

  8. 在降维空间中查看种子文件所在位置,提取同一簇语音数据

  9. 进行人工筛选,数据整理

准备工作

  1. 准备好音频文件是第一位的

  2. 选出目标角色的台词

    以云堇为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
有道是闻名不如见面,今天终于有缘与你相会,实属荣幸。我姓云,单名一个堇字,不才正是云翰社当家主事的人。我们戏社最近挂靠在和裕茶馆,日后还请多多赏光,常来听戏。

这两天总觉得身上腰封有点紧,难道…我胖了?

不勒头,不画脸,今天乐得清闲。

云婵娟来花婵娟,风流尽在山水间。

…嘶,你听、听见打雷没有?吓我一跳。

喂呀,冻煞我了。

暖阳和煦,真是好风光呀。

早,清晨露水正好煎茶,喝完这杯茶再出发吧。

午饭吃完之后,想睡一觉…哈…你不困吗?

一道去听听晚场说书吧,刘苏先生讲《裁雨声》是一绝。

夜里寒气重,早点歇息,晚安。

我开蒙早,刚记事的时候父亲就在教我读书了。可惜,他的本事要是有十分,我恐怕只学到了三分。写戏是够用了,但要和饱学之士相比,还差得太远。

我母亲早年是璃月港内大有名气的角儿,我学的戏都是她教的。你是不知道她有多严厉,一想起以前学戏的经历…不不不,我还是别想了。

最近有几场大戏,正好缺一位擅长舞剑的演员,我觉得你各方面都挺适合。怎么样,有兴趣搭班吗?休息时,还能一起品茶。

欸,你还听摇滚?正好正好,下回我想出去听演唱会的时候,就找你一块去。回来的时候,我就说是拜访你去了,这下戏社里的老人肯定找不到借口唠叨我。

这神之眼嘛…说不定岩王爷也喜欢听戏呢?大概他老人家听得开心,就把神之眼送给我了,哈哈。

我最喜欢尝试各种各样的饮品。老一辈发明的那些讲究茶饮,什么松间露呀梅上雪的,都让我试了个遍。现在正愁没有新饮品可以喝呢,欸,你有什么推荐吗?

我偶尔也想脱下戏服,远游海外,去更大的世界看看。为了实现这个心愿,得更加努力才行。

每天天刚破晓就能听见莺雀鸣啭,心情总能变得很好。

戏社里那几个老人天天念叨,说是听多了摇滚会被带坏,不让我去听辛焱的演唱会。嘴上说着为我好,哼,我看他们只是接受不了新事物而已。我才不会听他们的,辛焱的演唱会我每周都要去,就是这样。

胡桃她呀,表情丰富,脑筋还转得快,哪怕只是聊些家常都能「妙语连珠」。真让人羡慕。和这样的朋友在一起,无论做什么事情都很有趣。

我们这行的规矩代代相传,讲究多得很。有些事我只知其然,却不知其所以然。但钟离先生讲起这些竟能如数家珍,有这样懂行的观众,谁不开心呢?

天权星出手阔绰,没少资助我们戏社。领了她的情,自然也要回报给她最好的演出。

原来你也认识行秋。哈哈,他聪明得很,鬼主意那么多,怕是一不留神就会捉弄你,可得留个心眼啊。

《神女劈观》的剧本我早就念得烂熟,但直到今天我才知道「神女」原来真有其人,世上缘分果然奇妙。惟愿她往后都有好友相伴,不再孤寂。

我倒没见过玉衡星来听戏。印象里,路边偶遇这位时,她也总是一副行色匆匆的样子。

爱听戏的外国人士也有不少,这位枫原万叶就是其中之一。不论台上演的是文场还是武场,他总是安安静静地听,是个规矩的好观众。

我们戏社现在常唱的几出戏,有不少是我当家之后新写出来的。写也是我,唱也是我,一人多职,大概是因为这一点,我在璃月港有了些名气。

按照传统,伶人登台之前要勒头、画脸、穿行头,还有其他一些零碎的准备工作。虽然这样「全副武装」之后扮相会更好看,但也累人。所以我能清唱时就清唱,非得彩唱的话,我往往会挑轻便点的行头。
幸好和我们戏社长期合作的和裕茶馆管得宽松,要不然我真是要累惨了。

当初我刚登台唱戏,凭着观众抬爱,不免有些心高气傲。戏本不喜欢就不唱,戏台子小了也不唱,观众少了还是不唱。现在想来,那时候可真是…太傲慢了…以后万万不能这样。

回想起来,云翰社传到我手上也有段时间了。原本我只懂得写戏、唱戏,从来没把心思放在经营交际上。做了戏社的当家之后,才不得不在这些事上多长点心眼,因此体味到不少人情冷暖…
这时候我才真知道,人生如戏,戏如人生啊。

璃月多传说,大都是些魔神、仙人故事,所以璃月戏里最多的就是神仙戏。最近新演的《神女劈观》就属此类。虽然老戏迷们最爱听的就是这类故事,但如今是「人」的时代,我想多写些属于人类的故事,把天下人心唱给大家听。
这些想法,过去我不曾向人提起。多谢你能耐心听我说这些。有一知音,我很幸运。

爱好?当然是戏了。我是打定主意要唱一辈子的。

烦恼…唉,为了保持体型,我平时一口都不能多吃。人生在世,连美食都不能奢。

天枢肉、水煮黑背鲈、干锅腊肉…我都想吃。可惜平时基本吃不了,那个…我就随便问一问,你…会做这几道菜吗?

天天都是清炒虾仁,天天都是…再怎么做都还是那个味道,我是真的吃腻了。

美食,总会让人倍感幸福…可惜只能点到为止,不能再多吃了。

看着就很丰盛!稍等,我去找些饮品来,配着饭菜一起享用吧。

不好意思…这道菜肴呀,我是吃不了多少的。

原来今天是你的生日吗?我没有置办宴席的本事,只能为你唱一曲了。不过,这一曲只为你一个人唱,要听什么?你来定。

俗语说:「业精于勤而荒于嬉。」

勤学苦练至今,总算是有了些成果。

学枪一事,原先是为了台上舞枪好看,后来是为了掌握傍身武技。如今这两个目标都已达到。

枪术千变万化,奥妙无比,让我庆幸的是,练枪时一直有你相伴。我已经领悟到其中旨要。往后切磋时,你可得小心了。

语音识别

音频重采样

原始原神语音采样率 48k,有点高了,需要降到 16k

方法参考 重采样语音

语音识别

可以使用百度语音识别或者 科大讯飞 语音识别

对我来说,二者比较下来讯飞的识别更准,但是免费额度比百度少,新用户5W次,百度有15W 次。

这里分享一下我的 百度识别结果,仅供参考(讯飞的还在跑)。

筛选种子文件

我们现在有角色的语音文字GT,和所有角色的语音识别结果,现在需要将二者对应上,以找到该角色的部分语音。

这里还有个困难是我们的识别结果不一定准,由于是语音识别写成的文字,错别字、多字、少字都很正常,需要在识别结果有误差的情况下对齐文字。

这里我的解决思路是使用去掉标点的文字拼音字符串之间的最长公共子序列的长度来评定是否匹配。

核心功能在我的 python 库 mtutils 中,安装:

1
pip install mtutils

中文句子转成纯拼字字符串

1
2
3
4
5
import mtutils as mt

line = '中文,句子。?'
pinyin_str = mt.chinese_str_to_pinyin(line)
key_str = pinyin_str.replace('_', '')

最长公共子序列

1
2
import mtutils as mt
lsc_str = mt.find_lcseque(str1, str2)

评分标准设置为公共串长度的平方除以两个字符串的长度

1
2
length_match = len(mt.find_lcseque(key_str, target_sen))
match_score = length_match ** 2 / len(key_str) / len(target_sen)

筛选时仅记录得分最高的音频文件。

示例代码

直接贴上我的筛选代码,仅供参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import mtutils as mt


def get_sentences(role_name):
role_sentences = list()
words_file_path = mt.OS_join('base_data', role_name + '.txt')
assert mt.OS_exists(words_file_path)
file_lines = mt.file_read_lines(words_file_path)
file_lines = mt.get_list_from_list(file_lines, lambda x: x if len(x) > 0 else None)
for line in mt.tqdm(file_lines):
pinyin_str = mt.chinese_str_to_pinyin(line)
key_str = pinyin_str.replace('_', '')
role_sentences.append([key_str, line])
return role_sentences

if __name__ == '__main__':
role_name_list = ['神里绫华', '云堇']
for role_name in role_name_list:
role_sentences = get_sentences(role_name)

res_dict = mt.json_load('audio-rec-results.json')

match_res_dict = dict()

for target_sen_group in mt.tqdm(role_sentences):
target_sen, base_sen = target_sen_group

best_score = -1
best_length = 0
best_value = None
best_key = None

for key, value in mt.tqdm(res_dict.items()):
value = value[0]
if len(value) < 1:
continue
pinyin_str = mt.chinese_str_to_pinyin(value)
key_str = pinyin_str.replace('_', '')

length_match = len(mt.find_lcseque(key_str, target_sen))
match_score = length_match ** 2 / len(key_str) / len(target_sen)
if match_score > best_score:
best_score = match_score
best_length = length_match
best_value = value
best_key = key

match_res_dict[base_sen] = {
'target_sen': target_sen,
'base_sen': base_sen,
'target_len': len(target_sen),
'match_len': best_length,
'best_score': best_score,
'match_sen': best_value,
'match_file_name': best_key
}

mt.json_save(match_res_dict, role_name + '_res.json')
pass

这里分享我 云堇 的匹配结果(低分的被我删掉了)。

提取音频文件声纹特征

参考 声纹识别 ECAPA-TDNN

声纹识别可以直接输入 48k 采样率的音频数据,音频文件 57546 个,声纹的特征 192 维,因此得到了 $57546 \times 192$ 的特征矩阵。

三百多M,这里就不上传了。

特征降维

事实上 ECAPA-TDNN 的代码仓库作者使用的声纹匹配是直接用原始 192 维特征计算余弦相似度计算的,两个音频文件声纹特征的余弦相似度高,则是同一个人的,这在方向上是合理的

我尝试了这个方法发现召回率和准确率都不够高,这里我推荐用暴力、直观一点的方法,特征降维。

参考 降维方法 PCA、t-sne、Umap 的 python 实现

我将上述原始特征矩阵在这三种降维方法下做了尝试,二维可视化图像如下:

  • PCA 降维

PCA

  • Umap 降维

  • t-sne 降维

对比起来觉得 T-sne 分得更开,如果一坨就是一个角色就完美了(事实上差不多确实如此)

因此使用 T-sne 方法降维到 2D 数据,现在特征矩阵维度 $57546 \times 2$

特征聚类

可以手动选择tsne 特征上的一个区域,将其中的特征对应的数据筛选出来,看看是不是同一个人的,这样比较麻烦也不够优雅,我尝试了特征聚类

参考:常用聚类算法

DBSCAN 是最合适我的使用场景的,运算占用内存小,不需要设置类别数量,根据密度聚类。

  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from matplotlib import pyplot as plt
import numpy as np
import mtutils as mt

from sklearn.cluster import MiniBatchKMeans
from sklearn.cluster import DBSCAN

figsize=(18.5, 9.4)

fig, ax = plt.subplots(1, 1, figsize=figsize)

data = mt.pickle_load('tsne_fea_save.pickle')

model = DBSCAN(eps=1.5, min_samples=6)

X = data
cluster = model.fit(X)

clusters = np.unique(cluster.labels_)

# 散点图,按分类值着色
for sub_label in clusters[:]:
ax.scatter(X[cluster.labels_==sub_label, 0], X[cluster.labels_==sub_label, 1], color=(np.random.random(), np.random.random(), np.random.random()))

# mt.pickle_save(cluster.labels_, 'fea_label.pickle')
plt.show()

pass
  • 聚类效果

将除了噪声数据外的特征对应的文件按照聚类类别划分到不同文件夹

可视化种子文件位置

将种子文件的特征在降维特征上可视化出来:

那么我们有理由猜想,下图表示的这一坨,就是云堇的语音

人工确认

根据特征位置找到这一坨特征对应的文件,听一遍确认真的是云堇 ~

将这些数据对应的语音识别结果拿到整理出来,再修改修改,就得到了云堇的语音文件和内容标注了 ~

不容易啊 …

挑了一部分特征做了标注,基本上还是准的

参考资料



文章链接:
https://www.zywvvd.com/notes/study/audio/speech-classification/classify-audio-ys/


“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”

微信二维码

微信支付

支付宝二维码

支付宝支付

原神——提瓦特大陆语音分类识别
https://www.zywvvd.com/notes/study/audio/speech-classification/classify-audio-ys/
作者
Yiwei Zhang
发布于
2023年6月28日
许可协议