[简单实践]Noisy Print - 自制基于加性噪声模型的简易降噪器

NoisyPrint

最近在学习的过程中,突然想起一个在Adobe Audition中用过的功能。

为什么会想到这个功能呢,因为在我使用DeepFilter的过程中,我发现对于一些低信噪比的信号来说,DeepFilter很容易出现过拟合现象,导致音源的过度失真。那么有没有什么好办法解决这个问题呢?答案当然是有的,就是这个采样部分噪音,然后在频域上进行相减的方法。

项目地址,持续更新欢迎Star Github : LeventureQys/NoisyPrint

原理

1. 加性噪声模型

在信号处理领域,含噪声的信号通常可以被建模为:

x ( t ) = s ( t ) + n ( t ) x(t) = s(t) + n(t) x(t)=s(t)+n(t)

  • x(t):观测到的含噪声信号
  • s(t):原始干净信号
  • n(t):噪声信号

这个模型假设噪声是加性的,并且与原始信号不相关。

2. 频域中的表示

通过短时傅里叶变换(STFT),将时域信号转换到频域:

X ( ω ) = S ( ω ) + N ( ω ) X(\omega) = S(\omega) + N(\omega) X(ω)=S(ω)+N(ω)

  • X ( ω ) X(\omega) X(ω):含噪声信号的频谱
  • S ( ω ) S(\omega) S(ω): 原始信号的频谱
  • N ( ω ) N(\omega) N(ω): 噪声信号的频谱

这意味着在频域中,信号的频谱是线性叠加的。

综上

我们可以直接反过来计算,得到我们的 S ( ω ) S(\omega) S(ω) 然后在进行反傅里叶变换,获得时域信息。

总结流程:

  • 1.采集噪声样本并建立噪声频谱模型
  • 2.将含噪声的音频信号分帧处理
  • 3.对每一帧进行窗函数处理
  • 4.对窗化后的帧进行快速傅里叶变换(FFT)
  • 5.从信号的频谱中减去噪声频谱
  • 6.处理频谱减法后的负值和伪影
  • 7.进行逆快速傅里叶变换(IFFT)重建时域信号
  • 8.通过重叠相加(Overlap-Add)方法重建完整的信号
  • 9.后处理,如频谱平滑

实操:

1.采集噪声样本并建立噪声频谱模型

这一段我直接使用Audition进行的,具体不表,总之是两段音频,分别是全音频和纯噪音

原始音频:

纯噪音:

可以看得到,这音频中明显是有比较强烈的噪音的。

2. 3. 4.

这部分暂且不表,详情见下方代码:

import numpy as np
import scipy.io.wavfile as wav
import matplotlib.pyplot as plt
from scipy.signal import get_window
from scipy.fftpack import fft, ifft

# 1. 读取纯噪声信号,建立噪声频谱模型
noise_rate, noise_data = wav.read('./AudioSource/Noisy.wav')
# 如果噪声是立体声,转换为单声道
if len(noise_data.shape) > 1:
    noise_data = noise_data.mean(axis=1)

# 计算噪声频谱模型
frame_size = 1024
overlap = 512
noise_frames = []
for i in range(0, len(noise_data) - frame_size, overlap):
    frame = noise_data[i:i+frame_size]
    windowed_frame = frame * get_window('hamming', frame_size)
    spectrum = fft(windowed_frame)
    noise_frames.append(np.abs(spectrum))
# 计算平均噪声频谱
noise_spectrum = np.mean(noise_frames, axis=0)

# 2. 读取需要降噪的音频信号
rate, data = wav.read('./AudioSource/Source.wav')
if len(data.shape) > 1:
    data = data.mean(axis=1)

# 保存降噪前的频谱,用于后续对比
original_spectrum = []

# 进行降噪处理
output_data = np.zeros(len(data))
window = get_window('hamming', frame_size)
for i in range(0, len(data) - frame_size, overlap):
    # 2. 分帧
    frame = data[i:i+frame_size]
    # 3. 窗函数处理
    windowed_frame = frame * window
    # 4. FFT
    spectrum = fft(windowed_frame)
    # 保存原始频谱
    original_spectrum.append(np.abs(spectrum))

    # 5. 频谱减法
    magnitude = np.abs(spectrum)
    phase = np.angle(spectrum)
    subtracted_magnitude = magnitude - noise_spectrum
    # 6. 处理负值和伪影
    subtracted_magnitude = np.maximum(subtracted_magnitude, 0.0)

    # 7. IFFT
    reconstructed_spectrum = subtracted_magnitude * np.exp(1j * phase)
    reconstructed_frame = np.real(ifft(reconstructed_spectrum))
    # 8. Overlap-Add 重建信号
    output_data[i:i+frame_size] += reconstructed_frame * window

# 保存降噪后的频谱,用于对比
denoised_spectrum = []
for i in range(0, len(output_data) - frame_size, overlap):
    frame = output_data[i:i+frame_size]
    windowed_frame = frame * window
    spectrum = fft(windowed_frame)
    denoised_spectrum.append(np.abs(spectrum))

# 9. 后处理(可选,这里暂不实现)

# 保存降噪后的音频
wav.write('./AudioSource/Denosed.wav', rate, output_data.astype(np.int16))

# 绘制频谱对比
# 计算平均频谱
original_spectrum_mean = np.mean(original_spectrum, axis=0)
denoised_spectrum_mean = np.mean(denoised_spectrum, axis=0)
freqs = np.linspace(0, rate, frame_size)

plt.figure(figsize=(12,6))
plt.plot(freqs[:frame_size//2], 20*np.log10(original_spectrum_mean[:frame_size//2]), label='Original')
plt.plot(freqs[:frame_size//2], 20*np.log10(denoised_spectrum_mean[:frame_size//2]), label='Processed')
plt.xlabel('频率 (Hz)')
plt.ylabel('幅度 (dB)')
plt.title('降噪前后频谱对比')
plt.legend()
plt.show()

测试效果

这个库还有很多可以优化的空间,可以参考Audition下的控制面板,可以简单窥见可以优化的空间:

  • 1.优化曲线频域相减对于频域下不同值的曲线增益因子 r \mathcal{r} r在这里是没有考虑的,在实际的使用中是可以进行考虑的。

  • 2.这里是直接简单粗暴的直接减掉了所有的频域内容,实际上可以不那么生硬地进行剪辑。

  • 3.可以使用小范围的 FFT来进行实时性音频降噪的尝试。

  • 4.进行C++的移植,实际上这些库都有。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值