python基于三阶贝塞尔曲线的数据平滑算法


Posted in Python onDecember 27, 2019

前言

很多文章在谈及曲线平滑的时候,习惯使用拟合的概念,我认为这是不恰当的。平滑后的曲线,一定经过原始的数据点,而拟合曲线,则不一定要经过原始数据点。

一般而言,需要平滑的数据分为两种:时间序列的单值数据、时间序列的二维数据。对于前者,并非一定要用贝塞尔算法,仅用样条插值就可以轻松实现平滑;而对于后者,不管是 numpy 还是 scipy 提供的那些插值算法,就都不适用了。

本文基于三阶贝塞尔曲线,实现了时间序列的单值数据和时间序列的二维数据的平滑算法,可满足大多数的平滑需求。

贝塞尔曲线

关于贝塞尔曲线的数学原理,这里就不讨论了,直接贴出结论:

一阶贝塞尔曲线

python基于三阶贝塞尔曲线的数据平滑算法

python基于三阶贝塞尔曲线的数据平滑算法

二阶贝塞尔曲线

python基于三阶贝塞尔曲线的数据平滑算法

python基于三阶贝塞尔曲线的数据平滑算法

三阶贝塞尔曲线

python基于三阶贝塞尔曲线的数据平滑算法

python基于三阶贝塞尔曲线的数据平滑算法

算法描述

如果我们把三阶贝塞尔曲线的 P0 和 P3 视为原始数据,只要找到 P1 和 P2 两个点(我们称其为控制点),就可以根据三阶贝塞尔曲线公式,计算出 P0 和 P3 之间平滑曲线上的任意点。

python基于三阶贝塞尔曲线的数据平滑算法

现在,平滑问题变成了如何计算两个原始数据点之间的控制点的问题。步骤如下:

第1步:绿色直线连接相邻的原始数据点,计算出个线段的中点,红色直线连接相邻的中点

python基于三阶贝塞尔曲线的数据平滑算法

第2步:根据相邻两条绿色直线长度之比,分割其中点之间红色连线,标记分割点

python基于三阶贝塞尔曲线的数据平滑算法

第3步:平移红色连线,使其分割点与相对的原始数据点重合

python基于三阶贝塞尔曲线的数据平滑算法

第4步:调整平移后红色连线的端点与原始数据点的距离,通常缩减40%-80%

python基于三阶贝塞尔曲线的数据平滑算法

算法实现

# -*- coding: utf-8 -*-

import numpy as np

def bezier_curve(p0, p1, p2, p3, inserted):
 """
 三阶贝塞尔曲线
 
 p0, p1, p2, p3 - 点坐标,tuple、list或numpy.ndarray类型
 inserted  - p0和p3之间插值的数量
 """
 
 assert isinstance(p0, (tuple, list, np.ndarray)), u'点坐标不是期望的元组、列表或numpy数组类型'
 assert isinstance(p0, (tuple, list, np.ndarray)), u'点坐标不是期望的元组、列表或numpy数组类型'
 assert isinstance(p0, (tuple, list, np.ndarray)), u'点坐标不是期望的元组、列表或numpy数组类型'
 assert isinstance(p0, (tuple, list, np.ndarray)), u'点坐标不是期望的元组、列表或numpy数组类型'
 
 if isinstance(p0, (tuple, list)):
  p0 = np.array(p0)
 if isinstance(p1, (tuple, list)):
  p1 = np.array(p1)
 if isinstance(p2, (tuple, list)):
  p2 = np.array(p2)
 if isinstance(p3, (tuple, list)):
  p3 = np.array(p3)
 
 points = list()
 for t in np.linspace(0, 1, inserted+2):
  points.append(p0*np.power((1-t),3) + 3*p1*t*np.power((1-t),2) + 3*p2*(1-t)*np.power(t,2) + p3*np.power(t,3))
 
 return np.vstack(points)


def smoothing_base_bezier(date_x, date_y, k=0.5, inserted=10, closed=False):
 """
 基于三阶贝塞尔曲线的数据平滑算法
 
 date_x  - x维度数据集,list或numpy.ndarray类型
 date_y  - y维度数据集,list或numpy.ndarray类型
 k   - 调整平滑曲线形状的因子,取值一般在0.2~0.6之间。默认值为0.5
 inserted - 两个原始数据点之间插值的数量。默认值为10
 closed  - 曲线是否封闭,如是,则首尾相连。默认曲线不封闭
 """
 
 assert isinstance(date_x, (list, np.ndarray)), u'x数据集不是期望的列表或numpy数组类型'
 assert isinstance(date_y, (list, np.ndarray)), u'y数据集不是期望的列表或numpy数组类型'
 
 if isinstance(date_x, list) and isinstance(date_y, list):
  assert len(date_x)==len(date_y), u'x数据集和y数据集长度不匹配'
  date_x = np.array(date_x)
  date_y = np.array(date_y)
 elif isinstance(date_x, np.ndarray) and isinstance(date_y, np.ndarray):
  assert date_x.shape==date_y.shape, u'x数据集和y数据集长度不匹配'
 else:
  raise Exception(u'x数据集或y数据集类型错误')
 
 # 第1步:生成原始数据折线中点集
 mid_points = list()
 for i in range(1, date_x.shape[0]):
  mid_points.append({
   'start': (date_x[i-1], date_y[i-1]),
   'end':  (date_x[i], date_y[i]),
   'mid':  ((date_x[i]+date_x[i-1])/2.0, (date_y[i]+date_y[i-1])/2.0)
  })
 
 if closed:
  mid_points.append({
   'start': (date_x[-1], date_y[-1]),
   'end':  (date_x[0], date_y[0]),
   'mid':  ((date_x[0]+date_x[-1])/2.0, (date_y[0]+date_y[-1])/2.0)
  })
 
 # 第2步:找出中点连线及其分割点
 split_points = list()
 for i in range(len(mid_points)):
  if i < (len(mid_points)-1):
   j = i+1
  elif closed:
   j = 0
  else:
   continue
  
  x00, y00 = mid_points[i]['start']
  x01, y01 = mid_points[i]['end']
  x10, y10 = mid_points[j]['start']
  x11, y11 = mid_points[j]['end']
  d0 = np.sqrt(np.power((x00-x01), 2) + np.power((y00-y01), 2))
  d1 = np.sqrt(np.power((x10-x11), 2) + np.power((y10-y11), 2))
  k_split = 1.0*d0/(d0+d1)
  
  mx0, my0 = mid_points[i]['mid']
  mx1, my1 = mid_points[j]['mid']
  
  split_points.append({
   'start': (mx0, my0),
   'end':  (mx1, my1),
   'split': (mx0+(mx1-mx0)*k_split, my0+(my1-my0)*k_split)
  })
 
 # 第3步:平移中点连线,调整端点,生成控制点
 crt_points = list()
 for i in range(len(split_points)):
  vx, vy = mid_points[i]['end'] # 当前顶点的坐标
  dx = vx - split_points[i]['split'][0] # 平移线段x偏移量
  dy = vy - split_points[i]['split'][1] # 平移线段y偏移量
  
  sx, sy = split_points[i]['start'][0]+dx, split_points[i]['start'][1]+dy # 平移后线段起点坐标
  ex, ey = split_points[i]['end'][0]+dx, split_points[i]['end'][1]+dy # 平移后线段终点坐标
  
  cp0 = sx+(vx-sx)*k, sy+(vy-sy)*k # 控制点坐标
  cp1 = ex+(vx-ex)*k, ey+(vy-ey)*k # 控制点坐标
  
  if crt_points:
   crt_points[-1].insert(2, cp0)
  else:
   crt_points.append([mid_points[0]['start'], cp0, mid_points[0]['end']])
  
  if closed:
   if i < (len(mid_points)-1):
    crt_points.append([mid_points[i+1]['start'], cp1, mid_points[i+1]['end']])
   else:
    crt_points[0].insert(1, cp1)
  else:
   if i < (len(mid_points)-2):
    crt_points.append([mid_points[i+1]['start'], cp1, mid_points[i+1]['end']])
   else:
    crt_points.append([mid_points[i+1]['start'], cp1, mid_points[i+1]['end'], mid_points[i+1]['end']])
    crt_points[0].insert(1, mid_points[0]['start'])
 
 # 第4步:应用贝塞尔曲线方程插值
 out = list()
 for item in crt_points:
  group = bezier_curve(item[0], item[1], item[2], item[3], inserted)
  out.append(group[:-1])
 
 out.append(group[-1:])
 out = np.vstack(out)
 
 return out.T[0], out.T[1]


if __name__ == '__main__':
 import matplotlib.pyplot as plt
 
 x = np.array([2,4,4,3,2])
 y = np.array([2,2,4,3,4])
	
	plt.plot(x, y, 'ro')
 x_curve, y_curve = smoothing_base_bezier(x, y, k=0.3, closed=True)
 plt.plot(x_curve, y_curve, label='$k=0.3$')
 x_curve, y_curve = smoothing_base_bezier(x, y, k=0.4, closed=True)
 plt.plot(x_curve, y_curve, label='$k=0.4$')
 x_curve, y_curve = smoothing_base_bezier(x, y, k=0.5, closed=True)
 plt.plot(x_curve, y_curve, label='$k=0.5$')
 x_curve, y_curve = smoothing_base_bezier(x, y, k=0.6, closed=True)
 plt.plot(x_curve, y_curve, label='$k=0.6$')
 plt.legend(loc='best')
 
 plt.show()

下图为平滑效果。左侧是封闭曲线,两个原始数据点之间插值数量为默认值10;右侧为同样数据不封闭的效果,k值默认0.5.

python基于三阶贝塞尔曲线的数据平滑算法

参考资料

算法参考了 Interpolation with Bezier Curves 这个网页,里面没有关于作者的任何信息,在此只能笼统地向国际友人表示感谢!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
paramiko模块安装和使用(远程登录服务器)
Jan 27 Python
python中lambda函数 list comprehension 和 zip函数使用指南
Sep 28 Python
编写Python CGI脚本的教程
Jun 29 Python
Python抓取淘宝下拉框关键词的方法
Jul 08 Python
Python中取整的几种方法小结
Jan 06 Python
python 实现在txt指定行追加文本的方法
Apr 29 Python
python 解压pkl文件的方法
Oct 25 Python
pytorch对可变长度序列的处理方法详解
Dec 08 Python
Django框架文件上传与自定义图片上传路径、上传文件名操作分析
May 10 Python
详解解决Python memory error的问题(四种解决方案)
Aug 08 Python
django的autoreload机制实现
Jun 03 Python
python mongo 向数据中的数组类型新增数据操作
Dec 05 Python
python3获取文件中url内容并下载代码实例
Dec 27 #Python
用python拟合等角螺线的实现示例
Dec 27 #Python
PyTorch 对应点相乘、矩阵相乘实例
Dec 27 #Python
pytorch中tensor.expand()和tensor.expand_as()函数详解
Dec 27 #Python
python装饰器相当于函数的调用方式
Dec 27 #Python
Python 实现数组相减示例
Dec 27 #Python
Pandas 解决dataframe的一列进行向下顺移问题
Dec 27 #Python
You might like
php下通过POST还是GET来传值
2008/06/05 PHP
PHP array_push 数组函数
2009/12/26 PHP
php堆排序(heapsort)练习
2013/11/13 PHP
php断点续传之如何分割合并文件
2014/03/22 PHP
weiphp微信公众平台授权设置
2016/01/04 PHP
strpos() 函数判断字符串中是否包含某字符串的方法
2019/01/16 PHP
PHP中检查isset()和!empty()函数的必要性
2019/02/13 PHP
javascript DOM编程实例(智播客学习)
2009/11/23 Javascript
浅谈tudou土豆网首页图片延迟加载的效果
2010/06/23 Javascript
jQuery编写widget的一些技巧分享
2010/10/28 Javascript
js 在定义的时候立即执行的函数表达式(function)写法
2013/01/16 Javascript
js将json格式内容转换成对象的方法
2013/11/01 Javascript
jquery ajax请求方式与提示用户正在处理请稍等
2014/09/01 Javascript
js判断某个字符出现的次数的简单实例
2016/06/03 Javascript
使用Ajax与服务器(JSON)通信实例
2016/11/04 Javascript
详解angular分页插件tm.pagination二次触发问题解决方案
2018/07/20 Javascript
vue抽出组件并传值实例
2020/07/31 Javascript
Python文件右键找不到IDLE打开项解决办法
2015/06/08 Python
python删除指定类型(或非指定)的文件实例详解
2015/07/06 Python
Python利用神经网络解决非线性回归问题实例详解
2019/07/19 Python
django ModelForm修改显示缩略图 imagefield类型的实例
2019/07/28 Python
python3多线程知识点总结
2019/09/26 Python
HTML5页面音视频在微信和app下自动播放的实现方法
2016/10/20 HTML / CSS
学校司机岗位职责
2013/11/14 职场文书
销售业务员岗位职责
2014/01/29 职场文书
校园十大歌手策划书
2014/02/01 职场文书
大班幼儿评语大全
2014/04/30 职场文书
初中教师业务学习材料
2014/05/12 职场文书
工作求职信
2014/07/04 职场文书
女生抽烟检讨书
2014/10/05 职场文书
搞笑婚庆主持词
2015/06/29 职场文书
2016年敬老月活动总结
2016/04/05 职场文书
创业计划书之校园跑腿公司
2019/09/24 职场文书
创业计划书之电动车企业
2019/10/11 职场文书
redis三种高可用方式部署的实现
2021/05/11 Redis
css3中2D转换之有趣的transform形变效果
2022/02/24 HTML / CSS