【问题标题】:High pass filter in PythonPython中的高通滤波器
【发布时间】:2021-10-06 18:27:08
【问题描述】:

我正在使用 Python 录制 5 秒剪辑的实时音频,并且想要截断低于特定频率的所有声音,例如10kHz。到目前为止,这是我的脚本:

import pyaudio, wave, time, sys, os
from array import array
from scipy import signal

FORMAT=pyaudio.paInt16
CHANNELS=1
CHUNK=1024
RATE=44100 
RECORD_SECONDS=5

def butter_highpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
    return b, a

def butter_highpass_filter(data, cutoff, fs, order=5):
    b, a = butter_highpass(cutoff, fs, order=order)
    y = signal.filtfilt(b, a, data)
    return y
    
audio=pyaudio.PyAudio() 

stream=audio.open(format=FORMAT,channels=CHANNELS, 
                  rate=RATE,
                  input=True,
                  frames_per_buffer=CHUNK)
while True:
    # read data
    data=stream.read(CHUNK)
    data_chunk=array('h',data)
    data_chunk = butter_highpass_filter(data_chunk,10000,RATE)
    frames=[]
    for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
        data = stream.read(CHUNK)
        frames.append(data)
    # write to file
    words = ["RECORDING-", time.strftime("%Y%m%d-%H%M%S"), ".wav"]
    FILE_NAME= "".join(words) 
    wavfile=wave.open(FILE_NAME,'wb')
    wavfile.setnchannels(CHANNELS)
    wavfile.setsampwidth(audio.get_sample_size(FORMAT))
    wavfile.setframerate(RATE)
    wavfile.writeframes(b''.join(frames))
    wavfile.close()

但这似乎不起作用。我想剪掉低于指定频率的所有(或尽可能多的)声音。为什么我正在使用的滤波器似乎无法滤除 10kHz 以下的声音?我怎样才能让它发挥作用?

【问题讨论】:

  • 这基本上是2个问题。如何创建数字高通滤波器以及如何在 Python 中应用滤波器。如果你这样做是为了达到目的,如果你真的对滤波器设计感兴趣,比如作为大学课程的一部分,github 会有大量的例子供你借鉴 gist.github.com/endolith/5455375,我真的强烈推荐与您所在机构的员工互动,dsp.stackexchange.com 也有大量资源
  • 如果没有真正的尝试,也就是说没有标有 ENTER SOLUTION HERE 空格的代码,您不太可能得到社区的良好响应。
  • 嗨@fdcpp。添加了一些我尝试过的代码(之前没有包括因为试图简洁)。我不明白为什么这是两个问题 - 这只是我想要完成的一项任务,我认为我无法进一步分解它。
  • 我不同意,理解过滤器与语言无关,无论您使用 Python、C、Java 和 c... 一个 Python 实现可能生成一些语言特定的问题。如果您对过滤器感兴趣,请先解决这个问题。如果不是,我建议您尝试搜索一些解决方案(GitHub 将是一个好的开始)。 我找到了一些代码,它不起作用,帮助有点违背SO的精神。
  • 此外,Butterworth 函数位于单独的脚本中。展示您如何尝试将这些功能集成到您当前拥有的功能中会是一个好主意

标签: python audio signal-processing


【解决方案1】:

简介

我们的目标是对音频应用一个砖墙式 10 kHz 高通滤波器,然后将其保存。音频连续录制并在 5 秒内保存到单独的 .wav 文件中。

到目前为止我们所拥有的

目前当前脚本:

  • 声明一个函数来应用一个巴特沃斯高通滤波器 (butter_high-pass_filter),其输出是一个浮点值数组
    • butter_high-pass_filter 使用 signal.filtfilt
    • 函数的输入是短格式(错误 1)
data_chunk=array('h',data)
data_chunk = butter_high-pass_filter(data_chunk,10000,RATE)
  • data_chunk 从未使用过,因此从不将高通过音频帧保存到文件中(错误 2)
  • 数据被读取 5 秒值得音频
frames=[]
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)
  • stream.read 被阻塞,所以这将等到正确数量的音频被读取。
  • 然后将收集到的数据以相同格式写入wav 文件
words = ["RECORDING-", time.strftime("%Y%m%d-%H%M%S"), ".wav"]
FILE_NAME= "".join(words)
wavfile=wave.open(FILE_NAME,'wb')
wavfile.setnchannels(CHANNELS)
wavfile.setsampwidth(audio.get_sample_size(FORMAT))
wavfile.setframerate(RATE)
wavfile.writeframes(b''.join(frames))
wavfile.close()

解决方案

这里的问题是解决方案是多方面的,并且需要当前脚本中当前缺少的多个部分。 此外,为了避免进一步的复杂化,需要采用与最初预期的方法略有不同的方法。与实时应用过滤器不同,过滤器可以在保存之前简单地应用于wav 样本数据。 这个

  • 无需处理过滤器状态连续性
  • 限制在数据类型之间来回转换的需要

此外,外部永远while 循环已被删除。时间和记忆开始成为一个问题。该程序可以简单地一遍又一遍地重新运行,直到用例覆盖更加清晰

  • 为什么必须对音频进行高通滤波?
  • 为什么在记录所有数据(即应用于wav 文件后)不能进行过滤?
  • 为什么必须保存数据?
  • 数据格式有限制吗?位深度?采样率?

在回答这些问题之前,有太多可能的路线,每条路线都有限制,实际上可以在一个答案中涵盖。

故障

流程的完整细分将是

  1. 声明一个函数,将浮点样本数据作为输入,将浮点数据作为输出的高通滤波器
  2. 将来自 pyaudio 的 5 秒字节字符串数据连接到单个变量中
  3. 将数据解压缩为 16 位(有符号短)格式的样本数组
  4. 将样本缩放为 1.0 到 -1.0 之间的浮点格式
  5. 将数据发送到高通滤波器
  6. 在 16 位(有符号短)格式范围内缩放过滤器样本
  7. 将 16 位过滤样本打包成一个字节串
  8. 将过滤后的字节串数据写入 wav 文件。

脚本

import pyaudio, wave, time, sys, os, struct
from array import array
from scipy import signal

# 1. Declare a function that takes floating point sample data as input and high pass
#    filters with floating point data as output
# 2. concatenate 5 seconds of byte-string data from PyAudio into a single variable
# 3. unpack data as a 16-bit (signed short) format array of samples
# 4. scale samples to floating point format between 1.0 and -1.0
# 5. send data to high pass filter
# 6. scale filter samples in the range of 16-bit (signed short) format
# 7. pack 16-bit filtered samples into a byte string
# 8. write filtered byte-string data to a wav file.

# ---------------------------------------------------------------------
# 1. Declare High Pass Filter

def butter_highpass(cutoff: float, fs: float, order: int = 5) -> tuple:
    """
    Generate FIR and IIR coefficients for a butterworth highpass filter

    :param cutoff: cutoff frequency (hz)
    :param fs: sampling rate
    :param order: filter order
    :return: tuple of filter coefficients
    """
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
    return b, a


def butter_highpass_filter(data: [float], cutoff: float, fs: float, order: int = 5) -> [float]:
    """
    apply a butterworth high pass filter to sample data in floating point format

    :param data: float sample data array
    :param cutoff: filter cutoff (hz)
    :param fs: sample data sampling rate
    :param order: filter order
    :return: floating point array of filtered sample data
    """
    b, a = butter_highpass(cutoff, fs, order=order)
    y = signal.filtfilt(b, a, data)
    return y

# ---------------------------------------------------------------------
# Init Global Variables

FORMAT = pyaudio.paInt16
CHANNELS = 1
CHUNK = 1024
RATE = 44100
RECORD_SECONDS = 5

audio = pyaudio.PyAudio()

stream = audio.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)
# ---------------------------------------------------------------------
# Main Program

if __name__ == '__main__':

    # ---------------------------------------------------------------------
    # 2. concat 5 seconds of data into a single string
    frames = b''
    for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
        frames += stream.read(CHUNK)
    # ---------------------------------------------------------------------
    # 3. Unpack data as a  16 - bit
    sample_data = array('h', frames)
    # ---------------------------------------------------------------------
    # 4. scale samples to floating point format between 1.0 and -1.0
    samples = [sample_datum / (2**15) for sample_datum in sample_data]
    print("Max Amplitude:", max(samples))
    # ---------------------------------------------------------------------
    # 5. send data to high pass filter
    filtered_sample_data = butter_highpass_filter(samples, 10000.0, RATE)
    # filtered_sample_data = samples
    # ---------------------------------------------------------------------
    # 6. scale filter samples in the range of 16-bit (signed short) format
    #    (2 ** 14) for headroom (very lazy)
    sample_data_16_bit = [int(sample * (2 ** 14)) for sample in filtered_sample_data]
    # # ---------------------------------------------------------------------
    # # 7. pack 16-bit filtered samples into a byte string
    raw_data = [struct.pack('h', sample) for sample in sample_data_16_bit]
    # # ---------------------------------------------------------------------
    # # 8. Write Wav
    file_name = "".join(["RECORDING-", time.strftime("%Y%m%d-%H%M%S"), ".wav"])
    wavfile = wave.open(file_name, 'wb')
    wavfile.setnchannels(CHANNELS)
    wavfile.setsampwidth(audio.get_sample_size(FORMAT))
    wavfile.setframerate(RATE)
    wavfile.writeframes(b''.join(raw_data))
    wavfile.close()

评论

潜在的添加可能包括使用matplotlib 绘制频谱、标准化过滤后的音频、将过程封装到自定义函数中,但这些都留给 OP 作为练习。

以下是标准化后的音频频谱屏幕截图。正如@marco 在 cmets 中所引用的那样,过滤器确实有一个斜率,这是预期的。这可以通过增加过滤器的阶数来改善。

【讨论】:

  • 感谢@fdcpp 的详尽回答。它设置了一个while循环并过滤每个数据块的原因是我想使用脚本来监视指定频率上的实时音频。不使用data_chunk 的原因是因为它会转到我没有包含的脚本的不同部分。抱歉,这在我的问题中并不明显。再次感谢您的帮助,我会玩这个,看看我是否可以让它适合我正在做的事情。
  • @Thomas 如果你想监控一个频率范围,傅里叶变换对你没有使用它有什么限制?
  • 这被建议作为另一个问题stackoverflow.com/a/68388989/1640528 的答案,但问题是频谱的低端有很多能量,因此需要过滤它(虽然我对更好的方法的建议持开放态度)
  • @Thomas 在这种情况下,您可以进行傅立叶变换并简单地忽略
猜你喜欢
  • 2016-12-26
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 2021-09-25
  • 1970-01-01
  • 1970-01-01
  • 2014-02-09
  • 2013-03-29
相关资源
最近更新 更多