欢迎光临
我们一直在努力

python的先进制造技术工业场景模拟第十八篇:读取机器人工装抓取力测试数据,分析抓取力随工件重量的变化规律。

周二上午,机器人工作站调试现场。

"又掉了,"机器人工程师小赵盯着地上的铝合金壳体,"真空吸盘吸起来,转到第三个工位,啪——掉下去了。表面划伤,报废。"

我捡起壳体看了看,边缘有一道新鲜的磕痕。

"吸盘真空度多少?"我问。

"负压 65kPa,额定值,"小赵指着 HMI 屏幕,"但这是空抓测试的值。实际抓 2.5kg 的工件时,加速度一拉,有效吸附力只剩一半不到。系统里只记录了'抓取成功/失败'的布尔值,不记录实际抓取力随重量怎么变的。"

"你们怎么标定抓取力窗口的?"

"没有标定过程,"小赵摇头,"手册上说吸盘能吸 5kg,我们就认为 5kg 以内都安全。但实际不是——吸盘老化、工件表面有油、加速度方向变化,都会影响有效抓取力。我们现在的做法是:先跑几个,掉了就降速度,不掉了就认为'安全了'。但这是碰运气,不是工程。"

"有没有做过抓取力随重量变化的测试?"

"做过,"小赵调出一份 Excel,"但只有 20 组数据,散点图是我用 Excel 手画的。没有拟合曲线,没有置信区间,没有失效边界。工艺员问'3kg 的工件安全余量多少',我答不上来——因为我只有'成功/失败'的二值记录,没有连续力-重量曲线。"

"所以你需要的是抓取力-工件重量的连续映射模型?"

"对,"小赵眼睛亮了,"我需要:对每种工件重量,测 10 次抓取力,画散点图,拟合一条'重量→抓取力'的回归线,标出安全边界(比如抓取力必须 ≥ 工件重量 × 2 的安全系数)。然后工艺员输入工件重量,程序直接告诉他:推荐吸盘型号、最低真空度、最大允许加速度。不是'试一试会不会掉',是'数据告诉我安全余量有多少'。"

"明白了,"我打开 VS Code,"你需要一个程序:读取工装抓取力测试数据(工件重量、实测抓取力、测试次数、是否滑移),用线性回归拟合抓取力-重量关系,用 scipy 计算置信区间和失效概率,用 matplotlib 画散点+拟合线+安全边界,用 scikit-learn 做力预测。OOP 封装,数据自包含。"

我敲了几行代码:

import pandas as pd

import numpy as np

from scipy import stats

 

df = pd.read_csv("gripper_test.csv")

# 线性回归:抓取力 ~ 工件重量

slope, intercept, r_value, p_value, std_err = stats.linregress(

    df["weight_kg"], df["grip_force_n"]

)

# 安全边界:抓取力 ≥ 重量 × 9.81 × 安全系数

df["min_safe_force"] = df["weight_kg"] * 9.81 * 2.0

 

"这只是核心逻辑,"我说,"完整版会用 OOP 封装:一个类管数据加载(解析重量、抓取力、真空度、是否滑移),一个类管数据清洗(异常值检测、重复测试聚合),一个类管回归分析(线性/多项式拟合、置信区间、R²),一个类管安全边界计算(安全系数 × 重量 × g),一个类管可视化(散点图+拟合线+置信带+安全边界线)。数据自包含——用 numpy 合成一份含 150 组测试数据的抓取力记录,模拟不同重量下的力衰减规律,读者下载就能跑。"

小赵凑近屏幕:"所以你这东西,就是告诉我:1kg 的工件需要至少多少牛的抓取力、2.5kg 的需要多少、安全余量在哪里画条线——超过这条线就有滑移风险。下次换工件,输入重量就知道该调什么参数。"

"对。而且还能做失效预测,"我补充,"用 scikit-learn 的 SVR 或多项式回归,捕捉非线性衰减——轻工件时抓取力随重量线性增长,重工件时吸盘接近饱和,增长变缓。用 scipy 的 t-test 检验不同吸盘型号的差异显著性。机器人抓取的力控不是'吸上看掉不掉',是'用回归曲线量化安全边界,让每一次抓取都在置信区间内'。"

 

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

 

场景设定:工业机器人末端执行器(真空吸盘/气动夹爪/电动夹爪)在抓取不同重量工件时,有效抓取力随工件重量、加速度、吸盘老化程度呈现非线性衰减。实际调试中,工程师仅依靠"抓取成功/失败"的二值反馈和吸盘额定参数表,缺乏系统化的"工件重量→抓取力"连续测试与回归建模,导致参数选择靠试错、安全事故频发。

 

现场原话(叙事化):

 

"我不是不知道要测抓取力,"小赵说,"但测了又怎样?我拿个测力计,挂上 1kg 的砝码,读个数;换 2kg 的,再读一个。20 组数据手动画条线,Excel 里 R² 才 0.7 多——因为每次测试条件不完全一样:吸盘接触面积不同、表面粗糙度不同、真空建立时间不同。我想知道的是:在 95% 置信度下,2.5kg 工件的抓取力下限是多少?系统应该给我一个'安全窗口',不是给我一个'平均值'。但现成的力控软件不提供这个——它只给你实时监控曲线,不给你历史回归模型。"

核心矛盾:"离散的抓取成功/失败记录"与"连续的重量-力映射回归模型"之间的量化断层。需要一个"机器人工装抓取力-重量变化规律分析程序",用 

"pandas" 管理测试数据,用 

"numpy" 计算统计量,用 

"scipy" 做线性回归和置信区间,用 

"matplotlib" 绘制散点+拟合+安全边界,用 

"scikit-learn" 做非线性回归预测,输出结构化的安全参数推荐。

 

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

 

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

工业机器人技术基础:末端执行器与力控制 抓取力标定:从离散测试到连续回归模型。

先进制造技术基础:先进制造过程可靠性 抓取可靠性量化:置信区间与安全系数。

智能制造与数字孪生:机器人数字孪生 力-重量映射模型:虚拟标定指导实际调试。

先进制造新模式:零缺陷制造 滑移失效预防:数据驱动的安全边界设定。

 

一句话总结:我们需要构建一个"机器人工装抓取力-重量变化规律分析程序",用 

"scipy" 做线性回归与置信区间,用 

"scikit-learn" 做非线性拟合,用 

"matplotlib" 绘制安全边界图,实现从"试抓看掉不掉"到"量化安全余量"的转化。

 

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

 

3.1 问题本质:把抓取力测试想象成"称体重"

 

把工件重量和抓取力的关系想象成"弹簧秤称东西":

 

* 工件重量 = 你要称的菜:越重,弹簧拉得越长。

* 抓取力 = 弹簧实际能拉住的最大力:理论上重量越重,需要的力越大。

* 但现实不是线性的:轻的时候,吸盘吸得牢,力随重量线性增长;重到一定程度,吸盘快到极限了,再加重工件,抓取力增长变缓——就像弹簧快拉到头了,弹性变差。

* 安全边界 = 保险杠:你买的弹簧秤标称能称 5kg,但实际用到 4kg 以上指针就抖——所以你只在 3kg 以内用。安全边界就是那条"别过这条线"的红线。

* 置信区间 = 天气预报的降水概率:不是说"明天一定下雨",是"有 95% 的概率会下"。抓取力也是——不是每次都一模一样,有波动。置信区间告诉你:在最差情况下,力也不会低于多少。

 

工业应用:

 

* scipy.stats.linregress:一行代码算出斜率、截距、R²,告诉你"重量每增加 1kg,抓取力增加多少牛"。

* numpy.polyfit:多项式拟合捕捉非线性衰减——轻工件线性区用一次拟合,重工件饱和区用二次拟合。

* matplotlib fill_between:画置信带,让安全边界一目了然。

 

3.2 业务逻辑 → 代码映射

 

定义抓取力测试数据模型

   │

   ▼ GripDataLoader (pandas)

导入测试记录 CSV:

  pd.read_csv("gripper_test.csv")

  解析:工件重量、实测抓取力、真空度、是否滑移

   │

   ▼ DataCleaner (pandas)

数据清洗:

  去除滑移样本(标记但不删除)

  异常值检测(3σ 法则)

  按重量分组聚合(每组 10 次测试的均值和标准差)

   │

   ▼ ForceRegression (scipy/numpy)

回归分析:

  scipy.stats.linregress → 线性模型

  numpy.polyfit(degree=2) → 二次多项式

  计算 R²、RMSE

   │

   ▼ SafetyBoundary (numpy)

安全边界计算:

  最小安全力 = 重量 × 重力加速度 × 安全系数

  对比实测力 vs 安全边界 → 余量

   │

   ▼ GripVisualizer (matplotlib)

可视化:

  1. 散点图 + 线性拟合线 + 置信带

  2. 多项式拟合对比

  3. 安全边界线叠加

  4. 滑移点标注

   │

   ▼ ForcePredictor (scikit-learn)

抓取力预测:

  SVR / PolynomialFeatures + LinearRegression

  输入重量 → 输出预测抓取力 + 置信区间

   │

   ▼ SyntheticDataGenerator (numpy)

合成数据生成:

  模拟 150 组测试(6 个重量档 × 25 次重复)

  加入随机噪声和饱和效应

 

3.3 为什么用回归而不是简单平均?

 

* 问题:每个重量测 10 次,取平均只能告诉你"这个重量下平均多少力",不能告诉你"重量从 1kg 变到 5kg,力怎么变"。

* 处理策略:回归模型给出连续函数 

"F(weight)",任意重量都能插值预测。

* 工程合理性:实际生产中工件重量连续变化(2.3kg、2.7kg),不可能每个值都实测。

 

3.4 分析前后对比

 

维度 试抓试错 Python 回归分析

参数选择 "掉了就降速" 重量→力曲线量化安全余量

安全评估 二值(成功/失败) 连续置信区间 + 安全系数

预测能力 无 输入重量输出预测力

文档化 口头经验 回归方程 + R² + 报告

 

四、OOP 代码实现

 

4.1 项目结构

 

robot_grip_force/

├── robot_grip_force/

│ ├── __init__.py

│ ├── data_loader.py # 抓取力数据加载

│ ├── data_cleaner.py # 数据清洗

│ ├── force_regression.py # 回归分析

│ ├── safety_boundary.py # 安全边界计算

│ ├── visualizer.py # 可视化

│ ├── force_predictor.py # 抓取力预测

│ └── synthetic_data.py # 合成数据生成

├── tests/

│ ├── __init__.py

│ └── test_grip_force.py # 单元测试

├── results/

│ ├── scatter_fit.png # 散点+拟合+置信带

│ ├── polynomial_compare.png # 多项式对比

│ ├── safety_boundary.png # 安全边界图

│ ├── slip_points.png # 滑移点标注

│ ├── regression_report.txt # 回归分析报告

│ └── grip_test_data.csv # 合成测试数据

└── run_grip_analysis.py # 主程序入口

 

4.2 核心源码

 

<details>

 

<summary></summary>

 

"""机器人工装抓取力测试数据加载器"""

 

import pandas as pd

from pathlib import Path

from typing import Optional

 

 

class GripDataLoader:

    """

    抓取力测试数据加载器

 

    支持 CSV 格式的测试记录。

    """

 

    def __init__(self, filepath: str = "grip_test_data.csv",

                 encoding: str = "utf-8"):

        self.filepath = Path(filepath)

        self.encoding = encoding

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

 

    def load(self) -> pd.DataFrame:

        """

        加载抓取力测试数据

 

        Returns

        ——-

        pd.DataFrame

        """

        if not self.filepath.exists():

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

 

        self._raw_df = pd.read_csv(self.filepath, encoding=self.encoding)

 

        # 列名标准化

        col_aliases = {

            "weight_kg": ["重量", "weight", "mass", "weight_kg"],

            "grip_force_n": ["抓取力", "force", "grip_force", "grip_force_n", "measured_force"],

            "vacuum_kpa": ["真空度", "vacuum", "vacuum_kpa", "pressure"],

            "is_slip": ["滑移", "slip", "is_slip", "slipped", "failed"],

            "test_id": ["测试ID", "test_id", "id", "no"],

            "gripper_type": ["吸盘类型", "gripper", "gripper_type", "end_effector"],

        }

 

        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 typing import Dict, Optional, Tuple

 

 

class DataCleaner:

    """

    机器人工装抓取力数据清洗器

 

    异常值检测、滑移标记、按重量分组聚合。

    """

 

    def __init__(self, sigma_threshold: float = 3.0):

        self.sigma_threshold = sigma_threshold

 

    def clean(self,

              df: pd.DataFrame,

              weight_col: str = "weight_kg",

              force_col: str = "grip_force_n",

              slip_col: str = "is_slip") -> pd.DataFrame:

        """

        清洗抓取力数据

 

        Parameters

        ———-

        df : pd.DataFrame

            原始数据

        weight_col : str

            重量列名

        force_col : str

            抓取力列名

        slip_col : str

            滑移标记列名

 

        Returns

        ——-

        pd.DataFrame

        """

        result = df.copy()

 

        # 数值化

        result[weight_col] = pd.to_numeric(result[weight_col], errors="coerce")

        result[force_col] = pd.to_numeric(result[force_col], errors="coerce")

 

        # 滑移标记标准化

        if slip_col in result.columns:

            result[slip_col] = result[slip_col].map(

                lambda x: 1 if str(x).lower() in ("1", "true", "yes", "是", "y", "slip", "failed")

                else 0

            )

 

        # 过滤无效值

        mask = result[weight_col].notna() & result[force_col].notna()

        mask &= (result[weight_col] > 0) & (result[force_col] > 0)

        result = result[mask].copy()

 

        # 3σ 异常值检测(按重量分组)

        result["is_outlier"] = 0

        for weight in result[weight_col].unique():

            group_mask = result[weight_col] == weight

            group_forces = result.loc[group_mask, force_col]

            if len(group_forces) >= 3:

                mean_f = group_forces.mean()

                std_f = group_forces.std()

                if std_f > 0:

                    outlier_mask = group_mask & (

                        (result[force_col] < mean_f – self.sigma_threshold * std_f) |

                        (result[force_col] > mean_f + self.sigma_threshold * std_f)

                    )

                    result.loc[outlier_mask, "is_outlier"] = 1

 

        return result.reset_index(drop=True)

 

    def aggregate_by_weight(self,

                            df: pd.DataFrame,

                            weight_col: str = "weight_kg",

                            force_col: str = "grip_force_n",

                            slip_col: str = "is_slip") -> pd.DataFrame:

        """

        按重量分组聚合(均值、标准差、滑移率)

 

        Returns

        ——-

        pd.DataFrame

        """

        agg_df = df.groupby(weight_col).agg(

            count=(force_col, "count"),

            mean_force=(force_col, "mean"),

            std_force=(force_col, "std"),

            min_force=(force_col, "min"),

            max_force=(force_col, "max"),

        ).reset_index()

 

        if slip_col in df.columns:

            slip_agg = df.groupby(weight_col)[slip_col].agg(

                ["sum", "count"]

            )

            agg_df["slip_count"] = slip_agg["sum"]

            agg_df["slip_rate"] = (slip_agg["sum"] / slip_agg["count"] * 100).round(2)

        else:

            agg_df["slip_count"] = 0

            agg_df["slip_rate"] = 0.0

 

        # 填充空标准差

        agg_df["std_force"] = agg_df["std_force"].fillna(0)

 

        numeric_cols = agg_df.select_dtypes(include=[np.number]).columns

        agg_df[numeric_cols] = agg_df[numeric_cols].round(3)

 

        return agg_df.sort_values(weight_col).reset_index(drop=True)

 

</details>

 

<details>

 

<summary></summary>

 

"""抓取力回归分析"""

 

import numpy as np

import pandas as pd

from scipy import stats

from typing import Dict, Optional, Tuple

 

 

class ForceRegression:

    """

    机器人工装抓取力回归分析器

 

    线性回归 + 多项式回归 + 置信区间计算。

    """

 

    def __init__(self):

        pass

 

    def linear_regression(self,

                          weights: np.ndarray,

                          forces: np.ndarray) -> Dict:

        """

        线性回归: force = slope × weight + intercept

 

        Returns

        ——-

        dict

            斜率、截距、R²、p值、标准误

        """

        # 去除 NaN

        mask = ~(np.isnan(weights) | np.isnan(forces))

        weights = np.array(weights)[mask]

        forces = np.array(forces)[mask]

 

        if len(weights) < 2:

            return {"error": "样本量不足"}

 

        slope, intercept, r_value, p_value, std_err = stats.linregress(

            weights, forces

        )

 

        # 预测值

        y_pred = slope * weights + intercept

        # RMSE

        rmse = np.sqrt(np.mean((forces – y_pred) ** 2))

        # 调整 R²

        n = len(weights)

        r_squared = r_value ** 2

        adj_r_squared = 1 – (1 – r_squared) * (n – 1) / (n – 2) if n > 2 else r_squared

 

        return {

            "slope": round(slope, 4),

            "intercept": round(intercept, 4),

            "r_squared": round(r_squared, 4),

            "adj_r_squared": round(adj_r_squared, 4),

            "p_value": round(p_value, 6),

            "std_err": round(std_err, 4),

            "rmse": round(rmse, 4),

            "n_samples": n,

        }

 

    def polynomial_fit(self,

                       weights: np.ndarray,

                       forces: np.ndarray,

                       degree: int = 2) -> Dict:

        """

        多项式回归

 

        Returns

        ——-

        dict

            系数、R²

        """

        mask = ~(np.isnan(weights) | np.isnan(forces))

        weights = np.array(weights)[mask]

        forces = np.array(forces)[mask]

 

        if len(weights) <= degree:

            return {"error": "样本量不足"}

 

        coeffs = np.polyfit(weights, forces, degree)

        poly = np.poly1d(coeffs)

 

        y_pred = poly(weights)

        ss_res = np.sum((forces – y_pred) ** 2)

        ss_tot = np.sum((forces – np.mean(forces)) ** 2)

        r_squared = 1 – (ss_res / ss_tot) if ss_tot > 0 else 0

 

        rmse = np.sqrt(np.mean((forces – y_pred) ** 2))

 

        return {

            "coefficients": [round(c, 4) for c in coeffs],

            "degree": degree,

            "r_squared": round(r_squared, 4),

            "rmse": round(rmse, 4),

            "n_samples": len(weights),

        }

 

    def confidence_interval(self,

                            weights: np.ndarray,

                            forces: np.ndarray,

                            confidence: float = 0.95) -> Tuple[np.ndarray, np.ndarray]:

        """

        计算回归线的置信区间

 

        Returns

        ——-

        (lower, upper)

        """

        mask = ~(np.isnan(weights) | np.isnan(forces))

        weights = np.array(weights)[mask]

        forces = np.array(forces)[mask]

 

        if len(weights) < 3:

            return np.array([]), np.array([])

 

        slope, intercept, _, _, std_err = stats.linregress(weights, forces)

 

        # 预测值

        y_pred = slope * weights + intercept

 

        # 标准误

        n = len(weights)

        mean_x = np.mean(weights)

        s_xx = np.sum((weights – mean_x) ** 2)

 

        # t 临界值

        alpha = 1 – confidence

        t_val = stats.t.ppf(1 – alpha / 2, n – 2)

 

        # 置信区间

        se_fit = std_err * np.sqrt(

            1 / n + (weights – mean_x) ** 2 / s_xx

        )

        lower = y_pred – t_val * se_fit

        upper = y_pred + t_val * se_fit

 

        return lower, upper

 

    def predict_linear(self,

                       weights: np.ndarray,

                       forces: np.ndarray,

                       weight_new: float) -> Dict:

        """用线性回归预测新重量下的抓取力"""

        mask = ~(np.isnan(weights) | np.isnan(forces))

        weights = np.array(weights)[mask]

        forces = np.array(forces)[mask]

 

        slope, intercept, _, p_value, std_err = stats.linregress(weights, forces)

 

        y_pred = slope * weight_new + intercept

 

        # 预测区间

        n = len(weights)

        mean_x = np.mean(weights)

        s_xx = np.sum((weights – mean_x) ** 2)

        alpha = 0.05

        t_val = stats.t.ppf(1 – alpha / 2, n – 2)

 

        se_pred = std_err * np.sqrt(

            1 + 1 / n + (weight_new – mean_x) ** 2 / s_xx

        )

        lower = y_pred – t_val * se_pred

        upper = y_pred + t_val * se_pred

 

        return {

            "weight": weight_new,

            "predicted_force": round(y_pred, 2),

            "lower_95": round(lower, 2),

            "upper_95": round(upper, 2),

            "model_slope": round(slope, 4),

            "model_intercept": round(intercept, 4),

        }

 

</details>

 

<details>

 

<summary></summary>

 

"""安全边界计算"""

 

import numpy as np

import pandas as pd

from typing import Dict, Optional

 

 

class SafetyBoundary:

    """

    机器人工装抓取力安全边界计算器

 

    基于安全系数计算最小安全抓取力。

    """

 

    def __init__(self,

                 safety_factor: float = 2.0,

                 g: float = 9.81):

        self.safety_factor = safety_factor

        self.g = g

 

    def min_safe_force(self, weight_kg: float) -> float:

        """

        计算最小安全抓取力 (N)

 

        F_min = weight × g × safety_factor

        """

        return weight_kg * self.g * self.safety_factor

 

    def safety_margin(self,

                      weight_kg: float,

                      measured_force: float) -> Dict:

        """

        计算安全余量

 

        Returns

        ——-

        dict

        """

        min_force = self.min_safe_force(weight_kg)

        margin = measured_force – min_force

        margin_ratio = (margin / min_force * 100) if min_force > 0 else 0

 

        return {

            "weight_kg": weight_kg,

            "measured_force_n": round(measured_force, 2),

            "min_safe_force_n": round(min_force, 2),

            "safety_margin_n": round(margin, 2),

            "safety_margin_pct": round(margin_ratio, 2),

            "is_safe": margin > 0,

        }

 

    def calculate_for_dataframe(self,

                                df: pd.DataFrame,

                                weight_col: str = "weight_kg",

                                force_col: str = "grip_force_n") -> pd.DataFrame:

        """

        为整个数据集计算安全边界

 

        Returns

        ——-

        pd.DataFrame

        """

        result = df.copy()

        result["min_safe_force_n"] = result[weight_col].apply(

            self.min_safe_force

        )

        result["safety_margin_n"] = (

            result[force_col] – result["min_safe_force_n"]

        )

        result["safety_margin_pct"] = (

            result["safety_margin_n"] / result["min_safe_force_n"] * 100

        ).round(2)

        result["is_safe"] = result["safety_margin_n"] > 0

 

        return result

 

    def get_unsafe_summary(self,

                           df: pd.DataFrame) -> Dict:

        """不安全样本汇总"""

        if "is_safe" not in df.columns:

            return {}

 

        unsafe = df[~df["is_safe"]]

        total = len(df)

 

        return {

            "total_samples": total,

            "unsafe_count": len(unsafe),

            "unsafe_rate_pct": round(len(unsafe) / total * 100, 2) if total > 0 else 0,

            "unsafe_by_weight": unsafe.groupby("weight_kg").size().to_dict() if not unsafe.empty else {},

        }

 

</details>

 

<details>

 

<summary></summary>

 

"""抓取力数据可视化"""

 

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

from pathlib import Path

from typing import Optional, Tuple

 

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

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

 

 

class GripVisualizer:

    """机器人工装抓取力可视化"""

 

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

        self.results_dir = Path(results_dir)

        self.results_dir.mkdir(exist_ok=True)

 

    def plot_scatter_with_fit(self,

                               df: pd.DataFrame,

                               weights: np.ndarray,

                               forces: np.ndarray,

                               slope: float,

                               intercept: float,

                               lower_ci: np.ndarray = None,

                               upper_ci: np.ndarray = None,

                               weight_col: str = "weight_kg",

                               force_col: str = "grip_force_n") -> None:

        """散点图 + 线性拟合线 + 置信带"""

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

 

        # 散点(区分滑移)

        if "is_slip" in df.columns:

            safe_mask = df["is_slip"] == 0

            ax.scatter(df.loc[safe_mask, weight_col],

                        df.loc[safe_mask, force_col],

                        c="#3498DB", alpha=0.6, s=40,

                        label="抓取成功", edgecolors="white", linewidth=0.5)

            slip_mask = df["is_slip"] == 1

            if slip_mask.any():

                ax.scatter(df.loc[slip_mask, weight_col],

                            df.loc[slip_mask, force_col],

                            c="#E74C3C", marker="X", s=80,

                            label="发生滑移", edgecolors="darkred", linewidth=1)

        else:

            ax.scatter(df[weight_col], df[force_col],

                        c="#3498DB", alpha=0.6, s=40,

                        edgecolors="white", linewidth=0.5, label="测试点")

 

        # 拟合线

        w_sorted = np.sort(weights)

        fit_line = slope * w_sorted + intercept

        ax.plot(w_sorted, fit_line, color="#E67E22", linewidth=2.5,

                 label=f"线性拟合: F = {slope:.1f}W + {intercept:.1f}")

 

        # 置信带

        if low

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

赞(0)
未经允许不得转载:171主机测评 » python的先进制造技术工业场景模拟第十八篇:读取机器人工装抓取力测试数据,分析抓取力随工件重量的变化规律。
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址