DFT音频还原及降噪实战

news2024/11/27 6:19:47

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

声音数据

​ 声音可以用连续的波形来表示
​ 声音在计算机中的存储是离散
​ 计算机中存储的是声音的几个采样点的数据,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)]









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

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

相关文章

饥荒Mod 开发(十):制作一把AOE武器

饥荒Mod 开发(九)&#xff1a;物品栏排列 饥荒Mod 开发(十一)&#xff1a;修改物品堆叠 前面的文章介绍了很多基础知识以及如何制作一个物品&#xff0c;这次制作一把武器&#xff0c;装备之后可以用来攻击怪物。 制作武器贴图和动画 1.1 制作贴图。 先准备一张武器的贴图&a…

实现el-table操作列点击弹出echarts

代码&#xff1a; <el-table-column :width"90"><template #default"scope"><el-popover placement"left-end" width"550" trigger"click"><div><div style"font-size: 18px; margin-left…

Postman介绍和快速使用

Postman 是什么&#xff1f; Postman 是一个流行的API&#xff08;Application Programming Interface&#xff09;开发工具&#xff0c;它使得开发者可以很容易地创建、测试、共享和文档化API。Postman 提供了一个友好的用户界面&#xff0c;来发送HTTP请求&#xff0c;接收响…

How to helm install prometheus 【 helm 安装 prometheus 】

文章目录 1. 简介2. 简单部署3. 数据持久化部署3.1 设置必要的环境变量3.2 运行安装脚本3.3 查看 1. 简介 kube-prometheus-stack是一个基于Prometheus和Grafana的开源软件套件&#xff0c;用于在Kubernetes集群中进行监控和可视化。它提供了一套完整的工具和组件&#xff0c;…

Python Django 连接 PostgreSQL 操作实例

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python Django 连接 PostgreSQL 操作实例&#xff0c;全文3500字&#xff0c;阅读大约10分钟 在Web开发中&#xff0c;使用Django连接到PostgreSQL数据库是一种常见的选择。…

如何从 iPhone 上恢复已删除的照片教程分享

您是否错误地删除了 iPhone 上的错误照片&#xff1f;或者您可能已将手机恢复出厂设置&#xff0c;但现在所有照片都消失了&#xff1f;如果您现在遇到这样的情况&#xff0c;我们可以为您提供解决方案。 在本文中&#xff0c;我们将向您展示七种数据恢复方法&#xff0c;可以…

饥荒Mod 开发(十四):制作屏幕弹窗

饥荒Mod 开发(十三)&#xff1a;木牌传送 在上一个文章里面制作了一个传送选择页面&#xff0c;是一个全屏的窗口&#xff0c;那饥荒中如何制作一个全屏的窗口&#xff0c;下面介绍一下如何从零开始制作一个全屏窗口 制作屏幕窗口 饥荒中的全屏窗口都有一个基类 “Screen”,我…

使用Nginx实现负载均衡的实践指南

目录 前言1 负载均衡简介2 需要实现的效果3 准备2个tomcat服务器4 配置Nginx实现负载均衡5 Nginx的服务器策略5.1 轮询&#xff08;默认&#xff09;5.2 权重&#xff08;weight&#xff09;5.3 IP哈希&#xff08;ip_hash&#xff09;5.4 响应时间公平分配&#xff08;fair&am…

论文阅读:Learning sRGB-to-Raw-RGB De-rendering with Content-Aware Metadata

论文阅读&#xff1a;Learning sRGB-to-Raw-RGB De-rendering with Content-Aware Metadata Abstract 大多数的 Camera ISP 会将 RAW 图经过一系列的处理&#xff0c;变成 sRGB 图像&#xff0c;ISP 的处理中很多模块是非线性的操作&#xff0c;这些操作会破坏环境光照的线性…

【深度强化学习】TRPO、PPO

策略梯度的缺点 步长难以确定&#xff0c;一旦步长选的不好&#xff0c;就导致恶性循环 步长不合适 → 策略变差 → 采集的数据变差 → &#xff08;回报 / 梯度导致的&#xff09;步长不合适 步长不合适 \to 策略变差 \to 采集的数据变差 \to &#xff08;回报/梯度导致的&am…

【Unity】简单实现生成式电子围栏

【Unity】简单实现生成式电子围栏 三维电子围栏是一种通过使用三维技术和电子设备来建立虚拟围栏&#xff0c;用于监控和控制特定区域的系统。它可以通过使用传感器和摄像头来检测任何越界行为&#xff0c;并及时发出警报。这种技术可以应用于安防领域以及其他需要对特定区域进…

C#实现MQTT over WebSocket

如何在网页端实现MQTT消息的发布和订阅&#xff1f; 实现MQTT功能&#xff0c;可以发布和订阅主题通过WebSocket协议将MQTT消息转发给对应的网页端 带着这个实现思路&#xff0c;采用C#控制台程序实现MQTT服务端功能&#xff0c;web端可以直接使用websocket插件与服务端双向通…

在金属/绝缘体/p-GaN栅极高电子迁移率晶体管中同时实现大的栅压摆幅和增强的阈值电压稳定性

标题&#xff1a;Simultaneously Achieving Large Gate Swing and Enhanced Threshold Voltage Stability in Metal/Insulator/p-GaN Gate HEMT (IEDM2023) 摘要 摘要&#xff1a;对于增强型GaN功率晶体管的发展&#xff0c;栅压摆幅和阈值电压稳定性通常是互相排斥的。本文展…

Web前端-HTML(简介)

文章目录 1. HTML1.1概述1.2 HTML骨架标签1.3 HTML元素标签及分类1.4 HTML标签关系 2. 代码开发工具&#xff08;书写代码&#xff09;3. 文档类型<!DOCTYPE>4. 页面语言lang5. 字符集 1. HTML 1.1概述 HTML 指的是超文本标记语言 (Hyper Text Markup Language)&#x…

串口通信(6)-C#串口通信Modbus协议完整实例

本文讲解C#基于ModbusRTU协议串口通信完整实例。 前言 关于modbus的协议从上一篇中有介绍,本篇不在阐述。 串口通信(5)-C#串口通信数据接收不完整解决方案 创建实例 添加控件和事件等 参考界面文件 namespace ModbusRTUDemo {partial class MainForm{/// <summary>…

踩坑记录:java连接ssh的问题

目录 概述一、第一个问题解决 二、第二个问题分析解决 三、第三个问题分析解决 第四个问题解决 概述 手里有个CS架构的老系统&#xff0c;服务端要用SSH的方式传文件。没想到写了两天&#xff01;遇到一堆问题&#xff0c;于是记录下。&#xff08;老系统真恶心啊&#xff01;…

msvcp140.dll丢失怎样修复?全面分析msvcp140.dll的修复方法

在执行特定程序时&#xff0c;有可能遭遇msvcp140.dll文件遗失的困扰&#xff0c;此时该如何处理呢&#xff1f;此次将为您讲述面临此类问题的有效解决方案&#xff0c;涉及到多种修复方法&#xff0c;其中包括利用DLL修复工具进行操作。您可依据个人需求选择相应的修复方式&am…

学习Java第70天,过滤器Filter简介

过滤器概述 Filter,即过滤器,是JAVAEE技术规范之一,作用目标资源的请求进行过滤的一套技术规范,是Java Web项目中最为实用的技术之一 Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口 Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest和…

用GitBook制作自己的网页版电子书

用GitBook制作自己的网页版电子书 前言 几年前阅读过其他人用GitBook创建的文档&#xff0c;可以直接在浏览器中打开&#xff0c;页面干净整洁&#xff0c;非常清爽&#xff0c;至今印象深刻。 GitBook非常适合用来为个人或团队制作文档&#xff0c;对于我这种偶尔写博客的人…

Vue 实现一个弹出框,允许用户输入信息,并在确认时将输入的信息进行输出到控制台

父组件用来点击按钮弹出弹出框 <!--ParentComponent.vue--> <template><div><button click"showPopupV">点我会有个弹出框&#xff01;&#xff01;&#xff01;</button><PopupComponent v-if"showPopup" :data"p…