LINUX.ORG.RU

Применение FFTW для рисования спектрограммы

 , , , ,


0

2

Пытаюсь спектрограмму сигнала нарисовать, есть вот такой тестовый код:

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <math.h>
#include <complex.h>
#include <fftw3.h>

#define SQR(x) ((x)*(x))
#define XRESOL 1024
#define YRESOL 768

uint8_t pic[YRESOL][XRESOL] = {0};

#define FFT_CHUNK 512

void fft_calculate(double in[FFT_CHUNK], double complex out[(FFT_CHUNK/2)+1])
{
  fftw_plan p = fftw_plan_dft_r2c_1d(FFT_CHUNK, in, out, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT);
  fftw_execute(p);
  fftw_destroy_plan(p);
}

void convert(double complex in[(FFT_CHUNK/2)+1], double out[(FFT_CHUNK/2)+1])
{
  for (size_t i = 0; i < (FFT_CHUNK/2)+1; i++)
  {
    out[i] = sqrt(SQR(creal(in[i])) + SQR(cimag(in[i])));
  }
}

int main(void)
{
  double arr[XRESOL];
  for (size_t i = 0; i < XRESOL; i++)
  {
    arr[i] =
      sin((double)i * M_2_PI * 0.2)*0.1 +
      sin((double)i * M_2_PI * 0.4)*0.1 +
      sin((double)i * M_2_PI * 0.6)*0.1 +
      sin((double)i * M_2_PI * 0.8)*0.1 +
      sin((double)i * M_2_PI * 1.0)*0.1;
  }
  for (size_t i = 0; i < XRESOL - FFT_CHUNK; i++)
  {
    double complex tmp[(FFT_CHUNK/2)+1];
    double result[(FFT_CHUNK/2)+1];
    fft_calculate(&arr[i], tmp);
    convert(tmp, result);
    for (size_t j = 0; j < (FFT_CHUNK/2)+1; j++)
    {
      #define MULT 10
      pic[j][i] = (result[j] * MULT) > 0xff ? 0xff : (uint8_t)(result[j]* MULT);
      #undef MULT
    }
  }

  fwrite(pic, sizeof(pic), 1, stdout);
  return EXIT_SUCCESS;
}
Собрать-запустить можно вот так:
gcc main.c -lfftw3 -lm
./a.out | display -size 1024x768 -depth 8 gray:-
И оно нарисует пять полосок. Понятно, что в функции fft_calculate() можно переиспользовать план, как указано в этих доках http://www.fftw.org/fftw3_doc/New_002darray-Execute-Functions.html а не создавать каждый раз. Но вот как там с real to real функциями из этого fftw работать? И нужно ли их вообще тут использовать?

Вот эта функция

void convert(double complex in[(FFT_CHUNK/2)+1], double out[(FFT_CHUNK/2)+1])
{
  for (size_t i = 0; i < (FFT_CHUNK/2)+1; i++)
  {
    out[i] = sqrt(SQR(creal(in[i])) + SQR(cimag(in[i])));
  }
}
Правильно ли это? Насколько это вообще корректно? Может тут стоит использовать real to real функции, а не суммировать квадраты действительной и мнимой части, извлпкая потом квадратный корень?

http://www.fftw.org/fftw3_doc/Real_002dto_002dReal-Transforms.html для real to real преобразования там есть параметр fftw_r2r_kind

fftw_plan fftw_plan_r2r_1d(int n, double *in, double *out,
                           fftw_r2r_kind kind, unsigned flags);
Там есть много этих кайндов: http://www.fftw.org/fftw3_doc/Real_002dto_002dReal-Transform-Kinds.html какой из них мне надо использовать? Я попробовал так сделать
void fft_calculate(double in[FFT_CHUNK], double out[FFT_CHUNK])
{
  fftw_plan p = fftw_plan_r2r_1d(FFT_CHUNK, in, out, FFTW_DHT, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT);
  fftw_execute(p);
  fftw_destroy_plan(p);
}
но получилась какая-то ерунда

★★★★★

Последнее исправление: SZT (всего исправлений: 2)

Из info fftw:

The plan can be reused as many times as needed.

Лично я рисую спектр через fftw_plan_dft_r2c_1d(), выходит просто великолепно! В цикле только обновляется буфер и вызывается fftw_execute(plan).

anonymous
()
Ответ на: комментарий от anonymous

Ну так эта функция fftw_plan_dft_r2c_1d() возвращает комплексный результат т.е. амплитудный и фазовый спектры, а как их правильней всего наложить друг на друга, чтоб получить одну спектрограмму? И зачем тогда real to real функции в этом FFTW?

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

r2r transforms currently fall into three categories: DFTs of real input and complex-Hermitian output in halfcomplex format, DFTs of real input with even/odd symmetry (a.k.a. discrete cosine/sine transforms, DCTs/DSTs), and discrete Hartley transforms (DHTs), all described in more detail by the following sections

Если ты шаришь в этих тонкостях, то, наверное, у тебя не должно было возникнуть вопросов. Мне проще через r2c.

anonymous
()
Ответ на: комментарий от SZT

спектрограмму

Что, в твоём понимании, есть «спектрограмма»? Если на одном графике хочешь отобразить и спектр и фазу, то тебе нужно дополнительное измерение к экранным двум.

anonymous
()
Ответ на: комментарий от anonymous

Спектрограмма в моем понимании это двухмерный график, по оси y отмечены частоты, по оси x - время. Яркость конкретного «пикселя» на этом графике характеризует то, насколько сильно выражена вот такая-то частота в такой-то момент времени в сигнале. Если строить спектрограмму для y=sin(x)+sin(x*2) то там будет две параллельные прямые для соответствующих частот. А если сделать частотную модуляцию, т.е. например если частота будет возрастать и убывать по какому-то периодическому закону, то на этом графике будет кривая, по которой будет видно периодическое изменение частоты. В общем как-то так.

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

Тогда, я понимаю, фаза тебе не важна. Считай амплитуду по формуле.

К слову, формула, с которой у меня получилась лучшая картинка спектрограммы выглядит так: amp = sqrt(mag / sqrt(2*N) / 2) / 2; mag считается по формуле выше.

Вместе с логарифмической шкалой и линейной интерполяцией получаются, например, вот такие картинки из Aphex Twin - Equation: https://i.imgur.com/BxgcYtq.png

anonymous
()

Можно SDL2_Mixer попробовать заюзать и сделать восход солнца самому

Бла бла загрузка файла

...
static bool done =false;

void noEffect(int chan, void *stream, int len, void *udata)
{

   char * data = stream;

   for(int i=0; i< len; i++)
   {
      image_data[i] = преобразуешь_как_то(data[i]); //raw данные для 64x64 изображения 
   }
      сохранить_кусочек_куда_то(image_data,len);
}



if(!Mix_RegisterEffect(MIX_CHANNEL_POST, noEffect, NULL, NULL)) {
    printf("Mix_RegisterEffect: %s\n", Mix_GetError());
}

Включаешь проигрывание и отрисовываешь image_data;

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от SZT

sox умеет рисовать такой spectrogram.

anonymous
()

А точно ли нужно

использовать Си для этого? Например весь код на IDL со взятием FFT и выводом картинки займет 5 (ПЯТЬ) строк. Примерно та же история будет с numpy, mathematica и, видимо, octave.

С тем, что результат комплексный и его самому нужно переводить в реальные - уже разобрались.

Еще добавлю, что выводить красивую картинку - тоже сноровка нужна. Например выбор мин/макс значений, степени (это не обязательно лог, может быть ^0.5 или ^0.3) и цветовой палитры. Нужно пробовать, зависит от исходных данных. Это опять аргумент в пользу не Си.

sshestov ★★
()

Если мне не изменяет память, функция fftw_plan_dft_r2c_1d портит содержимое буфера in, т.е. заполнять буфер нужно после fftw_plan_dft_r2c_1d (перед execute). Флаг FFTW_PRESERVE_INPUT запрещает порчу буфера во время fftw_execute, а не в fftw_plan.

Puzan ★★★★★
()
Ответ на: А точно ли нужно от sshestov

Почему Си? Потому что я не хочу тащить в зависимости всякие питоны, матлабы, скилабы и тому подобное. Может я потом эту спектрограмму захочу генерить на каком-нибудь STM32 контроллере, рисовать на LCD дисплее.

SZT ★★★★★
() автор топика
Ответ на: комментарий от Puzan

Да, работает. Надо только весовую функцию еще использовать, http://www.dsplib.ru/content/winadd/win.html вот что-то из этого выбрать. Ну и надо еще определиться с тем, как сильно будут перекрываться области этих преобразований. Т,е. если делать БПФ над 128 семплами, то первое преобразование делать для семплов от 0 до 127, второе от 1 до 128, третье от 2 до 129 - это наверно избыточно. Может быть стоит делать шаги в половину от окна, т.е. вначале от 0 до 127, потом от 64 до 191 и так далее. Буду экспериментировать

SZT ★★★★★
() автор топика
Последнее исправление: SZT (всего исправлений: 2)
Ответ на: комментарий от SZT

Надо только весовую функцию еще использовать

Для твоих задач подойдёт Хэмминга или Блэкмана, не сильно сложные и достаточно точные.

Ну и надо еще определиться с тем, как сильно будут перекрываться области этих преобразований

Для твоей задачи перекрытие вообще не нужно.

Puzan ★★★★★
()

Исходники результата не забудьте опубликовать, мне интересно. Желательно с комментариями.

anonymous
()
Ответ на: комментарий от anonymous
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <math.h>
#include <complex.h>
#include <fftw3.h>

#define SQR(x) ((x)*(x))
#define XRESOL 1024
#define YRESOL 768
#define ARR_LEN 128000

uint8_t pic[YRESOL][XRESOL] = {0};

#define FFT_CHUNK 512

void fft_calculate(
   double in[FFT_CHUNK],
   double complex out[(FFT_CHUNK/2)+1],
   double window_data[FFT_CHUNK],
   const fftw_plan p
)
{
  double fft_chunk_data[FFT_CHUNK];
  for(size_t i = 0; i < FFT_CHUNK; i++)
  {
    fft_chunk_data[i] = window_data[i] * in[i];
  }

  fftw_execute_dft_r2c(p, fft_chunk_data, out);
}

void convert(double complex in[(FFT_CHUNK/2)+1], double out[(FFT_CHUNK/2)+1])
{
  for (size_t i = 0; i < (FFT_CHUNK/2)+1; i++)
  {
    out[i] = sqrt(SQR(creal(in[i])) + SQR(cimag(in[i])))/FFT_CHUNK;
  }
}

void gen_window(double out[], size_t sz)
{
  // https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Nuttall_window
  const double a0 = 0.3635819;
  const double a1 = 0.4891775;
  const double a2 = 0.1365995;
  const double a3 = 0.0106411;

  for (size_t n = 0; n < sz; n++)
  {
    out[n] = a0
    - a1 * cos( (2*M_PI*n) / sz)
    + a2 * cos( (4*M_PI*n) / sz)
    - a3 * cos( (6*M_PI*n) / sz);
  }
}

int main(void)
{
  const fftw_plan p = fftw_plan_dft_r2c_1d(FFT_CHUNK, NULL, NULL, FFTW_ESTIMATE);
  double window_data[FFT_CHUNK];
  gen_window(window_data, FFT_CHUNK);
  double arr[ARR_LEN];
  for (size_t i = 0; i < ARR_LEN; i++)
  {
    /*arr[i] = sin((50*M_2_PI*i)/512)
           + sin((100*M_2_PI*i)/512)
           + sin((150*M_2_PI*i)/512)
           + sin((200*M_2_PI*i)/512)
           + sin((250*M_2_PI*i)/512)
           + sin((300*M_2_PI*i)/512)
           + sin((350*M_2_PI*i)/512)
           + sin((400*M_2_PI*i)/512)
           + sin((450*M_2_PI*i)/512)
           + sin((500*M_2_PI*i)/512)
           + sin((550*M_2_PI*i)/512)
           + sin((600*M_2_PI*i)/512)
           + sin((650*M_2_PI*i)/512)
           + sin((700*M_2_PI*i)/512);*/
    arr[i] = sin(sin((0.5*M_2_PI*i)/512)*200
             + ((2000*M_2_PI*i)/512)); // frequency modulation
    
  }
  for (size_t i = 0; i < ARR_LEN - FFT_CHUNK; i += FFT_CHUNK/2) // 50% window overlap
  {
    double complex tmp[(FFT_CHUNK/2)+1];
    double result[(FFT_CHUNK/2)+1];

    fft_calculate(&arr[i], tmp, window_data, p);

    convert(tmp, result);

    for (size_t j = 0; j < (FFT_CHUNK/2)+1; j++)
    {
      #define MULT 3000 // threshold
      pic[j][i/(FFT_CHUNK/2)] = llround(result[j] * MULT) > 0xff ?
        0xff : llround(result[j] * MULT);
      #undef MULT
    }
  }

  fwrite(pic, sizeof(pic), 1, stdout);
  fftw_destroy_plan(p);
  return EXIT_SUCCESS;
}

Ну вот такой результат. Тут перекрытие 50% и окно Blackman–Nuttall. Картинка 1024x768 c 256 оттенками серого тупо выводится в stdout, и принять ее можно через display или convert от ImageMagick. Типа ./a.out | display -size 1024x768 -depth 8 gray:-

Не знаю что тут еще комментировать

SZT ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.