☰
python的机器人基础原理工业场景模拟第九十一篇:装配机器人,给定关节角度求解雅可比矩阵之后,求取雅可比伪逆,用于笛卡尔速度求解。
2026/10/23 12:25:36 网站建设 项目流程

《Python机器人基础原理》第九十一篇(修正版):装配机器人——基于雅可比伪逆的笛卡尔速度求解

声明:本篇全部为课程离线仿真代码,不能直接用于真实机器人控制器,规避工程误导风险。

1. 实际工业应用场景

周五下午,消费电子组装车间。一台3-DOF桌面级平面装配机器人(臂展R=0.65 m,额定负载3.0 kg,单轴最大角速度ω_max=3.0 rad/s,重复定位精度±0.02 mm)正在做精密插装任务——将USB Type-C插头对准插座,允许偏差±0.1 mm。

调试工程师小陈盯着示教器上的笛卡尔速度指令面板:"我现在给每个关节发速度指令

"[0.1, -0.05, 0.08] rad/s",末端在XY平面上走的轨迹根本不是直线。插装要求末端沿X方向匀速平移,速度精确控制在

"[0.02, 0, 0] m/s",但关节速度怎么反算?"

资深工程师老刘放下咖啡杯:"逆速度运动学。正问题是

"v = J·q_dot",反过来求

"q_dot",需要雅可比伪逆

"q_dot = J⁺·v"。但注意——你这台臂是3-DOF对3-DOF,非冗余,伪逆在

"rcond"很小的时候等价于直接求逆。当构型接近奇异(大臂小臂接近伸直,

"q₂→0°"),条件数飙升,

"||q_dot||"会跟着涨,不是天然有界的。真要防爆炸得用阻尼最小二乘法(DLS),那是以后的事。"

"那我现在先验证伪逆的基本行为——给定目标速度,扫描

"q₂"从60°递减到0.01°,看

"||q_dot||"和残差怎么随条件数变化。同时检查每个关节速度有没有超过

"ω_max=3.0 rad/s"。"

老刘点头:"对。

"q₃"只管末端朝向,纯位置控制时它速度恒为零——你这台臂做插装本质上就是2R平移加1个转腕。另外雅可比里位置和朝向行量纲不同,条件数κ的绝对值跟你的单位选择有关,κ=100这个阈值不是通用的。"

构型约定:本篇采用3-DOF平面R-R-R模型(前两关节控制XY位置,第三关节仅控制末端朝向θ)。DH参数:连杆1基座偏移、连杆2大臂长0.30 m、连杆3小臂长0.35 m、连杆4(末端法兰)全零恒等变换。雅可比通过中心差分数值计算(δ=1e-6 rad),伪逆通过

"np.linalg.pinv"(SVD-based)求解。技术栈限定numpy + matplotlib。

⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于逆速度运动学校核。代码中不包含任何真实机器人控制器实时运算或安全逻辑。不可直接用于产线设备。场景中的额定负载3.0 kg、重复定位精度±0.02 mm、ω_max=3.0 rad/s为现场机器人实际参数,本篇纯运动学速度分析不涉及动力学与精度模型,留待后续篇章。

2. 痛点分析

课程模块 本篇对应的工程痛点

第3章 连杆参数及运动学求解 → 3.3 速度雅可比矩阵 雅可比将关节速度映射到笛卡尔速度,差分法数值计算避免解析推导

第3章 → 3.4 逆运动学数值解法 伪逆

"J⁺"求解

"q_dot = J⁺·v",SVD处理秩亏,但非冗余方阵下等价于求逆

第6章 轨迹规划与控制 → 6.2 速度级规划 笛卡尔速度反解需检查关节角速度限值,奇异构型下伪逆解范数随条件数增长

本篇代码解决 L2 离线仿真校核层。输入:关节角

"q"、目标笛卡尔速度

"v_des"。处理:①中心差分计算雅可比

"J";②计算

"J⁺ = pinv(J)";③求解

"q_dot = J⁺·v_des";④扫描

"q₂"从60°到0.01°,记录

"κ(J)"、

"||q_dot||"、残差、各关节速度;⑤检查

"|q_dot_i| ≤ ω_max";⑥对朝向角分量做

"wrap_to_pi"处理。输出:条件数曲线 +

"||q_dot||" vs κ曲线 + 速度分量对比 + 奇异值分布 + 4张分析图表。

不覆盖:逆运动学位置级求解、阻尼最小二乘法(DLS)、动力学、实时控制。

3. 核心逻辑讲解

物理直觉

雅可比

"J"是机器人的"速度变速器"。对于本篇的3-DOF平面臂,末端位姿

"[x, y, θ]",

"J"是3×3矩阵。前两列由连杆几何决定,第三列

"J[:,2] = [0, 0, 1]"——

"q₃"只改变末端朝向,对XY位置零贡献。做纯位置速度控制(

"ωz=0")时,

"q₃"速度恒为零,本质上只需2个关节。

逆问题:已知

"v_des",求

"q_dot"。数学上解

"J·q_dot = v_des"。当

"J"满秩,

"J⁺ = J⁻¹",

"q_dot = J⁻¹·v_des",

"||q_dot|| ≈ ||v_des|| / σ_min",随条件数

"κ = σ_max/σ_min"线性增长。伪逆不会天然封顶——除非

"rcond"大到触发SVD截断(此时残差上升,丢失方向信息)。

业务逻辑 → 代码映射

输入:目标速度 v_des = [vx, vy, ωz]、扫描范围 q₂ ∈ [60°, 0.01°]

↓

【步骤1:对每个q₂计算关节角 q = [0, q₂, 0]】

↓

【步骤2:中心差分求雅可比】

J[:,i] = (FK(q+δe_i) - FK(q-δe_i)) / (2δ)

对θ分量做wrap_to_pi处理

↓

【步骤3:伪逆求解】

J_plus = pinv(J, rcond=1e-12)

q_dot = J_plus @ v_des

↓

【步骤4:诊断】

v_actual = J @ q_dot

residual = ||v_actual - v_des||

κ = cond(J)

σ = svd(J)

检查 |q_dot_i| ≤ ω_max

↓

【步骤5:扫描输出】

记录所有诊断量,生成4张图表

关键公式文字翻译

中心差分雅可比:

"J[:, i] ≈ (f(q+δe_i) - f(q-δe_i)) / (2δ)"。

"f"返回

"[x, y, θ]",

"θ"分量经

"wrap_to_pi"避免跨越±π。

"δ=1e-6"在float64下平衡截断误差

"δ²"和舍入误差

"ε/δ"。

伪逆(SVD):

"J = U·Σ·V^T",

"J⁺ = V·Σ⁺·U^T"。

"Σ⁺"将大于

"rcond·σ_max"的奇异值取倒数,其余置零。当

"rcond=1e-12"且

"σ_min > 1e-12·σ_max"时,伪逆等于逆。

条件数:

"κ = σ_max/σ_min"。混合量纲(位置行m/rad,朝向行无量纲),κ绝对值依赖单位约定,阈值不可移植。

4. OOP代码实现

技术栈

限定:numpy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方重型库。全部自实现。

项目结构树

assembly_jacobian_pseudo_inv/

├── run_jacobian_analysis.py

├── requirements.txt

├── robot/

│ ├── __init__.py

│ ├── models.py

│ ├── dh_kinematics.py

│ ├── jacobian_solver.py

│ └── visualize.py

├── tests/

│ ├── __init__.py

│ └── test_jacobian.py

├── results/

└── output/

核心源码

"robot/models.py" — 数据模型

"""

数据模型:关节限位、DH参数、机器人配置

全部参数内置,自包含可复现

"""

import numpy as np

import math

from dataclasses import dataclass, field

from typing import List, Tuple

@dataclass

class JointLimits:

"""

单个关节的限位定义

lower: 下限位 (rad)

upper: 上限位 (rad)

name: 关节名称

"""

lower: float

upper: float

name: str = ""

def __post_init__(self):

if self.lower >= self.upper:

raise ValueError(

f"关节{self.name}下限位({self.lower})必须<上限位({self.upper})")

if not (math.isfinite(self.lower) and math.isfinite(self.upper)):

raise TypeError(f"关节{self.name}限位必须为有限数值")

@dataclass

class RobotConfig:

"""

机器人完整配置(3-DOF平面R-R-R模型)

连杆1: 基座到J2(d1=0.0, a1=0.30m)

连杆2: 大臂(d2=0.0, a2=0.35m)

连杆3: 末端法兰(全零,仅朝向)

"""

joint_limits: List[JointLimits] = field(default_factory=list)

dh_params: List[Tuple[float, float, float, float]] = field(

default_factory=list)

def __post_init__(self):

if not self.joint_limits:

self.joint_limits = [

JointLimits(math.radians(-150), math.radians(150),

"J1_基座回转"),

JointLimits(math.radians(-170), math.radians(170),

"J2_大臂俯仰"),

JointLimits(math.radians(-180), math.radians(180),

"J3_末端回转"),

]

if not self.dh_params:

# 标准DH参数:(θ_offset, d, a, α)

# 连杆1: 大臂基座段, a=0.30m

# 连杆2: 小臂段, a=0.35m

# 连杆3: 末端法兰, 全零(恒等变换)

self.dh_params = [

(0.0, 0.0, 0.30, 0.0),

(0.0, 0.0, 0.35, 0.0),

(0.0, 0.0, 0.0, 0.0),

]

"robot/dh_kinematics.py" — 正运动学

"""

标准DH正运动学:3-DOF平面R-R-R机械臂

变换顺序:Rz(θ) · Tz(d) · Tx(a) · Rx(α)

末端位姿输出[x, y, θ](平面:θ为末端朝向角)

"""

import numpy as np

import math

from robot.models import RobotConfig

class DHKinematics:

def __init__(self, config: RobotConfig):

self.config = config

def _rot_z(self, theta):

c, s = math.cos(theta), math.sin(theta)

return np.array([

[c, -s, 0, 0],

[s, c, 0, 0],

[0, 0, 1, 0],

[0, 0, 0, 1]

], dtype=float)

def _trans_z(self, d):

M = np.eye(4)

M[2, 3] = d

return M

def _trans_x(self, a):

M = np.eye(4)

M[0, 3] = a

return M

def _rot_x(self, alpha):

c, s = math.cos(alpha), math.sin(alpha)

return np.array([

[1, 0, 0, 0],

[0, c, -s, 0],

[0, s, c, 0],

[0, 0, 0, 1]

], dtype=float)

def forward_kinematics(self, joint_angles: list):

"""正运动学:返回4×4齐次矩阵"""

if len(joint_angles) != 3:

raise ValueError("本模型需要3个关节角")

T_total = np.eye(4)

for i, (theta_offset, d, a, alpha) in enumerate(

self.config.dh_params):

theta = joint_angles[i] + theta_offset

T_i = (

self._rot_z(theta)

@ self._trans_z(d)

@ self._trans_x(a)

@ self._rot_x(alpha)

)

T_total = T_total @ T_i

return T_total

def get_end_effector_pose(self, joint_angles: list):

"""

返回末端位姿 [x, y, theta]

x, y: 基坐标系下的位置 (m)

theta: 末端朝向角 (rad),范围(-π, π]

"""

T = self.forward_kinematics(joint_angles)

x = T[0, 3]

y = T[1, 3]

theta = math.atan2(T[1, 0], T[0, 0])

return np.array([x, y, theta], dtype=float)

"robot/jacobian_solver.py" — 雅可比计算与伪逆求解

"""

雅可比矩阵数值计算 + 伪逆求解

中心差分法,对朝向角分量做wrap_to_pi处理

"""

import numpy as np

import math

from robot.models import RobotConfig

from robot.dh_kinematics import DHKinematics

def wrap_to_pi(angle):

"""

将角度归一化到(-π, π]

用于中心差分中θ分量的角度缠绕处理

"""

return (angle + math.pi) % (2 * math.pi) - math.pi

class JacobianSolver:

def __init__(self, config: RobotConfig,

kinematics: DHKinematics,

delta=1e-6):

"""

delta: 有限差分扰动步长 (rad)

选择1e-6是因为float64精度下截断误差δ²≈1e-12

与舍入误差ε/δ≈1e-10平衡

"""

self.config = config

self.kinematics = kinematics

self.delta = delta

def compute_jacobian(self, joint_angles: list):

"""

数值差分法计算雅可比矩阵 J (3×3)

J[:, i] = ∂f/∂q_i ≈ (f(q+δe_i) - f(q-δe_i)) / (2δ)

f返回 [x, y, theta]

theta分量做wrap_to_pi避免角度缠绕

"""

n = len(joint_angles)

J = np.zeros((3, n))

for i in range(n):

q_plus = list(joint_angles)

q_minus = list(joint_angles)

q_plus[i] += self.delta

q_minus[i] -= self.delta

pose_plus = self.kinematics.get_end_effector_pose(

q_plus)

pose_minus = self.kinematics.get_end_effector_pose(

q_minus)

# 对theta分量做角度差缠绕处理

raw_diff = pose_plus - pose_minus

raw_diff[2] = wrap_to_pi(raw_diff[2])

J[:, i] = raw_diff / (2 * self.delta)

return J

def compute_pseudo_inverse(self, J, rcond=1e-12):

"""

计算雅可比伪逆 J⁺ = pinv(J)

rcond控制奇异值截断阈值

"""

return np.linalg.pinv(J, rcond=rcond)

def solve_joint_velocities(self, joint_angles,

cartesian_velocity,

rcond=1e-12,

omega_max=3.0):

"""

给定关节角和目标笛卡尔速度,求解关节速度

cartesian_velocity: [vx, vy, ωz] (m/s, rad/s)

omega_max: 关节最大角速度 (rad/s)

返回诊断字典

"""

J = self.compute_jacobian(joint_angles)

J_plus = self.compute_pseudo_inverse(J, rcond=rcond)

q_dot = J_plus @ np.array(cartesian_velocity)

# 验证:计算实际可达的笛卡尔速度

v_actual = J @ q_dot

# 残差(各分量独立量纲,分别处理)

residual = np.linalg.norm(

v_actual - np.array(cartesian_velocity))

# 条件数

try:

cond_num = np.linalg.cond(J)

except np.linalg.LinAlgError:

cond_num = float('inf')

# 奇异值

singular_values = np.linalg.svd(J, compute_uv=False)

# 关节速度限值检查

velocity_violations = [

i for i, qd in enumerate(q_dot)

if abs(qd) > omega_max

]

return {

'q_dot': q_dot,

'J': J,

'J_plus': J_plus,

'v_actual': v_actual,

'residual': residual,

'residual_components': (v_actual

- np.array(cartesian_velocity)),

'condition_number': cond_num,

'singular_values': singular_values,

'jacobian_det': np.linalg.det(J),

'velocity_violations': velocity_violations,

'omega_max': omega_max,

}

def scan_q2_configurations(self, q2_degrees_list,

q1=0.0, q3=0.0,

cartesian_velocity=None,

rcond=1e-12,

omega_max=3.0):

"""

扫描不同q2构型(核心实验)

q2_degrees_list: q2角度列表(度)

"""

if cartesian_velocity is None:

cartesian_velocity = [0.02, 0.0, 0.0]

results = {

'q2_degrees': [],

'q2_radians': [],

'condition_numbers': [],

'singular_values': [],

'q_dot_norms': [],

'q_dots': [],

'residuals': [],

'residual_components': [],

'v_actuals': [],

'determinants': [],

'velocity_violations': [],

'jacobians': [],

}

for q2_deg in q2_degrees_list:

q2_rad = math.radians(q2_deg)

joint_angles = [q1, q2_rad, q3]

sol = self.solve_joint_velocities(

joint_angles, cartesian_velocity,

rcond=rcond, omega_max=omega_max)

results['q2_degrees'].append(q2_deg)

results['q2_radians'].append(q2_rad)

results['condition_numbers'].append(

sol['condition_number'])

results['singular_values'].append(

sol['singular_values'].copy())

results['q_dot_norms'].append(

np.linalg.norm(sol['q_dot']))

results['q_dots'].append(sol['q_dot'].copy())

results['residuals'].append(sol['residual'])

results['residual_components'].append(

sol['residual_components'].copy())

results['v_actuals'].append(

sol['v_actual'].copy())

results['determinants'].append(sol['jacobian_det'])

results['velocity_violations'].append(

sol['velocity_violations'].copy())

results['jacobians'].append(sol['J'].copy())

return results

"robot/visualize.py" — 可视化

"""

雅可比伪逆分析可视化:4张图表

① q₂扫描:条件数κ vs q₂角度(识别奇异趋势)

② ||q_dot|| vs 条件数κ(验证伪逆=求逆时范数随κ线性增长)

③ 目标速度 vs 实际可达速度各分量对比(验证精度)

④ 最小奇异值σ_min vs q₂(奇异接近的量化指标)

"""

import numpy as np

import matplotlib.pyplot as plt

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

'Noto Sans CJK SC']

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

class JacobianVisualizer:

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

self.results_dir = results_dir

import os

os.makedirs(results_dir, exist_ok=True)

def plot_condition_vs_q2(self, results):

"""① 条件数 vs q₂角度"""

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

q2_deg = results['q2_degrees']

conds = results['condition_numbers']

ax.semilogy(q2_deg, conds, 'b-o', markersize=4,

linewidth=1.5)

ax.set_xlabel('q₂ 角度 (°)')

ax.set_ylabel('雅可比条件数 κ(J)')

ax.set_title('雅可比条件数 vs q₂角度\n'

'(q₂→0°时κ→∞,接近奇异)')

ax.grid(True, alpha=0.3)

ax.axvline(x=5, color='orange', linestyle='--',

linewidth=1.5, label='q₂=5° 参考线')

ax.legend()

plt.tight_layout()

path = f"{self.results_dir}/01_condition_vs_q2.png"

plt.savefig(path, dpi=150)

plt.close()

return path

def plot_qdot_norm_vs_condition(self, results):

"""② ||q_dot|| vs 条件数κ(双对数,验证∝κ增长)"""

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

conds = results['condition_numbers']

qdot_norms = results['q_dot_norms']

ax.loglog(conds, qdot_norms, 'r-o', markersize=4,

linewidth=1.5, label='仿真数据')

# 理论参考线:||q_dot|| ∝ κ

# 取前两个点拟合比例

valid = [(c, n) for c, n in zip(conds, qdot_norms)

if np.isfinite(c) and c > 1 and n > 0]

if len(valid) >= 2:

c0, n0 = valid[0]

slope = n0 / c0

c_arr = np.logspace(np.log10(min(conds)),

np.log10(max(conds)), 50)

ax.loglog(c_arr, slope * np.array(c_arr), 'k--',

linewidth=1.5,

label='理论 ∝κ')

ax.set_xlabel('雅可比条件数 κ(J)')

ax.set_ylabel('关节速度范数 ||q_dot|| (rad/s)')

ax.set_title('关节速度范数 vs 条件数\n'

'(非冗余方阵下伪逆=求逆,||q_dot||∝κ)')

ax.grid(True, alpha=0.3)

ax.legend()

plt.tight_layout()

path = f"{self.results_dir}/02_qdot_norm_vs_condition.png"

plt.savefig(path, dpi=150)

plt.close()

return path

def plot_velocity_components(self, results,

cartesian_velocity):

"""③ 目标速度 vs 实际可达速度各分量对比"""

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

labels = ['Vx (m/s)', 'Vy (m/s)', 'ωz (rad/s)']

v_target = np.array(cartesian_velocity)

# 取最后一个构型(最接近奇异)展示

idx = -1

v_actual = results['v_actuals'][idx]

x_pos = np.arange(len(labels))

width = 0.35

axes[0].bar(x_pos - width/2, v_target, width,

label='目标', color='green', alpha=0.8)

axes[0].bar(x_pos + width/2, v_actual, width,

label='实际(最近奇异)', color='orange',

alpha=0.8)

axes[0].set_ylabel('速度')

axes[0].set_title(f'速度分量对比\n'

f'(q₂={results["q2_degrees"][idx]}°)')

axes[0].set_xticks(x_pos)

axes[0].set_xticklabels(labels)

axes[0].legend()

axes[0].grid(True, alpha=0.3)

# 残差分量(所有构型)

residual_comps = np.array(

results['residual_components'])

for i, ax in enumerate(axes[1:], 1):

ax.plot(results['q2_degrees'],

residual_comps[:, i-1], 'o-',

markersize=3, linewidth=1)

ax.set_xlabel('q₂ (°)')

ax.set_ylabel(f'{labels[i-1]} 残差')

ax.set_title(f'{labels[i-1]} 残差随q₂变化')

ax.grid(True, alpha=0.3)

plt.tight_layout()

path = f"{self.results_dir}/03_velocity_components.png"

plt.savefig(path, dpi=150)

plt.close()

return path

def plot_min_singular_values(self, results):

"""④ 最小奇异值 vs q₂"""

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

q2_deg = results['q2_degrees']

min_svs = [np.min(sv) for sv in results['singular_values']]

ax.semilogy(q2_deg, min_svs, 'g-o', markersize=4,

linewidth=1.5)

ax.set_xlabel('q₂ 角度 (°)')

ax.set_ylabel('最小奇异值 σ_min')

ax.set_title('最小奇异值 vs q₂角度\n'

'(σ_min→0表示接近奇异)')

ax.grid(True, alpha=0.3)

ax.axhline(y=1e-3, color='red', linestyle='--',

linewidth=1.5, label='σ_min=1e-3')

ax.legend()

plt.tight_layout()

path = f"{self.results_dir}/04_min_singular_values.png"

plt.savefig(path, dpi=150)

plt.close()

return path

5. 可视化要求

序号 图表文件 服务结论

①

"01_condition_vs_q2.png" q₂→0°时κ指数增长,确认奇异性由q₂决定

②

"02_qdot_norm_vs_condition.png" 非冗余方阵下伪逆=求逆,

"\|\|q_dot\|\|∝κ"线性增长(非有界),理论线吻合

③

"03_velocity_components.png" 目标vs实际速度分量对比+残差随q₂变化,验证求解精度

④

"04_min_singular_values.png" σ_min随q₂减小而趋零,量化奇异接近程度

6. README使用说明 + 核心知识点卡片

快速开始

pip install -r requirements.txt

python -m pytest tests/test_jacobian.py -v # 3/3 测试通过

python run_jacobian_analysis.py # 运行雅可比分析

数据格式说明

# 修改目标速度:编辑 cartesian_velocity = [vx, vy, omega_z]

# 修改q₂扫描范围:编辑 q2_degrees_list

# 修改伪逆截断阈值:编辑 rcond=1e-12

# 修改角速度限值:编辑 omega_max=3.0

参数 修改位置 说明

目标速度

"cartesian_velocity" m/s, rad/s

q₂扫描

"q2_degrees_list" 度

差分步长

"JacobianSolver(delta=)" rad

伪逆阈值

"rcond=" 默认1e-12

角速度限值

"omega_max=" rad/s

📌 知识点卡片

雅可比与伪逆(对应课本第3章3.3/3.4节)

- 雅可比矩阵:

"v = J·q_dot",差分法

"J[:,i] ≈ (f(q+δe_i)-f(q-δe_i))/(2δ)"

- 伪逆求解:

"q_dot = J⁺·v",SVD分解

"J⁺=V·Σ⁺·U^T"

- 非冗余方阵:

"rcond"极小时

"J⁺=J⁻¹",

"||q_dot||≈||v||/σ_min ∝ κ",非有界

- 奇异条件:本模型

"det(J) = L₁·L₂·sin(q₂)",

"q₂→0°"或

"±180°"时奇异

- q₃特性:

"J[:,2]=[0,0,1]",纯位置控制时

"q₃"速度恒为零

- 条件数量纲陷阱:混合量纲导致κ绝对值依赖单位约定,阈值不可移植

- 工程铁律:实时控制需DLS替代纯伪逆,κ>100时开始不稳定

7. 总结与工程师思考

工业落地难处

一、雅可比依赖运动学参数精度。 差分法需要精确的正运动学模型——DH参数偏差1 mm会导致雅可比列向量方向错误,反解出的关节速度偏离最优。现场臂长标定误差通常±0.5 mm,累积到雅可比层面可能引入5%~10%的方向偏差。

二、伪逆在非冗余方阵下不防爆炸。 本篇实验明确展示:

"||q_dot||"随κ线性增长,在

"q₂=0.01°"时可达数十rad/s,远超

"ω_max=3.0 rad/s"。伪逆"范数有界"只在真秩亏或冗余时才成立。实时控制必须用DLS,在

"J^T J + λ²I"上做文章。

三、混合量纲导致条件数阈值不可移植。 雅可比位置行单位m/rad,朝向行无量纲,κ的绝对值依赖你选什么单位。κ=100在某台机器上意味着不稳定,换一台臂长不同的可能完全不是同一回事。

工程师心得

小陈看着

"02_qdot_norm_vs_condition.png"上那条随κ飙升的曲线:"果然,

"||q_dot||"跟着κ线性涨——

"q₂=0.5°"时已经到15 rad/s了,远超

"ω_max=3.0"。伪逆根本不兜底。"

老刘点头:"对。这就是为什么要写DLS。纯伪逆在方阵满秩时就是求逆,奇异附近该炸还是炸。你这篇的图②把真相画出来了——比上一篇说'伪逆有界'诚实多了。"

"还有

"q₃"——我注意到纯位置指令下它的速度永远是零。做插装时这臂本质上就是2R。"

"没错。而且你注意到没有,

"q₂=0.01°"时残差还是零——因为

"rcond=1e-12"根本没触发截断。σ_min≈0.00005,远大于

"1e-12·σ_max"。残差要变大得把

"rcond"调到

"1e-2"以上——那时候伪逆才真正'放弃'那个方向。"

8. 写作与风格限制

本篇全部代码为课程离线仿真,运行在普通PC上,用于逆速度运动学校核。代码中不包含任何真实机器人控制器实时运算或安全逻辑。 完全去营销,中立客观。拒绝论文式生硬开头,保持故事叙事感。全部物理量标注完整单位。

9. 文末附录

环境配置

pip install numpy>=1.24.0 matplotlib>=3.7.0

"requirements.txt":

numpy>=1.24.0

matplotlib>=3.7.0

运行说明

cd assembly_jacobian_pseudo_inv

pip install -r requirements.txt

python -m pytest tests/ -v

python run_jacobian_analysis.py

参数修改指南

想改什么 改哪里 示例

目标速度

"cartesian_velocity"

"[0.02, 0, 0]"

q₂扫描范围

"q2_degrees_list"

"[60, 30, 10, 1, 0.1]"

臂长

"dh_params"中的

"a"值 改0.30→0.40

伪逆阈值

"rcond=" 改1e-3

角速度限值

"omega_max=" 改5.0

输出文件清单

文件 含义

"results/01_condition_vs_q2.png" 条件数vs q₂角度

"results/02_qdot_norm_vs_condition.png" 关节速度范数vs条件数

"results/03_velocity_components.png" 速度分量对比+残差变化

"results/04_min_singular_values.png" 最小奇异值分布

"output/jacobian_scan_data.csv" 详细扫描数据

仿真结果摘要

统计项 数值 数据来源

机器人模型 3-DOF R-R-R平面臂 配置

臂长 L1=0.30m, L2=0.35m DH参数

目标速度 [0.02, 0, 0] m/s 参数

q₂扫描点数 12 q2_degrees_list

q₂=0.01°时κ >10⁴ 计算结果

q₂=0.01°时||q_dot|| >15 rad/s 计算结果

角速度限值检查 2个构型超限 诊断

测试通过 3/3 pytest

正文完。以下为

"run_jacobian_a

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询