多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

python的先进制造技术工业场景模拟第十三篇:读取机器人轨迹采样数据,计算轨迹各段运动速度,标记速度突变点位。

python的先进制造技术工业场景模拟第十三篇:读取机器人轨迹采样数据,计算轨迹各段运动速度,标记速度突变点位。 周五下午机器人弧焊工作站。这周第三件了工艺工程师小李指着报废的焊接件焊缝起始段总有顿挫感——机器人不是平滑过渡而是像卡了一下再继续走。结果起弧点堆焊背面咬边。三个壳体每个四百多一千多块钱没了。我调出机器人离线编程软件的轨迹采样日志。屏幕上是一组组密集的坐标点时间戳、X/Y/Z 位置mm、关节角度°采样频率 125Hz每 8ms 一个点一条 30 秒的焊接轨迹约 3750 行记录。软件里不是有轨迹回放吗我问。有小李点开 3D 仿真窗口但它是按点播放的——你只能看到机器人沿着路径在动。我想知道的是轨迹上每一段的速度是多少、速度有没有突变、突变点在哪个位置。系统没有速度剖面分析这个功能——它只会显示当前点的瞬时速度突变点藏在上千个点里肉眼根本看不出来。所以你每次调完工艺都不知道速度曲线平不平滑对小李苦笑焊完发现起弧段有顿挫调出轨迹回放——点太密了看起来就是一条连续的线。我需要的是把整条轨迹的速度算出来画出速度曲线标记出速度突变的位置——这样我就能知道是路径规划的问题还是加减速参数没调好。现在只能凭经验猜猜完了再试焊试一件废一件。明白了我打开 VS Code你需要一个程序读取机器人轨迹采样数据用相邻两点间的距离除以时间间隔算出每个采样点的速度然后用数值微分算加速度找出加速度超过阈值的突变点。输出一张速度剖面图和突变点标记表。这样你一看图就知道速度在哪卡了。我敲了几行代码import pandas as pdimport numpy as np# 读取轨迹数据df pd.read_csv(robot_trajectory.csv, parse_dates[timestamp])# 计算位移dx np.diff(df[x_mm])dy np.diff(df[y_mm])dz np.diff(df[z_mm])dt np.diff(df[timestamp].astype(int64) // 1e9) # 秒distances np.sqrt(dx**2 dy**2 dz**2)velocities distances / dt # m/s# 加速度数值微分accelerations np.diff(velocities) / dt[1:]# 标记突变点加速度绝对值 阈值jerk_threshold 2.0 # m/s^3jerk_mask np.abs(accelerations) jerk_threshold这只是核心逻辑我说完整版会用 OOP 封装一个类管数据加载解析时间戳、坐标列一个类管数据清洗去重、插值、滤波一个类管速度计算位移/时间 移动平均平滑一个类管突变检测加速度阈值 滑动窗口一个类管可视化3D 轨迹图 速度剖面 突变点标记。数据自包含——用 numpy 合成一条含速度突变的 6 自由度机器人轨迹读者下载就能跑。小李凑近屏幕所以你这东西就是把几千个轨迹点变成一条速度曲线速度突然跳变的地方标红——我一看就知道机器人卡在哪了。对。而且还能做路径优化分析我补充比如用 NetworkX 把轨迹点连成图节点是路径点边权重是速度变化量用最短路径算法找最平滑路径。机器人轨迹的调试不是试焊看效果是用数据看速度剖面在顿挫出现之前就修正路径。一、实际应用场景真实痛点场景设定工业机器人弧焊/喷涂/搬运在执行任务时控制系统以固定频率如 125Hz记录末端执行器的空间坐标和关节角度。一条 30 秒的轨迹产生约 3750 行采样数据。工艺工程师需要分析轨迹的速度剖面识别速度突变点可能导致振动、顿挫或焊缝缺陷。常见痛点数据按点密集采样系统只提供 3D 轨迹回放不提供速度计算和突变检测功能依赖肉眼观察或试焊反馈。现场原话叙事化我不是不想调好路径小李说每个点的位置都在系统里8 毫秒一个点。但系统只会告诉我机器人从这里走到了那里——它不会告诉我走到中间的时候速度是不是突然变了。我想知道的是这条轨迹上速度在哪里突然跳了、跳了多少、是不是超过了机械臂的加减速能力。这些系统给不了我。焊完一看起弧段有顿挫——但那时候已经废了一件了。核心矛盾高密度空间坐标采样与工艺工程师需要的速度剖面与突变标记之间的分析断层。需要一个机器人轨迹速度分析与突变检测程序用numpy 计算位移和速度用scipy 做数值微分和滤波用matplotlib 绘制 3D 轨迹和速度剖面用scikit-learn 做异常检测输出结构化的速度报告和突变点清单。二、痛点分析映射到滨州职业学院《先进制造技术》课程模型《先进制造技术》模块 本篇痛点对应工业机器人技术基础机器人运动学与轨迹规划 速度剖面分析基于采样数据的轨迹速度计算与平滑性评估。先进制造技术基础先进制造系统的运动控制 加减速优化通过突变检测识别不合理的加减速段。智能制造与数字孪生机器人数字孪生 轨迹可视化3D 空间轨迹 速度颜色映射。先进制造新模式数据驱动的工艺优化 根因定位突变点关联路径段指导路径重规划。一句话总结我们需要构建一个机器人轨迹速度分析与突变检测程序用numpy 向量化计算位移和速度用scipy 做数值微分和滤波用matplotlib 绘制 3D 轨迹和速度剖面用scikit-learn 做突变检测实现从密集坐标点到速度突变报告的转化。三、核心逻辑讲解大白话3.1 问题本质把机器人轨迹想象成汽车行车记录仪把机器人的轨迹采样数据想象成你开车时的 GPS 轨迹* 每个采样点 GPS 定位点每 8 米记录一次你的位置x, y, z。* 速度 两点之间距离 ÷ 时间差GPS 点之间距离 10 米时间差 1 秒 → 速度 10 m/s36 km/h。* 加速度 速度变化 ÷ 时间差上一秒 10 m/s这一秒 15 m/s → 加速度 5 m/s²。* 速度突变 急刹车/急加速加速度突然超过 3 m/s² → 乘客会前倾/后仰对应机器人末端顿挫。* 3D 轨迹图 行车路线地图用颜色表示速度——绿色是顺畅红色是急变速区域。工业应用* numpy 向量化位移计算np.sqrt(np.diff(x)**2 np.diff(y)**2 np.diff(z)**2) 一行代码算出所有相邻点之间的距离比 for 循环快百倍。* scipy 数值微分scipy.gradient() 自动处理不等间距采样比手动差分更精确。* scikit-learn 异常检测IsolationForest 自动识别速度突变点不需要手动设阈值。3.2 业务逻辑 → 代码映射定义机器人轨迹数据模型│▼ TrajectoryDataLoader (pandas)加载轨迹 CSVpd.read_csv(robot_trajectory.csv, parse_dates[timestamp])解析时间戳、X/Y/Z 坐标、关节角度│▼ DataCleaner (pandas scipy)数据清洗去重相同时间戳插值缺失坐标scipy.signal.savgol_filter 平滑噪声│▼ VelocityCalculator (numpy scipy)速度计算位移 sqrt(dx² dy² dz²)速度 位移 / dtscipy.gradient 计算平滑速度│▼ JerkDetector (numpy scikit-learn)突变检测加速度 np.diff(velocity) / dt阈值法|acceleration| thresholdIsolationForest 异常检测标记突变点索引│▼ TrajectoryVisualizer (matplotlib networkx)可视化1. 3D 轨迹图颜色映射速度2. 速度剖面曲线 突变点标注3. 加速度分布直方图4. NetworkX 路径图节点采样点边权重速度变化│▼ SyntheticDataGenerator (numpy)合成数据生成用 numpy 生成含平滑段 突变段的 3D 轨迹确保数据自包含、可复现3.3 为什么用向量化而不是逐点循环* 问题3750 行轨迹数据逐点计算距离和速度——代码冗长速度慢。* 处理策略numpy 向量化操作底层 C 实现对 3750 个点求距离只需微秒级。* 工程合理性高速采样500Hz下一条轨迹可能有数万点向量化是唯一选择。3.4 分析前后对比维度 3D 轨迹回放 Python 程序分析判断依据 肉眼看有没有顿挫 精确计算加速度效率 回放完才知道 秒级出速度曲线突变定位 不知道在哪一段 精确标记突变点索引根因分析 猜 突变点关联路径段优化指导 无 输出突变位置供路径重规划四、OOP 代码实现4.1 项目结构robot_trajectory_analyzer/├── robot_trajectory_analyzer/│ ├── __init__.py│ ├── data_loader.py # 轨迹数据加载│ ├── data_cleaner.py # 数据清洗与平滑│ ├── velocity_calculator.py # 速度计算│ ├── jerk_detector.py # 突变检测│ ├── visualizer.py # 可视化│ └── synthetic_data.py # 合成数据生成├── tests/│ ├── __init__.py│ └── test_trajectory_analyzer.py # 单元测试├── results/│ ├── velocity_profile.csv # 速度剖面数据│ ├── jerk_points.csv # 突变点清单│ ├── trajectory_3d.png # 3D 轨迹图│ ├── velocity_profile.png # 速度剖面曲线│ ├── acceleration_hist.png # 加速度分布图│ └── trajectory_report.txt # 文本分析报告└── run_trajectory.py # 主程序入口4.2 核心源码detailssummary/summary机器人轨迹数据加载器import pandas as pdfrom pathlib import Pathfrom typing import Optionalclass TrajectoryDataLoader:机器人轨迹数据加载器支持 CSV 格式的空间坐标采样数据。def __init__(self, filepath: str, encoding: str utf-8):self.filepath Path(filepath)self.encoding encodingself._raw_df: Optional[pd.DataFrame] Nonedef load(self,timestamp_col: str timestamp,x_col: str x_mm,y_col: str y_mm,z_col: str z_mm,j1_col: str j1_deg,j2_col: str j2_deg,j3_col: str j3_deg) - pd.DataFrame:加载轨迹数据Parameters----------timestamp_col : str时间戳列名x_col, y_col, z_col : strX/Y/Z 坐标列名j1_col, j2_col, j3_col : str关节角度列名Returns-------pd.DataFrameif not self.filepath.exists():raise FileNotFoundError(f文件不存在: {self.filepath})self._raw_df pd.read_csv(self.filepath,encodingself.encoding,parse_dates[timestamp_col],)# 列名标准化col_aliases {timestamp_col: [时间, timestamp, time, t],x_col: [X, x, x_mm, pos_x, X_mm],y_col: [Y, y, y_mm, pos_y, Y_mm],z_col: [Z, z, z_mm, pos_z, Z_mm],j1_col: [J1, j1, j1_deg, joint1],j2_col: [J2, j2, j2_deg, joint2],j3_col: [J3, j3, j3_deg, joint3],}rename_map {}for target, aliases in col_aliases.items():if target not in self._raw_df.columns:for alias in aliases:if alias in self._raw_df.columns:rename_map[alias] targetbreakif rename_map:self._raw_df self._raw_df.rename(columnsrename_map)return self._raw_df.copy()def get_raw_data(self) - Optional[pd.DataFrame]:return self._raw_df.copy() if self._raw_df is not None else None/detailsdetailssummary/summary轨迹数据清洗与平滑import numpy as npimport pandas as pdfrom scipy.signal import savgol_filterfrom typing import Optional, Tupleclass DataCleaner:机器人轨迹数据清洗器去重、插值、Savitzky-Golay 平滑。def __init__(self,window_length: int 11,polyorder: int 3):self.window_length window_lengthself.polyorder polyorderdef clean(self,df: pd.DataFrame,timestamp_col: str timestamp,x_col: str x_mm,y_col: str y_mm,z_col: str z_mm) - pd.DataFrame:清洗轨迹数据Parameters----------df : pd.DataFrame原始数据timestamp_col : str时间戳列名x_col, y_col, z_col : str坐标列名Returns-------pd.DataFrameresult df.copy()# 时间戳处理if timestamp_col in result.columns:result[timestamp_col] pd.to_datetime(result[timestamp_col], errorscoerce)result result.dropna(subset[timestamp_col])result result.sort_values(timestamp_col).reset_index(dropTrue)# 去重result result.drop_duplicates(subset[timestamp_col])# 坐标数值化for col in [x_col, y_col, z_col]:if col in result.columns:result[col] pd.to_numeric(result[col], errorscoerce)# 删除坐标全为空coord_cols [c for c in [x_col, y_col, z_col] if c in result.columns]result result.dropna(subsetcoord_cols, howall)return result.reset_index(dropTrue)def smooth(self,df: pd.DataFrame,x_col: str x_mm,y_col: str y_mm,z_col: str z_mm) - pd.DataFrame:对坐标进行 Savitzky-Golay 平滑Parameters----------df : pd.DataFrame清洗后的数据x_col, y_col, z_col : str坐标列名Returns-------pd.DataFrameresult df.copy()# 确保窗口长度为奇数n len(result)window min(self.window_length, n - 1)if window % 2 0:window - 1if window 3:return resultfor col in [x_col, y_col, z_col]:if col in result.columns:try:result[col] savgol_filter(result[col].values, window, self.polyorder)except ValueError:pass # 数据不足跳过平滑return result/detailsdetailssummary/summary速度计算import numpy as npimport pandas as pdfrom scipy.gradient import gradientfrom typing import Dict, Optionalclass VelocityCalculator:机器人轨迹速度计算器基于位移/时间差计算速度使用 scipy.gradient 计算平滑速度。def __init__(self):passdef calculate(self,df: pd.DataFrame,timestamp_col: str timestamp,x_col: str x_mm,y_col: str y_mm,z_col: str z_mm) - pd.DataFrame:计算速度剖面Parameters----------df : pd.DataFrame清洗后的轨迹数据timestamp_col : str时间戳列名x_col, y_col, z_col : str坐标列名Returns-------pd.DataFrame新增 distance, velocity, velocity_smooth 列result df.copy()n len(result)if n 2:return result# 时间差秒timestamps pd.to_datetime(result[timestamp_col]).astype(int64) // 1e9dt np.diff(timestamps.astype(float))dt np.append(dt, dt[-1]) # 最后一个点复制前一个 dt# 位移dx np.diff(result[x_col].values, prependresult[x_col].values[0])dy np.diff(result[y_col].values, prependresult[y_col].values[0])dz np.diff(result[z_col].values, prependresult[z_col].values[0])# 距离distances np.sqrt(dx**2 dy**2 dz**2)distances[0] 0.0 # 第一个点距离为 0# 速度 距离 / 时间velocities np.zeros(n)velocities[1:] distances[1:] / dt[1:]# 平滑速度scipy.gradientvel_smooth np.zeros(n)if n 1:vel_smooth gradient(distances, timestamps)result[distance_mm] distancesresult[velocity_mm_s] velocitiesresult[velocity_smooth_mm_s] vel_smoothreturn resultdef get_summary(self, df: pd.DataFrame) - Dict:速度统计摘要if velocity_smooth_mm_s not in df.columns:return {}v df[velocity_smooth_mm_s].valuesv v[v 0] # 排除静止点if len(v) 0:return {}return {max_velocity: float(np.max(v)),min_velocity: float(np.min(v)),mean_velocity: float(np.mean(v)),std_velocity: float(np.std(v)),median_velocity: float(np.median(v)),}/detailsdetailssummary/summary速度突变检测import numpy as npimport pandas as pdfrom sklearn.ensemble import IsolationForestfrom typing import Dict, Optionalclass JerkDetector:机器人轨迹速度突变检测器基于加速度阈值和 Isolation Forest 异常检测。def __init__(self,jerk_threshold: float 1000.0, # mm/s^3contamination: float 0.05):self.jerk_threshold jerk_thresholdself.contamination contaminationdef detect(self,df: pd.DataFrame,timestamp_col: str timestamp,velocity_col: str velocity_smooth_mm_s) - pd.DataFrame:检测速度突变点Parameters----------df : pd.DataFrame含速度数据的 DataFrametimestamp_col : str时间戳列名velocity_col : str平滑速度列名Returns-------pd.DataFrame新增 acceleration, is_jerk_threshold, is_jerk_ml 列result df.copy()n len(result)if n 3:return result# 时间差timestamps pd.to_datetime(result[timestamp_col]).astype(int64) // 1e9dt np.diff(timestamps.astype(float))dt np.append(dt, dt[-1])# 加速度数值微分velocities result[velocity_col].valuesacceleration np.zeros(n)acceleration[1:] np.diff(velocities) / dt[1:]result[acceleration_mm_s2] acceleration# 加加速度jerk 加速度变化率jerk np.zeros(n)jerk[1:] np.diff(acceleration) / dt[1:]result[jerk_mm_s3] jerk# 阈值法result[is_jerk_threshold] np.abs(jerk) self.jerk_threshold# 机器学习异常检测if n 10:features np.column_stack([velocities,acceleration,jerk,])# 去除 NaNmask ~np.isnan(features).any(axis1)if mask.sum() 5:clf IsolationForest(contaminationself.contamination,random_state42,)pred clf.fit_predict(features[mask])is_anomaly np.zeros(n, dtypebool)is_anomaly[mask] pred -1result[is_jerk_ml] is_anomalyelse:result[is_jerk_ml] Falseelse:result[is_jerk_ml] Falsereturn resultdef get_jerk_points(self, df: pd.DataFrame) - pd.DataFrame:获取所有突变点mask df[is_jerk_threshold] | df.get(is_jerk_ml, False)return df[mask].copy()def summary(self, df: pd.DataFrame) - Dict:突变摘要jerk_points self.get_jerk_points(df)return {total_points: len(df),jerk_points_threshold: df[is_jerk_threshold].sum(),jerk_points_ml: df.get(is_jerk_ml, pd.Series(False)).sum(),jerk_percentage: round(len(jerk_points) / len(df) * 100, 2),}/detailsdetailssummary/summary轨迹数据可视化import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3Dimport networkx as nxfrom pathlib import Pathfrom typing import Optionalplt.rcParams[font.sans-serif] [SimHei, DejaVu Sans]plt.rcParams[axes.unicode_minus] Falseclass TrajectoryVisualizer:机器人轨迹可视化def __init__(self, results_dir: str results):self.results_dir Path(results_dir)self.results_dir.mkdir(exist_okTrue)def plot_3d_trajectory(self,df: pd.DataFrame,x_col: str x_mm,y_col: str y_mm,z_col: str z_mm,velocity_col: str velocity_smooth_mm_s,title: str 3D 轨迹颜色映射速度) - None:绘制 3D 轨迹图颜色映射速度fig plt.figure(figsize(12, 9))ax fig.add_subplot(111, projection3d)x df[x_col].valuesy df[y_col].valuesz df[z_col].valuesv df[velocity_col].values# 颜色映射scatter ax.scatter(x, y, z, cv, cmapviridis, s10, alpha0.8)ax.set_xlabel(X (mm))ax.set_ylabel(Y (mm))ax.set_zlabel(Z (mm))ax.set_title(title, fontsize14, fontweightbold)cbar plt.colorbar(scatter, axax, shrink0.6)cbar.set_label(速度 (mm/s))plt.tight_layout()plt.savefig(self.results_dir / trajectory_3d.png,dpi150, bbox_inchestight)plt.close()def plot_velocity_profile(self,df: pd.DataFrame,timestamp_col: str timestamp,velocity_col: str velocity_smooth_mm_s,jerk_col: str is_jerk_threshold,title: str 速度剖面曲线) - None:绘制速度剖面 突变点标注fig, ax plt.subplots(figsize(16, 6))timestamps df[timestamp_col].valuesvelocities df[velocity_col].values# 速度曲线ax.plot(timestamps, velocities, linewidth0.8, color#3498DB,alpha0.8, label速度)# 突变点标注if jerk_col in df.columns:jerk_mask df[jerk_col]jerk_times timestamps[jerk_mask]jerk_vels velocities[jerk_mask]ax.scatter(jerk_times, jerk_vels, color#E74C3C, s30,zorder5, label突变点)ax.set_xlabel(时间, fontsize12)ax.set_ylabel(速度 (mm/s), fontsize12)ax.set_title(title, fontsize14, fontweightbold)ax.legend()ax.grid(alpha0.3)plt.xticks(rotation45, haright)plt.tight_layout()plt.savefig(self.results_dir / velocity_profile.png,dpi150, bbox_inchestight)plt.close()def plot_acceleration_hist(self,df: pd.DataFrame,accel_col: str acceleration_mm_s2,title: str 加速度分布) - None:绘制加速度分布直方图fig, ax plt.subplots(figsize(10, 6))accel df[accel_col].valuesaccel accel[~np.isnan(accel) (accel ! 0)]ax.hist(accel, bins50, color#3498DB, edgecolorwhite,alpha0.85)ax.axvline(x0, color#E74C3C, linestyle-, linewidth1, alpha0.5)ax.set_xlabel(加速度 (mm/s²), fontsize12)ax.set_ylabel(频次, fontsize12)ax.set_title(title, fontsize14, fontweightbold)ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir / acceleration_hist.png,dpi150, bbox_inchestight)plt.close()def plot_path_graph(self,df: pd.DataFrame,x_col: str x_mm,y_col: str y_mm,jerk_col: str is_jerk_threshold,title: str 路径拓扑图边权重速度变化) - None:用 NetworkX 绘制路径拓扑图n min(len(df), 100) # 限制节点数if n 2:returnG nx.Graph()# 采样点step max(1, len(df) // n)sampled df.iloc[::step].head(n)for i in range(n):G.add_node(i, pos(sampled.iloc[i][x_col], sampled.iloc[i][y_col]))# 添加边for i in range(n - 1):G.add_edge(i利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表