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 相关文章推荐
Python求解平方根的方法
Mar 11 Python
Python实现数据库编程方法详解
Jun 09 Python
详解Python import方法引入模块的实例
Aug 02 Python
详解python OpenCV学习笔记之直方图均衡化
Feb 08 Python
几种实用的pythonic语法实例代码
Feb 24 Python
python中copy()与deepcopy()的区别小结
Aug 03 Python
python实现简单的单变量线性回归方法
Nov 08 Python
python的一些加密方法及python 加密模块
Jul 11 Python
详解Python对JSON中的特殊类型进行Encoder
Jul 15 Python
Django values()和value_list()的使用
Mar 31 Python
python基于pygame实现飞机大作战小游戏
Nov 19 Python
Django与数据库交互的实现
Jun 03 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
DOTA2 无惧惊涛骇浪 昆卡大型水友攻略
2020/04/20 DOTA
两个开源的Php输出Excel文件类
2010/02/08 PHP
destoon安装出现Internal Server Error的解决方法
2014/06/21 PHP
php通过exif_read_data函数获取图片的exif信息
2015/05/21 PHP
Paypal实现循环扣款(订阅)功能
2017/03/23 PHP
关于Laravel参数验证的一些疑与惑
2019/11/19 PHP
跟着JQuery API学Jquery 之三 筛选
2010/04/09 Javascript
JavaScript异步编程:异步数据收集的具体方法
2013/08/19 Javascript
Javascript学习笔记之 函数篇(三) : 闭包和引用
2014/11/23 Javascript
JavaScript判断图片是否已经加载完毕的方法汇总
2016/02/05 Javascript
jQuery实现点击某个div打开层,点击其他div关闭层实例分析(阻止冒泡)
2016/11/18 Javascript
JavaScript运动框架 多值运动(四)
2017/05/18 Javascript
详解React Native网络请求fetch简单封装
2017/08/10 Javascript
详解axios中封装使用、拦截特定请求、判断所有请求加载完毕)
2019/04/09 Javascript
小程序实现上下切换位置
2020/11/16 Javascript
微信小程序学习之自定义滚动弹窗
2020/12/20 Javascript
Python中使用中文的方法
2011/02/19 Python
python3模拟百度登录并实现百度贴吧签到示例分享(百度贴吧自动签到)
2014/02/24 Python
Python制作简单的网页爬虫
2015/11/22 Python
深入解析Python中的上下文管理器
2016/06/28 Python
关于python的bottle框架跨域请求报错问题的处理方法
2017/03/19 Python
Python字符串拼接的几种方法整理
2017/08/02 Python
python+opencv识别图片中的圆形
2020/03/25 Python
对matplotlib改变colorbar位置和方向的方法详解
2018/12/13 Python
python装饰器简介---这一篇也许就够了(推荐)
2019/04/01 Python
python ChainMap的使用和说明详解
2019/06/11 Python
5 分钟读懂Python 中的 Hook 钩子函数
2020/12/09 Python
用CSS3实现瀑布流布局的示例代码
2017/11/10 HTML / CSS
Fashion Eyewear美国:英国线上设计师眼镜和太阳镜的零售商
2016/08/15 全球购物
高一英语教学反思
2014/01/22 职场文书
2015年元旦活动总结
2014/05/09 职场文书
地陪导游欢迎词
2015/01/26 职场文书
《迟到》教学反思
2016/02/24 职场文书
干货:如何写好工作总结报告!
2019/05/10 职场文书
Go 语言下基于Redis分布式锁的实现方式
2021/06/28 Golang
使用RedisTemplat实现简单的分布式锁
2021/11/20 Redis