• 语音增强——谱减法的改进(过减法)及其python实现


    参考视频:

    https://www.bilibili.com/video/BV1CM4y1M7kb?spm_id_from=333.999.0.0&vd_source=77c874a500ef21df351103560dada737

    语音降噪是语音信号处理的初始步骤,目前已经有很多成熟的算法。而谱减法作为经典的降噪算法实现简单,运行处理快,被广泛的应用在语音降噪领域。

    基本谱减法的缺点及其改进
    谱减法最显著的缺点是会引入“音乐噪声”,由于谱减过程中可能出现负的幅度值,半波整流是一种直接的解决办法,但是这种非线性处理会导致频谱随机频率位置上出现小的、独立的峰值,在时域中表现为明显的多频颤音,也称为“音乐噪声”。如果处理不当,在某些语音段,“音乐噪声”的影响甚至比干扰噪声更为明显。造成“音乐噪声”的常见原因有:

    1、对谱减过程中负值的非线性处理。
    2、噪声谱估计不匹配。静音段的平均噪声谱可能与实际语音段的噪声分量有着较大差别,相减后会有残留的孤立噪声段。
    3、谱估计方法的误差。例如周期图等有偏功率谱估计方法带来的偏差。
    4、抑制函数有较大的可变性。
    为了减小音乐噪声,学者们提出了一系列的改进方法,感兴趣的读者可以自行了解。这里介绍过减法的使用方法,相比直接置零,该方法设置了一个谱值下限。具体可表示为:

     若γ=1,上述过程相当于能量谱的谱减法;若γ=0.5,上述过程相当于幅度谱的谱减法

    程序如下:

    1. import librosa
    2. from librosa.core.spectrum import amplitude_to_db
    3. import numpy as np
    4. from scipy.signal import lfilter, firwin, freqz
    5. import soundfile as sf
    6. import matplotlib.pyplot as plt
    7. if __name__ == "__main__":
    8. clean_wav_file = "sf1_cln.wav"
    9. clean,fs = librosa.load(clean_wav_file,sr=None)
    10. print(fs)
    11. noisy_wav_file = "sf1_n0L.wav"
    12. noisy,fs = librosa.load(noisy_wav_file,sr=None)
    13. # 计算 nosiy 信号的频谱
    14. S_noisy = librosa.stft(noisy,n_fft=256, hop_length=128, win_length=256) # D x T
    15. D,T = np.shape(S_noisy)
    16. Mag_noisy= np.abs(S_noisy)
    17. Phase_nosiy= np.angle(S_noisy)
    18. Power_nosiy = Mag_noisy**2
    19. print(fs)
    20. # 估计噪声信号的能量
    21. # 由于噪声信号未知 这里假设 含噪(noisy)信号的前30帧为噪声
    22. Mag_nosie = np.mean(np.abs(S_noisy[:,:30]),axis=1,keepdims=True)
    23. Power_nosie = Mag_nosie**2
    24. Power_nosie = np.tile(Power_nosie,[1,T])
    25. ## 方法1 直接减
    26. # # 能量减
    27. # Power_enhenc = Power_nosiy-Power_nosie
    28. # # 保证能量大于0
    29. # Power_enhenc[Power_enhenc<0]=0
    30. # Mag_enhenc = np.sqrt(Power_enhenc)
    31. # 幅度减
    32. # Mag_enhenc = np.sqrt(Power_nosiy) - np.sqrt(Power_nosie)
    33. # Mag_enhenc[Mag_enhenc<0]=0
    34. ## 方法2 超减
    35. # 引入参数
    36. alpha = 4
    37. gamma = 1
    38. Power_enhenc = np.power(Power_nosiy,gamma) - alpha*np.power(Power_nosie,gamma)
    39. Power_enhenc = np.power(Power_enhenc,1/gamma)
    40. # 对于过小的值用 beta* Power_nosie 替代
    41. beta = 0.0001
    42. mask = (Power_enhenc>=beta*Power_nosie)-0
    43. print(mask.shape)
    44. Power_enhenc = mask*Power_enhenc + beta*(1-mask)*Power_nosie
    45. Mag_enhenc = np.sqrt(Power_enhenc)
    46. # 对信号进行恢复
    47. S_enhec = Mag_enhenc*np.exp(1j*Phase_nosiy)
    48. enhenc = librosa.istft(S_enhec, hop_length=128, win_length=256)
    49. sf.write("enhce_2.wav",enhenc,fs)
    50. print(fs)
    51. # 绘制谱图
    52. plt.subplot(3,1,1)
    53. plt.specgram(clean,NFFT=256,Fs=fs)
    54. plt.xlabel("clean specgram")
    55. plt.subplot(3,1,2)
    56. plt.specgram(noisy,NFFT=256,Fs=fs)
    57. plt.xlabel("noisy specgram")
    58. plt.subplot(3,1,3)
    59. plt.specgram(enhenc,NFFT=256,Fs=fs)
    60. plt.xlabel("enhece specgram")
    61. plt.show()

    运行结果如下:

    上面的方法会导致谱线的不连续,因此在该方法基础上引入平滑机制。

    为了减小音乐噪声,这里介绍引入平滑机制的过减法,相比直接置零,该方法设置了一个谱值下限。在噪声估计阶段,计算一个最大噪声帧,如果谱减后某时频点的值小于最大噪声帧的对应频点值,则将其替换为相邻帧的最小值。具体可表示为:

     程序如下:

    1. import librosa
    2. from librosa.core.spectrum import amplitude_to_db
    3. import numpy as np
    4. from scipy.signal import lfilter, firwin, freqz
    5. import soundfile as sf
    6. import matplotlib.pyplot as plt
    7. if __name__ == "__main__":
    8. clean_wav_file = "sf1_cln.wav"
    9. clean,fs = librosa.load(clean_wav_file,sr=None)
    10. print(fs)
    11. noisy_wav_file = "sf1_n0L.wav"
    12. noisy,fs = librosa.load(noisy_wav_file,sr=None)
    13. # 计算 nosiy 信号的频谱
    14. S_noisy = librosa.stft(noisy,n_fft=256, hop_length=128, win_length=256) # D x T
    15. D,T = np.shape(S_noisy)
    16. Mag_noisy= np.abs(S_noisy)
    17. Phase_nosiy= np.angle(S_noisy)
    18. Power_nosiy = Mag_noisy**2
    19. print(fs)
    20. # 估计噪声信号的能量
    21. # 由于噪声信号未知 这里假设 含噪(noisy)信号的前30帧为噪声
    22. Mag_nosie = np.mean(np.abs(S_noisy[:,:31]),axis=1,keepdims=True)
    23. Power_nosie = Mag_nosie**2
    24. Power_nosie = np.tile(Power_nosie,[1,T])
    25. ## 方法1 直接减
    26. # # 能量减
    27. # Power_enhenc = Power_nosiy-Power_nosie
    28. # # 保证能量大于0
    29. # Power_enhenc[Power_enhenc<0]=0
    30. # Mag_enhenc = np.sqrt(Power_enhenc)
    31. # 幅度减
    32. # Mag_enhenc = np.sqrt(Power_nosiy) - np.sqrt(Power_nosie)
    33. # Mag_enhenc[Mag_enhenc<0]=0
    34. ## 方法2 超减
    35. # # 引入参数
    36. # alpha = 6
    37. # gamma = 1
    38. # Power_enhenc = np.power(Power_nosiy,gamma) - alpha*np.power(Power_nosie,gamma)
    39. # Power_enhenc = np.power(Power_enhenc,1/gamma)
    40. # # 对于过小的值用 beta* Power_nosie 替代
    41. # beta = 0.01
    42. # mask = (Power_enhenc>=beta*Power_nosie)-0
    43. # print(mask.shape)
    44. # Power_enhenc = mask*Power_enhenc + beta*(1-mask)*beta*Power_nosie
    45. # Mag_enhenc = np.sqrt(Power_enhenc)
    46. ## 方法3 引入平滑
    47. Mag_noisy_new = np.copy(Mag_noisy)
    48. k=1
    49. for t in range(k,T-k):
    50. Mag_noisy_new[:,t] = np.mean(Mag_noisy[:,t-k:t+k+1],axis=1)
    51. Power_nosiy = Mag_noisy_new**2
    52. # 超减法去噪
    53. alpha = 4
    54. gamma = 1
    55. Power_enhenc = np.power(Power_nosiy,gamma) - alpha*np.power(Power_nosie,gamma)
    56. Power_enhenc = np.power(Power_enhenc,1/gamma)
    57. # 对于过小的值用 beta* Power_nosie 替代
    58. beta = 0.0001
    59. mask = (Power_enhenc>=beta*Power_nosie)-0
    60. Power_enhenc = mask*Power_enhenc + beta*(1-mask)*Power_nosie
    61. Mag_enhenc = np.sqrt(Power_enhenc)
    62. Mag_enhenc_new = np.copy(Mag_enhenc)
    63. # 计算最大噪声残差
    64. maxnr = np.max(np.abs(S_noisy[:,:31])-Mag_nosie,axis =1)
    65. k = 1
    66. for t in range(k,T-k):
    67. index = np.where(Mag_enhenc[:,t]<maxnr)[0]
    68. temp = np.min(Mag_enhenc[:,t-k:t+k+1],axis=1)
    69. Mag_enhenc_new[index,t] = temp[index]
    70. # 对信号进行恢复
    71. S_enhec = Mag_enhenc_new*np.exp(1j*Phase_nosiy)
    72. enhenc = librosa.istft(S_enhec, hop_length=128, win_length=256)
    73. sf.write("enhce_3.wav",enhenc,fs)
    74. print(fs)
    75. # 绘制谱图
    76. plt.subplot(3,1,1)
    77. plt.specgram(clean,NFFT=256,Fs=fs)
    78. plt.xlabel("clean specgram")
    79. plt.subplot(3,1,2)
    80. plt.specgram(noisy,NFFT=256,Fs=fs)
    81. plt.xlabel("noisy specgram")
    82. plt.subplot(3,1,3)
    83. plt.specgram(enhenc,NFFT=256,Fs=fs)
    84. plt.xlabel("enhece specgram")
    85. plt.show()

    运行结果如下:

     

  • 相关阅读:
    代码随想录算法训练营Day62|冗余连接、冗余连接II
    Go语言实践案例之简单字典
    抓到Dubbo异步调用的小BUG,再送你一个贡献开源代码的机会
    前端基础自学整理|HTML + JavaScript + DOM事件
    Python入门教程之py文件与pyc文件的相互转换
    SpringBoot的核心原理(扒笔记记录)
    Python 工匠 第三章 容器类型
    移动端1px-从基本原理到开源解决方案介绍
    JavaScript基础 JavaScript第二天 1. 运算符
    Macos视频增强修复工具:Topaz Video AI for mac
  • 原文地址:https://blog.csdn.net/qq_42233059/article/details/126848214