我试图在java中基于properties(第五个用FFT变换)实现计数互相关。它应该适用于不同长度的信号。我使用普林斯顿大学的简单FFT库(FFT.java和Complex.java)。独立于目标信号,我在第三指数处接收互相关峰值。对于下面的示例,它应该放在0索引处,因为两个信号在前三个位置是相同的。我使该解决方案有可能同步两个不同大小的音频文件(一个是第二个文件的一部分)。
@Test
public void find_synchro_by_cross_correlation() {
double[] source = {1, 2, 3, 4, 5, 6, 7, 8};
double[] target = {1, 2, 3, 0, 0, 0, 0, 0}; //signal padded 0's
int n = source.length;
//creating complex arrays
Complex[] sourceComplex = new Complex[n];
Complex[] targetComplex = new Complex[n];
for (int i = 0; i < n; i++) {
sourceComplex[i] = new Complex(source[i], 0);
targetComplex[i] = new Complex(target[i], 0);
}
//counting FFT of both signals
Complex[] fftS = FFT.fft(sourceComplex);
Complex[] fftT = FFT.fft(targetComplex);
//conjugate the first one
for (int i = 0; i < fftS.length; i++) {
fftS[i] = fftS[i].conjugate();
}
// point-wise multiply
Complex[] timeProduct = new Complex[fftS.length];
for (int i = 0; i < fftS.length; i++) {
timeProduct[i] = fftS[i].times(fftT[i]);
}
// compute inverse FFT
Complex[] y = FFT.ifft(timeProduct);
System.out.println("id:\txcorr abs:\t\t\txcorr complexes:");
for (int i = 0; i < y.length; i++) {
Complex c = y[i];
System.out.println(i + "\t" +c.abs() + "\t\t\t" + c.toString());
}
System.out.println("Max arg: " + argmax(y));
Assert.assertEquals(0, DSP.argmax(y)); //I assume that the peak should be at 0 index
}
public static int argmax(Complex[] a)
{
double y = Double.MIN_VALUE;
int idx = -1;
for(int x = 0; x < a.length; x++)
{
if(a[x].abs() > y)
{
y = a[x].abs();
idx = x;
}
}
return idx;
}
调用结果:
id: xcorr abs: xcorr complexes:
0 14.0 14.0 + 5.551115123125783E-16i
1 16.0 16.0 + 1.133107779529596E-15i
2 26.0 26.0 - 8.881784197001252E-16i
3 44.0 44.0 - 2.021286199229721E-15i
4 38.0 38.0 - 5.551115123125783E-16i
5 32.0 32.0 - 6.432490598706545E-16i
6 26.0 26.0 + 8.881784197001252E-16i
7 20.0 20.0 + 1.5314274795707798E-15i
Max arg: 3 //I thought it should equals 0
对于参考/调试目的,这里有一个gist,其中包含下载完整代码的来源。
matlab / octave代码可以正常工作(但它仍然没有使用FFT算法):
pkg load signal
x = [2 3 4];
y = 1:1:10;
nx = length(x);
ny = length(y);
cc = nan(1,ny-nx+1);
for ii = 0 : ny-nx
id = (1:nx) + ii;
cc(ii+1) = sum(x.*y(id))/(sqrt(sum(x.^2)*sum(y(id).^2)));
end
[ccmx,idmx] = max(cc)
对于这个问题还有另一个解决方案,但是它仍需要很长时间,解决方案的复杂性是n ^ 2 lgn
Y = [2 3 4];
X = 1:1:10;
lngX = length(X);
lngY = length(Y);
assert(lngX >= lngY);
lags = 0:(lngX-lngY);
for i = lags
c(i+1) = xcorr(X(i+1:i+lngY) - mean(X(i+1:i+lngY)), Y - mean(Y),0,'coeff');
end
[m,i]=max(c);
printf('max=%f, lag=%d\n',c(i),lags(i));
plot(lags,c,'-',lags(i),c(i),'*r');
更新
我找到了一种解决方案,但我对其复杂性并不满意。我重写了最后一个matlab示例。我只计算一次较短信号的FFT以及系数的一部分(因为效率)。我计算每个可能滞后的0偏移(系数数组中的第一个元素)的相关性。此计算的峰值显示已滞后的信号量。如果您想在长度不同于n = power2的阵列上计算FFT,则必须更改FFT库。您可以使用FFTW java包装器或JTransforms。您可以在下面阅读代码。
@Test
public void find_synchro_by_circular_convolution() {
double[] source = {1, 2, 3, 4, 5, 6, 7, 8};
double[] target = {4, 5, 6, 7};
int n = target.length;
int lags = source.length - n + 1;
//normalize(target);
Complex[] targetComplex = convertToComplex(target);
double sumsqTarget = sumsq(target);
Complex[] fftTarget = FFT.fft(targetComplex);
Complex[] c = new Complex[lags];
for (int i = 0; i < lags; i++) {
//double[] shrinkedSource = normalize(source, i, n);
double[] shrinkedSource = new double[n];
shrinkArray(source, shrinkedSource, i, n);
Complex[] sourceComplex = convertToComplex(shrinkedSource);
//cross correlation coefficient
Complex[] fftSource = FFT.fft(sourceComplex);
conjugate(fftSource);
multiplyComplexXInPlace(fftSource, fftTarget);
Complex[] y = FFT.ifft(fftSource);
double rms = rms(sumsqTarget, shrinkedSource);
c[i] = y[0].scale(rms);
}
System.out.println("id:\txcorr abs:\t\t\txcorr complexes:");
for (int i = 0; i < lags; i++) {
Complex ci = c[i];
System.out.println(i + "\t" +ci.abs() + "\t\t\t" + ci.toString());
}
System.out.println("Max arg: " + DSP.argmax(c));
}
@NotNull
private Complex[] convertToComplex(double[] target) {
int n = target.length;
Complex[] targetComplex = new Complex[n];
for (int i = 0; i < n; i++) {
targetComplex[i] = new Complex(target[i], 0);
}
return targetComplex;
}
private void multiplyComplexXInPlace(Complex[] x, Complex[] y) {
for (int j = 0; j < x.length; j++) {
x[j] = x[j].times(y[j]);
}
}
private void conjugate(Complex[] fftSource) {
for (int j = 0; j < fftSource.length; j++) {
fftSource[j] = fftSource[j].conjugate();
}
}
private double[] normalize(double[] source, int i, int n) {
double[] normalized = new double[n];
shrinkArray(source, normalized, i, n);
normalize(normalized);
return normalized;
}
private void normalize(double[] target) {
double meanTarget = mean(target);
for (int i = 0; i < target.length; i++) {
target[i] = target[i] - meanTarget;
}
}
private void shrinkArray(double[] source, double[] destination, int startPosition, int length) {
System.arraycopy(source, startPosition, destination, 0, length);
}
private double mean(double[] x) {
double sum = 0;
int n = x.length;
if(n != 0) {
for (int i = 0; i < n; i++) {
sum += x[i];
}
return sum / x.length;
}
return sum;
}
private double rms(double cached_sumsq_x, float[] y) {
double sumsq_x = cached_sumsq_x;
double sumsq_y = sumsq(y);
return 1/Math.sqrt(sumsq_x*sumsq_y);
}
private double rms(double cached_sumsq_x, double[] y) {
double sumsq_x = cached_sumsq_x;
double sumsq_y = sumsq(y);
return 1/Math.sqrt(sumsq_x*sumsq_y);
}
private double rms(float[] x, float[] y) {
double sumsq_x = sumsq(x);
double sumsq_y = sumsq(y);
return 1/Math.sqrt(sumsq_x*sumsq_y);
}
private double sumsq(float[] x) {
double sumsq = 0.0;
int n = x.length;
for (int i = 0; i < n; i++) {
sumsq += x[i]*x[i];
}
return sumsq;
}
private double sumsq(double[] x) {
double sumsq = 0.0;
int n = x.length;
for (int i = 0; i < n; i++) {
sumsq += x[i]*x[i];
}
return sumsq;
}
代码的结果:
id: xcorr abs: xcorr complexes:
0 0.9759000729485332 0.9759000729485332
1 0.9941037221861875 0.9941037221861875
2 0.9990767240206739 0.9990767240206739
3 1.0 1.0
4 0.9995437747834998 0.9995437747834998
Max arg: 3
但是......来吧。简单的线性溶胶更快!在频域中计算它真的很难吗?
import static java.lang.Math.abs;
public class LinearSynchronisation {
public long findSynchroSamples(Float[] longFile, Float[] shortfile) {
double lowestDifference = Double.MAX_VALUE;
long maxCorrelationSampleNumer = 0;
for (int j = 0; j < longFile.length - shortfile.length; j++) {
double diff = getCorrelation(longFile, shortfile, j);
if (diff < lowestDifference) {
maxCorrelationSampleNumer = j;
lowestDifference = diff;
}
}
return maxCorrelationSampleNumer;
}
private double getCorrelation(Float[] longFile, Float[] shortFile, int start) {
double diff = 0;
for (int i = 0; i < shortFile.length; i++) {
diff += abs(longFile[start+i] - shortFile[i]);
}
return diff;
}
public static void main(String[] args) {
Float[] source = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
Float[] target = {4.0f, 5.0f, 6.0f, 7.0f};
LinearSynchronisation ls = new LinearSynchronisation();
long synchroSamples = ls.findSynchroSamples(source, target);
System.out.println(synchroSamples);
}
}