DFT音频还原及降噪实战

傅里叶变换与信息隐写术(二)

声音数据

​ 声音可以用连续的波形来表示
​ 声音在计算机中的存储是离散
​ 计算机中存储的是声音的几个采样点的数据,1 秒钟采样 5 个点就表示采样频率是 5 Hz(每隔 0.25 秒取一个点,注意第 0 秒也取)
​ 采样频率越高,听到的声音就越平滑、越连续

​ 比如这段1Hz的音频采样频率为16.1kHz,就意味着它在这份文件中1 秒钟存储了 16100 个数据点。
image-20221014103111905

​ 以下代码可以用于生成一段音频

#!/usr/bin/env python
# coding=utf-8

import wave
import numpy as np

frameRate = 16100 # 将采样率设置为1.61kHz
time = 10         # 要输出的声音文件的时间长度
volumn = 40       # 声音文件最大大小
pi = np.pi        # 用于表示正弦波
t = np.arange(0, time, 1.0 / frameRate) # 需要在哪些点上进行取样

def gen_wave_data(fileName, realF):
    wave_data = volumn * np.sin(2.0 * pi * realF * t); # 2pi*真实频率*总时长
    wave_data = wave_data.astype(np.int8)
    f = wave.open(fileName, "wb")

    f.setnchannels(1)                   # 声道数,可以理解为立体度
    f.setsampwidth(1)                   # 采样位宽,表示多少个字节表示一个数据点(1字节:0-256  2字节:0-65535),可以理解为分辨率
    f.setframerate(frameRate)           # 多少数据代表1秒的音频
    f.writeframes(wave_data.tostring()) # 把声音数据写到文件中
    f.close()
    print("write data to : " + fileName)

gen_wave_data("1Hz.wav", 1)
gen_wave_data("2Hz.wav", 2)
gen_wave_data("3Hz.wav", 3)
gen_wave_data("4Hz.wav", 4)
gen_wave_data("5Hz.wav", 5)

运行结果如下图所示

image-20221014114524387

接着,我们可以尝试用代码对我们生成的音频进行绘图,完整代码如下

#!/usr/bin/env python
# coding=utf-8

import wave
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as pl
import numpy as np

def draw_wave_data(fileName, plotNum):
    f = wave.open(fileName, "rb")
    params = f.getparams()             # 参数params在下面有解释
    
    str_data = f.readframes(params[3]) # 将所有数据以字符串形式读出
    f.close()

    wave_data = np.fromstring(str_data, dtype = np.int8) # 将字符串数据转换成数值数据
    t = np.arange(0, len(wave_data) * 1.0 / params[2], 1.0 / params[2]) # ;总时间 = 总数据量 / 一个分段的数据量;步长 = 1 / 频率
    pl.subplot(plotNum)
    pl.title(fileName)
    pl.plot(t, wave_data)
    pl.xlabel("time (seconds)")

draw_wave_data("1Hz.wav", 321)
draw_wave_data("2Hz.wav", 322)
draw_wave_data("3Hz.wav", 323)
draw_wave_data("4Hz.wav", 324)
draw_wave_data("5Hz.wav", 325)
pl.show()

打印参数 params 的内容,结果如下图所示。很明显,前四个参数就是我们之前设置的参数:单声道、1 位宽、采样频率、总时长(总数据量)。image-20221014115649372

最后运行结果如下

image-20221016010031048

离散傅里叶变换 DFT

离散傅里叶变换主要用于信号分解,给一个合成得到的波形,DFT 可以将其中包含的多种不同频率的波形区分出来。公式(1)如下图所示,其中 x(n) 表示第 n 个声音信号,e 项表示 m 种频率,X(m) 表示第 m 种频率的分析结果。
X ( m ) = ∑ n = 0 N − 1 x ( n ) ∗ e − j 2 π m n / N (1) X(m)=\sum_{n=0}^{N-1}x(n)*e^{-j2\pi mn/N} \tag{1} X(m)=n=0N1x(n)ej2πmn/N(1)
​ 上述公式可以理解为,通过上式计算,如果声音数据 x 中包含了第 m 种频率的信号,则 X(m) 会给出第 m 种频率的分析结果(振幅、俯角)。
​ 其中,x 为时域, X 为频域,声音信号是按时间顺序给出,根据 DFT 计算后,声音信号按频率划分。因此,DFT 本质上是实现了时域到频域的转换。
​ 至于 e 项的含义,有以下补充性质
e j 2 π m n / N = ω n k = cos ⁡ ( 2 π k n ) + sin ⁡ ( 2 π k n ) i e − j 2 π m n / N = ω n − k = cos ⁡ ( 2 π k n ) − sin ⁡ ( 2 π k n ) i e^{j2\pi mn/N}=\omega_n^k=\cos(\frac{2\pi k}{n})+\sin(\frac{2\pi k}{n})i \\ e^{-j2\pi mn/N}=\omega_n^{-k}=\cos(\frac{2\pi k}{n})-\sin(\frac{2\pi k}{n})i ej2πmn/N=ωnk=cos(n2πk)+sin(n2πk)iej2πmn/N=ωnk=cos(n2πk)sin(n2πk)i
​ 从而,我们上面的公式(1)可以变换为
X ( m ) = ∑ n = 0 N − 1 x ( n ) ∗ ω N − n m X ( m ) = ∑ n = 0 N − 1 x ( n ) ∗ ( ω N − m ) n (2) X(m)=\sum_{n=0}^{N-1}x(n)*\omega_N^{-nm} \tag{2} \\ X(m)=\sum_{n=0}^{N-1}x(n)*{(\omega_N^{-m})}^n X(m)=n=0N1x(n)ωNnmX(m)=n=0N1x(n)(ωNm)n(2)
​ 进一步,上式可以表示为矩阵乘法的形式

image-20221014183201005

实战1 :声音还原

image-20221014183616037

​ 首先读取要分析的文件

#!/usr/bin/env python
# coding=utf-8

import wave
import numpy as np

f = wave.open("secret.wav", "rb")
params = f.getparams()            # 读取参数

str_data = f.readframes(params[3])
f.close()

wave_data = np.fromstring(str_data, dtype = np.int8)

output_fileName = "input.data"
fout = open(output_fileName, "w")

fout.write(str(params[3]) + "\n")
for x in wave_data:
    fout.write(str(x) + "\n")
fout.close()
print("write wave to : " + output_fileName)

​ 最后的效果是得到了 161000 个数值,存入 input.data中去(也可以侧面看出 secret.wav 是一段 10 秒的音频)

​ 接下来实现一下 DFT 算法,大部分都可以照搬 FFT 的代码,只不过对照(2)式的矩阵乘法表示可以看出这里使用的是逆运算,因此稍作修改

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <math.h>
using namespace std;

class Complex {
public :
    Complex(double r = 0.0, double i = 0.0) : r(r), i(i) {}
    Complex &operator*=(const Complex &rhl) {
        double a = r, b = i, c = rhl.r, d = rhl.i;
        r = a * c - b * d;
        i = a * d + b * c;
        return *this;
    }
    double m() { return sqrt(r * r + i * i); }    # 求复数模值
    Complex operator*(const Complex &rhl) const {
        Complex ret(*this);
        ret *= rhl;
        return ret;
    }
    Complex &operator+=(const Complex &rhl) {
        r += rhl.r;
        i += rhl.i;
        return *this;
    }
    Complex operator+(const Complex &rhl) const {
        Complex ret(*this);
        ret += rhl;
        return ret;
    }
    Complex &operator-=(const Complex &rhl) {
        r -= rhl.r;
        i -= rhl.i;
        return *this;
    }
    Complex operator-(const Complex &rhl) const {
        Complex ret(*this);
        ret -= rhl;
        return ret;
    }
    Complex &operator/=(const double x) {
        r /= x;
        i /= x;
        return *this;
    }
    Complex operator/(const double x) const {
        Complex ret(*this);
        ret /= x;
        return ret;
    }

    double r, i;
};

ostream &operator<<(ostream &out, const Complex &a) {
    out << a.r << "+" << a.i << "i";
    return out;
}

struct FastFourierTransform {
    #define PI acos(-1)
    void __transform(vector<Complex> &a, int n, int type = 1) {
        if (n == 1) { return ; }
        int m = n / 2;
        vector<Complex> a1(m), a2(m);
        for (int i = 0; i < m; i++) a1[i] = a[i << 1], a2[i] = a[i << 1 | 1];
        __transform(a1, m, type);
        __transform(a2, m, type);
        Complex wn(1, 0), w1(cos(2.0 * PI / n), type * sin(2.0 * PI / n));
        for (int i = 0; i < m; i++) {
            a[i] = a1[i] + wn * a2[i];
            a[i + m] = a1[i] - wn * a2[i];
            wn *= w1;
        }
        return ;
    }
    vector<Complex> dft(vector<Complex> &a, int n) {
        vector<Complex> temp(n);
        for (int i = 0, m = a.size(); i < m; i++) temp[i] = a[i];
        __transform(temp, n, -1);          # 面对正向变换,type置-1(w_n^{-k})
        return temp;
    }
    vector<Complex> idft(vector<Complex> &a, int n) {
        vector<Complex> temp(n);
        for (int i = 0, m = a.size(); i < m; i++) temp[i] = a[i];
        __transform(temp, n);              # 面对逆向变换,type置1(w_n^k)
        for (int i = 0; i < n; i++) temp[i] /= n;
        return temp;
    }
    #undef PI
} fft;

int main() {
    int n, N = 1;
    cin >> n;             
    n /= 100;
    vector<Complex> x(n); # 表示时域信号,一共有n项
    for (int i = 0; i < n; i++) cin >> x[i].r; # 读入时域信号的值
    while (N < n) N <<= 1; # N是2的整数次方
    vector<Complex> X = fft.dft(x, N); # 离散傅里叶变换,变换为频域信号
    for (int i = 0; i < N; i++) {
        cout << X[i].r << " " << X[i].i << " ";
        cout << X[i].m() * 2.0 / N << endl;      # X的模值(实部平方加虚部平方开根号),即复平面上线段长度
    }
    return 0;
}

运行结果如下,这就是离散傅里叶的分析结果

image-20221014190530482

image-20221014190631598

​ 现在分析结果不太直观,我们用程序将它画进图里

#!/usr/bin/env python
# coding=utf-8

import wave
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as pl
import numpy as np

def draw_wave_data(x, y, plotNum, title):
    pl.subplot(plotNum)
    pl.title(title)
    pl.plot(x, y)

fin = open("output.data", "r")
data = [x.split(" ") for x in fin.read().strip().split("\n")]
data = np.array(data)
data = data.T

x =  np.arange(0, len(data[0]))
draw_wave_data(x, data[0], 221, "Real part")
draw_wave_data(x, data[1], 222, "Imag part")
draw_wave_data(x, data[2], 223, "Mag")

pl.show()

运行结果如下图所示

image-20221016005808340

从结果可以看出以下信息

image-20221016121629142

实战2 :声音降噪

​ 下图是经傅里叶变换后的声音,表示每一种频域的振幅(对称,越接近中间越是高频信号)

image-20221016122409542

​ 噪音:声音比较小,频率比较高

​ 过滤所有高频信号,然后将频域写回时域

// 将上面dft.cpp的main函数部分进行如下改动
int main() {
    int n, N = 1;
    cin >> n;
    vector<Complex> x1(n);
    for (int i = 0; i < n; i++) cin >> x1[i].r;
    while (N < n) N <<= 1;
    vector<Complex> X = fft.dft(x1, N); // X中存储的是经傅里叶变换后的频域数据
    for (int k = N / 4, i = N / 2 - k, I = N / 2 + k; i < I; i++) X[i].clear(); // 我们删除中间n/4+n/4=n/2的数据
    vector<Complex> x2 = fft.idft(X, N); // 将频域数据写回时域
    for (int i = 0; i < n; i++) cout << int(x2[i].r) << endl;
    return 0;
}

image-20221016124733254

X = fft.dft(x1, N); // X中存储的是经傅里叶变换后的频域数据
for (int k = N / 4, i = N / 2 - k, I = N / 2 + k; i < I; i++) X[i].clear(); // 我们删除中间n/4+n/4=n/2的数据
vector x2 = fft.idft(X, N); // 将频域数据写回时域
for (int i = 0; i < n; i++) cout << int(x2[i].r) << endl;
return 0;
}


[外链图片转存中...(img-DEgV0c6L-1702784603248)]









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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python:import自定义包或py文件时,pyCharm正常但终端运行提示ModuleNotFoundError: No module named错误

问题 示例项目引用items.py&#xff0c;项目在pycharm开发工具中可以正常运行&#xff0c;但使用终端直接运行会报错ModuleNotFoundError: No module named。如下图。 原因 pycharm开发工具运行正常&#xff0c;说明目录和引用模块是没问题的。问题在于终端的运行环境只搜索文…

链表基础知识(二、双向链表头插、尾插、头删、尾删、查找、删除、插入)

目录 一、双向链表的概念 二、 双向链表的优缺点分析​与对比 2.1双向链表特点&#xff1a; 2.2双链表的优劣&#xff1a; 2.3循环链表的优劣 2.4 顺序表和双向链表的优缺点分析​ 三、带头双向循环链表增删改查实现 3.1SList.c 3.2创建一个新节点、头节点 3.3头插 3.…

手拉手EasyExcel极简实现web上传下载(全栈)

环境介绍 技术栈 springbootmybatis-plusmysqleasyexcel 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。 他能让你在不用考虑性…

华为鸿蒙应用--欢迎页SplashPage+倒计时跳过(自适应手机和平板)-ArkTs

鸿蒙ArkTS 开发欢迎页SplashPage倒计时跳过&#xff0c;可自适应平板和手机&#xff1a; 一、SplashPage.ts import { BreakpointSystem, BreakPointType, Logger, PageConstants, StyleConstants } from ohos/common; import router from ohos.router;Entry Component struct…

数据结构之<树>的介绍

树的基本概念 在数据结构中&#xff0c;树&#xff08;Tree&#xff09;是一种层次结构&#xff0c;由节点和边组成。树的基本概念包括根节点、子节点、父节点、兄弟节点等。节点拥有零个或多个子节点&#xff0c;除了根节点外&#xff0c;每个节点有且仅有一个父节点。树的层…

数据结构-猴子吃桃问题

一、需求分析 有一群猴子摘了一堆桃子&#xff0c;他们每天都吃当前桃子的一半且再多吃一个&#xff0c;到了第10天就只余下一个桃子。用多种方法实现求出原来这群猴子共摘了多少个桃子。要求&#xff1a; 1)采用数组数据结构实现上述求解&#xff1b; 2)采用链数据结构实现上述…

13、Kafka副本机制详解

Kafka 副本机制详解 1、副本定义2、副本角色3、In-sync Replicas&#xff08;ISR&#xff09;4、Unclean 领导者选举&#xff08;Unclean Leader Election&#xff09; 所谓的副本机制&#xff08;Replication&#xff09;&#xff0c;也可以称之为备份机制&#xff0c;通常是指…

离线编译安装opencv库及多版本切换[ubuntu]

系统版本&#xff1a;ubuntu18.04 库版本&#xff1a;opencv4.6.0 & opencv3.6.0 一、多版本安装前准备 1. 卸载已经安装的opencv版本[可选] 1.1 卸载从软件仓库中安装的opencv sudo apt-get purge libopencv* 1.2 卸载使用source自行编译安装的opencv 首先进入原先编译…

人生感悟 | 又是一年,眼看要2024了

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 刚过完大雪节气没两天&#xff0c;气温开始急转直下&#xff0c;走在路上明显感觉冷了许多。看天气预报很多地区已经开始下雪了。 看日历已经12月9号了&#xff0c;12月份&#xff0c;一年的最后一个月&#xff0c;2…

自然语言处理阅读第二弹

HuggingFace 镜像网站模型库 NLP中的自回归模型和自编码模型 自回归&#xff1a;根据上文内容预测下一个可能的单词&#xff0c;或者根据下文预测上一个可能的单词。只能利用上文或者下文的信息&#xff0c;不能同时利用上文和下文的信息。自编码&#xff1a;对输入的句子随…

【TB作品】STM32 PWM之实现呼吸灯,STM32F103RCT6,晨启

文章目录 完整工程参考资料实验过程 实验任务&#xff1a; 1&#xff1a;实现PWM呼吸灯&#xff0c;定时器产生PWM&#xff0c;控制实验板上的LED灯亮灭&#xff1b; 2&#xff1a;通过任意两个按键切换PWM呼吸灯输出到两个不同的LED灯&#xff0c;实现亮灭效果&#xff1b; 3&…

FRP 内网穿透工具部署

FRP 介绍 frp 是一个专注于内网穿透的高性能反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c;且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 官方网站&#xff1a;https://gofrp.org/zh-cn/ 项目地…

ARS430毫米波雷达标定步骤

工具准备&#xff1a;CANoe&#xff0c; 标定工程文件&#xff0c;雷达标定板&#xff0c;三脚架&#xff0c;激光器&#xff0c;平口钳&#xff0c;气泡水平仪&#xff0c;小镜子&#xff0c;双面胶。 将车辆放置在车辆前方至少有20米空白视野的场地上。使用气泡水平仪大概使…

谈一谈网络协议中的传输层

文章目录 UDPTCPTCP为什么可靠 UDP 传输层的作用是负责能够从发送端到传输端。 我们的主机上有多个程序&#xff0c;那么怎么分辨哪个信息是发给哪个程序的呢&#xff1f;—端口号。其是一个16位的无符号整型&#xff0c;端口号分为知名端口号&#xff08;0-1023&#xff09;和…

基于YOLOv8深度学习的路面标志线检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

使用sha512对上传到linux服务器的文件进行校验

什么是SHA-512 SHA-512&#xff08;安全散列算法 512 位&#xff09;是一种密码散列函数&#xff0c;属于SHA-2家族的一部分。它是由美国国家安全局&#xff08;NSA&#xff09;设计的一种安全散列算法&#xff0c;用于产生数字摘要&#xff0c;通常用于数据完整性验证、数字签…

3D角色生成式AI:原理及实现

自从开创性论文Denoising Diffusion Probabilistic Models发布以来&#xff0c;此类图像生成器一直在改进&#xff0c;生成的图像质量在多个指标上都击败了 GAN&#xff0c;并且与真实图像无法区分。 NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis…

《点云处理》 提取点云内点和外点

前言 关于内点&#xff08;inliers&#xff09;和外点&#xff08;outliers&#xff09;在点云处理方向上是个非常常见的名词。有时候&#xff0c;内点也会被称之为有效点&#xff0c;而外点会被称之为无效点。所谓有效和无效都是相对而言的&#xff0c;无效不一定是真的没有意…

拖拽属性 draggable

H5 新增的属性 draggable&#xff0c;它能够给与一切的 html 元素拖动的效果。 拖拽元素 属性为 draggable"true" 的元素&#xff0c;可拖动&#xff0c;且拖动时鼠标变为禁用图标 ps: 直接写 draggable 可能无效 ondragstart 开始拖拽时触发&#xff08;按下鼠标…

【SpringMVC】SpringMVC简介、过程分析、bean的加载和控制

文章目录 1. SpringMVC简介2. SpringMVC入门案例文件结构第一步&#xff1a;坐标导入第二步&#xff1a;创建SpringMVC容器的控制器类第三步&#xff1a;初始化SpringMVC环境&#xff0c;设定Spring加载对应的bean第四步&#xff1a;初始化Servlet容器&#xff0c;加载SpringMV…