3、凸轮曲线数学基础:曲线参数方程、贝塞尔曲线与B样条曲线、曲线连续性(C0/C1/C2)
各位好,我是老张。今天咱们聊聊凸轮曲线背后的数学工具。说实话,我刚入行那会儿,觉得凸轮设计就是画几条线,转一转就完事了。直到第一次调试变焦镜头,发现画面在某个焦段突然抖动——嗯,那才明白曲线连续性有多重要。
这一节,我带你从数学底层把凸轮曲线看透。别怕公式,我会用项目里的真实案例帮你理解。
3.1 曲线参数方程:凸轮运动的数学语言
凸轮曲线,说白了就是描述「旋转角度」和「镜筒位移」之间的一一对应关系。我们通常用参数方程来表达:
// 参数方程的一般形式
// t 是归一化参数,范围 [0, 1]
// θ 是凸轮旋转角度
// s 是镜筒轴向位移
θ(t) = f(t)
s(t) = g(t)
为什么用参数方程?我举个例子。有一次设计一个 10 倍变焦镜头,要求从广角端到长焦端,镜筒移动 25mm。如果直接用角度-位移的显式函数,很难控制中间段的加速度变化。用参数方程,我可以把角度和位移都表达成 t 的函数,独立控制它们的节奏。
核心要点:参数 t 本身没有物理意义,它只是一个「进度条」。t=0 对应起始位置,t=1 对应终止位置。通过调整 f(t) 和 g(t) 的映射关系,我们可以设计出任意复杂的运动规律。
常见的参数方程形式包括:
- 线性参数方程:θ(t) = θ₀ + Δθ·t,s(t) = s₀ + Δs·t。最简单,但加速度突变严重,实际很少用。
- 多项式参数方程:比如三次多项式 s(t) = a₀ + a₁t + a₂t² + a₃t³。可以控制速度和加速度的连续性。
- 三角函数参数方程:适合做平滑的加减速段。
我的经验:在变焦镜头设计中,我习惯先用三次多项式做初步曲线,再用更高阶的样条做优化。因为三次多项式已经能保证加速度连续(C2),而高阶多项式容易产生龙格现象——就是曲线两端剧烈振荡,你想想看,镜筒在两端抖起来多可怕。
3.2 贝塞尔曲线:直观的曲线设计工具
贝塞尔曲线,是凸轮设计里最常用的工具之一。为什么?因为它直观。你只需要拖动几个控制点,曲线形状就跟着变了。
三次贝塞尔曲线的数学形式:
// 三次贝塞尔曲线
// P₀, P₁, P₂, P₃ 是四个控制点
// t 是参数,范围 [0, 1]
B(t) = (1-t)³·P₀ + 3(1-t)²·t·P₁ + 3(1-t)·t²·P₂ + t³·P₃
这里 P₀ 和 P₃ 是曲线的起点和终点,P₁ 和 P₂ 控制曲线的「走向」。我记得有一次,一个年轻工程师问我:「为什么我的贝塞尔曲线在起点处有尖角?」我让他检查 P₀ 和 P₁ 的连线方向——果然,P₁ 的位置没放好,导致曲线在起点处的切线方向突变。
避坑指南:我曾经因为贝塞尔曲线的全局性吃过亏。贝塞尔曲线有一个特点:移动任何一个控制点,整条曲线都会改变。这在设计长行程凸轮时非常麻烦——你只想微调中间某一段,结果两端也跟着变了。后来我改用 B 样条曲线,才解决了这个问题。
贝塞尔曲线的优点:
- 控制直观,拖拽控制点就能调整形状
- 凸包性质:曲线始终在控制点构成的凸多边形内
- 端点插值:曲线一定经过起点和终点
缺点也很明显:
- 全局控制性:改一点动全身
- 复杂曲线需要高阶贝塞尔,计算不稳定
3.3 B样条曲线:局部控制的利器
B 样条曲线,说白了就是「分段贝塞尔」。它把整条曲线分成若干段,每段用低阶贝塞尔拼接起来。最关键的是——移动一个控制点,只影响它附近的几段曲线。
B 样条曲线的数学定义:
// k 次 B 样条曲线
// Pᵢ 是控制点,i = 0, 1, ..., n
// Nᵢₖ(t) 是 k 次 B 样条基函数
// t 是参数,范围 [tₖ₋₁, tₙ₊₁]
C(t) = Σᵢ₌₀ⁿ Pᵢ · Nᵢₖ(t)
基函数 Nᵢₖ(t) 的递推公式(Cox-de Boor 公式):
// 0 次基函数
Nᵢ₀(t) = 1, 如果 tᵢ ≤ t < tᵢ₊₁
Nᵢ₀(t) = 0, 其他
// k 次基函数
Nᵢₖ(t) = (t - tᵢ)/(tᵢ₊ₖ - tᵢ) · Nᵢₖ₋₁(t)
+ (tᵢ₊ₖ₊₁ - t)/(tᵢ₊ₖ₊₁ - tᵢ₊₁) · Nᵢ₊₁ₖ₋₁(t)
看着复杂?其实你不需要手算。实际项目中,我们用现成的库函数。重要的是理解它的特性:
- 局部支撑性:每个控制点只影响它附近的 k+1 段曲线
- 连续性可控:k 次 B 样条曲线内部具有 Cᵏ⁻¹ 连续性
- 节点向量:通过调整节点位置,可以控制曲线在特定点的「松紧」程度
实战经验:我在设计一款 30 倍变焦镜头时,凸轮曲线总长度超过 60mm。用贝塞尔曲线,每次调整中间段都要重新拟合整条曲线。改用三次 B 样条后,我把曲线分成 8 段,每段 4 个控制点。想调整哪一段,就改哪一段的控制点,其他段纹丝不动。调试效率提升了至少 3 倍。
3.4 曲线连续性:C0、C1、C2 到底意味着什么?
曲线连续性,是凸轮设计里最容易忽略、也最容易出问题的地方。我见过太多凸轮曲线,数学上看着完美,实际跑起来镜筒抖得像筛糠——问题就出在连续性上。
三种连续性的定义:
| 连续性等级 | 数学含义 | 物理意义 | 凸轮设计中的影响 |
|---|---|---|---|
| C0 | 位置连续 | 镜筒位置无跳变 | 最基本的,否则镜筒会「瞬移」 |
| C1 | 速度连续 | 镜筒速度无跳变 | 否则会有冲击感,画面抖动 |
| C2 | 加速度连续 | 镜筒加速度无跳变 | 否则会产生惯性力突变,影响成像稳定性 |
为什么会这样?你想想看,凸轮机构里,镜筒是通过弹簧压在凸轮槽里的。如果加速度突变,弹簧力会突然变化,镜筒就会在槽里「弹跳」。我曾经调试一款 4K 摄像机镜头,画面在某个焦段总是出现微弱的横向抖动。查了三天,最后发现是凸轮曲线在拼接点处只有 C1 连续性,加速度有跳变。改成 C2 连续后,问题立刻消失。
重要提醒:不要以为 C2 连续就一定够用。在一些高精度场合,比如电影镜头,甚至要求 C3 连续(加加速度连续)。但 C2 是变焦镜头设计的「及格线」——低于这个标准,基本没法用。
如何保证连续性?
- 使用 B 样条曲线时,k 次曲线内部自动保证 Cᵏ⁻¹ 连续
- 在拼接点处,让前后两段曲线的导数相等
- 用 Python 的 scipy.interpolate 库可以自动生成 C2 连续的样条曲线
3.5 知识体系总览
下面这张图,是我自己总结的凸轮曲线数学知识框架。每次带新人,我都会先让他们看这张图——先把全局脉络理清楚,再深入细节。
3.6 Python 实战:生成并分析凸轮曲线
光说不练假把式。下面我用 Python 演示如何生成一条 C2 连续的凸轮曲线。这段代码我用了很多年,每次做新项目都会拿它做快速验证。
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline
# 定义关键点:角度(度)和位移(mm)
theta = np.array([0, 30, 60, 90, 120, 150, 180])
displacement = np.array([0, 5, 12, 18, 22, 24, 25])
# 使用三次样条插值,自动保证 C2 连续
cs = CubicSpline(theta, displacement, bc_type='natural')
# 生成密集点用于绘图
theta_dense = np.linspace(0, 180, 500)
disp_dense = cs(theta_dense)
# 计算一阶导数(速度)和二阶导数(加速度)
velocity = cs.derivative()(theta_dense)
acceleration = cs.derivative(2)(theta_dense)
# 绘图
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 8))
ax1.plot(theta_dense, disp_dense, 'b-', linewidth=2)
ax1.plot(theta, displacement, 'ro', markersize=8)
ax1.set_ylabel('位移 (mm)')
ax1.set_title('凸轮曲线 - 位移')
ax1.grid(True)
ax2.plot(theta_dense, velocity, 'g-', linewidth=2)
ax2.set_ylabel('速度 (mm/°)')
ax2.set_title('速度曲线(一阶导数)')
ax2.grid(True)
ax3.plot(theta_dense, acceleration, 'r-', linewidth=2)
ax3.set_xlabel('角度 (°)')
ax3.set_ylabel('加速度 (mm/°²)')
ax3.set_title('加速度曲线(二阶导数)')
ax3.grid(True)
plt.tight_layout()
plt.show()
# 检查连续性
print(f"位移曲线 C0 连续: {np.all(np.isfinite(disp_dense))}")
print(f"速度曲线 C1 连续: {np.all(np.isfinite(velocity))}")
print(f"加速度曲线 C2 连续: {np.all(np.isfinite(acceleration))}")
使用建议:这段代码里,我用了 scipy 的 CubicSpline,它默认生成的就是 C2 连续的曲线。你只需要提供关键点的角度和位移,剩下的交给库函数。但要注意——关键点不能太少,否则曲线形状可能不符合预期。我一般每 10° 到 15° 设置一个关键点。
运行这段代码,你会看到三条曲线:位移、速度、加速度。如果加速度曲线是光滑的,没有尖角或跳变,说明曲线达到了 C2 连续。如果加速度曲线有折点,那就要检查关键点设置是否合理,或者改用更高阶的样条。
好了,这一节的内容就到这里。凸轮曲线的数学基础,说白了就是三件事:用参数方程描述运动,用贝塞尔或 B 样条控制形状,用连续性保证运动平稳。下次你设计凸轮时,不妨先问问自己:我的曲线是 C2 连续的吗?
公众号:蓝海资料掘金营,微信deep3321