周三下午,机器人焊接工作站。

“老周,这机器人又跑偏了,”调试员小赵揉着眼睛,“离线编程里轨迹漂漂亮亮,导到真机上一跑,焊枪尖离工件差了快3毫米。系统里TCP标定值还是上次校的,谁知道中间夹具动过、基座微偏、甚至机器人自己齿轮间隙都变了。”

我接上导出的机器人D-H参数表、标定记录、离线轨迹点云、实机示教对比点、误差日志。

“这里面有啥?”我问。

“模型里每个关节的α、a、d、θ理论值,标定值,离线生成的焊接轨迹点(几百个),还有实机用激光跟踪仪采的十几个对比点,”小赵说,“可离线软件只显示‘轨迹已生成’,不告诉你标了和没标之间差多少,哪个关节的误差贡献最大,点位偏差在空间里怎么分布的。”

“最亏的是返工,”小赵补一句,“离线程序导进去,试跑发现偏差大,只能回手动示教。一个复杂焊缝几百个点,逐个改坐标,改完下次标定一变又白改。以前靠老师傅‘感觉哪个轴偏了’手动调D-H,调完也不知道改善多少。”

“我就想干一件事,”小赵说,“给一套机器人模型,模拟标定误差——就是往D-H参数里加随机偏移量,然后重新算正向运动学,看每个轨迹点和理论位置的偏差有多大。要能量化:TCP末端最大偏多少、平均偏多少、哪个关节的误差对末端影响最大、误差在空间里是不是有方向性。像个小误差传播仿真器,不用每次都搬激光跟踪仪。”

“机器人离线程序误差不是看‘轨迹漂不漂’,”我接话,“是看‘D-H参数偏移→关节微小误差→正向运动学递推→末端位姿偏差→轨迹点云偏移量→空间分布→主因关节识别’。用 numpy 做齐次变换和误差递推,scipy 做误差分布拟合,pandas 管轨迹点,matplotlib 画偏差矢量场+散点+热力,networkx 建‘关节-误差-末端偏差’关联图,sklearn 做偏差等级分类。”

“对,”小赵点头,“要能说清‘基座标偏移0.5mm时TCP末端最大偏2.3mm,方向沿Y轴正方向;2轴α角误差0.3°贡献了末端偏差的41%;轨迹中段偏差最大,两端小’。”

“OOP 封好,”我开工程,“机器人模型、D-H误差注入器、正向运动学求解器、轨迹偏差计算器、误差分布分析器、偏差分类器、可视化器,合成6轴机器人+轨迹,下载就能跑。”

敲了行原型:

# 目标: D-H标定误差 → 正向运动学 → 末端位姿偏差 → 轨迹点云偏移

# 方法: numpy齐次变换 + scipy分布拟合 + RF分类 + networkx关联

 

小赵凑近看:“那以后看报告:各关节误差贡献柱图,轨迹点偏差散点,偏差矢量场,关节-误差-末端关联图,偏差等级预测。改个标定精度,绿区就是合格区间。”

“对,”我接话,“误差仿真不是‘看轨迹歪没歪’,是‘看见哪个关节在偷偷把枪尖推偏’。数字孪生里挂这个误差看板,就是小赵的‘标定尺’。”

 

一、实际应用场景(真实痛点)

 

场景设定:6轴关节型工业机器人弧焊工作站,离线编程软件生成焊接轨迹(空间点位序列),导入真机前需做模型标定。当前离线软件仅显示"轨迹已生成",不量化标定误差对末端执行器(焊枪TCP)的空间偏差影响。

 

现场原话(叙事化):

 

"不是离线软件不行,"小赵说,"是标定误差看不见。D-H参数标完,基座标、工具标、工件标各有一堆数字,每个都有±误差。可这些误差叠到末端TCP上到底偏多少?软件不告诉你。导到真机上,焊枪尖离焊缝差两三毫米,只能手动拖回去。"

"最亏的是反复标定,"小赵说,"标定一次大半天,改一个关节的补偿值,全轨迹都要重算。以前靠经验——'2轴好像低了,调高一点',调完试跑,还偏就再调。像蒙眼调收音机,拧一下听一下。"

核心矛盾:"离线轨迹生成 + 一次性标定" 与 "D-H参数误差→关节空间→笛卡尔空间→末端位姿偏差→轨迹点云偏移量→空间分布→主因关节识别" 之间的断层。

 

二、痛点分析(映射到滨州职业学院《先进制造技术》课程模型)

 

《先进制造技术》课程模块 本篇痛点对应

工业机器人技术基础:D-H参数、正向/逆向运动学、标定、精度补偿 D-H误差注入→正向运动学递推→末端偏差

先进制造技术基础:几何误差、精度分配、误差传递 误差传播链建模

数控加工与CAD/CAM技术:刀具补偿、坐标系变换 TCP位姿偏差映射

智能制造与数字孪生:机器人数字模型、标定数据驱动 误差看板挂孪生

先进制造新模式:数据驱动精度补偿、自适应标定 按误差贡献排序优先标定

 

一句话总结:我们需要一个"D-H参数误差注入→齐次变换递推→末端位姿偏差→轨迹点云偏移→空间分布分析→偏差等级分类→关节贡献关联图"程序,实现从"标完就导"到"标前先算偏差、标后量化改善"的闭环。

 

三、核心逻辑讲解(大白话)

 

3.1 问题本质:把机器人想成"叠罗汉"

 

把6轴机器人想成6个人叠罗汉:

 

- 每个人代表一个关节,叠在一起最上面的人手里举着焊枪(TCP)

- 每个人的站姿都有微小偏差:脚没踩正(基座标偏)、腰歪一点(连杆扭角α偏)、手伸长不一(连杆长a偏)、站的高低不对(偏置d偏)

- 下面的人歪1°,传到最上面可能偏好几毫米

- 标定就是量每个人的偏差,补偿回去

- 但补偿哪个最划算? 得算每个偏差对最上面那个人位置的影响有多大

 

3.2 业务逻辑 → 代码映射

 

输入: 理论D-H参数 + 标定误差范围 + 离线轨迹点(关节角序列)

   │

   ▼ RobotModel (numpy)

机器人模型:

  6轴D-H表: [α, a, d, θ] × 6

  齐次变换矩阵 T_i = RotX(α)·TransX(a)·TransZ(d)·RotZ(θ)

  正向运动学: T_total = T_1 × T_2 × ... × T_6

  末端位姿 = T_total 的平移分量 + 旋转分量

   │

   ▼ DHErrorInjector (numpy)

误差注入:

  给每个D-H参数加偏移: Δα, Δa, Δd, Δθ ~ 均匀/正态

  可设单参数偏移(敏感性分析) 或 全参数偏移(综合误差)

   │

   ▼ ForwardKinematics (numpy)

正向运动学求解:

  对每组(理论+偏移)D-H参数算 T_total_err

  末端位置 P_err = T_total_err[:3,3]

  理论位置 P_ref = T_total_ref[:3,3]

  偏差 ΔP = P_err - P_ref

  偏差大小 ‖ΔP‖ = sqrt(dx²+dy²+dz²)

   │

   ▼ TrajectoryDeviation (numpy)

轨迹偏差计算:

  对离线轨迹每个点(关节角组)算偏差

  输出: 每点偏差向量 + 偏差大小

   │

   ▼ ErrorDistribution (scipy)

误差分布分析:

  偏差大小分布: 正态/对数正态拟合

  方向分布: 各轴偏差均值/方差

  空间热力: 按工作空间分区统计

   │

   ▼ JointContribution (numpy)

关节贡献分析:

  单参数敏感性: 只偏一个参数, 看末端偏多少

  贡献比 = 单参数偏差 / 全参数偏差

   │

   ▼ DeviationClassifier (sklearn)

偏差等级分类:

  特征: 各关节误差值, 位置, 轨迹段

  标签: 优(‖ΔP‖≤0.5mm)/良(0.5~2mm)/超差(>2mm)

  RF分类 + 5折宏F1

   │

   ▼ RobotViz (matplotlib + networkx)

可视化:

  1. 轨迹点偏差散点(3D或2D投影)

  2. 偏差矢量场(箭头=偏差方向)

  3. 各关节误差贡献柱图

  4. 关节-误差-末端关联图(networkx)

  5. 偏差等级预测vs实际散点

  6. 工作空间偏差热力图

   │

   ▼ SyntheticRobot (numpy)

合成数据:

  6轴机器人理论D-H + 焊接轨迹(50点) + 误差注入

 

3.3 为什么不能"标完就导"

 

视角 问题

离线软件 只显示"轨迹已生成",不量化误差

一次性标定 不知道哪个参数对末端影响最大

手动试调 蒙眼拧收音机

误差传播仿真 看见每个关节偏差对末端的贡献

偏差矢量场 看偏差往哪个方向偏

关节贡献排序 优先标定贡献最大的关节

 

3.4 分析前后对比

 

维度 传统方式 本程序

误差可见性 试跑才发现偏 标前算出偏差分布

标定优先级 全标/凭经验 按贡献排序标定

补偿效果 调完不知改善多少 量化偏差降低量

空间分布 不知道偏哪 矢量场显示方向

知识沉淀 师傅手感 误差传播指纹库

 

四、OOP 代码实现

 

4.1 项目结构

 

robot_calib_error_sim/

├── robot_calib_error_sim/

│ ├── __init__.py

│ ├── robot_model.py # 机器人D-H模型(numpy)

│ ├── dh_error_injector.py # 误差注入(numpy)

│ ├── forward_kinematics.py # 正向运动学(numpy)

│ ├── trajectory_deviation.py # 轨迹偏差计算

│ ├── error_distribution.py # 误差分布分析(scipy)

│ ├── joint_contribution.py # 关节贡献分析

│ ├── deviation_classifier.py # 偏差等级分类(sklearn)

│ ├── robot_viz.py # 可视化

│ └── synthetic_robot.py # 合成机器人+轨迹

├── tests/

│ ├── __init__.py

│ └── test_robot_calib.py

├── results/

│ ├── deviation_scatter.png

│ ├── deviation_vector.png

│ ├── joint_contribution_bar.png

│ ├── joint_error_tcp_graph.png

│ ├── deviation_grade_scatter.png

│ ├── workspace_heatmap.png

│ ├── trajectory_detail.csv

│ └── calib_report.txt

└── run_robot_calib.py

 

4.2 核心源码

 

<details><summary></summary>

 

"""6轴机器人D-H模型 (numpy)。"""

 

import numpy as np

from dataclasses import dataclass, field

from typing import List, Optional

 

 

@dataclass

class DHParam:

    """单关节D-H参数。"""

    alpha: float # 连杆扭角 (rad)

    a: float # 连杆长度 (mm)

    d: float # 关节偏置 (mm)

    theta: float # 关节角 (rad)

    joint_type: str = "revolute" # revolute/prismatic

 

 

@dataclass

class RobotModel:

    """6轴关节型机器人。"""

    dh_params: List[DHParam]

    name: str = "6DOF_Welding"

 

    def __post_init__(self):

        assert len(self.dh_params) == 6, "需要6个关节D-H参数"

 

    def get_dh_array(self) -> np.ndarray:

        """返回 (6,4) 数组: [alpha, a, d, theta]。"""

        arr = np.zeros((6, 4))

        for i, p in enumerate(self.dh_params):

            arr[i] = [p.alpha, p.a, p.d, p.theta]

        return arr

 

</details>

 

<details><summary></summary>

 

"""D-H误差注入器 (numpy)。"""

 

import numpy as np

from dataclasses import dataclass

from typing import Tuple, Optional

from .robot_model import RobotModel, DHParam

 

 

@dataclass

class DHErrors:

    """D-H参数误差集合。"""

    delta_alpha: np.ndarray # (6,) rad

    delta_a: np.ndarray # (6,) mm

    delta_d: np.ndarray # (6,) mm

    delta_theta: np.ndarray # (6,) rad

 

 

class DHErrorInjector:

    """

    给理论D-H参数注入误差。

    支持: 全随机注入 / 单参数偏移 / 指定误差向量

    """

 

    def __init__(self, seed: int = 42):

        self.seed = seed

        self.rng = np.random.RandomState(seed)

 

    def inject_random(self, robot: RobotModel,

                     bounds: Optional[dict] = None) -> Tuple[RobotModel, DHErrors]:

        """

        随机注入误差。

        bounds: {param: (min, max)} 默认合理范围

        """

        if bounds is None:

            bounds = {

                "alpha": (-0.005, 0.005), # ±0.005 rad ≈ ±0.3°

                "a": (-1.0, 1.0), # ±1mm

                "d": (-1.0, 1.0), # ±1mm

                "theta": (-0.003, 0.003), # ±0.003 rad ≈ ±0.17°

            }

        d_alpha = self.rng.uniform(*bounds["alpha"], 6)

        d_a = self.rng.uniform(*bounds["a"], 6)

        d_d = self.rng.uniform(*bounds["d"], 6)

        d_theta = self.rng.uniform(*bounds["theta"], 6)

 

        err = DHErrors(d_alpha, d_a, d_d, d_theta)

        err_robot = self._apply_errors(robot, err)

        return err_robot, err

 

    def inject_single_joint(self, robot: RobotModel,

                           joint_idx: int, param: str,

                           delta: float) -> RobotModel:

        """单关节单参数偏移(敏感性分析)。"""

        err = DHErrors(

            delta_alpha=np.zeros(6),

            delta_a=np.zeros(6),

            delta_d=np.zeros(6),

            delta_theta=np.zeros(6),

        )

        if param == "alpha":

            err.delta_alpha[joint_idx] = delta

        elif param == "a":

            err.delta_a[joint_idx] = delta

        elif param == "d":

            err.delta_d[joint_idx] = delta

        elif param == "theta":

            err.delta_theta[joint_idx] = delta

        return self._apply_errors(robot, err), err

 

    def _apply_errors(self, robot: RobotModel, err: DHErrors) -> RobotModel:

        new_params = []

        for i, p in enumerate(robot.dh_params):

            new_params.append(DHParam(

                alpha=p.alpha + err.delta_alpha[i],

                a=p.a + err.delta_a[i],

                d=p.d + err.delta_d[i],

                theta=p.theta + err.delta_theta[i],

                joint_type=p.joint_type,

            ))

        return RobotModel(new_params, robot.name)

 

</details>

 

<details><summary></summary>

 

"""正向运动学求解器 (numpy)。"""

 

import numpy as np

from typing import Tuple

from .robot_model import RobotModel

 

 

class ForwardKinematics:

    """

    基于D-H参数的正向运动学。

    T_i = RotX(α) · TransX(a) · TransZ(d) · RotZ(θ)

    """

 

    @staticmethod

    def dh_matrix(alpha: float, a: float, d: float, theta: float) -> np.ndarray:

        """单关节齐次变换矩阵。"""

        ct = np.cos(theta)

        st = np.sin(theta)

        ca = np.cos(alpha)

        sa = np.sin(alpha)

        T = np.array([

            [ct, -st*ca, st*sa, a*ct],

            [st, ct*ca, -ct*sa, a*st],

            [0, sa, ca, d],

            [0, 0, 0, 1],

        ], dtype=float)

        return T

 

    def solve(self, robot: RobotModel,

             joint_angles: np.ndarray) -> np.ndarray:

        """

        计算末端位姿。

        joint_angles: (6,) 关节角(rad)

        返回: 4x4 齐次变换矩阵

        """

        T = np.eye(4)

        for i, p in enumerate(robot.dh_params):

            theta = p.theta + joint_angles[i] if p.joint_type == "revolute" else p.theta

            Ti = self.dh_matrix(p.alpha, p.a, p.d, theta)

            T = T @ Ti

        return T

 

    def get_position(self, robot: RobotModel,

                    joint_angles: np.ndarray) -> np.ndarray:

        """返回末端位置 (3,)。"""

        T = self.solve(robot, joint_angles)

        return T[:3, 3]

 

    def get_pose(self, robot: RobotModel,

                joint_angles: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:

        """返回 (position (3,), rotation_matrix (3,3))。"""

        T = self.solve(robot, joint_angles)

        return T[:3, 3], T[:3, :3]

 

</details>

 

<details><summary></summary>

 

"""轨迹偏差计算器 (numpy)。"""

 

import numpy as np

import pandas as pd

from dataclasses import dataclass

from typing import List

from .robot_model import RobotModel

from .forward_kinematics import ForwardKinematics

 

 

@dataclass

class PointDeviation:

    point_idx: int

    joint_angles: np.ndarray

    ref_position: np.ndarray

    err_position: np.ndarray

    deviation_vector: np.ndarray

    deviation_magnitude: float

 

 

class TrajectoryDeviation:

    """

    对离线轨迹每个点计算偏差。

    """

 

    def __init__(self, fk: ForwardKinematics):

        self.fk = fk

 

    def compute(self, ref_robot: RobotModel, err_robot: RobotModel,

               trajectory: np.ndarray) -> List[PointDeviation]:

        """

        trajectory: (N, 6) 关节角序列

        """

        results = []

        for i in range(len(trajectory)):

            angles = trajectory[i]

            p_ref = self.fk.get_position(ref_robot, angles)

            p_err = self.fk.get_position(err_robot, angles)

            dev_vec = p_err - p_ref

            dev_mag = np.linalg.norm(dev_vec)

            results.append(PointDeviation(

                i, angles, p_ref, p_err, dev_vec, dev_mag

            ))

        return results

 

    def to_dataframe(self, deviations: List[PointDeviation]) -> pd.DataFrame:

        rows = []

        for d in deviations:

            rows.append({

                "point_idx": d.point_idx,

                "ref_x": d.ref_position[0],

                "ref_y": d.ref_position[1],

                "ref_z": d.ref_position[2],

                "err_x": d.err_position[0],

                "err_y": d.err_position[1],

                "err_z": d.err_position[2],

                "dev_dx": d.deviation_vector[0],

                "dev_dy": d.deviation_vector[1],

                "dev_dz": d.deviation_vector[2],

                "dev_magnitude": d.deviation_magnitude,

            })

        return pd.DataFrame(rows)

 

</details>

 

<details><summary></summary>

 

"""误差分布分析 (scipy)。"""

 

import numpy as np

import pandas as pd

from scipy.stats import norm, lognorm

from typing import Dict

 

 

class ErrorDistribution:

    def analyze(self, deviations_df: pd.DataFrame) -> Dict:

        """分析偏差大小分布。"""

        mags = deviations_df["dev_magnitude"].values

        dx = deviations_df["dev_dx"].values

        dy = deviations_df["dev_dy"].values

        dz = deviations_df["dev_dz"].values

 

        result = {

            "count": len(mags),

            "mean_mag": float(np.mean(mags)),

            "max_mag": float(np.max(mags)),

            "min_mag": float(np.min(mags)),

            "std_mag": float(np.std(mags)),

            "p95_mag": float(np.percentile(mags, 95)),

            "mean_dx": float(np.mean(dx)),

            "mean_dy": float(np.mean(dy)),

            "mean_dz": float(np.mean(dz)),

            "std_dx": float(np.std(dx)),

            "std_dy": float(np.std(dy)),

            "std_dz": float(np.std(dz)),

        }

 

        # 正态拟合

        try:

            params = norm.fit(mags)

            result["norm_fit"] = {"mu": params[0], "sigma": params[1]}

        except Exception:

            result["norm_fit"] = None

 

        # 方向主导轴

        dir_vars = {"X": result["std_dx"], "Y": result["std_dy"], "Z": result["std_dz"]}

        result["dominant_axis"] = max(dir_vars, key=dir_vars.get)

        result["direction_bias"] = {

            "X": result["mean_dx"],

            "Y": result["mean_dy"],

            "Z": result["mean_dz"],

        }

 

        return result

 

</details>

 

<details><summary></summary>

 

"""关节贡献分析器。"""

 

import numpy as np

import pandas as pd

from .robot_model import RobotModel

from .dh_error_injector import DHErrorInjector

from .forward_kinematics import ForwardKinematics

from .trajectory_deviation import TrajectoryDeviation

 

 

class JointContribution:

    """

    单参数敏感性分析 → 各关节各参数对末端偏差的贡献比。

    """

 

    def __init__(self):

        self.fk = ForwardKinematics()

        self.injector = DHErrorInjector(seed=42)

 

    def analyze(self, ref_robot: RobotModel,

               trajectory: np.ndarray,

               base_errors: object) -> pd.DataFrame:

        """

        对全参数误差下的每个关节每个参数做敏感性分析。

        base_errors: DHErrors对象(全参数偏移)

        """

        # 全参数偏差基准

        err_robot, _ = self.injector._apply_errors_from_dherrors(

            ref_robot, base_errors)

        td = TrajectoryDeviation(self.fk)

        base_devs = td.compute(ref_robot, err_robot, trajectory)

        base_mean_mag = np.mean([d.deviation_magnitude for d in base_devs])

 

        rows = []

        params = ["alpha", "a", "d", "theta"]

        for j in range(6):

            for p in params:

                # 单参数偏移(用base_errors中该参数的实际偏移量)

                delta = getattr(base_errors, f"delta_{p}")[j]

                if abs(delta) < 1e-10:

                    contribution = 0.0

                else:

                    single_err_robot, _ = self.injector.inject_single_joint(

                        ref_robot, j, p, delta)

                    single_devs = td.compute(ref_robot, single_err_robot, trajectory)

                    single_mean = np.mean([d.deviation_magnitude for d in single_devs])

                    contribution = single_mean / (base_mean_mag + 1e-10)

                rows.append({

                    "joint": j + 1,

                    "param": p,

                    "delta": delta,

                    "contribution": contribution,

                })

 

        return pd.DataFrame(rows)

 

</details>

 

<details><summary></summary>

 

"""偏差等级分类 (sklearn)。"""

 

import numpy as np

import pandas as pd

from typing import Dict

from sklearn.ensemble import RandomForestClassifier

from sklearn.model_selection import cross_val_score, KFold

 

 

class DeviationClassifier:

    """优(≤0.5mm)/良(0.5~2mm)/超差(>2mm)。"""

 

    def __init__(self, random_state: int = 42):

        self.model_ = None

        self.feat = ["dev_dx", "dev_dy", "dev_dz", "dev_magnitude"]

 

    @staticmethod

    def _label(mag):

        if mag <= 0.5:

            return "优"

        if mag <= 2.0:

            return "良"

        return "超差"

 

    def fit(self, df: pd.DataFrame):

        y = df["dev_magnitude"].apply(self._label).values

        self.model_ = RandomForestClassifier(

            n_estimators=300, max_depth=5, min_samples_leaf=1,

            random_state=42, n_jobs=-1)

        self.model_.fit(df[self.feat].values, y)

        return self

 

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

        y = df["dev_magnitude"].apply(self._label).values

        kf = KFold(5, shuffle=True, random_state=42)

        sc = cross_val_score(self.model_, df[self.feat].values, y,

                           cv=kf, scoring="f1_macro")

        imp = dict(zip(self.feat, self.model_.feature_importances_))

        return {"f1_macro": float(sc.mean()),

               "importance": dict(sorted(imp.items(),

                                        key=lambda x:x[1], reverse=True))}

 

    def predict(self, df: pd.DataFrame) -> np.ndarray:

        return self.model_.predict(df[self.feat].values)

 

</details>

 

<details><summary></summary>

 

"""机器人误差可视化 (matplotlib + networkx)。"""

 

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

from pathlib import Path

import networkx as nx

 

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

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

 

 

class RobotViz:

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

        self.results_dir = Path(results_dir)

        self.results_dir.mkdir(exist_ok=True)

 

    def deviation_scatter(self, df):

        fig, axes = plt.subplots(1, 3, figsize=(15, 4))

        for ax, col, title in zip(axes, ["dev_dx","dev_dy","dev_dz"],

                                  ["ΔX (mm)","ΔY (mm)","ΔZ (mm)"]):

            ax.scatter(df.point_idx, df[col], c=df["dev_magnitude"],

                      cmap="YlOrRd", s=30, edgecolors="k", alpha=0.8)

            ax.set_xlabel("轨迹点序号")

            ax.set_ylabel(title)

            ax.set_title(f"偏差分量 {title}")

            ax.grid(alpha=0.3)

        plt.tight_layout()

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

                   dpi=150, bbox_inches="tight")

        plt.close()

 

    def deviation_vector(self, df, sample_n=20):

        fig, ax = plt.subplots(figsize=(8, 8))

        # 取末端位置均值作原点

        origin = df[["ref_x","ref_y","ref_z"]].mean().values

        sampled = df.iloc[::max(1,len(df)//sample_n)]

        for _, row in sampled.iterrows():

            start = row[["ref_x","ref_y"]].values

            dx = row["dev_dx"] * 5 # 放大显示

            dy = row["dev_dy"] * 5

            ax.quiver(start[0], start[1], dx, dy,

                     angles="xy", scale_units="xy", scale=1,

                     color="#E74C3C", alpha=0.7, width=0.003)

        ax.set_xlabel("X (mm)", fontsize=12)

        ax.set_ylabel("Y (mm)", fontsize=12)

        ax.set_title("偏差矢量场(俯视XY, 箭头放大5×)",

                    fontsize=13, fontweight="bold")

        ax.grid(alpha=0.3)

        plt.tight_layout()

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

                   dpi=150, bbox_inches="tight")

        plt.close()

 

    def joint_contribution_bar(self, contrib_df):

        fig, ax = plt.subplots(figsize=(12, 5))

        # 按关节汇总

        joint_sum = contrib_df.groupby("joint")["contribution"].sum().reset_index()

        bars = ax.bar(joint_sum.joint.astype(str), joint_sum.contribution,

                     color="#2980B9", edgecolor="k", alpha=0.85)

        # 标注最大贡献参数

        for j in joint_sum.joint:

            sub = contrib_df[contrib_df.joint == j]

            max_row = sub.loc[sub.contribution.idxmax()]

            ax.text(j-1, sub.contribution.sum()+0.02,

                   f"{max_row.param}", ha="center", fontsize=8, color="#E74C3C")

        ax.set_xlabel("关节号", fontsize=12)

        ax.set_ylabel("总贡献比", fontsize=12)

        ax.set_title("各关节误差对末端偏差的贡献",

                    fontsize=13, fontweight="bold")

        ax.grid(alpha=0.3, axis="y")

        plt.tight_layout()

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

                   dpi=150, bbox_inches="tight")

        plt.close()

 

    def joint_error_tcp_graph(self):

        fig, ax = plt.subplots(figsize=(10, 6))

        G = nx.DiGraph()

        for j in range(1, 7):

            G.add_node(f"J{j}", bipartite="joint")

        G.add_node("TCP_X"); G.add_node("TCP_Y"); G.add_node("TCP_Z")

        G.add_node("末端偏差")

        for j in range(1, 7):

            G.add_edge(f"J{j}", "末端偏差", weight=0.3)

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

    

Logo

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

更多推荐