6.16. 曲线与积分¶
数学相关的几页介绍了接受一个数组并产生一个数组(或标量)的操作——算术、归约、广播。本页则涵盖另一类操作:把数组视为采样后的函数,并就函数本身提出问题。在样本之间插值、对样本拟合曲线、对其下方的面积求积分、将其与另一个缓冲区做卷积。
所有这些都接受 ndarray 输入,并返回一个浮点标量或浮点型 ndarray。
6.16.1. 插值¶
interp() 执行一维线性插值。xp 是一个单调递增的一维自变量数组;fp 是与之对应的因变量值;x 是需要求插值的位置:
xp = np.array([0.0, 1.0, 2.0, 3.0])
fp = np.array([10.0, 20.0, 25.0, 30.0])
x = np.array([0.5, 1.5, 2.5])
np.interp(x, xp, fp)
# array([15.0, 22.5, 27.5])
在 [xp[0], xp[-1]] 范围之外,结果会分别被裁剪到 fp[0] 和 fp[-1];left= 和 right= 关键字可以覆盖这两个端点值。
在摄像头上用于将标定表重新映射到任意采样位置——比如热敏电阻的温度-电压表、非线性的像素响应曲线、逐通道的伽马查找表。一次库调用,对输入遍历一遍,无需 Python 循环。
6.16.2. 多项式拟合与求值¶
polyfit() 通过最小二乘法对数据点 (x, y) 拟合一个 deg 阶的多项式,并返回系数(最高阶在前):
x = np.array([0.0, 1.0, 2.0, 3.0, 4.0])
y = np.array([0.0, 1.1, 3.9, 9.1, 15.8])
coeffs = np.polyfit(x, y, 2)
# array([1.0, 0.0, 0.0]) (approximately)
当省略 x 时,会使用 range(len(y))——这对于给一个没有关联 x 轴、规则采样的缓冲区快速拟合多项式很有用:
np.polyfit(y, 2)
polyval() 在 x 处对以 p 为系数的多项式求值。输入 x 可以是标量(返回浮点数)或 ndarray(返回 ndarray):
p = np.polyfit(x, y, 2)
fitted = np.polyval(p, x)
自然的搭配方式是:在标定时调用一次 polyfit,存下系数,然后每一帧调用 polyval 来对所得曲线求值。多项式求值步骤对每个样本只需少量浮点运算,即使在最小的摄像头上也很廉价。
6.16.3. 卷积¶
convolve() 返回两个一维数组的全长离散线性卷积。仅实现了 full 模式;输出长度为 len(a) + len(v) - 1。对结果进行切片即可获得桌面版 numpy 所提供的 same 和 valid 模式的相同效果:
a = np.array([1.0, 2.0, 3.0])
v = np.array([0.5, 0.5])
np.convolve(a, v)
# array([0.5, 1.5, 2.5, 1.5])
对于短的 FIR 滤波器和平滑核(矩形、三角、高斯)很有用,这些情况下搭建 SOS 链显得多余。运行时间正比于两个数组长度的乘积——对短核来说没问题,但对长核很快就会比 FFT 卷积更昂贵。
6.16.4. 梯形积分¶
trapz() 通过复合梯形法则对采样后的函数求积分:
x = np.linspace(0, np.pi, num=128)
y = np.sin(x)
np.trapz(y, x) # ~ 2.0
当采样间隔均匀且只关心步长时,传入 dx=;当样本间隔不均匀时,传入 x=。这是对已采集的传感器数据求积分的正确选择,因为此时无法获得解析形式。
对于已经过带限处理的样本数据(例如经过抗混叠滤波后的音频缓冲区),梯形法则以样本数的平方收敛,这意味着将缓冲区长度加倍可将误差缩小为原来的四分之一。
当被积函数不是一个样本缓冲区,而是应用程序可以在任意点上求值的 Python 函数时,正确的选择是另一类求解器。