генерация правильной спектрограммы с использованием функции fftw и окна

Для проекта мне нужно иметь возможность генерировать спектрограмму из файла .WAV. Я прочитал следующее должно быть сделано:

  1. Получить N (преобразование размера) образцов
  2. Применить окно функция
  3. Выполните быстрое преобразование Фурье, используя образцы
  4. Нормализовать вывод
  5. Генерация спектрограммы

На изображении ниже вы видите две спектрограммы синусоидальной волны 10000 Гц, используя хэннинг оконная функция. Слева вы видите спектрограмму, сгенерированную дерзость и справа моя версия. Как вы можете видеть, в моей версии намного больше линий / шумов. Это утечка в разных баках? Как бы я получил четкое изображение, подобное тому, которое создает смелость. Должен ли я сделать некоторую постобработку? Я еще не сделал никакой нормализации, потому что не до конца понимаю, как это сделать.

введите описание изображения здесь

Обновить

я нашел этот учебное пособие, объясняющее, как генерировать спектрограмму в C ++. Я собрал источник, чтобы увидеть, какие различия я смог найти.

Моя математика очень ржавая, если честно, поэтому я не уверен, что здесь делает нормализация:

    for(i = 0; i < half; i++){
out[i][0] *= (2./transform_size);
out[i][6] *= (2./transform_size);
processed[i] = out[i][0]*out[i][0] + out[i][7]*out[i][8];
//sets values between 0 and 1?
processed[i] =10. * (log (processed[i] + 1e-6)/log(10)) /-60.;
}

после этого я получил это изображение (кстати, я перевернул цвета):

введите описание изображения здесь

Затем я взглянул на разницу входных сэмплов, предоставленных моей звуковой библиотекой и учебной. Мои были намного выше, поэтому я вручную нормализовал это, разделив его на коэффициент 32767,9. Я тогда иду это изображение, которое выглядит довольно хорошо, я думаю. Но деление на это число кажется неправильным. И я хотел бы увидеть другое решение.

введите описание изображения здесь

Вот полный соответствующий исходный код.

void Spectrogram::process(){
int i;
int transform_size = 1024;
int half = transform_size/2;
int step_size = transform_size/2;
double in[transform_size];
double processed[half];
fftw_complex *out;
fftw_plan p;

out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * transform_size);for(int x=0; x < wavFile->getSamples()/step_size; x++){

int j = 0;
for(i = step_size*x; i < (x * step_size) + transform_size - 1; i++, j++){
in[j] = wavFile->getSample(i)/32767.9;
}

//apply window function
for(i = 0; i < transform_size; i++){
in[i] *= windowHanning(i, transform_size);
//            in[i] *= windowBlackmanHarris(i, transform_size);
}

p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE);

fftw_execute(p); /* repeat as needed */

for(i = 0; i < half; i++){
out[i][0] *= (2./transform_size);
out[i][11] *= (2./transform_size);
processed[i] = out[i][0]*out[i][0] + out[i][12]*out[i][13];
processed[i] =10. * (log (processed[i] + 1e-6)/log(10)) /-60.;
}

for (i = 0; i < half; i++){
if(processed[i] > 0.99)
processed[i] = 1;
In->setPixel(x,(half-1)-i,processed[i]*255);
}}fftw_destroy_plan(p);
fftw_free(out);
}

9

Решение

Это не совсем ответ относительно того, что не так, а скорее пошаговая процедура для отладки.

  1. Как вы думаете, что делает эта линия? processed[i] = out[i][0]*out[i][0] + out[i][12]*out[i][13] Вероятно, это неверно: fftw_complex typedef double fftw_complex[2]так что у вас есть только out[i][0] а также out[i][1]где первая — это действительная, а вторая — мнимая часть результата для этого бина. Если массив является непрерывным в памяти (что это такое), то out[i][12] вероятно так же, как out[i+6][0] и так далее. Что-нибудь из этого будут пройти за конец массива, добавив случайные значения.

  2. Правильно ли работает ваша оконная функция? Распечатайте windowHanning (i, transform_size) для каждого i и сравните с эталонной версией (например, numpy.hanning или эквивалентом matlab). Это наиболее вероятная причина, то, что вы видите, выглядит как плохая оконная функция, вроде.

  3. Распечатайте обработанный и сравните с эталонной версией (при том же вводе, конечно, вам придется распечатать ввод и переформатировать его для подачи в pylab / matlab и т. Д.). Однако, -60 и 1e-6 — это факторы выдумки, которые вам не нужны, один и тот же эффект лучше сделать другим способом. Рассчитайте так:

    power_in_db[i] = 10 * log(out[i][0]*out[i][0] + out[i][1]*out[i][1])/log(10)
    
  4. Распечатайте значения power_in_db[i] для того же я, но для всех х (горизонтальная линия). Они примерно одинаковы?

  5. Если все пока хорошо, оставшийся подозреваемый устанавливает значения пикселей. Будьте очень откровенны в отношении отсечения до диапазона, масштабирования и округления.

    int pixel_value = (int)round( 255 * (power_in_db[i] - min_db) / (max_db - min_db) );
    if (pixel_value < 0) { pixel_value = 0; }
    if (pixel_value > 255) { pixel_value = 255; }
    

Здесь, опять же, распечатайте значения в горизонтальной линии и сравните со значениями в градациях серого в вашей программе (вручную, используя палитру цветов в фотошопе или gimp или аналогичном).

На этом этапе вы будете проверять все от начала до конца и, вероятно, найдете ошибку.

6

Другие решения

Код, который вы произвели, был почти правильным. Итак, вы не оставили меня много, чтобы исправить:

void Spectrogram::process(){
int transform_size = 1024;
int half = transform_size/2;
int step_size = transform_size/2;
double in[transform_size];
double processed[half];
fftw_complex *out;
fftw_plan p;

out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * transform_size);for (int x=0; x < wavFile->getSamples()/step_size; x++) {

// Fill the transformation array with a sample frame and apply the window function.
// Normalization is performed later
// (One error was here: you didn't set the last value of the array in)
for (int j = 0, int i = x * step_size; i < x * step_size + transform_size; i++, j++)
in[j] = wavFile->getSample(i) * windowHanning(j, transform_size);

p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE);

fftw_execute(p); /* repeat as needed */

for (int i=0; i < half; i++) {
// (Here were some flaws concerning the access of the complex values)
out[i][0] *= (2./transform_size);                         // real values
out[i][1] *= (2./transform_size);                         // complex values
processed[i] = out[i][0]*out[i][0] + out[i][1]*out[i][1]; // power spectrum
processed[i] = 10./log(10.) * log(processed[i] + 1e-6);   // dB

// The resulting spectral values in 'processed' are in dB and related to a maximum
// value of about 96dB. Normalization to a value range between 0 and 1 can be done
// in several ways. I would suggest to set values below 0dB to 0dB and divide by 96dB:

// Transform all dB values to a range between 0 and 1:
if (processed[i] <= 0) {
processed[i] = 0;
} else {
processed[i] /= 96.;             // Reduce the divisor if you prefer darker peaks
if (processed[i] > 1)
processed[i] = 1;
}

In->setPixel(x,(half-1)-i,processed[i]*255);
}

// This should be called each time fftw_plan_dft_r2c_1d()
// was called to avoid a memory leak:
fftw_destroy_plan(p);
}

fftw_free(out);
}

Две исправленные ошибки, скорее всего, были ответственны за небольшое изменение результатов последовательных преобразований. Окно Хеннинга очень хорошо подходит для минимизации «шума», поэтому другое окно не решило бы проблему (на самом деле @Alex я уже указал на 2-ю ошибку в его пункте 2. Но в его пункте 3. он добавил -Inf -bug as log (0) не определено, что может произойти, если ваш волновой файл содержит отрезок с точными значениями 0. Чтобы избежать этого, константа 1e-6 достаточно хороша).

Не спрашивается, но есть некоторые оптимизации:

  1. положил p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE); вне основного цикла,

  2. предварительно рассчитать оконную функцию вне основного цикла,

  3. отказаться от массива processed и просто использовать временную переменную для хранения одной спектральной линии за раз,

  4. два умножения out[i][0] а также out[i][1] можно отказаться в пользу одного умножения с константой в следующей строке. Я оставил это (и другие вещи) для вас, чтобы улучшить

  5. Благодаря @Maxime Coorevits также можно избежать утечки памяти: «Каждый раз, когда вы звоните fftw_plan_dft_rc2_1d() память выделена FFTW3. В вашем коде вы звоните только fftw_destroy_plan() вне внешней петли. Но на самом деле, вы должны называть это каждый раз, когда вы запрашиваете план «.

4

Audacity обычно не отображает один частотный интервал на одну горизонтальную линию, ни один период дискретизации на одну вертикальную линию. Визуальный эффект в Audacity может быть связан с передискретизацией изображения спектрограммы, чтобы соответствовать области рисования.

2
По вопросам рекламы [email protected]