概念理解
- 语音识别:通过一种语音检索算法来识别一段音频内容的含义。
- 音频文件:指带有声音的文件,比如音乐。
- MFCC:音频文件转为数字的过程
- 时域:振幅Y和时间T的关系。
- 频域:振幅Y和频率F的关系。
- 短时傅里叶变换:通过对每个时间极短的音频片段做傅里叶变换,来得到这个时间段的频率分布,之所以成为短时,是因为这个算法只有再短时内语音音频的频率以及振幅是比较平稳的,如果时间过长就会导致傅里叶变换不准确。
- 频谱图:每个时间点出现的频率分布图,即每一帧或者每时刻上出现的频率数值和振幅数值,为了用二维数据表示这个三维数据,把振幅压缩到二维平面中,使用颜色的浅重来表示振幅的大小。[见图1A]
- 稀疏矩阵/星云图:经过处理的频谱图,把频谱图颜色重的变成星星【锚点】,把其它变成空白。[见图1B]
- 指纹哈希:指纹哈希是由星座图组成的,星座图中时间-频率点对组合在一起。选择锚点,每个锚点都有一个与之相关联的目标区域。每个锚点依次与目标区域内的点配对,每一对产生两个频率分量加上点之间的时间差.[见图1C]
- Hash:哈希值,32位的不可重复的数字,数据库用来存储数据的一种方式,优点查询速度快,缺点存储空间剧增,典型的拿空间换时间。
- time:时间值,32位不可重复的数字
- 哈希·时间偏移记录:Hash:time = [f1:f2:?t]:t1?[见图1D]
- 扇出因子:在计算哈系数时,一个锚点的目标区域中一次处理相关锚点的个数,可理解为KNN中的K值,计算相邻点的个数
- 密度因子:单位区域内锚点的个数
- 每秒处理音频记录的哈希数 = 每秒星座点的密度因子?* 进入目标区域的扇出因子[F],也就是为什么扇出因子直接导致存储空间成本的变化
- 噪音:
- 降噪算法:【腐蚀算法】一种降噪的算法,类似于图形算法里面的原理https://jingyan.baidu.com/article/f96699bbf99d9e894f3c1b4c.html
- 张量:维度的意思
- 阈值:满足条件的最小值或者最大值
算法原理
由于商业原因,以下只是部分代码,只讲了大概原理,其原理是通过给歌曲建立指纹库来验证被检测样本是哪个歌曲,如果某段歌曲匹配了,那么它们在时间上会出现线性关系。
具体步骤如下:
A、建立歌曲指纹
1、读取歌曲
通过librosa包获取语音数据
# 语音预处理,生成频谱数据
def _pre_music(self, music_path):
# 音频时间序列,音频的采样率
y, sr = librosa.load(
path=music_path,
sr=hp.fingerprint.core.sr,
)
arr2D = librosa.stft(
y=y,
n_fft=hp.fingerprint.core.n_fft,
hop_length=hp.fingerprint.core.hop_length,
win_length=hp.fingerprint.core.win_length
)
return np.abs(arr2D) ** 2
2、获取频谱图
通过Au软件可以看出来,音频的频谱图
??随着每个时间点的放大,可以看到每一个采样点的之间的频谱图
?把声谱图转为频谱图的过程是通过MFCC得到的,有兴趣的可以自己搜索相关技术文章,这属于信号处理专业的相关知识。
转为频谱图:shape = [频率,帧数]
# 生成频谱图
arr2D = mlab.specgram(
# 数据
channel_samples,
# 窗口大小
NFFT=wsize,
# 采样率
Fs=Fs,
# 加窗
window=mlab.window_hanning,
# 重叠率
noverlap=int(wsize * wratio))[0]
# (2049, 5027)
# print(np.shape(arr2D))
3、频谱图转为星图
把频谱图里面的频率0变为最小值,防止无法取对数
取对数,作用:某些值过大
取对数之后会出现无穷小的值,把这些值变为0
求局部最大值【即锚点-星星】,neighborhood控制着样本密度
# maximun_filter 找到局部最大值所对应的坐标
struct = generate_binary_structure(2, 2) # 2维 离中心点一个距离
neighborhood = iterate_structure(struct, hp.fingerprint.core.neighborhood) # 中心点扩大到离中心点40个距离
local_max = maximum_filter(spectrogram, footprint=neighborhood) == spectrogram
# 局部最大值
amps = spectrogram[local_max]
获取局部最大值的坐标【即锚点坐标】:[时间,频率], shape = [y,x] = [频率,时间]
# 获得peakes 是按[行,列]返回的:j是频率 i是时间
j, i = np.where(local_max)
?通过过滤能量最小值来减少噪音
4、存储指纹和歌曲ID
根据时间进行排序
peakes = sorted(peakes)
时间偏移坐标:t1
一个锚点的哈希个数取决于,当前锚点与其它锚点计算的范围:0<t1<200,样本密度和扇出因子是控制哈希·时间偏移记录的数量。
# 锚点
for i in range(len(peakes)):
# 近邻点
for j in range(1, hp.fingerprint.core.near_num):
# 判断下标是否越界
if (i + j) < len(peakes):
t_1 = peakes[i][0]
t_2 = peakes[i + j][0]
f_1 = peakes[i][1]
f_2 = peakes[i + j][1]
delta_t = t_2 - t_1
?建立每个锚点的哈希值:[f1,f2,delta_t]
hash_str = "%s|%s|%s" % (str(freq1), str(freq2), str(t_delta))
h = hashlib.sha1(hash_str.encode('utf-8'))
yield (h.hexdigest()[0:20], t1)
数据库中的存储的数据是:哈希值,时间偏移记录,歌曲ID,
B、待测歌曲样本
获取待测样本数据,通过短时傅里叶变换转为频谱图
def get_audio():
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter)
parser.add_argument('-s', '--seconds', nargs='?')
args = parser.parse_args()
if not args.seconds:
args.seconds = "7"
seconds = int(args.seconds)
chunksize = 2 ** 12
channels = 1
record_forever = False
listener = Listener()
listener.start_recording(
seconds=seconds,
chunksize=chunksize,
channels=channels
)
while True:
bufferSize = int(listener.rate / listener.chunksize * seconds)
print("正在录音")
for i in range(0, bufferSize):
number = listener.process_recording()
if not record_forever: break
listener.stop_recording()
print("录音停止")
listener.save_recorded('xxx.mp3')
return './xxx.mp3'
?样本频谱图
样本星座图?
- 用最小值替换全0,取log能量对数,把负无穷小取0
- 通过maximum_filter()获取最大值坐标,得到星座图
- 最后通过偏离每个锚点,计算得到每个锚点和目标区域的Hash:time
- 在数据库中通过查询hash来得到匹配的match_hashes,以及t_offset
- 统计match_hashes中每个t_offset所对应的歌曲id个数
- 个数最多的歌曲id就是该歌曲,t_offset是开始的地方
if len(matches) > 0:
match_collections = {}
max_count = 0
start_time = None
final_music_id = None
x, y, z = [], [], []
for matche in matches:
# 数据库t 样本t
music_id, t, t_sample = matche
x.append(int(t))
y.append(t_sample)
z.append(music_id)
# 偏移值
offset = int(t) - t_sample
# 判断字典里面是否有这个偏移
if offset not in match_collections:
match_collections[offset] = {}
pass
# 判断字典里面是否有这个music_id
if music_id not in match_collections[offset]:
match_collections[offset][music_id] = 0
pass
match_collections[offset][music_id] += 1
# 判断这个偏移下的music_id是否是最多
if match_collections[offset][music_id] > max_count:
max_count = match_collections[offset][music_id]
start_time = offset
final_music_id = music_id
print(x, y, z)
drow_plot(x, y, z, show=True)
print(match_collections, max_count)
# 开始匹配时间
print((start_time * hp.fingerprint.hop_length / hp.fingerprint.sr))
music_name = connector.find_music_by_id(final_music_id)
print(music_name)
结果
线性关系图:
?
|