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

前言

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

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

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

贝塞尔曲线

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

一阶贝塞尔曲线

二阶贝塞尔曲线

三阶贝塞尔曲线

算法描述

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

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

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

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

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

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

算法实现

# -*- 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.

参考资料

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

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

(0)

相关推荐

  • python使用插值法画出平滑曲线

    本文实例为大家分享了python使用插值法画出平滑曲线的具体代码,供大家参考,具体内容如下 实现所需的库 numpy.scipy.matplotlib 实现所需的方法 插值 nearest:最邻近插值法 zero:阶梯插值 slinear:线性插值 quadratic.cubic:2.3阶B样条曲线插值 拟合和插值的区别 简单来说,插值就是根据原有数据进行填充,最后生成的曲线一定过原有点. 拟合是通过原有数据,调整曲线系数,使得曲线与已知点集的差别(最小二乘)最小,最后生成的曲线不一定经过原有点

  • python用插值法绘制平滑曲线

    本文实例为大家分享了python用插值法绘制平滑曲线的具体代码,供大家参考,具体内容如下 原图: 平滑处理后: 代码实现如下: # 1. 随机构造数据 import numpy as np x = range(10) y = np.random.randint(10,size=10) # 2. 绘制原图 import matplotlib as mpl import matplotlib.pyplot as plt %matplotlib inline # jupyter notebook显示绘

  • python+opencv实现高斯平滑滤波

    功能: 创建两个滑动条来分别控制高斯核的size和σσ的大小,这个程序是在阈值分割的那个程序上改动的.阈值分割程序在这 注意:由于σ=0σ=0时,opencv会根据窗口大小计算出σσ,所以,从0滑动σσ的滑动条时,会出现先边清晰又变模糊的现象 python+opencv实现阈值分割 python+opencv实现霍夫变换检测直线 (2016-5-10)到OpenCV-Python Tutorials's documentation!可以下载 代码: # -*- coding: utf-8 -*-

  • python利用插值法对折线进行平滑曲线处理

    在用python绘图的时候,经常由于数据的原因导致画出来的图折线分界过于明显,因此需要对原数据绘制的折线进行平滑处理,本文介绍利用插值法进行平滑曲线处理: 实现所需的库 numpy.scipy.matplotlib 插值法实现 nearest:最邻近插值法 zero:阶梯插值 slinear:线性插值 quadratic.cubic:2.3阶B样条曲线插值 - 拟合和插值的区别 1.插值:简单来说,插值就是根据原有数据进行填充,最后生成的曲线一定过原有点. 2拟合:拟合是通过原有数据,调整曲线系

  • python构建指数平滑预测模型示例

    指数平滑法 其实我想说自己百度的- 只有懂的人才会找到这篇文章- 不懂的人-看了我的文章-还是不懂哈哈哈 指数平滑法相比于移动平均法,它是一种特殊的加权平均方法.简单移动平均法用的是算术平均数,近期数据对预测值的影响比远期数据要大一些,而且越近的数据影响越大.指数平滑法正是考虑了这一点,并将其权值按指数递减的规律进行分配,越接近当前的数据,权重越大:反之,远离当前的数据,其权重越小.指数平滑法按照平滑的次数,一般可分为一次指数平滑法.二次指数平滑法和三次指数平滑法等.然而一次指数平滑法适用于无趋

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

    前言 很多文章在谈及曲线平滑的时候,习惯使用拟合的概念,我认为这是不恰当的.平滑后的曲线,一定经过原始的数据点,而拟合曲线,则不一定要经过原始数据点. 一般而言,需要平滑的数据分为两种:时间序列的单值数据.时间序列的二维数据.对于前者,并非一定要用贝塞尔算法,仅用样条插值就可以轻松实现平滑:而对于后者,不管是 numpy 还是 scipy 提供的那些插值算法,就都不适用了. 本文基于三阶贝塞尔曲线,实现了时间序列的单值数据和时间序列的二维数据的平滑算法,可满足大多数的平滑需求. 贝塞尔曲线 关于

  • Android 利用三阶贝塞尔曲线绘制运动轨迹的示例

    本篇文章主要介绍了Android 利用三阶贝塞尔曲线绘制运动轨迹的示例,分享给大家,具体如下: 实现点赞效果,自定义起始点以及运动轨迹 效果图: xml布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/rl_root&

  • Python基于多线程实现抓取数据存入数据库的方法

    本文实例讲述了Python基于多线程实现抓取数据存入数据库的方法.分享给大家供大家参考,具体如下: 1. 数据库类 """ 使用须知: 代码中数据表名 aces ,需要更改该数据表名称的注意更改 """ import pymysql class Database(): # 设置本地数据库用户名和密码 host = "localhost" user = "root" password = "&quo

  • python基于OpenCV模块实现视频流数据切割为图像帧数据(流程分析)

    动态视频流数据的处理可以转化为静态图像帧的处理,这样就可以在不改动图像模型的情况下实现视频流数据的处理工作,当然视频流数据也可以采用视频的处理方法来直接处理,这里今天主要是实践一下视频流数据的预处理工作,即:将视频流数据切割为图像帧数据,实践内容很简单,具体如下所示: #!usr/bin/env python #encoding:utf-8 ''' __Author__:沂水寒城 功能: 视频数据切割成图像数据 ''' import os import cv2 import numpy as n

  • android中贝塞尔曲线的应用示例

    前言: 贝塞尔曲线又称贝兹曲线,它的主要意义在于无论是直线或曲线都能在数学上予以描述.最初由保罗·德卡斯特里奥(Paul de Casteljau)于1959年运用德卡斯特里奥演算法开发(de Casteljau Algorithm),在1962,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表.目前广泛应用于图形绘制领域来模拟光滑曲线,为计算机矢量图形学奠定了基础.在一些图形处理软件中都能见到贝塞尔曲线,比如CorelDraw中翻译成"贝赛尔工具":而在Firewo

  • Android贝塞尔曲线初步学习第一课

    贝塞尔曲线有一阶.二阶.三阶.N阶 一阶就是一条直线,有起点终点,没有控制点,对应方法就是 canvas.drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint) ; 二阶为曲线,有起点终点,一个控制点,对应方法就是 path.quadTo(float x1, float y1, float x2, float y2); 其中x1.y1为控制点坐标, x2.y2为终点坐标,效果如下:

  • Unity贝塞尔曲线之美体验

    最近项目中用到了贝塞尔曲线,学习完成后记录一下自己的学习结果吧. 一阶贝塞尔曲线 一阶贝塞尔曲线就是一条线,我们很容易根据 t 求出 t 点的位置. P(t)=P0+(P1-P0)*t =(1-t)*P0+tP1  :   t[ 0,1] ,且其等同于线性插值. 二阶贝塞尔曲线 取平面内三个不共线的点,AB:AC=CD:CE,这个时候BD又是一条直线,可以按照一阶的贝塞尔方程来进行线性插值了. P(B)=(1-t)*P0+tP1 ; P(D)=(1-t)P1+tP2  ; P(t)=(1-t)*

  • Android自定义View绘制贝塞尔曲线实现流程

    目录 前言 二阶贝塞尔曲线 三阶贝塞尔曲线 前言 对于Android开发,实现贝塞尔曲线还是比较方便的,有对应的API供你调用.由于一阶贝塞尔曲线就是一条直线,实际没啥多大用处,因此,下面主要讲解二阶和三阶. 二阶贝塞尔曲线 在Android中,使用quadTo来实现二阶贝塞尔 path.reset() path.moveTo(startX, startY) path.quadTo(currentX, currentY, endX, endY) canvas.drawPath(path, cur

  • Android自定义View绘制贝塞尔曲线中小红点的方法

    目录 前言 需求 效果图 代码 主要问题 简单画法 使用贝塞尔曲线 前言 上一篇文章用扇形图练习了一下安卓的多点触控,实现了单指旋转.二指放大.三指移动,四指以上同时按下进行复位的功能.今天这篇文章用很多应用常见的小红点,来练习一下贝塞尔曲线的使用. 需求 这里想法来自QQ的拖动小红点取消显示聊天条数功能,不过好像是记忆里的了,现在看了下好像效果变了.总而言之,就是一个小圆点,拖动的时候变成水滴状,超过一定范围后触发消失回调,核心思想如下: 1.一个正方形view,中间是小红点,小红点距离边框有

  • js基于div丝滑实现贝塞尔曲线

    目录 引言 分析 实现 布局 如何计算y的偏移量 Math.sin() 和 Math.cos() 正弦曲线 余弦曲线 计算更多信息 完善剩余 完成跑马灯制作 贝塞尔曲线 简单封装一下方法 完整示例 style SCript 引言 今天遇到朋友发来的一个ui图,询问我如何实现下图这样的效果[vue项目],(听说是类似LED灯的展示效果),于是便有了今天的小demo,要实现一个类似下图的动效,上面的灯会一直重复滚动,但是这个并不是什么难点,主要在于如何实现这种平滑的曲线,日常我们的开发的div在我们

随机推荐