这是个很简单的数学工具。有的问题可能就只需要简单的数学工具就能解决。
线性插值
线性插值法:是指使用连接两个已知量的直线来确定在这两个已知量之间的一个未知量的值的方法。
线性插值相比其他插值方式,如抛物线插值,具有简单、方便的特点。
线性插值可以用来近似代替原函数,也可以用来计算得到查表过程中表中没有的数值。它是实现精确快速查找表的一种非常好的方法。
设y=f(x) 在x0 和x1上值分别为,y0,y1,求多项式:
y
=
φ
1
(
x
)
=
a
0
+
a
1
x
y=\varphi _1\left( x \right) =a_0+a_1x
y=φ1(x)=a0+a1x
满足:
φ
1
(
x
0
)
=
y
0
,
φ
1
(
x
1
)
=
y
1
\varphi _1\left( x_0 \right) =y_0,\varphi _1\left( x_1 \right) =y_1
φ1(x0)=y0,φ1(x1)=y1
得到
y
=
φ
1
(
x
)
=
y
0
+
y
1
−
y
0
x
1
−
x
0
(
x
−
x
0
)
y=\varphi _1\left( x \right) =y_0+\frac{y_1-y_0}{x_1-x_0}\left( x-x_0 \right)
y=φ1(x)=y0+x1−x0y1−y0(x−x0)
这样就能直接得到y。我们需要满足x在x0和x1之间。假设x不在x0和x1之间,公式也是满足,叫线性外插。
其实就是一个一元一次方程,y=kx+b,我们需要计算的就是斜率k,以及截距b。通过这个一元一次方程去计算区间内的x对应的y。
线性插值举例
-
ntc电阻测温度
ntc的温度曲线近似成一个数学表达式,但是需要用到math.h中的,占资源,计算慢。可以用线性插值的方法,比如只需要0-100度。保存一百个数据,以及对应的adc的值,之后根据测的adc进行插值。 -
速度规划
知道目标速度,加速度,根据这次速度反馈,设定下次速度给定。 -
分段pid
已知对应速度阈值的序列,以及对应pid的序列。根据速度反馈,或者速度给定,进行插值计算,让pid的更平滑。 -
图像里的线性插值,双线性插值,三线性插值。处理二维三维的图像
还很多。
程序设计
- 传入曲线的xy的表格
- 利用二分法查找区间
- 利用公式理计算区间内x的值
程序.h:
/***************************************************************
* @Copyright(C) 2020-2021, wangchongwei
* @FileName: linear_interp.h
* @Author: wangchongwei
* @Version: 0.1.1
* @LastDate: 2021.8.14
************************************************************/
#ifndef _LINEAR_INTERP_H_
#define _LINEAR_INTERP_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct LinearInterp
{
// 初始化曲线
void (*Init)(struct LinearInterp *self,
float *arr_x, float *arr_y,
uint32_t size);
// 调用计算
float (*Cal)(struct LinearInterp *self, float val);
float out;
struct
{
float *arr_x;
float *arr_y;
int32_t size;
}pvt;
};
void LinearInterp_Create(struct LinearInterp *self);
#ifdef __cplusplus
}
#endif
#endif
/***************************************************************
* @Copyright(C) 2020-2021, wangchongwei
* @FileName: linear_interp.c
* @Author: wangchongwei
* @Version: 0.1.1
* @LastDate: 2021.8.14
************************************************************/
#include "linear_Interp.h"
#include "string.h"
static void _Init(struct LinearInterp *self,
float *arr_x, float *arr_y,
uint32_t size)
{
self->pvt.arr_x = arr_x;
self->pvt.arr_y = arr_y;
self->pvt.size = size;
}
static float _Cal(struct LinearInterp *self, float val)
{
int32_t left = 0, mid = 0;
int32_t right = self->pvt.size - 1;
float x0,y0,x1,y1;
// 二分法
while (left <= right)
{
mid = (left+right)/2;
if (val<self->pvt.arr_x[mid])
right = mid - 1;
else if (self->pvt.arr_x[mid] < val)
left = mid + 1;
else
break;
}
// 头尾检测
if (mid == (self->pvt.size - 1)) {
mid = mid-1;
}
else if (mid == 0){
mid = 0;
}
else{
mid = (self->pvt.arr_x[mid] <= val)?mid:mid-1; // 中间二分查找出现的情况
}
// 3.Linear interpolation
x0 = self->pvt.arr_x[mid];
y0 = self->pvt.arr_y[mid];
x1 = self->pvt.arr_x[mid+1];
y1 = self->pvt.arr_y[mid+1];
self->out = y0+(((y1-y0)*(val - x0))/(x1-x0));
return self->out;
}
void LinearInterp_Create(struct LinearInterp *self)
{
memset(self , 0 , sizeof(struct LinearInterp));
self->Init = _Init;
self->Cal = _Cal;
}
测试:
float arr_x[5] = {1,5,8,13,17};
float arr_y[5] = {4,45,10,40,20};
float res;
LinearInterp_Create(&line_interp);
line_interp.Init(&line_interp, arr_x,arr_y,5);
res = line_interp.Cal(&line_interp,6);
qDebug()<<"插值结果:"<<res;
插值结果: 33.3333
我按1的步长,从-10-30的区间进行插值,画图:
代码肯定还有很多优化的地方,计算时间,内存,效率。都应该考虑,但是在考虑都通用的情况下,必然会舍去一些。
比如优化:
提前计算出每个区间的斜率和截距。查找到后,只需要做一次乘法和加法。
gitee地址:
https://gitee.com/wang_chong_wei/qt_wave.git
被抛弃的写随笔公众号改写技术文章了,感兴趣的可以关注公众号:王崇卫