python的机器人基础原理工业场景模拟第七篇:码垛现场,修改连杆D-H偏移参数,观察参数扰动对机械臂的末端位置产生的偏移量。
《Python机器人基础原理》第七篇:码垛现场——输入修改连杆D-H偏移参数,观察参数扰动对机械臂末端位置产生的偏移量
1. 实际工业应用场景
周三夜班,某电商物流分拣中心的码垛工作站。
"又掉了一箱。"夜班班长老李捡起掉在地上的快递纸箱,掸了掸灰,重新码回托盘。这一小时已经掉了4箱。
这条线用一台4轴码垛机器人把分拣好的纸箱从传送带抓起,按3×4×2的垛型码到托盘上。节拍要求3.0 s/箱。机器人末端装了一个真空吸盘阵列,吸盘平面与法兰面平行。
"你标定过了吗?"老李问站在示教器旁边的调试员小周。
"标了。四点法标了TCP,基座标也是用三点法标的。"
"那为什么每次抓第三层的时候就开始偏?第一层第二层都准,到了第三层吸盘就往后仰,箱子放上去重心不稳。"
小周翻出参数表:"D-H参数用的是厂家给的默认值。a₂=0.40 m, a₃=0.35 m, d₁=0.85 m, α₂=π/2。会不会是手册值跟实际有偏差?"
"手册值是理论值。这台机器装了三年了,大臂连杆换过一次——上次撞了托盘,换的新连杆,不光长度可能变了,安装面的平行度也可能偏了,导致俯仰轴跟基座标不垂直。"
"我一般直接上示教器手动补偿。"
"手动补偿能解决当前垛型,换一个垛型又偏了。你给我写一个离线工具——我输入D-H参数的偏移量,你告诉我末端位置偏移多少。我要看到灵敏度数据,知道哪个参数对末端影响最大,优先标定哪个。"
这就是本篇要解决的问题:给定一台4-DOF码垛机械臂的标准D-H参数,在标称值基础上施加系统性偏移扰动(Δa、Δd、Δα),逐级重新计算正运动学,量化每个D-H参数扰动对末端执行器位置的影响。不调用任何第三方机器人库,纯numpy实现,让工程师在离线状态就能回答"参数差一点到底偏多少"这个问题。
机器人构型参数:4-DOF关节型码垛机械臂(3旋转+1垂直平移),延续第六篇已验证构型。d₁=0.85 m(基座高度),a₂=0.40 m(大臂),a₃=0.35 m(小臂),a₄=0.25 m(末端延伸),α序列[0, π/2, -π/2, 0]。负载末端真空吸盘阵列+纸箱合计8.0 kg。单箱码垛节拍3.0 s,重复定位精度±0.5 mm。
⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于D-H参数扰动灵敏度分析。不可直接用于真实机器人控制器、PLC或运动控制卡。真实产线参数标定需使用激光跟踪仪配合厂商专用软件。
2. 痛点分析
课程模块 本篇对应的工程痛点
第3章 连杆参数及运动学求解 → 3.2 D-H参数法 手册D-H参数与实际几何存在加工/装配偏差,需量化每个参数扰动对末端的影响幅度
第3章 连杆参数及运动学求解 → 3.3 正运动学 参数偏移通过正运动学逐级传递,需重新计算串联变换得到末端偏移量
第3章 连杆参数及运动学求解 → 3.5 工作空间分析 参数扰动改变实际工作空间边界,影响码垛可达区域
本篇代码解决 L1 离线仿真校核层。输入D-H参数偏移量(Δa、Δd、Δα),输出末端位置偏移向量和灵敏度系数。代码不是设备实时控制器,不做逆解迭代,不做轨迹补偿。它只做一件事:给参数"加个扰动",看末端"偏了多少"——让标定工程师在动手调机之前先知道该优先标定哪些参数。
不覆盖:逆运动学、轨迹规划、动力学、力控制、6轴以上构型、实时操作系统。
3. 核心逻辑讲解
物理直觉
D-H参数就像机器人的"基因"。a是骨头长度,d是关节间距,α是关节轴夹角。手册给的是"标准基因",但每台机器装出来都有微小差异——连杆加工公差±0.5 mm、装配时安装面不平行导致α偏了几角分、换过备件后的尺寸变化。
这些微小差异对末端的影响分两类,规律完全不同:
长度参数(a、d):1:1刚性平移。 对连杆i的a加δ,等于把i之后整条子链刚体平移δ。末端偏移的模长永远恰好等于δ,跟关节角无关。这不是近似,是几何必然——变换矩阵里a乘的是cosθ和sinθ,加δ后差值就是δ·[cosθ, sinθ, 0],模长恰好是|δ|。方向会变(关节角决定误差往哪个方向投影),但大小不放大。
角度参数(α、θ):杠杆臂放大。 α偏δ_rad,误差乘上从扰动关节到末端的水平距离(杠杆臂)。1 m臂展下α偏0.1°(≈0.0017 rad),末端偏将近1.7 mm。偏0.2°就偏3.5 mm。这就是为什么装配时安装面差一点点,末端就能偏好几毫米——角度误差被臂展杠杆放大了。
本篇做的事情很朴素:固定一组关节角(模拟码垛工作点),把每个D-H参数分别加一个微小偏移,重新跑一遍正运动学,看末端位置差了多少。长度参数验证1:1传递,角度参数验证杠杆放大。灵敏度系数告诉你"改这个参数1个单位,末端偏多少"——标定优先级排序的直接依据。
业务逻辑 → 代码映射
输入:标称D-H参数 + 参数偏移列表(每个参数独立扰动)
↓
步骤1:数据模型校验——偏移量范围合理、link_index < 连杆数
↓
步骤2:对每个待扰动的参数:
a. 深拷贝标称参数,施加Δ偏移
b. 重新计算正运动学,得到扰动末端位置
c. 与标称末端位置求差,得到偏移向量ΔP
d. 计算灵敏度 = |ΔP| / |Δ参数|
↓
步骤3:批量扫描——对关键参数做多步长扫描,
长度参数验证|ΔP|=|δ|,角度参数验证线性放大
↓
步骤4:自洽校验——零偏移时末端位置与标称值完全一致(误差<1e-12)
↓
输出:各参数灵敏度排名 + 偏移量向量 + 扫描曲线数据
为什么适合离线仿真
- 参数扰动分析是纯正运动学重复计算,无微分方程,无积分漂移
- 每个扰动独立计算,普通PC秒级完成全参数扫描
- 结果直接指导现场标定优先级——角度参数优先标定,长度参数用卷尺就能验证
- 工程师可以输入自己机器的实际偏差估算值,预判末端误差范围
关键公式文字翻译
参数扰动后的末端位置:
P_disturbed = FK(nominal_DH + Δparam, joint_angles)
其中FK代表正运动学串联计算,与第六篇完全一致。
末端偏移向量:
ΔP = P_disturbed - P_nominal
这是3维向量,表示xyz三个方向的偏移量。取模长得到总偏移距离。
灵敏度系数:
S = |ΔP| / |Δparam|
对长度参数,S恒等于1.0(mm/mm),方向由当前关节角决定。对角度参数,S ≈ 杠杆臂长度(mm/rad),与臂展成正比。
4. OOP代码实现
技术栈
严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。sympy不引入(本篇纯数值批量计算)。
工程说明:本篇的
"dh_transform.py"和
"forward_kinematics.py"逻辑与第六篇一致。工程上应抽成公共包,此处为保持各篇独立可运行,在各自目录下维护完整副本。
项目结构树
robot_dh_sensitivity/
├── run_dh_sensitivity.py
├── requirements.txt
├── sensitivity/
│ ├── __init__.py
│ ├── models.py
│ ├── dh_transform.py
│ ├── forward_kinematics.py
│ ├── perturbation.py
│ └── visualize.py
├── tests/
│ ├── __init__.py
│ └── test_dh_sensitivity.py
└── results/
核心源码
"sensitivity/models.py" — dataclass数据模型
"""4-DOF码垛机械臂D-H参数与扰动定义"""
from dataclasses import dataclass
from typing import List
@dataclass
class DHParam:
"""
标准D-H参数(Denavit-Hartenberg经典形式)
theta: 关节角 (rad)
d: 连杆偏距 (m)
a: 连杆长度 (m)
alpha: 连杆扭角 (rad)
joint_type: 'revolute' 或 'prismatic'
"""
theta: float
d: float
a: float
alpha: float
joint_type: str = 'revolute'
def __post_init__(self):
if self.joint_type not in ('revolute', 'prismatic'):
raise ValueError(
f"joint_type必须为'revolute'或'prismatic', "
f"实际'{self.joint_type}'")
@dataclass
class DHPerturbation:
"""
单个D-H参数的扰动定义
link_index: 连杆编号 (0-based, 0~3)
param_name: 参数名 ('a', 'd', 'alpha')
delta: 偏移量 (m 或 rad)
"""
link_index: int
param_name: str
delta: float
def __post_init__(self):
if self.param_name not in ('a', 'd', 'alpha'):
raise ValueError(
f"param_name必须为'a'/'d'/'alpha', "
f"实际'{self.param_name}'")
if not (0 <= self.link_index <= 3):
raise ValueError(
f"link_index必须在0~3之间, 实际{self.link_index}")
@dataclass
class Robot4DOFParams:
"""
4-DOF码垛机械臂完整参数
延续第六篇已验证构型,alpha=[0, π/2, -π/2, 0]
"""
links: List[DHParam]
joint_names: List[str]
payload_mass: float = 8.0 # kg, 吸盘阵列+纸箱
base_height: float = 0.85 # m
gravity: float = 9.81
def __post_init__(self):
if len(self.links) != 4:
raise ValueError(f"需要4组D-H参数, 实际{len(self.links)}组")
if abs(self.links[0].d - self.base_height) > 1e-9:
raise ValueError("d1与base_height不一致")
@classmethod
def default_4dof(cls):
"""默认4-DOF码垛机械臂D-H参数表"""
pi = 3.141592653589793
links = [
DHParam(theta=0.0, d=0.85, a=0.0, alpha=0.0,
joint_type='revolute'),
DHParam(theta=0.0, d=0.0, a=0.40, alpha=pi/2,
joint_type='revolute'),
DHParam(theta=0.0, d=0.0, a=0.35, alpha=-pi/2,
joint_type='revolute'),
DHParam(theta=0.0, d=0.0, a=0.25, alpha=0.0,
joint_type='prismatic'),
]
joint_names = ['joint1_yaw', 'joint2_pitch1',
'joint3_pitch2', 'joint4_lift']
return cls(links=links, joint_names=joint_names,
payload_mass=8.0, base_height=0.85)
"sensitivity/dh_transform.py" — 标准D-H变换矩阵
"""
标准D-H变换矩阵计算
变换顺序:绕z转θ → 沿z移d → 沿x移a → 绕x转α
"""
import numpy as np
from sensitivity.models import DHParam
class DHTransform:
"""单级标准D-H变换矩阵生成器"""
@staticmethod
def compute_T(theta, d, a, alpha):
"""
标准D-H齐次变换矩阵 T_{i-1}^{i}
"""
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.0, sa, ca, d],
[0.0, 0.0, 0.0, 1.0]
], dtype=float)
return T
@staticmethod
def compute_T_from_param(param: DHParam):
return DHTransform.compute_T(
param.theta, param.d, param.a, param.alpha)
"sensitivity/forward_kinematics.py" — 正运动学串联
"""
正运动学求解器:逐级左乘D-H变换矩阵
"""
import numpy as np
from sensitivity.models import Robot4DOFParams
from sensitivity.dh_transform import DHTransform
class ForwardKinematics4DOF:
"""4-DOF正运动学求解器"""
def __init__(self, params: Robot4DOFParams):
self.params = params
self.n_joints = len(params.links)
def update_joint_values(self, joint_values):
"""更新关节变量到D-H参数"""
if len(joint_values) != self.n_joints:
raise ValueError(
f"需要{self.n_joints}个关节值, "
f"实际{len(joint_values)}个")
for i in range(self.n_joints):
if self.params.links[i].joint_type == 'revolute':
self.params.links[i].theta = joint_values[i]
else:
self.params.links[i].d = joint_values[i]
def compute_forward_kinematics(self):
"""逐级左乘,返回T_chain列表"""
T_chain = [np.eye(4, dtype=float)]
T_cumulative = np.eye(4)
for i in range(self.n_joints):
T_i = DHTransform.compute_T_from_param(
self.params.links[i])
T_cumulative = T_cumulative @ T_i
T_chain.append(T_cumulative.copy())
return T_chain
def get_end_effector_pose(self):
"""提取末端位姿"""
T_chain = self.compute_forward_kinematics()
T_final = T_chain[-1]
position = T_final[:3, 3].copy()
rotation_matrix = T_final[:3, :3].copy()
return position, rotation_matrix, T_final
"sensitivity/perturbation.py" — 参数扰动与灵敏度分析核心
"""
D-H参数扰动引擎:对标称参数施加偏移,重算正运动学,量化末端偏移
核心业务逻辑:参数敏感性分析
"""
import numpy as np
from copy import deepcopy
from sensitivity.models import (
Robot4DOFParams, DHParam, DHPerturbation)
from sensitivity.forward_kinematics import ForwardKinematics4DOF
class DHPerturbationAnalyzer:
"""
D-H参数扰动分析器
对每个参数独立施加扰动,计算末端位置偏移量和灵敏度系数
"""
def __init__(self, nominal_params: Robot4DOFParams,
joint_angles):
"""
nominal_params: 标称D-H参数
joint_angles: 固定的关节角配置 (长度4)
"""
self.nominal_params = nominal_params
self.joint_angles = list(joint_angles)
# 计算标称末端位置
fk_nominal = ForwardKinematics4DOF(
deepcopy(nominal_params))
fk_nominal.update_joint_values(self.joint_angles)
self.nominal_position, _, _ = (
fk_nominal.get_end_effector_pose())
def apply_single_perturbation(self, perturbation: DHPerturbation):
"""
对单个参数施加单次扰动,返回末端偏移向量
长度参数灵敏度恒为1.0(mm/mm),角度参数按杠杆臂放大
"""
# 运行时再查一次link_index,防止越界
if perturbation.link_index >= len(self.nominal_params.links):
raise IndexError(
f"link_index {perturbation.link_index} "
f"超出连杆数量 {len(self.nominal_params.links)}")
# 深拷贝参数,避免污染标称值
perturbed_params = deepcopy(self.nominal_params)
link = perturbed_params.links[perturbation.link_index]
if perturbation.param_name == 'a':
link.a += perturbation.delta
elif perturbation.param_name == 'd':
link.d += perturbation.delta
elif perturbation.param_name == 'alpha':
link.alpha += perturbation.delta
# 重算正运动学
fk_perturbed = ForwardKinematics4DOF(perturbed_params)
fk_perturbed.update_joint_values(self.joint_angles)
perturbed_position, _, _ = (
fk_perturbed.get_end_effector_pose())
# 计算偏移向量
offset = perturbed_position - self.nominal_position
offset_norm = np.linalg.norm(offset)
# 灵敏度 = 偏移量 / 参数变化量
if abs(perturbation.delta) > 1e-15:
sensitivity = offset_norm / abs(perturbation.delta)
else:
sensitivity = 0.0
return {
'perturbation': perturbation,
'nominal_position': self.nominal_position.copy(),
'perturbed_position': perturbed_position.copy(),
'offset_vector': offset,
'offset_norm': offset_norm,
'sensitivity': sensitivity
}
def batch_perturb(self, perturbations: list):
"""批量扰动分析:对每个扰动独立计算"""
results = []
for p in perturbations:
result = self.apply_single_perturbation(p)
results.append(result)
return results
def sweep_parameter(self, link_index, param_name,
delta_range):
"""
对单个参数做多步长扫描
长度参数预期:|ΔP| = |δ|(V形,折点在δ=0)
角度参数预期:|ΔP| ≈ |δ| × 杠杆臂(V形)
注意:扫描返回全范围数据,绘图时取δ≥0半边做线性拟合
"""
if link_index >= len(self.nominal_params.links):
raise IndexError(
f"link_index {link_index} 超出连杆数量")
sweep_results = []
for delta in delta_range:
p = DHPerturbation(
link_index=link_index,
param_name=param_name,
delta=delta)
result = self.apply_single_perturbation(p)
sweep_results.append({
'delta': delta,
'offset_norm': result['offset_norm'],
'offset_vector': result['offset_vector'].copy()
})
return sweep_results
def zero_perturbation_check(self, tol=1e-12):
"""
自洽校验:零偏移时末端位置应与标称值完全一致
这是验证扰动引擎数值正确性的底线测试
"""
zero_p = DHPerturbation(link_index=0, param_name='a',
delta=0.0)
result = self.apply_single_perturbation(zero_p)
return {
'error': result['offset_norm'],
'pass': result['offset_norm'] < tol
}
"sensitivity/visualize.py" — 可视化模块
"""D-H参数扰动灵敏度分析可视化:4张核心图表"""
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
class Visualizer:
def __init__(self, results_dir="results"):
self.results_dir = results_dir
import os
os.makedirs(results_dir, exist_ok=True)
def plot_sensitivity_bar(self, results_list, labels=None):
"""
① 各参数扰动灵敏度柱状图
长度参数灵敏度恒为1.0 (mm/mm),直接显示数值
角度参数换算为 mm/° 显示(m/rad → mm/°)
工程结论:角度参数灵敏度远大于长度参数,标定优先级更高
"""
if labels is None:
labels = [f"L{r['perturbation'].link_index}_"
f"{r['perturbation'].param_name}"
for r in results_list]
display_values = []
param_types = []
for r in results_list:
pname = r['perturbation'].param_name
sens = r['sensitivity']
if pname in ('a', 'd'):
# 长度参数:m/m → mm/mm,数值不变
display_values.append(sens)
param_types.append('length')
else:
# 角度参数:m/rad → mm/°
sens_deg = sens * 1000.0 * (np.pi / 180.0)
display_values.append(sens_deg)
param_types.append('angle')
fig, ax = plt.subplots(figsize=(12, 6))
colors = ['steelblue' if t == 'length' else 'darkorange'
for t in param_types]
bars = ax.bar(range(len(labels)), display_values,
color=colors, edgecolor='black', alpha=0.85)
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels, rotation=45, ha='right')
ax.set_ylabel('Sensitivity (blue: mm/mm, orange: mm/°)',
fontsize=10)
ax.set_title('D-H Parameter Sensitivity Ranking\n'
'Length params: ~1.0 (1:1 translation)\n'
'Angle params: amplified by lever arm',
fontsize=11)
ax.grid(True, alpha=0.3, axis='y')
for bar, val, ptype in zip(bars, display_values,
param_types):
height = bar.get_height()
unit = 'mm/mm' if ptype == 'length' else 'mm/°'
ax.text(bar.get_x() + bar.get_width() / 2., height,
f'{val:.3f}\n{unit}', ha='center', va='bottom',
fontsize=8)
plt.tight_layout()
path = f"{self.results_dir}/01_sensitivity_bar.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_offset_vectors_3d(self, results_list):
"""② 各参数扰动产生的末端偏移向量3D箭头图"""
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
nominal = results_list[0]['nominal_position'] if \
results_list else np.zeros(3)
for i, r in enumerate(results_list):
offset = r['offset_vector'] * 1000 # 转mm
ax.quiver(nominal[0] * 1000, nominal[1] * 1000,
nominal[2] * 1000,
offset[0], offset[1], offset[2],
length=1.0, normalize=False,
color=plt.cm.tab10(i % 10),
alpha=0.8, label=f"L{r['perturbation']."
f"link_index}_"
f"{r['perturbation'].param_name}")
ax.set_xlabel('X (mm)')
ax.set_ylabel('Y (mm)')
ax.set_zlabel('Z (mm)')
ax.set_title('End-Effector Offset Vectors from D-H Perturbations')
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/02_offset_vectors_3d.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_parameter_sweep(self, sweep_results, param_label,
delta_unit='m', param_type='length'):
"""
③ 单参数多步长扫描曲线
只取δ≥0的半边数据做线性拟合,避免V形折弯点干扰
"""
deltas = np.array([s['delta'] for s in sweep_results])
offsets = np.array([s['offset_norm'] * 1000
for s in sweep_results])
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(deltas, offsets, 'b-o', linewidth=1.5, markersize=5,
label='|ΔP| vs Δparam')
# 只取δ≥0的半边做线性拟合
pos_mask = deltas >= 0
if np.sum(pos_mask) > 1:
pos_deltas = deltas[pos_mask]
pos_offsets = offsets[pos_mask]
slope = np.polyfit(pos_deltas, pos_offsets, 1)[0]
y_label_unit = 'mm/mm' if delta_unit == 'm' and param_type == 'length' else 'mm/°'
ax.plot(pos_deltas,
slope * pos_deltas, 'r--', linewidth=2,
label=f'Linear fit (slope={slope:.1f} {y_label_unit})')
ax.set_xlabel(f'Parameter Change Δ ({delta_unit})')
ax.set_ylabel('End-Effector Offset |ΔP| (mm)')
ax.set_title(f'Parameter Sweep: {param_label}\n'
f'(Linear fit on δ≥0 half)')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/03_parameter_sweep.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_sensitivity_heatmap(self, sensitivity_matrix,
row_labels, col_labels=None):
"""
④ 灵敏度热力图(每参数一行,xyz三方向列)
sensitivity_matrix: shape (n_params, 3),单位为 m/m 或 m/rad
显示时×1000转为 mm/mm 或 mm/rad
"""
if col_labels is None:
col_labels = ['ΔX', 'ΔY', 'ΔZ']
fig, ax = plt.subplots(figsize=(8, max(4, 0.5 * len(row_labels))))
display_mat = np.abs(sensitivity_matrix) * 1000 # m→mm
im = ax.imshow(display_mat, cmap='hot', aspect='auto')
ax.set_xticks(range(3))
ax.set_xticklabels(col_labels)
ax.set_yticks(range(len(row_labels)))
ax.set_yticklabels(row_labels)
ax.set_title('Sensitivity Heatmap per Direction\n'
'(mm/mm for length, mm/rad for angle)')
for r in range(display_mat.shape[0]):
for c in range(3):
val = display_mat[r, c]
ax.text(c, r, f'{val:.1f}', ha='center', va='center',
fontsize=9, color='white' if val > 500
else 'black')
plt.colorbar(im, ax=ax, shrink=0.8)
plt.tight_layout()
path = f"{self.results_dir}/04_sensitivity_heatmap.png"
plt.savefig(path, dpi=150)
plt.close()
return path
5. 可视化要求
序号 图表文件 服务结论
①
"01_sensitivity_bar.png" 各参数灵敏度柱状图,长度参数(mm/mm)与角度参数(mm/°)分色显示,标定优先级排序
②
"02_offset_vectors_3d.png" 各扰动下末端偏移向量3D箭头,展示误差传播方向随关节角旋转
③
"03_parameter_sweep.png" 单参数多步长扫描曲线,δ≥0半边线性拟合验证灵敏度系数
④
"04_sensitivity_heatmap.png" 灵敏度热力图(每参数×xyz方向),识别各方向最敏感参数
6. README使用说明 + 核心知识点卡片
快速开始
pip install -r requirements.txt
python -m pytest tests/test_dh_sensitivity.py -v # 3/3 测试通过
python run_dh_sensitivity.py # 运行参数扰动分析
数据格式说明
参数 修改位置 说明
D-H标称值
"Robot4DOFParams.default_4dof()" 中的links列表 修改a/d/alpha
关节角
"DHPerturbationAnalyzer(params, joint_angles)" 中的
"joint_angles" 模拟不同工作点
扰动定义
"DHPerturbation(link_index, param_name, delta)" delta单位m或rad
扫描范
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)