用c 代码来研究 dft(discrete fourier transform)

///
// author: hjjdebug
// date : 2024年 06月 24日 星期一 15:59:53 CST
// descpripton:
// 用c 代码来研究 dft(discrete fourier transform)
///

文章目录

  • 甲: DFT 的定义:
  • 乙: 下面给出用c代码实现的dft 公式, 验证了手工计算的正确性.
  • 丙:理解还不够深刻? 再来一段程序,专门创建所需要的信号及采样数据.
  • 丁: 利用程序进行试验
    • 试验1: 创建一个1k赫兹的频率信号,数据格式S16,幅度{-30000,30000}用16k赫兹的采用频率采样.
    • 试验2: 用上边数据(1khz信号,16k采样频率),取16个点进行dft 运算,查看其频谱输出
    • 试验3: 仍然用1khz,16k采样频率,但窗口size取32点,计算其dft, 此时频率分辨率是0.5khz
    • 试验4: 做一个1khz, 2khz合成的信号,用16k采样频率采样,用dft 分析.

甲: DFT 的定义:

对于长度为n的实数队列x, 它的DFT 定义如下:
X(k) = xigema(x(n)exp(-j2pikn/N)

其中:
k 就是频率的索引,(0<=k<N),
n 就是时间的索引,(0<=n<N)
j 表示虚数单位.
pi 就是3.1415926
exp 就是以e为底的指数函数
exp(-j)表示顺时针旋转的意思?

xigema 就是把它们的值累加的意思.
X(k) 就是以k为自变量的复数数组

假设有一个4点的实数序列x=[1,2,3,4],我们计算它的DFT
则需要计算每个频率点的DFT.
对于k=0,由公式得到:
X(0)= 1exp(-j2pi00/4)+2exp(-j2pi01/4)+3exp(-j2pi02/4)+4exp(-j2pi03/4)=1+2+3+4=10
对于k=1,由公式得到:
X(1)= 1exp(-j2pi10/4)+2exp(-j2pi11/4)+3exp(-j2pi12/4)+4exp(-j2pi13/4)=1+2exp(-jpi/2)+3exp(-jpi)+4exp(-j3pi/2)=1-2j-3+4j=-2+2j

对于k=2,由公式得到:
X(2)= 1exp(-j2pi20/4)+2exp(-j2pi21/4)+3exp(-j2pi22/4)+4exp(-j2pi23/4)=1+2exp(-jpi)+3exp(-j2pi)+4exp(-j3pi)=1-2+3-4=-2

对于k=3,由公式得到:
X(3)= 1exp(-j2pi30/4)+2exp(-j2pi31/4)+3exp(-j2pi32/4)+4exp(-j2pi33/4)=1+2exp(-j3pi/2)+3exp(-j3pi)+4exp(-j9pi/2)=1+2j-3-4j=-2-2j
于是,其结果为: (说实话,我好几个数都计算错了,总是正负号搞错,是看了计算机结果才更正的!)
X[]={10+0j,-2+2j,-2+0j,-2-2j

乙: 下面给出用c代码实现的dft 公式, 验证了手工计算的正确性.

$ cat main.cpp 
#include <stdio.h>
#include <stdlib.h>
#include <math.h>#define PI 3.141593double realComput(double x[], int ndft, int k);
double imageComput(double x[], int ndft, int k);typedef struct {double real;double image;
} Complex;Complex* dft(double x[], int ndft) {Complex* dftRes = (Complex*)malloc(ndft * sizeof(Complex));if (dftRes == NULL) {return NULL;}for (int i = 0; i < ndft; ++i) {dftRes[i].real = realComput(x, ndft, i);dftRes[i].image = imageComput(x, ndft, i);
//		printf("%lf + %lfi\n", dftRes[i].real, dftRes[i].image);}return dftRes;
}double realComput(double x[], int ndft, int k) {double realPart = 0;double omiga=2*PI/ndft*k;printf("k:%d,ndft:%d,omiga:%lf\n",k,ndft, omiga);double theta;double delta;double shadow;for (int i = 0; i < ndft; ++i) {
//		delta = x[i] * cos(2 * PI / ndft * k * i);theta = omiga*i;double theta_m = ((float)((k*i)%ndft))/ndft * (2*PI); //不超过2*PI 的角度shadow = cos(theta);delta = x[i] * shadow;printf("k:%d,i:%d,theta:%lf,theta_m:%lf,x:%lf,shadow:%lf,delta:%lf\n",k,i,theta,theta_m,x[i],shadow,delta);realPart += delta;}return realPart;
}//因为image 是累减,所以是顺时针方向旋转
double imageComput(double x[], int ndft, int k) {double imagePart = 0;double theta;double shadow;for (int i = 0; i < ndft; ++i) {imagePart -= x[i] * sin(2 * PI / ndft * k * i);theta=2*PI/ndft*k*i;shadow=sin(theta);printf("--k:%d,i:%d,theta:%lf,x:%lf,shadow:%lf,delta:%lf\n",k,i,theta,x[i],shadow,x[i]*shadow);}return imagePart;
}double* ampSpectrum(Complex* dftRes, int ndft) {double* amp = (double*)malloc(sizeof(double) * ndft);if (amp == NULL) {return NULL;}for (int i = 0; i < ndft; ++i) {//幅度我加了/ndftamp[i] = sqrt(dftRes[i].real * dftRes[i].real+ dftRes[i].image* dftRes[i].image)/ndft;}return amp;
}double* phaseSpectrum(Complex* dftRes, int ndft) {double* phase = (double*)malloc(sizeof(double) * ndft);if (phase == NULL) {return NULL;}for (int i = 0; i < ndft; ++i) {phase[i] = atan2(dftRes[i].image, dftRes[i].real);}return phase;
}#define  WIN_S 16
void testDFT() {
//	double x[] = { 1, 2, 3, 4};double x[WIN_S];short sht[WIN_S];int res;FILE *fp=fopen("audio.data","rb");if(!fp){printf("error open file\n");exit(1);}//读取32个符号整数,进行dft变换res=fread(sht,sizeof(short),WIN_S,fp);if(res!=WIN_S){printf("error read file\n");exit(1);}int ndft = sizeof(x) / sizeof(double);for(int i=0;i<ndft;i++){x[i]=sht[i];}Complex* dftRes = dft(x, ndft);for (int i = 0; i < ndft; ++i) {printf("%lf + %lfi\n", dftRes[i].real, dftRes[i].image);}printf("\n");double* ampRes = ampSpectrum(dftRes, ndft);for (int i = 0; i < ndft; ++i) {printf("%lf, ", ampRes[i]);}printf("\n");double* phaRes = phaseSpectrum(dftRes, ndft);for (int i = 0; i < ndft; ++i) {
//		printf("%lf, ", phaRes[i]);}printf("\n");free(dftRes);free(ampRes);free(phaRes);
}int main() {testDFT();return 0;
}

输出复数值:
10.000000 + 0.000000i
-1.999998 + 2.000001i
-2.000000 + 0.000003i
-2.000005 + -1.999997i

由此你还会想到更多的问题:例如:

  1. 输出频率是什么? 答: 输出的频率是Fs/N, Fs/N2, Fs/N3… Fs/N*(N-1), 其中N 是采样点数,就是说输出频率是采样频率N等分.
    推论: 要想使输出频率
  2. 采样频率是什么? 答: 采样频率Fs 就是数据采样采到1,2,3,4 数据时使用的频率. 要求采样频率至少比信号频率大2倍
  3. 信号频率是什么? 答: 就是输入的正弦(或余弦)波频率,可想象成匀速圆周运动在y轴上的投影.
  4. dft输出的幅度是什么意思?

丙:理解还不够深刻? 再来一段程序,专门创建所需要的信号及采样数据.

create_audio.cpp

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char **argv)
{if (argc != 4) {fprintf(stderr, "Usage: %s <output_file> <fm> <fs> \n""fm is signal frequency, fs is sample frequency\n""Example: %s audio.data 1000 16000 \n", argv[0],argv[0]);exit(1);}char *filename = argv[1];FILE *file = fopen(filename, "wb");if (!file) {fprintf(stderr, "Could not open destination file %s\n", filename);exit(1);}int fm=0,fs=0;sscanf(argv[2],"%d",&fm);if(fm==0) exit(2);sscanf(argv[3],"%d",&fs);if(fs==0) exit(3);/* encode a single tone sound */int i,j,k;int nb_samples = 1024;
//	int channels = 2;int channels = 1;int sample_rate=fs;float t = 0;//这里由2个频率, 信号频率,每秒钟转过一个omiga角,每次采样时在y轴投影都不同//采样频率,决定了下一次的采样时间
//对sin函数,2pi 弧度是一个周期, 均匀圆周运动,其角频率为2*pi*fmint		omiga = 2 * M_PI * fm;//每次采样的时间递长值float tincr = 1.0 / sample_rate; typedef unsigned short uint16_t;//分配内存,存储一个frame 的数据uint16_t *sample_data = (uint16_t*)malloc(nb_samples * channels *sizeof(uint16_t)); //内存容量要考虑上通道个数for (i = 0; i < 200; i++) { // create 200 个 frame, 可持续时间200 * 1024 /fsfor (j = 0; j < nb_samples; j++) //每个frame 固定为1024点{sample_data[channels*j] = (int)(sin(omiga*t) * 30000); //正负值为-30000 - +30000for (k = 1; k < channels; k++)sample_data[channels*j + k] = sample_data[channels*j];t += tincr;}fwrite(sample_data, 1, nb_samples * channels *sizeof(uint16_t), file);}fprintf(stderr, "play the output file with the command:\n""ffplay -f s16le -ac %d -ar %d %s\n",channels,sample_rate,filename);		//指明数据采样格式,通道数,采样率就可以播放原始数据了fclose(file);return 0;
}

丁: 利用程序进行试验

试验1: 创建一个1k赫兹的频率信号,数据格式S16,幅度{-30000,30000}用16k赫兹的采用频率采样.

采样过程可想象成如下图示:

采样的数据如下表所示:
0000h: 00 00 D8 2C DC 52 43 6C 30 75 45 6C DE 52 DA 2C …Ø,ÜRCl0uElÞRÚ,
0010h: 02 00 2B D3 26 AD BE 93 D0 8A BA 93 20 AD 23 D3 …+Ó&­¾“Њº“ ­#Ó
0020h: FB FF D3 2C D8 52 41 6C 30 75 47 6C E2 52 DF 2C ûÿÓ,ØRAl0uGlâRß,
0030h: 08 00 30 D3 2A AD C0 93 D1 8A B8 93 1C AD 1E D3 …0Ó*­À“ÑŠ¸“.­.Ó
0040h: F5 FF CD 2C D4 52 3F 6C 2F 75 49 6C E6 52 E5 2C õÿÍ,ÔR?l/uIlæRå,
0050h: 0E 00 35 D3 2E AD C2 93 D1 8A B6 93 18 AD 19 D3 …5Ó.­Â“ÑŠ¶“.­.Ó

试验2: 用上边数据(1khz信号,16k采样频率),取16个点进行dft 运算,查看其频谱输出

 由于采样率是16K, 16个点其频率分辨率是1K, 能够把我们的1k信号给过滤出来.其频谱图(振幅图)为:

2.000000, 239998.747258, 9.897716, 5.690789, 4.487543, 3.588161, 2.606504, 3.878767, 2.000068, 3.879591, 2.581657, 3.547289, 4.425996, 5.653179, 9.610440, 239998.947999,

我们一下就看到了第2个值239998!! 显然它对应着1K频率,它这个幅度值代表了什么?

试验3: 仍然用1khz,16k采样频率,但窗口size取32点,计算其dft, 此时频率分辨率是0.5khz

结果如下:
6.000000, 20.862890, 479996.500013, 33.166659, 19.795433, 13.383284, 11.795656, 10.343337, 10.027572, 6.892723, 6.725493, 4.885620, 5.213012, 4.484607, 7.032958, 6.083789, 6.000091, 6.060954, 7.024984, 4.464189, 5.163309, 4.845143, 6.652847, 6.829602, 9.917541, 10.198174, 11.703062, 13.056493, 19.220876, 31.801808, 479996.901704, 18.721646,
分析: 耀眼的是第3项479996,(我们只分析前半部分频率<Fs/2, 因为后半部分与前半部分是共轭的.)
目测比16采样点又大了一倍.
479996/32=14999.9
239998/16=14999.9

研究代码,这个幅度值由实部,虚部用勾股计算而来.实部与虚部由时序数值累加而来,其数值只所以很大,
是对时序采样点累加的结果,可以对其归一化(除以采样点数),就消除了采样点数的影响.
归一化后的幅度谱图(16点):
0.125000, 14999.921704, 0.618607, 0.355674, 0.280471, 0.224260, 0.162907, 0.242423, 0.125004, 0.242474, 0.161354, 0.221706, 0.276625, 0.353324, 0.600652, 14999.934250,

那这个14999.9是什么东西? 这个描述就有点复杂了,采样点的最大幅值是30000.
16个采样点数据投射到1khz 的旋转转盘上,实部与虚部共同作用形成的合力是15000.
而投射到其它频率上,形成的合力几乎可以忽略(目测小于1).

试验4: 做一个1khz, 2khz合成的信号,用16k采样频率采样,用dft 分析.

这就需要修改代码了,不过很简单的. (注意修改幅值大小使其和不要超限制65536/2)
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char **argv)
{if (argc != 4) {fprintf(stderr,"Description: add a 5k frequecy to output.\n""Usage: %s <output_file> <fm> <fs> \n""fm is signal frequency, fs is sample frequency\n""Example: %s audio.data 1000 16000 \n", argv[0],argv[0]);exit(1);}char *filename = argv[1];FILE *file = fopen(filename, "wb");if (!file) {fprintf(stderr, "Could not open destination file %s\n", filename);exit(1);}int fm=0,fs=0;sscanf(argv[2],"%d",&fm);if(fm==0) exit(2);sscanf(argv[3],"%d",&fs);if(fs==0) exit(3);/* encode a single tone sound */int i,j;int nb_samples = 1024;int channels = 1;int sample_rate=fs;float t = 0;//这里由2个频率, 信号频率,每秒钟转过一个omiga角,每次采样时在y轴投影都不同//采样频率,决定了下一次的采样时间//对sin函数,2pi 弧度是一个周期, 均匀圆周运动,其角频率为2*pi*fmint		omiga = 2 * M_PI * fm;int omiga2 = 2 *M_PI * 5000; //5k 频率信号int val_5k;//每次采样的时间递长值float tincr = 1.0 / sample_rate; typedef unsigned short uint16_t;//分配内存,存储一个frame 的数据uint16_t *sample_data = (uint16_t*)malloc(nb_samples * sizeof(uint16_t)); for (i = 0; i < 200; i++) { // create 200 个 frame, 可持续时间200 * 1024 /fsfor (j = 0; j < nb_samples; j++) //每个frame{val_5k = (int)(sin(omiga2*t) * 10000);sample_data[j] = (int)(sin(omiga*t) * 10000)+val_5k; //正负值为-10000 - +10000, 2个值想加不会超过2万,在数据表达范围以内t += tincr;}fwrite(sample_data, 1, nb_samples * sizeof(uint16_t), file);}fprintf(stderr, "play the output file with the command:\n""ffplay -f s16le -ac %d -ar %d %s\n",channels,sample_rate,filename);		//指明数据采样格式,通道数,采样率就可以播放原始数据了fclose(file);return 0;
}

直接看dft输出吧.

0.187500, 4999.898941, 0.276690, 0.635727, 0.764330, 4999.632634, 0.739231, 0.515267, 0.187523, 0.512679, 0.728653, 4999.630904, 0.758657, 0.634583, 0.276951, 4999.905571,
看到1k,5khz处确实有频率输出了. 大于8k就不用看了,它们与前8k共轭.

于是这3段程序演示了如何对信号进行时域采样, 如何进行dft转换并验证输入频率等. 如此dtf不再是一头雾水,也算可以理解和使用了吧.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/34289.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧在线医疗在线诊疗APP患者端+医生端音视频诊疗并开处方

智慧在线医疗&#xff1a;音视频诊疗新纪元 &#x1f310; 智慧医疗新篇章 随着科技的飞速发展&#xff0c;智慧医疗正逐步走进我们的生活。特别是在线医疗&#xff0c;凭借其便捷、高效的特点&#xff0c;已成为许多患者的首选。而其中的“智慧在线医疗患者端医生端音视频诊疗…

Jrebel热部署

1、下载包 2、解压后本地启动exe文件 3、配置 http://127.0.0.1:8888/{GUID} https://www.guidgen.com/ 获取 GUID 4、激活后&#xff0c;Jrebel针对本项目模块进行勾选 5、如果报错&#xff0c;setting设置offine

代码随想录训练营Day 69|并查集理论基础、卡码网107.寻找存在的路径

1.并查集理论基础 并查集理论基础 | 代码随想录 并查集可以解决什么问题呢&#xff1f; 主要就是集合问题&#xff0c;两个节点在不在一个集合&#xff0c;也可以将两个节点添加到一个集合中。 注意&#xff1a;求根是求箭头出发的数 路径压缩&#xff1a;求根的根。把根的根的…

解析JSON字符串

QJsonDocument类用于解析JSON字符串&#xff0c;

详解 | DigiCert EV代码签名证书

简介 DigiCert EV 代码签名证书是一种高级别的代码签名证书&#xff0c;它不仅提供了标准代码签名证书的所有安全特性&#xff0c;还增加了额外的身份验证流程&#xff0c;以确保软件开发者或发布者的身份得到最严格验证。这对于提升软件的信任度、防止恶意篡改和确保下载安全…

10,PWM

.通过定时器 计数器:根据时钟频率计数 时钟源:为计数器提供时钟 重装栽植:计数的最大值 想改变周期和频率&#xff1a;需要调节定时器的时钟源和重装栽植 想改变占空比&#xff1a;调节定时器的比较值

vue3的网站项目内嵌到别的项目内部,通过用户名免登陆

前言&#xff1a;想把vue3的网站项目1内嵌到别的项目2内部。 希望在项目2内&#xff0c;点击一个按钮就出现一个页面进入项目1&#xff0c;其中用户名密码是互通的&#xff08;这一块需要接口调用实现同步&#xff09;&#xff0c;仔细一想&#xff0c;原理应该是提供一个地址链…

求满足abc + cba = 1333的a、b、c分别是什么

已知 abccba1333&#xff0c;其中 a、b、c 均为一个数字&#xff0c;编写一个程序求出 a、b、 c 分别代表什么数字&#xff1f; 可以考虑采用暴力枚举的方法&#xff0c;分别求出数的个位、十位、百位&#xff0c;然后相乘判断。代码如下&#xff1a; #include <stdio.h&g…

【Python系列】FastAPI 中的路径参数和非路径参数解析问题

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

dockercompose部署redis哨兵模式并集成springboot

第一步 编写compose文件 docker-compose.yml version: 3.8networks:redis-network:driver: bridgeservices:redis-master:image: redis:7.2.4container_name: redis-mastercommand: ["sh", "-c", "redis-server --protected-mode no --slave-announ…

产品经理基础入门

一、产品基础&#xff08;需求收集、需求管理、需求分析、结构图、流程图、原型、PRD文档、用户画像、后台的角色管理&#xff09; 产品经理定义&#xff1a; 1.市场分析&#xff1a;找准市场方向&#xff0c;确定哪个市场是值得进入的。 2.用户分析&#xff1a;针对目标市场…

python项目加密和增加时间许可证

1.bat&#xff0c;执行如下的命令&#xff0c;第一句是更新或增加许可证 第二句是加密draw_face.py python offer.py pyarmor obfuscate -O dist draw_face.py绘制自制人脸.py&#xff0c;调用加密的代码draw_face代码 import sys import os import cv2# 添加加密模块所在的路…

爬虫笔记16——异步爬取二手汽车数据去重存入MySQL

需要用到的库 #异步数据库 pip install aiomysql #reids数据库进行去重 pip install redis #用hashlib进行md5加密 pip install hashlib #基于异步IO的网络请求库 pip install aiohttp #xpath获取静态页面数据 pip install lxml目标网站 目标网站&#xff1a;https://www.che…

高考专业组 07组 08组 武汉大学

武汉大学的招生都什么废物点心&#xff0c;搜个专业组都没官方解释&#xff01; 07组&#xff1a;理学&#xff0c;详见下表专业代码07xxxx&#xff0c;例如数学、物理、化学 08组&#xff1a;工学&#xff0c;详见下表专业代码08xxxx&#xff0c;例如机械、电子信息、自动化、…

每天一个数据分析题(三百七十八)- 系统聚类

在系统聚类方法中&#xff0c;哪种系统聚类是直接利用了组内的离差平方和&#xff1f; A. 最长距离法 B. 重心法 C. Ward法 D. 类平均法 数据分析认证考试介绍&#xff1a;点击进入 题目来源于CDA模拟题库 点击此处获取答案 数据分析专项练习题库 内容涵盖Python&#…

R语言做图

目录 1. 图形参数 2. 低级图形 3. 部分高级图形 参考 1. 图形参数 图形参数用于设置图形中各种属性。 有些参数直接用在绘图函数内&#xff0c;如plot函数可以用 pch&#xff08;点样式&#xff09;、col&#xff08;颜色&#xff09;、cex&#xff08;文字符号大小倍数&…

ONLYOFFICE 桌面编辑器 8.1

ONLYOFFICE 简介 ONLYOFFICE 是一个开源的办公套件&#xff0c;它提供了在线文档编辑器、表格编辑器和演示文稿编辑器&#xff0c;这些编辑器能够兼容 Microsoft Office 格式&#xff08;.docx, .xlsx, .pptx&#xff09;以及其他流行的标准格式。ONLYOFFICE 的核心功能包括多…

Spcok测试代码抛异常场景

测试代码抛异常场景 ‍ class ExceptionSpec extends Specification {def validateService new ValidateService()Unrolldef "验证UserInfo"() {when: "调用校验方法"validateService.validateUser(user)then: "捕获异常并设置需要验证的异常值&qu…

注意力机制的原理

注意力机制的原理 注意力机制是深度学习中的一种关键组件&#xff0c;尤其是在处理序列数据&#xff0c;如自然语言处理任务时&#xff0c;它允许模型关注输入序列的不同部分&#xff0c;而不是对所有元素赋予相同的权重。其基本思想是为每个输入位置赋予一个权重&#xff0c;…

分类预测 | ZOA-PCNN-AT-SVM斑马优化并行卷积-支持向量机融合注意力机制的故障识别

分类预测 | ZOA-PCNN-AT-SVM斑马优化并行卷积-支持向量机融合注意力机制的故障识别 目录 分类预测 | ZOA-PCNN-AT-SVM斑马优化并行卷积-支持向量机融合注意力机制的故障识别分类效果基本描述程序设计参考资料 分类效果 基本描述 1.ZOA-PCNN-AT-SVM斑马优化并行卷积-支持向量机融…