3. 时域分析:波形可视化、短时能量计算、过零率分析
各位同学,欢迎来到第三讲。前面我们聊了海豚声波的基础知识,也讲了怎么把声音信号读进Python。今天咱们要动真格的了——直接看波形、算能量、数过零率。
说白了,时域分析就是“看信号本身的样子”。不搞傅里叶变换,不碰频域,就在时间轴上做文章。我刚开始接触海豚声波时,觉得时域太简单了,不就是画个图嘛。后来才发现,很多关键信息就藏在波形里。
3.1 波形可视化——先看看长什么样
拿到一段海豚声波,第一件事是什么?我个人习惯是:先画波形图。就像医生看X光片一样,波形图能告诉你很多信息。
海豚的叫声大致分三类:
- 咔哒声(Click):短促、尖锐,像敲击声。用于回声定位。
- 哨叫声(Whistle):连续、平滑,频率变化。用于社交沟通。
- 突发脉冲(Burst Pulse):介于两者之间,一串快速咔哒声。
画波形图其实很简单,用matplotlib几行代码就搞定。但这里有个坑——采样率一定要设对。我曾经因为忘记设置采样率,画出来的时间轴全是错的,还纳闷海豚怎么叫得这么慢。
import numpy as np
import matplotlib.pyplot as plt
import soundfile as sf
# 读取音频文件
data, sr = sf.read('dolphin_click.wav')
# 只取前0.1秒,方便观察
duration = 0.1
samples = int(duration * sr)
segment = data[:samples]
# 生成时间轴
time = np.linspace(0, duration, samples)
# 画图
plt.figure(figsize=(12, 4))
plt.plot(time, segment, color='#2E86AB', linewidth=0.8)
plt.xlabel('时间 (秒)')
plt.ylabel('振幅')
plt.title('海豚咔哒声波形图')
plt.grid(True, alpha=0.3)
plt.show()
你想想看,如果波形是稀疏的几个尖峰,那大概率是咔哒声。如果是连续起伏的波浪线,那就是哨叫声。嗯,这里要注意:海豚的咔哒声非常短,通常只有几十微秒到几百微秒。所以画图时时间轴要放大到毫秒级。
3.2 短时能量计算——声音有多“响”
波形图能看形状,但看不出能量变化。什么叫能量?说白了就是声音的“力度”。海豚在不同行为下,发出的声音能量差异很大。
短时能量的计算思路很简单:把信号切成一小段一小段(叫“帧”),每帧算一个能量值。为什么要“短时”?因为海豚的声音是非平稳的,能量随时在变。你算一整段的平均能量,那等于啥也没看出来。
公式长这样:
E(m) = Σ [x(n) · w(n - m)]²
其中x(n)是信号,w(n)是窗函数,m是帧序号。
我在项目中遇到过一个问题:直接算平方和,结果数值特别大,画出来全是尖峰。后来发现是没做归一化。所以建议你们算完后除以帧长,得到“每样本平均能量”。
def short_time_energy(signal, frame_size=256, hop_size=128):
"""
计算短时能量
signal: 输入信号
frame_size: 帧长(样本点数)
hop_size: 帧移(相邻帧重叠的样本数)
"""
energy = []
for start in range(0, len(signal) - frame_size, hop_size):
frame = signal[start:start + frame_size]
# 汉宁窗,减少频谱泄露
window = np.hanning(frame_size)
frame_windowed = frame * window
# 计算能量并归一化
e = np.sum(frame_windowed ** 2) / frame_size
energy.append(e)
return np.array(energy)
# 使用示例
energy = short_time_energy(data, frame_size=512, hop_size=256)
time_energy = np.arange(len(energy)) * hop_size / sr
plt.figure(figsize=(12, 4))
plt.plot(time_energy, energy, color='#D64933', linewidth=1.0)
plt.xlabel('时间 (秒)')
plt.ylabel('短时能量')
plt.title('海豚声波短时能量曲线')
plt.grid(True, alpha=0.3)
plt.show()
帧长怎么选?我一般用256或512点(采样率48kHz时,对应约5-10毫秒)。帧长太短,能量波动太剧烈;帧长太长,时间分辨率不够。你想想看,海豚的咔哒声才几十微秒,帧长要是设成1秒,那能量峰值早被平均掉了。
3.3 过零率分析——声音有多“糙”
过零率(Zero Crossing Rate, ZCR),名字听着唬人,其实概念很简单:单位时间内,信号穿过零轴的次数。
为什么关心这个?因为海豚的不同叫声,过零率差异很大:
- 咔哒声:频率高、变化快,过零率很高(几百到上千次/秒)
- 哨叫声:频率相对稳定,过零率中等(几十到几百次/秒)
- 背景噪声:随机波动,过零率不稳定
计算过零率时,有个细节要注意:信号可能有直流偏移。如果信号整体偏上或偏下,过零率会严重失真。我曾经吃过这个亏——分析一段海豚录音,过零率低得离谱,后来发现是水听器有直流偏置。解决办法很简单:先减去均值。
def zero_crossing_rate(signal, frame_size=256, hop_size=128):
"""
计算短时过零率
"""
# 去除直流分量
signal = signal - np.mean(signal)
zcr = []
for start in range(0, len(signal) - frame_size, hop_size):
frame = signal[start:start + frame_size]
# 过零率计算:相邻样本符号变化的次数
sign_changes = np.sum(np.abs(np.diff(np.sign(frame)))) / 2
zcr_frame = sign_changes / frame_size
zcr.append(zcr_frame)
return np.array(zcr)
# 使用示例
zcr = zero_crossing_rate(data, frame_size=256, hop_size=128)
time_zcr = np.arange(len(zcr)) * hop_size / sr
plt.figure(figsize=(12, 4))
plt.plot(time_zcr, zcr, color='#2D6A4F', linewidth=1.0)
plt.xlabel('时间 (秒)')
plt.ylabel('过零率')
plt.title('海豚声波过零率曲线')
plt.grid(True, alpha=0.3)
plt.show()
3.4 三者结合——知识体系
波形、能量、过零率,这三个工具要配合使用。我一般的工作流程是:
- 先画波形图,看信号的整体形态
- 算短时能量,定位活跃片段
- 算过零率,判断发声类型
下面这张图展示了三者的关系:
你看,这三个指标从不同角度描述了信号的特征。波形看“形状”,能量看“大小”,过零率看“粗糙度”。三者结合,就能对海豚的发声行为有个初步判断。
好了,这一讲的内容就到这里。时域分析是信号处理的基础,虽然看起来简单,但用好了能解决很多实际问题。下次你拿到一段海豚录音,不妨先试试这三个工具,看看能发现什么有趣的现象。