DSPSYSTEM Теория и практика цифровой обработки сигналов

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.



Пытаюсь разобраться с БПФ

Сообщений 1 страница 10 из 10

1

Приветствую участников форума!
Есть проблема - результаты БПФ и прямого преобразования совершенно не похожи друг на друга, при этом прямое преобразование выглядит верным, а в быстром появляются лишние гармоники, и уровень основных не соответствует действительному. Для того, чтобы разобраться в этом, я написал тривиальное преобразование при N=8 без использования циклов и каких-либо алгоритмов, просто написав весь код построчно, используя формулы из статьи о БПФ с прореживанием по времени. Даже все обозначения оставил как в статье. Вот часть кода быстрого преобразования:

Код:
//заполнение входного массива
for(int i=0;i<8;i++)
 s[i].Re=60*sin(0.75*M_PI*i);
for(int i=0;i<8;i++)
 s[i].Im=0.0;

//first step
S00[0]=s[0]+s[4];
S00[1]=s[0]-s[4];

S01[0]=s[2]+s[6];
S01[1]=s[2]-s[6];

S10[0]=s[1]+s[5];
S10[1]=s[1]-s[5];

S11[0]=s[3]+s[7];
S11[1]=s[3]-s[7];

//second step

complex W14;
W14.Re=0.0; W14.Im=-1.0;

S0[0]=S00[0]+S01[0];
S0[1]=S00[1]+W14*S01[1];
S0[2]=S00[0]-S01[0];
S0[3]=S00[1]-W14*S01[1];

S1[0]=S10[0]+S11[0];
S1[1]=S10[1]+W14*S11[1];
S1[2]=S10[0]-S11[0];
S1[3]=S10[1]-W14*S11[1];

//third step - final

complex W18,W38;

W18.Re=cos(-M_PI*0.25); W18.Im=sin(-M_PI*0.25); W38.Re=cos(-M_PI*0.75); W38.Im=sin(-M_PI*0.75);

S[0]=S0[0]+S1[0];
S[1]=S0[1]+W18*S1[1];
S[2]=S0[2]+W14*S1[2];
S[3]=S0[3]+W38*S1[3];
S[4]=S0[0]-S1[0];
S[5]=S0[1]-W18*S1[1];
S[6]=S0[2]-W14*S1[2];
S[7]=S0[3]-W38*S1[3];

//вывод значений Re и Im в таблицу
for(int i=0;i<8;i++)
 {
 StringGrid1->Cells[0][i]=S[i].Re;
 StringGrid1->Cells[1][i]=S[i].Im;
 }

//вычисление амплитуды гармоник
for(int i=0;i<8;i++)
 g[i]=S[i].ABS()/4.0;

//вывод значений амплитуд
for(int i=0;i<8;i++)
 {
 StringGrid1->Cells[2][i]=g[i];
 }

ProgressBar1->Position=g[0];
ProgressBar2->Position=g[1];
ProgressBar3->Position=g[2];
ProgressBar4->Position=g[3];
ProgressBar5->Position=g[4];
ProgressBar6->Position=g[5];
ProgressBar7->Position=g[6];
ProgressBar8->Position=g[7];
}

Вот такой вот китайский код :blush:

А вот код прямого преобразования:

Код:
//заполнение входного массива
for(int i=0;i<8;i++)
 s[i].Re=60*sin(0.75*M_PI*i);
for(int i=0;i<8;i++)
 s[i].Im=0.0;

//главный цикл
for(int gar=0;gar<8;gar++)
  {
  out[gar].Re=0.0;
  out[gar].Im=0.0;
  for(int inp=0;inp<8;inp++)
    {
    complex W;
    W.Re=cos(-M_PI*gar*inp/4.0);
    W.Im=sin(-M_PI*gar*inp/4.0);
    out[gar]=out[gar]+s[inp]*W;
    }
  }

//вывод значений Re и Im в таблицу
for(int i=0;i<8;i++)
  {
  StringGrid1->Cells[3][i]=out[i].Re;
  StringGrid1->Cells[4][i]=out[i].Im;
  }

//вычисление амплитуды гармоник
for(int i=0;i<8;i++)
   g2[i]=out[i].ABS()/4.0;

//вывод значений амплитуд
for(int i=0;i<8;i++)
 {
 StringGrid1->Cells[5][i]=g2[i];

 }

ProgressBar1->Position=g2[0];
ProgressBar2->Position=g2[1];
ProgressBar3->Position=g2[2];
ProgressBar4->Position=g2[3];
ProgressBar5->Position=g2[4];
ProgressBar6->Position=g2[5];
ProgressBar7->Position=g2[6];
ProgressBar8->Position=g2[7];

Отредактировано YurOK (2010-07-16 15:39:50)

0

2

картинка не прикрепилась, попробую чуть позже

0

3

http://s54.radikal.ru/i146/1007/26/9aa3d5173e8e.gif
http://s002.radikal.ru/i197/1007/19/770e4bb7a37d.gif

0

4

ProgressBar'ами показаны амплитуды гармоник для наглядности, на верхнем скриншоте прямое преобразование, на нижнем - быстрое. Также в таблице изображены вычисленные значения, видно, что для прямого преобразования всё четко - одна гармоника с амплитудой 60 и её отражение, связянное с зеркальным эффектом. А вот для быстрого результаты другие - амплитуда основной гармоники ~67, её отражения ~55, и еще откуда-то берется последняя гармоника с амплитудой ~52. Не пойму, в чём ошибка

Отредактировано YurOK (2010-07-16 17:21:42)

0

5

Мне сложно ответить где у вас ошибка, поскольку вы используете встроенный класс в билдер complex, который увы отсутствует в VC. ПОэтому я не могу отладить вашу программу. Могу лишь дать свою написанную на VC (вы сможете ее откомпилить у себя и пошагово проследить отличие вашей и моей проги). Поскольку в VC нет класса работы с комплексными числами все операции пришлось разделить на реальные  и мнимые части. Думаю разберетесь.

Код:
#include <stdio.h>
#include <stdlib.h>
#define _USE_MATH_DEFINES
#include <math.h>


int main(){
	//заполнение входного массива
	double s[8];
	for(int i=0;i<8;i++)
    s[i]=60*sin(0.75*M_PI*i);

	double S00[2], S01[2], S10[2], S11[2]; 
	//first step
	S00[0]=s[0]+s[4];
	S00[1]=s[0]-s[4];

	S01[0]=s[2]+s[6];
	S01[1]=s[2]-s[6];

	S10[0]=s[1]+s[5];
	S10[1]=s[1]-s[5];

	S11[0]=s[3]+s[7];
	S11[1]=s[3]-s[7];

	//second step

	double S0_Re[4], S0_Im[4], S1_Re[4], S1_Im[4];

	S0_Re[0]=S00[0]+S01[0];    	S0_Im[0] = 0.0;
	S0_Re[1]=S00[1];        S0_Im[1] = -S01[1];
	S0_Re[2]=S00[0]-S01[0];    	S0_Im[2] = 0.0;
	S0_Re[3]=S00[1];        S0_Im[3] = S01[1];

	S1_Re[0]=S10[0]+S11[0];    	S1_Im[0] = 0.0;
	S1_Re[1]=S10[1];        S1_Im[1] = -S11[1];
	S1_Re[2]=S10[0]-S11[0];    	S1_Im[2] = 0.0;
	S1_Re[3]=S10[1];        S1_Im[3] = S11[1];


	// третий шаг
	double S_Re[8], S_Im[8];
	double W_Re[] = {1.0,  cos(-M_PI/4.0),  0.0,  cos(-3.0*M_PI/4.0) };
	double W_Im[] = {0.0,  sin(-M_PI/4.0), -1.0,  sin(-3.0*M_PI/4.0) };

    // реальная часть спектра                                       // мнимая часть спектра
	S_Re[0] = S0_Re[0]+ (S1_Re[0]*W_Re[0] - S1_Im[0]*W_Im[0]);    S_Im[0] = S0_Im[0]+ (S1_Re[0]*W_Im[0] + S1_Im[0]*W_Re[0]);
	S_Re[1] = S0_Re[1]+ (S1_Re[1]*W_Re[1] - S1_Im[1]*W_Im[1]);    S_Im[1] = S0_Im[1]+ (S1_Re[1]*W_Im[1] + S1_Im[1]*W_Re[1]);
	S_Re[2] = S0_Re[2]+ (S1_Re[2]*W_Re[2] - S1_Im[2]*W_Im[2]);    S_Im[2] = S0_Im[2]+ (S1_Re[2]*W_Im[2] + S1_Im[2]*W_Re[2]);
	S_Re[3] = S0_Re[3]+ (S1_Re[3]*W_Re[3] - S1_Im[3]*W_Im[3]);    S_Im[3] = S0_Im[3]+ (S1_Re[3]*W_Im[3] + S1_Im[3]*W_Re[3]);
	S_Re[4] = S0_Re[0] - (S1_Re[0]*W_Re[0] - S1_Im[0]*W_Im[0]);    S_Im[4] = S0_Im[0]- (S1_Re[0]*W_Im[0] + S1_Im[0]*W_Re[0]);
	S_Re[5] = S0_Re[1] - (S1_Re[1]*W_Re[1] - S1_Im[1]*W_Im[1]);    S_Im[5] = S0_Im[1]- (S1_Re[1]*W_Im[1] + S1_Im[1]*W_Re[1]);
	S_Re[6] = S0_Re[2] - (S1_Re[2]*W_Re[2] - S1_Im[2]*W_Im[2]);    S_Im[6] = S0_Im[2]- (S1_Re[2]*W_Im[2] + S1_Im[2]*W_Re[2]);
	S_Re[7] = S0_Re[3] - (S1_Re[3]*W_Re[3] - S1_Im[3]*W_Im[3]);    S_Im[7] = S0_Im[3]- (S1_Re[3]*W_Im[3] + S1_Im[3]*W_Re[3]);

	// амплитудный спектр
	for(int i = 0; i<8; i++)
    printf("%f\n", sqrt(S_Re[i]*S_Re[i]+S_Im[i]*S_Im[i])/4.0);


	system("Pause");
	return 0;
}

Результат работы

Код:
0.000000
0.000000
0.000000
60.000000
0.000000
60.000000
0.000000
0.000000
Для продолжения нажмите любую клавишу . . .

Если не будет компилится удалите строку #define _USE_MATH_DEFINES. Удачи. Если что пишите.

0

6

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

Код:
#include <math.h>

struct complex
{
double Re;
double Im;

complex& operator*(complex &z1)
 {
 double rr,ri;
 rr=z1.Re*Re-z1.Im*Im;
 ri=z1.Re*Im+z1.Im*Re;
 z1.Re=rr; z1.Im=ri;
 return z1;
 }


complex operator+(complex &z1)
 {
 complex temp;
 temp.Re=Re+z1.Re;
 temp.Im=Im+z1.Im;
 return temp;
 }

complex operator-(complex &z1)
 {
 complex temp;
 temp.Re=Re-z1.Re;
 temp.Im=Im-z1.Im;
 return temp;
 }

double ABS()
{
return sqrt(int(Re)*int(Re)+int(Im)*int(Im));
}
};

0

7

Прогнал пошагово...ошибки начинаются со второго шага. Видимо, неправильно реализовал перегрузку операторов(не зря советуют её остерегаться=)). Попробую все действия расписать для двух отдельных массивов для реальной и мнимой части

0

8

YurOK написал(а):

Прогнал пошагово...ошибки начинаются со второго шага. Видимо, неправильно реализовал перегрузку операторов(не зря советуют её остерегаться=)). Попробую все действия расписать для двух отдельных массивов для реальной и мнимой части

Вот именно поэтому я предпочитаю делать вычисления без использования complex и ему подобных конструкция,  с разделением реальной и мнимой части.  :flag:

0

9

Спасибо большое за гарантированно рабочий код и за помощь=) Месяц я потратил на это...вот что получилось:

Код:
#include <math.h>

//для 256 точек
#define T 8
#define N (1<<T)


struct cpx
{
double Re,Im;
 cpx()
  {Re=0.0;  Im=0.0;}
};


inline double amp(cpx z)
{
return sqrt(z.Re*z.Re+z.Im*z.Im);
}


double input[N],amps[N];
cpx out[N],buf[N];

const int id8[256]=
{0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,
 8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,
 4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,
 12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,
 2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,
 10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,
 6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,
 14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,
 1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,
 9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,
 5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,
 13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,
 3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,
 11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,
 7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,
 15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255};



int main()
{
for(int i=0;i<N;i++)
 input[i]=60*sin(0.75*M_PI*float(i));

//шаг первый-без умножений
for(int i=0;i<(N>>1);i++)
{
out[2*i].Re=input[id8[2*i]]+input[id8[2*i+1]];
out[2*i+1].Re=input[id8[2*i]]-input[id8[2*i+1]];
}

//второй и последующие шаги
for(int step=2;step<=T;step++)
 {
 //на нечётном шаге используем buf[] как входной массив, out[] - как выходной. На чётном - наоборот
 if(step%2)
 {
  for(int rep=0;rep<(N>>step);rep++)   //цикл повторения укороченных преобразований
      {
            //первая операция сложения без умножения на поворотный коэффициент
       out[(1<<step)*rep].Re=buf[(1<<step)*rep].Re+buf[(1<<step)*rep+(1<<(step-1))].Re;    
       out[(1<<step)*rep].Im=buf[(1<<step)*rep].Im+buf[(1<<step)*rep+(1<<(step-1))].Im;
            //вторая операция сложения - с умножением на поворотные коэффициенты. Таких операций 1,3,7...
         for(int cw=1;cw<(1<<(step-1));cw++)
         {
          cpx WkN;  WkN.Re=cos(2.0*M_PI*cw/float(1<<step));   WkN.Im=-sin(2.0*M_PI*cw/float(1<<step));
           out[(1<<step)*rep+cw].Re=buf[(1<<step)*rep+cw].Re+(buf[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Re-buf[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Im);
           out[(1<<step)*rep+cw].Im=buf[(1<<step)*rep+cw].Im+(buf[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Re+buf[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Im);
         }
          //первая операция вычитания без умножения на поворотный коэффициент
       out[(1<<step)*rep+(1<<(step-1))].Re=buf[(1<<step)*rep].Re-buf[(1<<step)*rep+(1<<(step-1))].Re;
       out[(1<<step)*rep+(1<<(step-1))].Im=buf[(1<<step)*rep].Im-buf[(1<<step)*rep+(1<<(step-1))].Im;
        //вторая операция вычитания - с умножением на поворотные коэффициенты. Таких операций 1,3,7...
        for(int cw=1;cw<(1<<(step-1));cw++)
         {
          cpx WkN;  WkN.Re=cos(2.0*M_PI*cw/float(1<<step));   WkN.Im=-sin(2.0*M_PI*cw/float(1<<step));
           out[(1<<step)*rep+cw+(1<<(step-1))].Re=buf[(1<<step)*rep+cw].Re-(buf[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Re-buf[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Im);
           out[(1<<step)*rep+cw+(1<<(step-1))].Im=buf[(1<<step)*rep+cw].Im-(buf[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Re+buf[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Im);
         }

      }
  }
 //то же, но входной и выходной массив поменяны местами
 else
  {
    for(int rep=0;rep<(N>>step);rep++)
      {
       buf[(1<<step)*rep].Re=out[(1<<step)*rep].Re+out[(1<<step)*rep+(1<<(step-1))].Re;
       buf[(1<<step)*rep].Im=out[(1<<step)*rep].Im+out[(1<<step)*rep+(1<<(step-1))].Im;
        for(int cw=1;cw<(1<<(step-1));cw++)
         {
          cpx WkN;  WkN.Re=cos(2*M_PI*cw/(1<<step));   WkN.Im=-sin(2*M_PI*cw/(1<<step));
           buf[(1<<step)*rep+cw].Re=out[(1<<step)*rep+cw].Re+(out[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Re-out[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Im);
           buf[(1<<step)*rep+cw].Im=out[(1<<step)*rep+cw].Im+(out[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Re+out[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Im);
         }

       buf[(1<<step)*rep+(1<<(step-1))].Re=out[(1<<step)*rep].Re-out[(1<<step)*rep+(1<<(step-1))].Re;
       buf[(1<<step)*rep+(1<<(step-1))].Im=out[(1<<step)*rep].Im-out[(1<<step)*rep+(1<<(step-1))].Im;
        for(int cw=1;cw<(1<<(step-1));cw++)
         {
          cpx WkN;  WkN.Re=cos(2*M_PI*cw/(1<<step));   WkN.Im=-sin(2*M_PI*cw/(1<<step));
           buf[(1<<step)*rep+cw+(1<<(step-1))].Re=out[(1<<step)*rep+cw].Re-(out[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Re-out[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Im);
           buf[(1<<step)*rep+cw+(1<<(step-1))].Im=out[(1<<step)*rep+cw].Im-(out[(1<<step)*rep+cw+(1<<(step-1))].Im*WkN.Re+out[(1<<step)*rep+cw+(1<<(step-1))].Re*WkN.Im);
         }

      }
  }
 }

if(T%2)
 {
 for(int i=0;i<N;i++)
   amps[i]=int(2.0*amp(out[i])/N);
 }
else
 {
 for(int i=0;i<N;i++)
   amps[i]=int(2.0*amp(buf[i])/N);
 }

return 0;
}

Массив const int id8[256] с двоично-инверсными индексами элементов был посчитан заранее с помощью такого кода:

Код:
ofstream File("output_inverse.txt");
File<<"unsigned int index[256]={";
for(unsigned int i=0;i<256;i++)
 {
 unsigned int temp=(i>>7)|
                           ((i>>5)&(0x02))|
                           ((i>>3)&(0x04))|
                           ((i>>1)&(0x08))|
                           ((i<<1)&(0x10))|
                           ((i<<3)&(0x20))|
                           ((i<<5)&(0x40))|
                            (i<<7);
 File<<unsigned(temp);
 if(i!=255)
  File<<',';
 else
  File<<"};";
 }

Следущая моя цель - написать оптимизированный код для 8 bit микроконтроллера AVR, используя целочисленную арифметику и заранее посчитанные массивы констант.

P.S. Всё-таки есть что-то в этом ДПФ такое...волшебное, притягивающее :rolleyes:

Отредактировано YurOK (2010-07-18 13:28:01)

0

10

YurOK написал(а):

Всё-таки есть что-то в этом ДПФ такое...волшебное, притягивающее :rolleyes:

YurOK написал(а):

оптимизированный код для 8 bit микроконтроллера

Я сам начинал именно с этого и написал. Если есть вопросы - обращайтесь.

0