【语音算法】语音的预处理--去噪


引言

在人工智能中,算法固然很重要,但语音的预处理却直接地决定了算法的性能上限,因此有必要对语音进行去噪处理。

方法1

通过截取音频中的已知噪音部分,根据该噪音样本对整个音频进行降噪。截取噪音使用ffmpeg,降噪使用sox

1. 将音频流和视频流拆分为2个不同的文件:
视频: ffmpeg -i input.mp4 -vcodec copy -an tmpvid.mp4
音频: ffmpeg -i input.mp4 -acodec pcm_s16le -ar 128k -vn tmpaud.wav
2. 从上一步的音频结果文件中剪切一个噪声样本:
ffmpeg -i itmpaud.wav -ss 00:00:00.0 -t 00:00:00.5 noiseaud.wav
-ss: 从开始的时间偏移. (h: m: s.ms).
-t duration: 表示要剪切的音频段的持续时间(h: m: s.ms),以便下一步用来作为噪声文件。
选择一段没有语音、只有噪音的音频(例如,讲话者静音时的那一秒钟)。
3. 使用sox生成噪音profile:
sox noiseaud.wav -n noiseprof noise.prof
4. 清除音频流中的噪声样本:
sox tmpaud.wav tmpaud-clean.wav noisered noise.prof 0.21
更改0.21以调整采样率的灵敏度级别(0.2-0.3通常提供最佳结果)。
5. 使用ffmpeg将新的音频和视频流合并到一起:
ffmpeg -i tmpvid.mp4 -i tmpaud-clean.wav -map 0:v -map 1:a -c:v copy -c:a aac -b:a 128k out.mp4

如果只是要简单的实现语音去噪,那么直接进行3、4步的操作,将整段噪声语音作为噪声文件也可。

sox tmpaud.wav -n noiseprof noise.prof
sox tmpaud.wav tmpaud-clean.wav noisered noise.prof 0.21

方法2

谱减法:谱减算法为最早的语音降噪算法之一,它的提出,基于一个简单的原理:假设语音中的噪声只有加性噪声,只要将带噪语音谱减去噪声谱,就可以得到纯净语音幅度。这么做的前提是噪声信号是平稳的或者缓慢变化的。

#!/usr/bin/env python
import numpy as np
import wave
import math
import ctypes as ct


class FloatBits(ct.Structure):
    _fields_ = [
        ('M', ct.c_uint, 23),
        ('E', ct.c_uint, 8),
        ('S', ct.c_uint, 1)
    ]


class Float(ct.Union):
    _anonymous_ = ('bits',)
    _fields_ = [
        ('value', ct.c_float),
        ('bits', FloatBits)
    ]


def nextpow2(x):
    if x < 0:
        x = -x
    if x == 0:
        return 0
    d = Float()
    d.value = x
    if d.M == 0:
        return d.E - 127
    return d.E - 127 + 1

# 打开WAV文档
f = wave.open("input.wav")
# 读取格式信息
# (nchannels, sampwidth, framerate, nframes, comptype, compname)
params = f.getparams()
nchannels, sampwidth, framerate, nframes = params[:4]
fs = framerate
# 读取波形数据
str_data = f.readframes(nframes)
f.close()
# 将波形数据转换为数组
x = np.fromstring(str_data, dtype=np.short)
# 计算参数
len_ = 20 * fs // 1000 # 样本中帧的大小
PERC = 50 # 窗口重叠占帧的百分比
len1 = len_ * PERC // 100  # 重叠窗口
len2 = len_ - len1   # 非重叠窗口
# 设置默认参数
Thres = 3
Expnt = 2.0
beta = 0.002
G = 0.9
# 初始化汉明窗
win = np.hamming(len_)
# normalization gain for overlap+add with 50% overlap
winGain = len2 / sum(win)

# Noise magnitude calculations - assuming that the first 5 frames is noise/silence
nFFT = 2 * 2 ** (nextpow2(len_))
noise_mean = np.zeros(nFFT)

j = 0
for k in range(1, 6):
    noise_mean = noise_mean + abs(np.fft.fft(win * x[j:j + len_], nFFT))
    j = j + len_
noise_mu = noise_mean / 5

# --- allocate memory and initialize various variables
k = 1
img = 1j
x_old = np.zeros(len1)
Nframes = len(x) // len2 - 1
xfinal = np.zeros(Nframes * len2)

# =========================    Start Processing   ===============================
for n in range(0, Nframes):
    # Windowing
    insign = win * x[k-1:k + len_ - 1]
    # compute fourier transform of a frame
    spec = np.fft.fft(insign, nFFT)
    # compute the magnitude
    sig = abs(spec)

    # save the noisy phase information
    theta = np.angle(spec)
    SNRseg = 10 * np.log10(np.linalg.norm(sig, 2) ** 2 / np.linalg.norm(noise_mu, 2) ** 2)


    def berouti(SNR):
        if -5.0 <= SNR <= 20.0:
            a = 4 - SNR * 3 / 20
        else:
            if SNR < -5.0:
                a = 5
            if SNR > 20:
                a = 1
        return a


    def berouti1(SNR):
        if -5.0 <= SNR <= 20.0:
            a = 3 - SNR * 2 / 20
        else:
            if SNR < -5.0:
                a = 4
            if SNR > 20:
                a = 1
        return a

    if Expnt == 1.0:  # 幅度谱
        alpha = berouti1(SNRseg)
    else:  # 功率谱
        alpha = berouti(SNRseg)
    #############
    sub_speech = sig ** Expnt - alpha * noise_mu ** Expnt;
    # 当纯净信号小于噪声信号的功率时
    diffw = sub_speech - beta * noise_mu ** Expnt
    # beta negative components

    def find_index(x_list):
        index_list = []
        for i in range(len(x_list)):
            if x_list[i] < 0:
                index_list.append(i)
        return index_list

    z = find_index(diffw)
    if len(z) > 0:
        # 用估计出来的噪声信号表示下限值
        sub_speech[z] = beta * noise_mu[z] ** Expnt
        # --- implement a simple VAD detector --------------
    if SNRseg < Thres:  # Update noise spectrum
        noise_temp = G * noise_mu ** Expnt + (1 - G) * sig ** Expnt  # 平滑处理噪声功率谱
        noise_mu = noise_temp ** (1 / Expnt)  # 新的噪声幅度谱
    # flipud函数实现矩阵的上下翻转,是以矩阵的“水平中线”为对称轴
    # 交换上下对称元素
    sub_speech[nFFT // 2 + 1:nFFT] = np.flipud(sub_speech[1:nFFT // 2])
    x_phase = (sub_speech ** (1 / Expnt)) * (np.array([math.cos(x) for x in theta]) + img * (np.array([math.sin(x) for x in theta])))
    # take the IFFT

    xi = np.fft.ifft(x_phase).real
    # --- Overlap and add ---------------
    xfinal[k-1:k + len2 - 1] = x_old + xi[0:len1]
    x_old = xi[0 + len1:len_]
    k = k + len2
# 保存文件
wf = wave.open('output.wav', 'wb')
# 设置参数
wf.setparams(params)
# 设置波形文件 .tostring()将array转换为data
wave_data = (winGain * xfinal).astype(np.short)
wf.writeframes(wave_data.tostring())
wf.close()

总结

个人感觉谱减法更加耗时,且适用场景相对有限。利用sox来去噪可能是一种相对更成熟的方法。

参考文献

http://www.zoharbabin.com/how-to-do-noise-reduction-using-ffmpeg-and-sox/
https://github.com/itaa/soja-box/tree/master/enhance_speach

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
时变维纳滤波器(Time-Varying Wiener Filter,TVWF)是一种常用的语音去噪算法。它基于Wiener滤波器,可以根据信号的特点自适应地调整滤波器参数,以在保留语音信号的同时减少噪声。 下面是基于TVWF的语音去噪算法的Matlab实现步骤: 1. 读入含噪声语音文件,并进行预处理,如加窗、分帧等操作。 2. 对每一帧数据进行短时傅里叶变换(Short-Time Fourier Transform,STFT),得到该帧频谱。 3. 估计该帧噪声的功率谱,可以采用前几帧的平均功率谱作为噪声估计。 4. 利用该帧频谱和噪声功率谱,计算出Wiener滤波器的增益函数。 5. 应用增益函数到该帧频谱上,得到去噪后的频谱。 6. 对去噪后的频谱进行逆短时傅里叶变换(Inverse Short-Time Fourier Transform,ISTFT),得到该帧去噪后的时域信号。 7. 将所有帧的去噪后时域信号拼接起来,得到整个去噪后的语音信号。 下面是Matlab代码实现: ```matlab % 读入含噪声语音文件 [x, Fs] = audioread('noisy.wav'); % 设置参数,如帧长、帧移、窗函数等 frameLen = 0.02; % 20ms frameShift = 0.01; % 10ms win = hamming(frameLen * Fs); % 分帧,并进行STFT frames = enframe(x, win, frameShift * Fs); spectra = abs(fft(frames)); % 估计噪声功率谱,取前5帧的平均值 noiseSpectra = mean(spectra(:, 1:5), 2); % 初始化Wiener滤波器的增益函数 gainFunc = ones(size(noiseSpectra)); % 对每一帧数据进行处理 for i = 1:size(spectra, 2) % 计算Wiener滤波器的增益函数 gainFunc = noiseSpectra ./ (noiseSpectra + spectra(:, i)); % 应用增益函数到该帧频谱上 spectra(:, i) = spectra(:, i) .* gainFunc; end % 对去噪后的频谱进行ISTFT cleanFrames = real(ifft(spectra)); cleanFrames = cleanFrames .* repmat(win, 1, size(cleanFrames, 2)); cleanFrames = ola(cleanFrames, win, frameShift * Fs); % 拼接所有帧,得到整个去噪后的语音信号 y = cleanFrames(:); % 保存去噪后的语音文件 audiowrite('clean.wav', y, Fs); ``` 需要注意的是,该算法的效果取决于噪声估计的准确度和Wiener滤波器的增益函数计算方法。可以根据实际情况进行调整和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值