在音频文件中查找摘录开始位置:Python中两个数组的互相关系数
我遇到了一个问题,真是让人抓狂,之前在StackOverflow上找到的答案都没能帮到我,所以我来请教大家。
整体问题
我想创建一个函数,能够找到一个音频片段在一个较大音频文件中的确切开始时间戳。为了测试,我使用了一个5分钟的音频文件和其中的一个43秒的片段。下面是我在Audacity中对齐的两个音频文件:这个片段正好在00:01:55.554920开始。
我还希望这个函数只有在置信值超过某个阈值时才返回一个值,这个阈值会作为函数的一个参数。我打算通过检查两个对齐信号之间的相关系数是否超过给定的阈值来实现这一点。
换句话说,这里有一个简化版的代码:
find_excerpt_starting_sample(original_audio, excerpt, threshold):
# Find the cross-correlation coefficients for each lag
xcorr = cross_correlation(original_audio, excerpt)
# Return the lag of the max correlation if it is over threshold
if np.max(xcorr) > threshold:
return np.argmax(xcorr)
else:
raise Exception("No correlation over threshold found.")
我一直在寻找合适的cross_correlation
函数,但我的尝试都没有返回一个在0到1之间的数组。
简化的问题
由于我在音频文件上的尝试没有结果,我尝试在两个数字数组上做同样的事情:
y1 = [2, 22, 14, 8, 0, 4, 8, 16, 26, 6, 12, 14, 16, 2, 6]
y2 = [4, 8, 16, 26, 6, 12]
这里,y2是y1的一个子集(从索引5开始)。为了确保函数独立于幅度尺度,我将y2的所有值都减半:
y1 = [2, 22, 14, 8, 0, 4, 8, 16, 26, 6, 12, 14, 16, 2, 6]
y2 = [2, 4, 8, 13, 3, 6]
我想创建一个交叉相关函数,返回一个数组,其中滞后5的值为1。
我到目前为止的尝试
np.corrcoef
如果我们只是做一个简单的相关性,并将片段滑动到原始音频上,这样是有效的:
import numpy as np
import matplotlib as plt
corr = np.zeros(len(y1) - len(y2))
for i in range(len(y1) - len(y2)):
corr[i] = np.corrcoef(y1[i:i+len(y2)], y2)[0][1]
print(corr)
plt.plot(corr)
plt.show()
输出结果是:
[ 0.18961375 -0.71250433 -0.56075283 -0.08468414 0.21913077 1. -0.04179451 -0.46803451 -0.24815461]
但问题是,这种方法对于较长的文件来说真的很低效。
scipy.signal.correlate
现在,我开始使用Stack Overflow上找到的一个主要解决方案,也就是scipy.signal的correlate函数。它返回找到的正确滞后值。然而,由于它执行的是卷积,所以无法量化相关性。
from scipy import signal
xcorr = signal.correlate(y1, y2, mode="full")
lags = signal.correlation_lags(len(y1), len(y2), mode="full")
print(xcorr)
print(lags)
plt.plot(lags, xcorr)
plt.show()
输出结果是:
[ 12 138 176 392 390 332 224 232 356 402 596 486 478 414 422 252 186 88 28 12]
[-5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
我看到了一些解决方案,但它们并没有按我想要的方式工作。
首先,一个解决方案在这里建议使用这个函数来归一化系数:
corr = signal.correlate(y1 / np.std(y1), y2 / np.std(y2), 'full') / min(len(y1), len(y2))
lags = signal.correlation_lags(len(y1), len(y2), mode="full")
print(c)
plt.plot(lags, c)
plt.show()
输出结果是:
[0.0736392 0.84685082 1.08004163 2.40554727 2.39327407 2.03735126 1.37459844 1.42369124 2.18462966 2.46691327 3.65741371 2.98238769 2.93329489 2.54055247 2.58964528 1.54642325 1.14140763 0.54002082 0.17182481 0.0736392 ]
如你所见,最大值不是1,而是3.65741371。
然后我尝试了另一个在这里找到的解决方案:
y1n = y1 / np.std(y1)
y2n = y2 / np.std(y2)
xcorr = signal.correlate(y1n, y2n, mode="full")
lags = signal.correlation_lags(len(y1), len(y2), mode="full")
print(xcorr)
plt.plot(lags, xcorr)
plt.show()
输出结果是:
[ 0.44183521 5.08110495 6.48024979 14.43328362 14.35964442 12.22410756 8.24759064 8.54214745 13.10777798 14.80147963 21.94448224 17.89432612 17.59976931 15.24331484 15.53787165 9.27853947 6.8484458 3.24012489 1.03094883 0.44183521]
再次强调,交叉相关的最大值不是1,而是21.94448224。
求助
关于相关性我还有很多不懂的地方,我已经深入研究过,但在深入之前,我想请教一下,如果你们能指点我正确的方向,以及我到目前为止做错了什么。
非常感谢!
2 个回答
我根据@Onyambu的代码做了一些调整(再次感谢你的帮助!),把复杂的卷积操作换成了signal.correlate()
。这是我得到的结果:
import numpy as np
def cross_correlation(y1, y2):
y2_normalized = (y2 - y2.mean()) / y2.std() / np.sqrt(y2.size)
y1_m = signal.correlate(y1, np.ones(y2.size), 'valid') ** 2 / y2_normalized.size
y1_m2 = signal.correlate(y1 ** 2, np.ones(y2.size), "valid")
cross_correlation = signal.correlate(y1, y2_normalized, "valid") / np.sqrt(y1_m2 - y1_m)
这个代码在对音频文件进行交叉相关时速度快多了;我提到的那些音频片段(分别是5分钟43秒,采样率为44100 Hz)的执行时间不到2秒。
峰值为1.0,精确到毫秒。
注意:在进行交叉相关之前,我使用y_env = np.abs(scipy.signal.hilbert(y))
获取了两个音频文件的包络线,并用b, a = scipy.signal.butter(2, filter_over, "low", fs=44100)
和y_filt = lfilter(b, a, y_env)
进行了50 Hz的低通滤波。如果你有很长的数据,也可以在任何时候进行降采样。
你可以用卷积来解决这个问题:
def cross_corr(x, y):
x = np.array(x)
y = np.array(y[::-1])
yi = (y - y.mean())/ y.std() / np.sqrt(y.size)
x_m = np.convolve(x, np.ones(yi.size), 'valid')**2/yi.size
x_m2 = np.convolve(x**2, np.ones(yi.size), 'valid')
return np.convolve(x, yi, 'valid')/np.sqrt(x_m2 - x_m)
cross_corr(y1,y2)
array([ 0.18961375, -0.71250433, -0.56075283, -0.08468414, 0.21913077,
1. , -0.04179451, -0.46803451, -0.24815461, 0.77716484])
这个方法比最初的解决方案快很多倍。