-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Question] How can I find a cubic Bezier curve that passes through a specified point? #242
Comments
@miu200521358 I can likely help you but want some clarification. Your 16 points will overdetermine a cubic (4 control points). Do you mean a cubic that exactly fits or a cubic this is the "best fit" in some metric? Or did you just mean a Bézier curve (not not actually need to be cubic)? |
So you have 16 data points, but only 4 free points for a cubic. One way to approximate the control points would be to use the 4x4 identity matrix as a symbolic representation of the 4 control points (1 per row). For example, consider our "symbolic" control points In [1]: import bezier
In [2]: import numpy as np
In [3]: representative = bezier.Curve.from_nodes(np.eye(4))
In [4]: representative.elevate().nodes
Out[4]:
array([[1. , 0.25, 0. , 0. , 0. ],
[0. , 0.75, 0.5 , 0. , 0. ],
[0. , 0. , 0.5 , 0.75, 0. ],
[0. , 0. , 0. , 0.25, 1. ]]) I.e. this says In order to "project" our 16 points down to just 4, we can find a transform in the other direction by degree elevating from degree In [5]: elevated = representative
In [6]: for _ in range(4, 15 + 1):
...: elevated = elevated.elevate()
...:
In [7]: elevated.degree
Out[7]: 15
In [8]: transform = elevated.nodes.T
In [9]: transform.shape
Out[9]: (16, 4) Using our 16 data points from above In [10]: ys = np.array([0, 7.027885760013675e-07, 1.3352245924580508e-05, 7.185419521038572e-05, 0.000247954241316628, 0.0006740029696562511, 0.0015907714640571724, 0.00342941120391
...: 56, 0.006821667242492446, 0.013031496324572234, 0.024556098240622215, 0.04575246255862098, 0.08269242982725422, 0.1322613906866914, 0.17519720275554274, 0.20047545179963588
...: ])
In [11]: xs = np.arange(0, len(ys))
In [12]: nodes = np.vstack([xs, ys]) we can use a least squares projection to find some best fit In [13]: reduced_t, residuals, rank, _ = np.linalg.lstsq(transform, nodes.T, rcond=None)
In [14]: residuals
Out[14]: array([5.47125882e-29, 8.07090066e-04])
In [15]: rank
Out[15]: 4
In [16]: reduced = reduced_t.T This shows we have a very good fit for In [17]: reduced
Out[17]:
array([[-9.02365312e-15, 5.00000000e+00, 1.00000000e+01,
1.50000000e+01],
[-2.26101770e-04, 8.68120557e-03, -5.17256724e-02,
2.14974631e-01]]) We can turn this into a curve, and plot it, then compare that plot to the data points. In [18]: import matplotlib.pyplot as plt
In [19]: import seaborn
In [20]: seaborn.set()
In [21]: curve = bezier.Curve.from_nodes(reduced)
In [21]: ax = curve.plot(256)
In [22]: _ = ax.plot(xs, ys, marker="o", linestyle="none") As you can see, the fit is good, but far from perfect. This means other methods (this was essentially a least squares fit) would also fail to find an exact match. |
So I now realize my answer is "wrong" in that I treated the Backing up a bit, I'm assuming your points on the curve In [1]: import bezier
In [2]: import numpy as np
In [3]: representative = bezier.Curve.from_nodes(np.eye(4))
In [4]: s_vals = np.linspace(0.0, 1.0, 5)
In [5]: s_vals
Out[5]: array([0. , 0.25, 0.5 , 0.75, 1. ])
In [6]: with np.printoptions(linewidth=1000):
...: print(representative.evaluate_multi(s_vals))
...:
[[1. 0.421875 0.125 0.015625 0. ]
[0. 0.421875 0.375 0.140625 0. ]
[0. 0.140625 0.375 0.421875 0. ]
[0. 0.015625 0.125 0.421875 1. ]] In other words, This is exactly what we can do for our 16 points ( In [7]: s_vals = np.linspace(0.0, 1.0, 16)
In [8]: transform = representative.evaluate_multi(s_vals).T
In [9]: ys = np.array([0, 7.027885760013675e-07, 1.3352245924580508e-05, 7.185419521038572e-05, 0.000247954241316628, 0.0006740029696562511, 0.0015907714640571724, 0.0034294112039156, 0.006821667242492446, 0.0
...: 13031496324572234, 0.024556098240622215, 0.04575246255862098, 0.08269242982725422, 0.1322613906866914, 0.17519720275554274, 0.20047545179963588])
In [10]: xs = np.arange(0, len(ys))
In [11]: nodes = np.vstack([xs, ys])
In [12]: reduced_t, residuals, rank, _ = np.linalg.lstsq(transform, nodes.T, rcond=None)
In [13]: residuals
Out[13]: array([5.22252497e-29, 8.07090066e-04])
In [14]: rank
Out[14]: 4
In [15]: reduced = reduced_t.T
In [16]: with np.printoptions(linewidth=1000):
...: print(reduced)
...:
[[-1.76090753e-15 5.00000000e+00 1.00000000e+01 1.50000000e+01]
[-2.26101770e-04 1.50843117e-02 -7.65425640e-02 2.14974631e-01]] But as before the In [17]: import matplotlib.pyplot as plt
In [18]: import seaborn
In [19]: seaborn.set()
In [20]: curve = bezier.Curve.from_nodes(reduced)
In [21]: ax = curve.plot(256)
In [22]: _ = ax.plot(xs, ys, marker="o", linestyle="none")
In [23]: plt.show() |
Dear dhermes Thank you for your consideration! |
MotionSupporter_1.03_β08 miumiu ・スムージング ・間引き処理を見直し ・dhermes/bezier#242 ・エラーログメッセージを修正 ・多段分割・多段統合の画面インデックス名を123からXYZに戻した
Thank you for publishing a very nice library!
I'm currently working on a problem with this library.
I have values measured over time.
Example)
I want to generate a cubic Bezier curve that passes through these values.
(Not the Catmull-Rom curve)
If you know how to solve this problem by using your library, please let me know.
Thank you.
The text was updated successfully, but these errors were encountered: