python的机器人基础原理工业场景模拟第三十八篇:机械臂加减速处理,对原始轨迹做平滑滤波,消除速度突变带来的运动冲击。
《Python机器人基础原理》第三十八篇:机械臂加减速处理——对原始轨迹做平滑滤波,消除速度突变带来的运动冲击
1. 实际工业应用场景
周三下午,某锂电电芯搬运产线,一台3-DOF平面SCARA机器人(基座旋转关节惯量0.85 kg·m²、额定扭矩35 N·m;大臂长400 mm、质量4.2 kg、质心距基座220 mm;小臂长300 mm、质量2.6 kg、质心距肘关节140 mm;末端额定负载2.0 kg;节拍要求2.8 s/次,重复定位精度±0.1 mm)正在执行电芯从输送带(0.15 m, -0.20 m)到托盘(0.45 m, 0.30 m)的搬运任务。
小周把示教器递给老郑,屏幕上显示着刚生成的梯形速度轨迹——从起点到终点,起停段线性加减速,加速度0.5 m/s²,中间匀速0.25 m/s。
"这条轨迹速度曲线是梯形,起停段加速度从0跳到0.5 m/s²,再在终点从-0.5 m/s²跳回0。关节角速度曲线在每段衔接处都有尖峰。"
老郑没接示教器,盯着屏幕上关节角速度曲线的两个尖刺。
"你知道这俩尖刺意味着什么吗?"
"加速度突变……会产生冲击?"
"冲击。"老郑用指关节敲了敲控制柜外壳,"你的基座电机额定扭矩35 N·m,惯量0.85 kg·m²。加速度从0跳到0.5 m/s²,等效关节角加速度跳变约3.5 rad/s²,扭矩需求瞬间从2 N·m飙到5 N·m。每次起停都是一次扭矩阶跃,齿轮箱承受反复冲击。连续跑八小时,减速机寿命直接砍半。"
"那我该怎么处理?"
"平滑滤波。你的原始轨迹点位序列本身没问题,但点位之间的速度衔接是硬的——梯形速度规划在加减速段切换时加速度不连续,经逆运动学映射到关节空间后被臂长进一步放大。你需要对轨迹做Savitzky-Golay平滑滤波,在消除加速度阶跃的同时保留路径形状。"
"具体怎么做?"
"Savitzky-Golay滤波器在每个点附近取一个滑动窗口,用二次多项式拟合窗口内的数据,用拟合值替代原始值。既能消除加速度跳变,又不严重扭曲轨迹。你今天先跑离线仿真,量化冲击消除效果。"
本篇解决的问题:给定一条梯形速度规划的原始轨迹,使用Savitzky-Golay滤波对轨迹点位进行平滑处理,通过逆运动学映射到关节空间计算角速度冲击,对比滤波前后的关节角速度、笛卡尔加速度曲线,量化冲击消除效果。
⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于离线轨迹平滑算法验证。不可直接用于真实机器人控制器或产线设备。真实工业现场需结合伺服前馈控制、加速度规划(S曲线)、振动抑制算法。代码作者不对直接使用本代码于真实设备造成的任何后果负责。
2. 痛点分析
课程模块 本篇对应的工程痛点
第6章 轨迹规划与控制 → 6.2/6.3 轨迹规划基础 梯形速度规划在加减速切换段加速度不连续,导致关节扭矩阶跃冲击
第5章 机器人关节驱动与控制 → 5.1 驱动系统基础 扭矩阶跃激发机械共振,末端定位时间延长,减速机寿命下降
第2章 位姿描述与坐标变换 → 2.3 速度雅可比 笛卡尔空间加速度阶跃经IK映射到关节空间,被臂长放大为更大的关节角加速度尖峰
本篇代码解决 L2 离线轨迹后处理层。输入:梯形速度规划的原始轨迹点位序列(N×3)+ 时间戳。处理:Savitzky-Golay滤波平滑位置 → 数值差分求速度/加速度 → IK映射到关节空间求角速度 → 对比原始与平滑后曲线。输出:4张对比图表 + 冲击量化指标(加速度峰值降低比、Jerk RMS降低比、路点最大偏差)。
不覆盖:在线S曲线插补、伺服前馈补偿、柔性关节振动抑制、实时轨迹修正。
3. 核心逻辑讲解
物理直觉
你开车从静止猛踩油门到60 km/h,乘客会往后仰——这是加速度突变。如果分三秒慢慢踩上去,乘客几乎感觉不到。机械臂同理:梯形速度规划在起停段加速度从0跳到0.5 m/s²再跳回0,电机扭矩跟着阶跃,减速机齿轮承受反复冲击。平滑滤波做的事,就是在加速度曲线的"棱角"上抹一层圆角,让扭矩输出变得柔和。
Savitzky-Golay滤波的本质:在轨迹上每个点附近取一个滑动窗口(比如前后各5个点,共11个点),用二次多项式拟合这个窗口内的数据,用拟合曲线在该点的值替代原始值。这样做的好处是既能消除高频抖动(平滑效果),又能保留信号的低频趋势(轨迹形状不扭曲)。窗口越大越平滑,但轨迹会"缩水"——拐角被磨圆,末端偏离原始路点。
业务逻辑 → 代码映射
输入:梯形速度规划原始轨迹 P_raw[N][3] + 时间戳 t[N]
↓
【步骤1:位置平滑】
对 x, y, z 三个维度分别做 Savitzky-Golay 滤波(窗口11,多项式阶数2)
输出 P_smooth[N][3]
↓
【步骤2:数值差分求速度/加速度】
使用 np.gradient 计算速度 v 和加速度 a(内部自动处理边界,无数值伪影)
v_raw = gradient(P_raw, t, axis=0)
a_raw = gradient(v_raw, t, axis=0)
↓
【步骤3:逆运动学映射到关节空间】
对每一点:q_raw[i] = IK(P_raw[i]), q_smooth[i] = IK(P_smooth[i])
关节角速度:ω_raw = gradient(q_raw, t, axis=0)
↓
【步骤4:冲击量化】
acc_peak_reduction = (max(|a_raw|) - max(|a_smooth|)) / max(|a_raw|) × 100%
jerk_rms_reduction = (RMS(jerk_raw) - RMS(jerk_smooth)) / RMS(jerk_raw) × 100%
max_deviation = max(‖P_smooth[i] - P_raw[i]‖) # 路点保真度
↓
输出:对比图表 + 量化指标
关键公式文字翻译
Savitzky-Golay卷积系数:滤波器在每个窗口内用二次多项式 y = c₀ + c₁·x + c₂·x² 拟合。拟合权重由最小二乘法确定,卷积系数预先算好存为数组。对每个点,将窗口内原始值与系数逐点相乘再求和,得到平滑后的值。窗口大小=11、多项式阶数=2是最常用的组合——平滑力度足够且不严重扭曲轨迹。
加速度峰值降低比:(原始加速度峰值 - 平滑后加速度峰值) ÷ 原始加速度峰值 × 100%。反映滤波消除冲击的效果。工程上要求降低50%以上才有实际意义。
Jerk(加加速度)RMS值:加速度对时间的导数的均方根。Jerk越大,机械冲击越剧烈。平滑后Jerk RMS应显著降低(通常降低60%以上),说明扭矩变化率被有效控制。
路点最大偏差:平滑后轨迹与原始轨迹之间逐点欧氏距离的最大值。反映平滑处理对路径形状的扭曲程度。工程上要求 < 1 mm(与第34篇路点通过精度一致)。
4. OOP代码实现
技术栈
严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。
项目结构树
smoothing_sim/
├── run_smoothing_sim.py
├── requirements.txt
├── robot/
│ ├── __init__.py
│ ├── models.py
│ ├── inverse_kinematics.py
│ ├── trajectory_generator.py
│ ├── smoothing_filter.py
│ ├── impact_quantifier.py
│ └── visualize_smoothing.py
├── tests/
│ ├── __init__.py
│ └── test_smoothing_sim.py
└── results/
核心源码
"robot/models.py" — 数据模型
"""
数据模型:DH参数、连杆、轨迹点位、平滑配置
全部参数内置,自包含可复现
"""
from dataclasses import dataclass, field
from typing import List
import numpy as np
@dataclass
class DHLink:
"""单个连杆的D-H参数(标准D-H约定)"""
a: float # 连杆长度 (m)
alpha: float # 连杆扭转角 (rad)
d: float # 连杆偏移 (m)
theta_offset: float = 0.0 # 关节角偏置 (rad)
@dataclass
class RobotConfig:
"""机器人配置:3-DOF平面SCARA"""
links: List[DHLink]
joint_limits: List[tuple] = None
def __post_init__(self):
if self.joint_limits is None:
self.joint_limits = [
(-170 * np.pi / 180, 170 * np.pi / 180), # 基座
(-150 * np.pi / 180, 150 * np.pi / 180), # 大臂
(-150 * np.pi / 180, 150 * np.pi / 180), # 小臂
]
# 标定DH参数
CALIBRATED_LINKS = [
DHLink(a=0.400, alpha=0.0, d=0.150, theta_offset=0.0), # 基座+大臂
DHLink(a=0.300, alpha=0.0, d=0.0, theta_offset=0.0), # 小臂
DHLink(a=0.0, alpha=0.0, d=0.0, theta_offset=0.0), # 预留
]
@dataclass
class TrajectoryPoint:
"""轨迹点位(含时间戳)"""
time: float
position: np.ndarray # [x, y, z] (m)
@dataclass
class SmoothingConfig:
"""平滑滤波配置——仅保留本篇实际使用的参数"""
sg_window: int = 11 # Savitzky-Golay窗口大小(奇数,≥5)
sg_polyorder: int = 2 # 多项式阶数(通常2或3)
@dataclass
class ImpactMetrics:
"""冲击量化指标"""
acc_peak_raw: float # 原始加速度峰值 (m/s²)
acc_peak_smooth: float # 平滑后加速度峰值 (m/s²)
acc_peak_reduction: float # 加速度峰值降低百分比 (%)
jerk_rms_raw: float # 原始jerk RMS (m/s³)
jerk_rms_smooth: float # 平滑后jerk RMS (m/s³)
jerk_rms_reduction: float # jerk RMS降低百分比 (%)
max_deviation: float # 平滑后路点最大偏差 (m)
"robot/inverse_kinematics.py" — 逆运动学
"""
逆运动学模块
3-DOF平面SCARA解析解
θ₁ = φ ∓ α,θ₂ = arccos((r²-a₁²-a₂²)/(2a₁a₂))
不可达时raise ValueError,不钳位
"""
import numpy as np
from robot.models import RobotConfig
class InverseKinematics:
"""3-DOF平面SCARA逆运动学解析求解器"""
def __init__(self, config: RobotConfig, elbow_mode: str = "up"):
self.config = config
self.a1 = config.links[0].a
self.a2 = config.links[1].a
self.d_base = config.links[0].d
self.elbow_mode = elbow_mode
def solve(self, position: np.ndarray) -> np.ndarray:
"""
求解逆运动学
Args:
position: 末端期望位置 [x, y, z] (m)
Returns:
关节角数组 [θ₁, θ₂, θ₃]
"""
x, y, z = position[0], position[1], position[2]
r = np.sqrt(x**2 + y**2)
# 可达性检查
r_min = abs(self.a1 - self.a2)
r_max = self.a1 + self.a2
if r < r_min - 1e-6 or r > r_max + 1e-6:
raise ValueError(
f"点位 ({x:.3f}, {y:.3f}, {z:.3f}) 不可达:"
f"水平距离 r={r:.4f} m 超出工作空间 "
f"[{r_min:.4f}, {r_max:.4f}] m"
)
cos_theta2 = (r**2 - self.a1**2 - self.a2**2) / (2 * self.a1 * self.a2)
if abs(cos_theta2) > 1.0:
raise ValueError(f"arccos参数越界: {cos_theta2:.6f},点位不可达")
theta2 = np.arccos(cos_theta2)
phi = np.arctan2(y, x)
cos_alpha = (r**2 + self.a1**2 - self.a2**2) / (2 * self.a1 * r)
if abs(cos_alpha) > 1.0:
raise ValueError(f"α的arccos参数越界: {cos_alpha:.6f},点位不可达")
alpha = np.arccos(cos_alpha)
if self.elbow_mode == "up":
theta1 = phi - alpha
else:
theta1 = phi + alpha
# SCARA第3轴联动:保持末端水平
theta3 = -theta2
# 限位检查
limits = self.config.joint_limits
for j, (val, name) in enumerate([(theta1, "基座"), (theta2, "大臂"), (theta3, "小臂")]):
if not (limits[j][0] <= val <= limits[j][1]):
raise ValueError(
f"{name}关节角 {val*180/np.pi:.1f}° 超出限位 "
f"[{limits[j][0]*180/np.pi:.1f}°, {limits[j][1]*180/np.pi:.1f}°]"
)
return np.array([theta1, theta2, theta3], dtype=np.float64)
"robot/trajectory_generator.py" — 梯形速度轨迹生成
"""
轨迹生成模块
真正实现梯形速度规划:匀加速→匀速→匀减速
起停段加速度阶跃,用于模拟未做平滑处理的规划器输出
"""
import numpy as np
from robot.models import TrajectoryPoint
class TrajectoryGenerator:
"""
梯形速度轨迹生成器
三段式:匀加速(0→v_max, a=0.5) → 匀速(v_max=0.25) → 匀减速(-0.5→0)
总时长2.8 s,与老郑台词完全对齐
"""
def __init__(self, total_time: float = 2.8, n_points: int = 140):
self.total_time = total_time
self.n_points = n_points
def generate_trapezoidal_trajectory(self) -> list:
"""
生成梯形速度轨迹
运动参数:
- 最大速度 v_max = 0.25 m/s
- 加速度 a = 0.5 m/s²
- 加速时间 t_acc = v_max / a = 0.5 s
- 减速时间 t_dec = 0.5 s
- 匀速时间 t_const = 2.8 - 0.5 - 0.5 = 1.8 s
- 总位移 = 0.5×0.5×0.5 + 0.25×1.8 + 0.5×0.5×0.5 = 0.0625 + 0.45 + 0.0625 = 0.575 m
起点 (0.15, -0.20, 0.15) → 终点 (0.45, 0.30, 0.15)
水平位移 Δx = 0.30 m, Δy = 0.50 m, 总水平距离 = √(0.3²+0.5²) = 0.583 m ≈ 0.575 m
"""
dt = self.total_time / (self.n_points - 1)
trajectory = []
# 起点和终点
start_pos = np.array([0.15, -0.20, 0.15])
end_pos = np.array([0.45, 0.30, 0.15])
# 水平方向总位移
delta_xy = end_pos[:2] - start_pos[:2]
dist_xy = np.linalg.norm(delta_xy)
# 运动参数
v_max = 0.25 # m/s
acc = 0.5 # m/s²
t_acc = v_max / acc # 0.5 s
t_dec = 0.5 # s
t_const = self.total_time - t_acc - t_dec # 1.8 s
# 匀速段位移
s_const = v_max * t_const # 0.45 m
# 加减速段位移
s_acc = 0.5 * acc * t_acc**2 # 0.0625 m
s_dec = s_acc # 0.0625 m
total_dist = s_acc + s_const + s_dec # 0.575 m
# 方向单位向量
direction = delta_xy / dist_xy
for i in range(self.n_points):
t = i * dt
# 计算当前时刻沿水平方向的总位移
if t <= t_acc:
# 匀加速段
s = 0.5 * acc * t**2
elif t <= t_acc + t_const:
# 匀速段
s = s_acc + v_max * (t - t_acc)
else:
# 匀减速段
t_dec_elapsed = t - t_acc - t_const
s = s_acc + s_const + v_max * t_dec_elapsed - 0.5 * acc * t_dec_elapsed**2
# 映射到x, y坐标
x = start_pos[0] + direction[0] * s * (dist_xy / total_dist)
y = start_pos[1] + direction[1] * s * (dist_xy / total_dist)
z = start_pos[2] # Z恒定
pos = np.array([x, y, z], dtype=np.float64)
trajectory.append(TrajectoryPoint(time=t, position=pos))
return trajectory
"robot/smoothing_filter.py" — 平滑滤波核心
"""
平滑滤波模块
实现Savitzky-Golay滤波
对原始轨迹点位序列进行平滑处理
"""
import numpy as np
from scipy.signal import savgol_filter
from robot.models import SmoothingConfig
class SmoothingFilter:
"""
轨迹平滑滤波器——仅实现本篇实际使用的Savitzky-Golay方法
"""
def __init__(self, config: SmoothingConfig):
self.config = config
def savitzky_golay_smooth(self, positions: np.ndarray) -> np.ndarray:
"""
对位置序列做Savitzky-Golay滤波
Args:
positions: 原始位置序列 (N, 3)
Returns:
平滑后位置序列 (N, 3)
"""
smoothed = np.zeros_like(positions)
window = self.config.sg_window
polyorder = self.config.sg_polyorder
# 确保窗口大小为奇数且不大于数据长度
if window >= len(positions):
window = len(positions) - 1 if len(positions) % 2 == 0 else len(positions)
if window < 3:
return positions.copy()
for dim in range(3):
smoothed[:, dim] = savgol_filter(
positions[:, dim],
window_length=window,
polyorder=polyorder
)
return smoothed
"robot/impact_quantifier.py" — 冲击量化
"""
冲击量化模块
使用np.gradient计算差分(内部自动处理边界,无数值伪影)
计算加速度峰值降低比、jerk RMS降低比、路点最大偏差
"""
import numpy as np
from robot.models import ImpactMetrics
class ImpactQuantifier:
"""
冲击量化计算器
所有差分使用np.gradient,边界由内部一阶差分处理,不产生伪影
"""
def compute_metrics(self, times: np.ndarray,
pos_raw: np.ndarray,
pos_smooth: np.ndarray) -> ImpactMetrics:
"""
计算冲击量化指标
Args:
times: 时间戳 (N,)
pos_raw: 原始位置 (N, 3)
pos_smooth: 平滑后位置 (N, 3)
Returns:
ImpactMetrics
"""
dt = np.mean(np.diff(times))
# 速度——使用np.gradient(内部处理边界,不产生伪影)
vel_raw = np.gradient(pos_raw, times, axis=0)
vel_smooth = np.gradient(pos_smooth, times, axis=0)
# 加速度
acc_raw = np.gradient(vel_raw, times, axis=0)
acc_smooth = np.gradient(vel_smooth, times, axis=0)
# 加速度峰值(取各维度合量的最大值)
acc_mag_raw = np.linalg.norm(acc_raw, axis=1)
acc_mag_smooth = np.linalg.norm(acc_smooth, axis=1)
acc_peak_raw = np.max(acc_mag_raw)
acc_peak_smooth = np.max(acc_mag_smooth)
acc_peak_reduction = (acc_peak_raw - acc_peak_smooth) / acc_peak_raw * 100.0
# Jerk(加速度导数)
jerk_raw = np.gradient(acc_raw, times, axis=0)
jerk_smooth = np.gradient(acc_smooth, times, axis=0)
jerk_mag_raw = np.linalg.norm(jerk_raw, axis=1)
jerk_mag_smooth = np.linalg.norm(jerk_smooth, axis=1)
jerk_rms_raw = np.sqrt(np.mean(jerk_mag_raw**2))
jerk_rms_smooth = np.sqrt(np.mean(jerk_mag_smooth**2))
jerk_rms_reduction = (jerk_rms_raw - jerk_rms_smooth) / jerk_rms_raw * 100.0
# 路点最大偏差(平滑后轨迹保真度)
deviation = np.linalg.norm(pos_smooth - pos_raw, axis=1)
max_deviation = np.max(deviation)
return ImpactMetrics(
acc_peak_raw=acc_peak_raw,
acc_peak_smooth=acc_peak_smooth,
acc_peak_reduction=acc_peak_reduction,
jerk_rms_raw=jerk_rms_raw,
jerk_rms_smooth=jerk_rms_smooth,
jerk_rms_reduction=jerk_rms_reduction,
max_deviation=max_deviation
)
"robot/visualize_smoothing.py" — 可视化
"""
平滑滤波效果可视化
4张核心图表:XY轨迹对比+关节角速度对比+加速度对比+冲击指标(双子图)
"""
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
class SmoothingVisualizer:
def __init__(self, results_dir="results"):
self.results_dir = results_dir
import os
os.makedirs(results_dir, exist_ok=True)
def plot_xy_trajectory(self, times, pos_raw, pos_smooth):
"""
① XY平面轨迹对比:原始 vs 平滑后
"""
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(pos_raw[:, 0], pos_raw[:, 1], 'r-', linewidth=1.5,
alpha=0.6, label='Raw (Trapezoidal Velocity)')
ax.plot(pos_smooth[:, 0], pos_smooth[:, 1], 'g-', linewidth=2.0,
label='Smoothed (Savitzky-Golay)')
ax.scatter(pos_raw[0, 0], pos_raw[0, 1], c='green', marker='o',
s=120, label='Start', zorder=5)
ax.scatter(pos_raw[-1, 0], pos_raw[-1, 1], c='red', marker='s',
s=120, label='End', zorder=5)
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_title('XY Trajectory: Raw vs Smoothed')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
plt.tight_layout()
path = f"{self.results_dir}/01_xy_trajectory.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_joint_velocity_comparison(self, times, q_vel_raw, q_vel_smooth):
"""
② 关节角速度对比(经IK映射,验证"关节角速度尖峰"叙事)
"""
fig, axes = plt.subplots(3, 1, figsize=(10, 8), sharex=True)
joint_names = ['Base Joint (rad/s)', 'Upper Arm (rad/s)', 'Forearm (rad/s)']
for j in range(3):
ax = axes[j]
ax.plot(times, q_vel_raw[:, j], 'r-', linewidth=1.0, alpha=0.6,
label='Raw')
ax.plot(times, q_vel_smooth[:, j], 'g-', linewidth=1.5,
label='Smoothed')
# 标注加速/匀速/减速段
t_acc_end = 0.5
t_const_end = 2.3
ax.axvline(x=t_acc_end, color='blue', linestyle=':', alpha=0.5)
ax.axvline(x=t_const_end, color='blue', linestyle=':', alpha=0.5)
ax.set_ylabel(joint_names[j], fontsize=9)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
axes[2].set_xlabel('Time (s)')
fig.suptitle('Joint Angular Velocity: Raw vs Smoothed (via IK Mapping)',
fontsize=11)
plt.tight_layout()
path = f"{self.results_dir}/02_joint_velocity_comparison.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_acceleration_comparison(self, times, pos_raw, pos_smooth):
"""
③ 笛卡尔加速度对比
"""
dt = np.mean(np.diff(times))
vel_raw = np.gradient(pos_raw, times, axis=0)
vel_smooth = np.gradient(pos_smooth, times, axis=0)
acc_raw = np.gradient(vel_raw, times, axis=0)
acc_smooth = np.gradient(vel_smooth, times, axis=0)
acc_mag_raw = np.linalg.norm(acc_raw, axis=1)
acc_mag_smooth = np.linalg.norm(acc_smooth, axis=1)
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(times, acc_mag_raw, 'r-', linewidth=1.0, alpha=0.6,
label='Raw Acceleration')
ax.plot(times, acc_mag_smooth, 'g-', linewidth=1.5,
label='Smoothed Acceleration')
# 标注加减速段
ax.axvline(x=0.5, color='blue', linestyle=':', alpha=0.5,
label='Accel→Const')
ax.axvline(x=2.3, color='blue', linestyle=':', alpha=0.5,
label='Const→Decel')
ax.set_xlabel('Time (s)')
ax.set_ylabel('Acceleration Magnitude (m/s²)')
ax.set_title('Cartesian Acceleration: Impact Elimination')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/03_acceleration_comparison.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_impact_metrics(self, metrics):
"""
④ 冲击量化指标(双子图:上子图绝对值,下子图百分比)
"""
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# 上子图:绝对值
abs_labels = ['Acc Peak\nRaw (m/s²)', 'Acc Peak\nSmooth (m/s²)',
'Jerk RMS\nRaw (m/s³)', 'Jerk RMS\nSmooth (m/s³)']
abs_values = [metrics.acc_peak_raw, metrics.acc_peak_smooth,
metrics.jerk_rms_raw, metrics.jerk_rms_smooth]
colors_abs = ['red', 'green', 'red', 'green']
bars1 = ax1.bar(abs_labels, abs_values, color=colors_abs, alpha=0.7)
for bar in bars1:
height = bar.get_height()
ax1.annotate(f'{height:.3f}',
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3), textcoords="offset points",
ha='center', va='bottom', fontsize=9)
ax1.set_ylabel('Absolute Value', fontsize=10)
ax1.set_title('Impact Metrics: Absolute Values', fontsize=11)
ax1.grid(True, alpha=0.3, axis='y')
# 下子图:百分比
pct_labels = ['Acc Peak\nReduction (%)', 'Jerk RMS\nReduction (%)',
'Max Deviation\n(mm)']
pct_values = [metrics.acc_peak_reduction, metrics.jerk_rms_reduction,
metrics.max_deviation * 1000] # mm
colors_pct = ['green', 'green', 'orange']
bars2 = ax2.bar(pct_labels, pct_values, color=colors_pct, alpha=0.7)
for bar in bars2:
height = bar.get_height()
ax2.annotate(f'{height:.1f}',
xy=(bar.get_x() +
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)