周五下午,机器人弧焊工作站。

"这周第三件了,"工艺工程师小李指着报废的焊接件,"焊缝起始段总有顿挫感——机器人不是平滑过渡,而是像'卡了一下'再继续走。结果起弧点堆焊,背面咬边。三个壳体,每个四百多,一千多块钱没了。"

我调出机器人离线编程软件的轨迹采样日志。屏幕上是一组组密集的坐标点:时间戳、X/Y/Z 位置(mm)、关节角度(°),采样频率 125Hz(每 8ms 一个点),一条 30 秒的焊接轨迹约 3750 行记录。

"软件里不是有轨迹回放吗?"我问。

"有,"小李点开 3D 仿真窗口,"但它是按'点'播放的——你只能看到机器人沿着路径在动。我想知道的是:轨迹上每一段的速度是多少、速度有没有突变、突变点在哪个位置。系统没有'速度剖面分析'这个功能——它只会显示当前点的瞬时速度,突变点藏在上千个点里,肉眼根本看不出来。"

"所以你每次调完工艺,都不知道速度曲线平不平滑?"

"对,"小李苦笑,"焊完发现起弧段有顿挫,调出轨迹回放——点太密了,看起来就是一条连续的线。我需要的是:把整条轨迹的速度算出来,画出速度曲线,标记出速度突变的位置——这样我就能知道是路径规划的问题还是加减速参数没调好。现在只能凭经验猜,猜完了再试焊,试一件废一件。"

"明白了,"我打开 VS Code,"你需要一个程序:读取机器人轨迹采样数据,用相邻两点间的距离除以时间间隔算出每个采样点的速度,然后用数值微分算加速度,找出加速度超过阈值的突变点。输出一张速度剖面图和突变点标记表。这样你一看图就知道速度在哪'卡了'。"

我敲了几行代码:

import pandas as pd

import 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^3

jerk_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/s(36 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)

加载轨迹 CSV:

  pd.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²)

  速度 = 位移 / dt

  scipy.gradient 计算平滑速度

   │

   ▼ JerkDetector (numpy + scikit-learn)

突变检测:

  加速度 = np.diff(velocity) / dt

  阈值法:|acceleration| > threshold

  IsolationForest 异常检测

  标记突变点索引

   │

   ▼ 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 核心源码

 

<details>

 

<summary></summary>

 

"""机器人轨迹数据加载器"""

 

import pandas as pd

from pathlib import Path

from typing import Optional

 

 

class TrajectoryDataLoader:

    """

    机器人轨迹数据加载器

 

    支持 CSV 格式的空间坐标采样数据。

    """

 

    def __init__(self, filepath: str, encoding: str = "utf-8"):

        self.filepath = Path(filepath)

        self.encoding = encoding

        self._raw_df: Optional[pd.DataFrame] = None

 

    def 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 : str

            X/Y/Z 坐标列名

        j1_col, j2_col, j3_col : str

            关节角度列名

 

        Returns

        -------

        pd.DataFrame

        """

        if not self.filepath.exists():

            raise FileNotFoundError(f"文件不存在: {self.filepath}")

 

        self._raw_df = pd.read_csv(

            self.filepath,

            encoding=self.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] = target

                        break

 

        if rename_map:

            self._raw_df = self._raw_df.rename(columns=rename_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

 

</details>

 

<details>

 

<summary></summary>

 

"""轨迹数据清洗与平滑"""

 

import numpy as np

import pandas as pd

from scipy.signal import savgol_filter

from typing import Optional, Tuple

 

 

class DataCleaner:

    """

    机器人轨迹数据清洗器

 

    去重、插值、Savitzky-Golay 平滑。

    """

 

    def __init__(self,

                 window_length: int = 11,

                 polyorder: int = 3):

        self.window_length = window_length

        self.polyorder = polyorder

 

    def 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.DataFrame

        """

        result = df.copy()

 

        # 时间戳处理

        if timestamp_col in result.columns:

            result[timestamp_col] = pd.to_datetime(

                result[timestamp_col], errors="coerce"

            )

            result = result.dropna(subset=[timestamp_col])

            result = result.sort_values(timestamp_col).reset_index(drop=True)

            # 去重

            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], errors="coerce")

 

        # 删除坐标全为空

        coord_cols = [c for c in [x_col, y_col, z_col] if c in result.columns]

        result = result.dropna(subset=coord_cols, how="all")

 

        return result.reset_index(drop=True)

 

    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.DataFrame

        """

        result = df.copy()

 

        # 确保窗口长度为奇数

        n = len(result)

        window = min(self.window_length, n - 1)

        if window % 2 == 0:

            window -= 1

        if window < 3:

            return result

 

        for 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

 

</details>

 

<details>

 

<summary></summary>

 

"""速度计算"""

 

import numpy as np

import pandas as pd

from scipy.gradient import gradient

from typing import Dict, Optional

 

 

class VelocityCalculator:

    """

    机器人轨迹速度计算器

 

    基于位移/时间差计算速度,使用 scipy.gradient 计算平滑速度。

    """

 

    def __init__(self):

        pass

 

    def 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") // 1e9

        dt = np.diff(timestamps.astype(float))

        dt = np.append(dt, dt[-1]) # 最后一个点复制前一个 dt

 

        # 位移

        dx = np.diff(result[x_col].values, prepend=result[x_col].values[0])

        dy = np.diff(result[y_col].values, prepend=result[y_col].values[0])

        dz = np.diff(result[z_col].values, prepend=result[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.gradient)

        vel_smooth = np.zeros(n)

        if n > 1:

            vel_smooth = gradient(distances, timestamps)

 

        result["distance_mm"] = distances

        result["velocity_mm_s"] = velocities

        result["velocity_smooth_mm_s"] = vel_smooth

 

        return result

 

    def get_summary(self, df: pd.DataFrame) -> Dict:

        """速度统计摘要"""

        if "velocity_smooth_mm_s" not in df.columns:

            return {}

 

        v = df["velocity_smooth_mm_s"].values

        v = 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)),

        }

 

</details>

 

<details>

 

<summary></summary>

 

"""速度突变检测"""

 

import numpy as np

import pandas as pd

from sklearn.ensemble import IsolationForest

from typing import Dict, Optional

 

 

class JerkDetector:

    """

    机器人轨迹速度突变检测器

 

    基于加速度阈值和 Isolation Forest 异常检测。

    """

 

    def __init__(self,

                 jerk_threshold: float = 1000.0, # mm/s^3

                 contamination: float = 0.05):

        self.jerk_threshold = jerk_threshold

        self.contamination = contamination

 

    def detect(self,

               df: pd.DataFrame,

               timestamp_col: str = "timestamp",

               velocity_col: str = "velocity_smooth_mm_s") -> pd.DataFrame:

        """

        检测速度突变点

 

        Parameters

        ----------

        df : pd.DataFrame

            含速度数据的 DataFrame

        timestamp_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") // 1e9

        dt = np.diff(timestamps.astype(float))

        dt = np.append(dt, dt[-1])

 

        # 加速度(数值微分)

        velocities = result[velocity_col].values

        acceleration = 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,

            ])

            # 去除 NaN

            mask = ~np.isnan(features).any(axis=1)

            if mask.sum() > 5:

                clf = IsolationForest(

                    contamination=self.contamination,

                    random_state=42,

                )

                pred = clf.fit_predict(features[mask])

                is_anomaly = np.zeros(n, dtype=bool)

                is_anomaly[mask] = pred == -1

                result["is_jerk_ml"] = is_anomaly

            else:

                result["is_jerk_ml"] = False

        else:

            result["is_jerk_ml"] = False

 

        return result

 

    def 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),

        }

 

</details>

 

<details>

 

<summary></summary>

 

"""轨迹数据可视化"""

 

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

import networkx as nx

from pathlib import Path

from typing import Optional

 

plt.rcParams["font.sans-serif"] = ["SimHei", "DejaVu Sans"]

plt.rcParams["axes.unicode_minus"] = False

 

 

class TrajectoryVisualizer:

    """机器人轨迹可视化"""

 

    def __init__(self, results_dir: str = "results"):

        self.results_dir = Path(results_dir)

        self.results_dir.mkdir(exist_ok=True)

 

    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, projection="3d")

 

        x = df[x_col].values

        y = df[y_col].values

        z = df[z_col].values

        v = df[velocity_col].values

 

        # 颜色映射

        scatter = ax.scatter(x, y, z, c=v, cmap="viridis", s=10, alpha=0.8)

 

        ax.set_xlabel("X (mm)")

        ax.set_ylabel("Y (mm)")

        ax.set_zlabel("Z (mm)")

        ax.set_title(title, fontsize=14, fontweight="bold")

 

        cbar = plt.colorbar(scatter, ax=ax, shrink=0.6)

        cbar.set_label("速度 (mm/s)")

 

        plt.tight_layout()

        plt.savefig(self.results_dir / "trajectory_3d.png",

                     dpi=150, bbox_inches="tight")

        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].values

        velocities = df[velocity_col].values

 

        # 速度曲线

        ax.plot(timestamps, velocities, linewidth=0.8, color="#3498DB",

                alpha=0.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", s=30,

                        zorder=5, label="突变点")

 

        ax.set_xlabel("时间", fontsize=12)

        ax.set_ylabel("速度 (mm/s)", fontsize=12)

        ax.set_title(title, fontsize=14, fontweight="bold")

        ax.legend()

        ax.grid(alpha=0.3)

        plt.xticks(rotation=45, ha="right")

 

        plt.tight_layout()

        plt.savefig(self.results_dir / "velocity_profile.png",

                     dpi=150, bbox_inches="tight")

        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].values

        accel = accel[~np.isnan(accel) & (accel != 0)]

 

        ax.hist(accel, bins=50, color="#3498DB", edgecolor="white",

                alpha=0.85)

        ax.axvline(x=0, color="#E74C3C", linestyle="-", linewidth=1, alpha=0.5)

 

        ax.set_xlabel("加速度 (mm/s²)", fontsize=12)

        ax.set_ylabel("频次", fontsize=12)

        ax.set_title(title, fontsize=14, fontweight="bold")

        ax.grid(alpha=0.3)

 

        plt.tight_layout()

        plt.savefig(self.results_dir / "acceleration_hist.png",

                     dpi=150, bbox_inches="tight")

        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:

            return

 

        G = 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解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐