多项式拟合

继上篇 [[传感器信号处理]] 说到的多项式拟合,这里具体讲讲多项式拟合方法的应用

案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import numpy as np
import matplotlib.pyplot as plt

# ==========================================
# 步骤 1: 模拟物理世界 (生成非线性数据)
# ==========================================
# 假设我们用 7 瓶标气做校准:0, 10, 20... 100 ppm
true_concentration = np.array([0, 10, 20, 40, 60, 80, 100])

# 模拟传感器的物理响应:
# V = 5 * (1 - e^(-0.015 * C))
# 这是一种典型的饱和响应,浓度越高,电压涨得越慢
A = 5.0  # 最大电压 (V)
k = 0.015 
raw_voltage = A * (1 - np.exp(-k * true_concentration))

# 加一点点噪声,模拟真实电路的波动
np.random.seed(42)
raw_voltage += np.random.normal(0, 0.005, size=len(raw_voltage))

print("原始电压测量值 (V):", np.round(raw_voltage, 3))

# ==========================================
# 步骤 2: 算法核心 (计算线性化系数)
# ==========================================
# 目标:找到一个多项式 C = f(V),把电压 V 变回浓度 C
# 这里我们选用 3次多项式 (Cubic Fitting)
degree = 3
# polyfit(x, y, deg) -> 注意这里 x 是电压(输入), y 是浓度(目标)
coeffs = np.polyfit(raw_voltage, true_concentration, degree)

# 生成多项式函数对象
linearization_func = np.poly1d(coeffs)

print("\n--- 计算出的线性化系数 (存入单片机) ---")
print(f"a (3次项): {coeffs[0]:.4f}")
print(f"b (2次项): {coeffs[1]:.4f}")
print(f"c (1次项): {coeffs[2]:.4f}")
print(f"d (截距):  {coeffs[3]:.4f}")

# ==========================================
# 步骤 3: 验证效果 (应用系数)
# ==========================================
# 把原始的弯曲电压带入公式,看看算出来的浓度是不是直的
calculated_concentration = linearization_func(raw_voltage)

print("\n校准后计算浓度 (ppm):", np.round(calculated_concentration, 1))
print("真实标准气浓度 (ppm):", true_concentration)

# ==========================================
# 步骤 4: 画图展示
# ==========================================
plt.figure(figsize=(12, 5))

# 图1:拉直前(原始物理响应)
plt.subplot(1, 2, 1)
plt.scatter(true_concentration, raw_voltage, color='red', s=80, label='实测点')
plt.plot(true_concentration, raw_voltage, 'r--', alpha=0.5)
plt.title("线性化前:传感器原始响应 (弯曲)", fontsize=14)
plt.xlabel("真实浓度 (ppm)", fontsize=12)
plt.ylabel("传感器电压 (V)", fontsize=12)
plt.grid(True, linestyle=':', alpha=0.6)
plt.legend()

# 图2:拉直后(经过算法处理)
plt.subplot(1, 2, 2)
# 画出理想直线
plt.plot([0, 100], [0, 100], 'k-', alpha=0.3, linewidth=3, label='理想直线 (y=x)')
# 画出校准后的点
plt.scatter(true_concentration, calculated_concentration, color='green', marker='s', s=80, label='算法输出')
plt.title("线性化后:最终显示数值 (笔直)", fontsize=14)
plt.xlabel("真实浓度 (ppm)", fontsize=12)
plt.ylabel("屏幕显示浓度 (ppm)", fontsize=12)
plt.grid(True, linestyle=':', alpha=0.6)
plt.legend()

plt.tight_layout()
plt.show()

输出结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
原始电压测量值 (V): [2.000e-03 6.960e-01 1.299e+00 2.264e+00 2.966e+00 3.493e+00 3.892e+00]

--- 计算出的线性化系数 (存入单片机) ---
a (3次项): 1.0962
b (2次项): -1.7772
c (1次项): 15.9618
d (截距):  -0.2106

校准后计算浓度 (ppm): [-0.2 10.4 19.9 39.5 60.1 80.6 99.6]
真实标准气浓度 (ppm): [  0  10  20  40  60  80 100]

在这段代码中,模拟了一个真实的物理场景:

  1. 物理世界(不完美): 你的传感器(比如 NDIR 探测器)是非线性的。随着浓度升高,电压增加得越来越慢(出现了饱和效应),这是一条弯曲的线。
  2. 算法世界(修正): 我们通过多项式拟合,强行算出一组系数。利用这组系数,把那个弯曲的电压信号,反算成了笔直的浓度信号。

从代码输出中,我们得到了 4 个核心系数(3 次多项式): [ 1.096, -1.777, 15.962, -0.210 ],这意味着,单片机里的公式是这样的: $$浓度 = 1.096 \times V^3 - 1.777 \times V^2 + 15.962 \times V - 0.210$$

Licensed under CC BY-NC-SA 4.0