简介:面向工业视觉引导焊接与机器人轨迹规划交叉方向的研究人员和工程师,这份PDF系统讲解如何基于PyTorch实现三维点云配准,并与强化学习结合以优化焊接机器人轨迹规划。内容涵盖PyTorch基础、张量与自动求导、三维点云配准原理、常见配准算法、强化学习基础及DQN等算法,并给出整体架构设计、点云配准网络搭建、状态与奖励函数整合思路,以及相应代码实现、实验数据集和结果分析。资源以单个PDF文档提供,共30页,压缩包整体1.92MB,支持目录跳转与左侧大纲快速定位,便于按章节检索。文档从引言、技术概述到方法融合、实验与展望形成完整链条,还包含实验设计、评估指标和不同场景下的鲁棒性测试,可帮助读者理解实际部署中的关键难点与优化方向。目前已有68人学习,适合希望从理论到实践完整掌握工业视觉焊接项目技术路径的读者。
1. 焊接视觉引导里的 PyTorch 点云配准与强化学习,到底卡在哪
在一台新焊接工作站调试到第 27 天的视觉工程师,通常会碰到这样的事:3D 相机把焊缝扫出来了,点云配准跑得很快,ICP 结果也很干净,但机器人一焊上去,焊缝还是偏了 3 毫米。这个问题多半不在配准,而在配准和轨迹规划之间少了一个角度——用强化学习去修正轨迹,而不是追求把 CAD 和实际工件拟合到 0.1 毫米。工业视觉引导焊接里,PyTorch 三维点云配准负责回答“工件在哪”,强化学习负责回答“焊枪该往哪偏一点”,两者串起来才是一条完整的机器人轨迹规划链路。本文按这个顺序把模型选择、损失函数、奖励设计、闭环部署和现场参数讲清楚,中间给出可以直接改到自己项目里的代码和参数表。适合视觉算法、机器人集成和自动化产线侧的人看,新手能照步骤跑通最小循环,老手可以直接对照参数表和坑位做检查。
2. 用 PyTorch 实现三维点云配准:预处理、编码器与损失函数
2.1 为什么焊接场景不直接裸用 ICP,而要训练一个配准网络
焊接工件的 CAD 模型与相机实测点云之间通常有 5° 到 30° 的姿态差,同时工件边缘有反光、飞溅、夹具遮挡,ICP 很容易收敛到局部极小。学习式配准的本质是把“找初始值”也交给网络:网络从整片点云的局部结构估计一个比较稳的粗变换,再用小范围 ICP 或损失函数监督下的迭代精化,得到最终 R、t。在我接触的项目里,一般把配准网络的结果当作初始化,后续再用 10~20 轮 ICP 精配,而不是用网络一步到位。PyTorch 在处理这类问题上的优势在于,可以在一个框架里同时写点云编码器、变换回归和 Chamfer 损失,不需要另外调用 C++ 库。你也不用把整个流程做成端到端模型,拆成两段反而更容易排查问题:先看配准网络给的角度对不对,再看 ICP 精配有没有收敛。
2.2 一个能跑通的最小配准网络结构
下面是一个输入源点云 P 和目标点云 Q、输出刚体变换 R,t 的 PyTorch 网络。它用 PointNet 式 max pooling 提取全局特征,然后回归四元数和平移,最后在验证阶段把四元数转成旋转矩阵。
import torch import torch.nn as nn import torch.nn.functional as F class PointEncoder(nn.Module): """输入 [B, 3, N],输出 [B, 64] 的全局点云特征""" def __init__(self, out_dim=64): super().__init__() self.mlp = nn.Sequential( nn.Conv1d(3, 32, 1), nn.BatchNorm1d(32), nn.ReLU(), nn.Conv1d(32, 64, 1), nn.BatchNorm1d(64), nn.ReLU(), nn.Conv1d(64, out_dim, 1), ) def forward(self, x): return self.mlp(x).max(dim=-1)[0] class PointNetRegistration(nn.Module): """预测源点云到目标点云的刚体变换 delta[:3] 是平移残差 delta[3:] 是未归一化的四元数 """ def __init__(self): super().__init__() self.enc = PointEncoder(64) self.head = nn.Sequential( nn.Linear(128, 256), nn.ReLU(), nn.Linear(256, 7), # 3+4 ) def forward(self, src, tgt): f_src = self.enc(src) f_tgt = self.enc(tgt) delta = self.head(torch.cat([f_src, f_tgt], dim=-1)) t = delta[:, :3] q = F.normalize(delta[:, 3:], dim=-1) return t, q这段代码里,src和tgt的 shape 都是[batch_size, 3, num_points],为了统一输入,我会先对两个点云各取 2048 个点。网络头部分用 7 维输出,前三维是平移,后四维是四元数;在训练时可以直接用四元数算损失,在推理时再用四元数转矩阵的方式把 q 变成 3x3 旋转矩阵。把旋转参数化为四元数的好处是,不需要做矩阵正交化,梯度更新后只要执行F.normalize就能保证 R 的可逆性,而且比直接回归 9 维旋转矩阵少 2 个自由度,训练更容易收敛。
想在这里再接一个细配准,我一般会把这个网络输出的 R,t 作为初始位姿,再用 ICP 跑 20 轮迭代。不要只依赖网络输出,否则点云重叠率低于 70% 时误差会很大。如果是管线上的实时任务,可以把 ICP 放到 C++ 侧实现,PyTorch 网络只负责粗配准,两个进程之间用 gRPC 或共享内存传位姿矩阵。
2.3 损失函数:用 Chamfer 距离监督刚体变换
配准网络的输出是一个变换,但真实场景里没有“变换的标签”可直接监督,因此我们用变换后的源点云和目标点云之间的距离作为损失。下面是一个按 batch 写的双向 Chamfer 距离:
def chamfer_distance(transformed_src, tgt): # transformed_src: [B, N, 3] # tgt: [B, M, 3] B, N, _ = transformed_src.shape M = tgt.shape[1] src_exp = transformed_src.unsqueeze(2) # [B, N, 1, 3] tgt_exp = tgt.unsqueeze(1) # [B, 1, M, 3] dist = torch.sum((src_exp - tgt_exp) ** 2, dim=-1) # [B, N, M] dist_src_to_tgt = dist.min(dim=-1)[0].mean(dim=-1) dist_tgt_to_src = dist.min(dim=-2)[0].mean(dim=-2) return (dist_src_to_tgt + dist_tgt_to_src) / 2这个损失的计算复杂度是 O(N * M),N 取 2048、M 取 2048 时,显存占用会到 512 MB 左右,因为要存 2048x2048 的距离矩阵。如果显存不够,我一般把点数降到 512,或者用torch.cdist后只取最小值,再或者用下采样后的关键点计算。双向 Chamfer 比单向严格:它同时要求变换后的源点云中的每一个点都能在目标中找到对应,目标点云中的点也必须在源中有对应,这能避免网络把某个局部“藏”起来导致的结果骗分。你还可以在这个损失后面加一个旋转矩阵的正交性惩罚项,虽然四元数天然满足单位长度,但如果是拆成两个网络分支输出的,建议补一个torch.norm(R @ R.T - I)。
训练数据的生成是另一个关键。我常用 CAD 模型均匀采样出 2048 个点作为源点云,然后在真实传感器点云或仿真点云中随机抠出目标点云,叠加 3 到 8mm 随机平移和 5 到 25 度随机旋转。合成数据要加入噪声和离群点,否则网络学到的只是理想形状匹配。在焊接任务里还要对着焊缝区域局部采样,否则网络会对整个工件平均对齐,导致焊缝局部偏差被磨平。
2.4 训练参数表和现场最容易踩的三个坑
下面这组参数是我在一个车身结构件焊接项目上的调试起点,可以直接抄去对比:
| 参数 | 数值 / 范围 | 说明 |
|---|---|---|
| 输入点云点数 | 2048 | 采样不足会丢焊缝局部几何 |
| 体素下采样尺寸 | 2 mm | 传感器点云密度大了先滤波 |
| 学习率 | 1e-4 | Adam 默认 betas 即可 |
| batch size | 8 | 2080Ti 上勉强跑得动 |
| Chamfer 距离权重 | 1.0 | 平移与旋转统一在一个度量里 |
| 旋转损失权重 | 0.1 | 防止角度误差被 Chamfer 平均稀释 |
| 迭代配准轮数 | 20 | 网络输出后加 ICP 精配 |
三个坑:第一,不要直接拿原始传感器点云当输入,它每帧有几十万个点,直接进网络不但慢,而且密度不同会让特征编码偏向稀疏区域;先用体素滤波,把体素尺寸定在 2mm 左右,既保留焊缝形状又减少计算量。第二,点云配准的 Rotation 与 Translation 在损失里的尺度差异很大,如果只用一个 Chamfer 距离,网络会优先优化平移,旋转误差会长期卡在 5° 以上,需要给旋转加一个单独的 geodesic 损失。第三,在焊接场景里有飞溅和烟尘,目标点云常有离群点,如果 Chamfer 对所有点做 hard min,离群点会绑架训练,最好的办法是先对最近邻距离做截断,只让距离小于 5mm 的点参与反向传播。
3. 用强化学习做焊接轨迹规划的建模:状态、奖励、TD3 策略
3.1 为什么焊接轨迹修正选择强化学习而不是纯 PID 或路径重规划
焊接机器人需要沿着一条参考轨迹运动,但这个参考轨迹通常离线示教或从 CAD 生成,工件每批都有热变形、夹持误差和装配间隙。传统做法是根据传感器实时反馈,在控制器里加一个 PID 纠偏,但焊接路径是多维的,纠偏量与焊缝偏差之间是非线性、有滞后、带惯性的,很难用固定增益覆盖所有姿态。强化学习在这里学的是一个“策略”,也就是从当前焊缝局部特征和位姿误差到焊枪修正量的映射。常见的替代方案是重新做路径规划,但路径规划在高频工况下太慢,而且焊接场景里的障碍相对固定,没必要每次重新规划。
一个反直觉的结论是:在焊接任务里强化学习不需要学整条轨迹,只需要学“修正量”。完整轨迹仍由示教或离线规划生成,强化学习策略叠加在轨迹之上,输出一个小的偏移。这样既保留了传统轨迹规划的稳定性,又让系统具备了应对工件变形的能力。我看到的很多失败案例,都是试图让 RL 直接生成关节空间轨迹,结果动作爆炸,硬件根本不敢接。
3.2 状态和动作定义:让策略看到配准结果和焊枪姿态
我通常把强化学习策略的输入设计成三个部分拼接:当前机器人关节角(或者焊枪位姿)、与参考轨迹的偏差(来自视觉配准)、局部点云的几何特征(用上一个 PointNet 编码器输出)。动作是下一时刻焊枪在刀具坐标系下的 x、y、z 平移修正量和绕工具 z 轴的角度修正量,共 4 维连续动作。
| 状态分量 | 维度 | 说明 |
|---|---|---|
| 当前焊枪位置 T_base_to_tool | 3 | 基座坐标系下的位置 |
| 当前焊枪姿态欧拉角 | 3 | 用绕 z/y/x 的顺序 |
| 参考轨迹点与视觉配准偏差 | 6 | 位置 + 姿态偏差 |
| 局部焊缝点云特征 | 64 | PointNet 编码结果 |
| 上一时刻动作 | 4 | 用于平滑惩罚 |
动作空间 4 维、-0.05 到 0.05 米,旋转 -0.02 到 0.02 弧度。需要限制动作幅度,否则训练刚开始策略会乱跳,容易焊穿。状态向量里欧拉角部分要特别注意单位统一,很多策略训练到最后不收敛,是因为角度用了度、位置用了米,数值量纲不在一个尺度,梯度更新时角度变化被位置变化覆盖。我一般把欧拉角先除以 180/pi 变成弧度,再整体做一次标准化。
3.3 奖励函数:让策略明白“贴得近”更重要,还是“焊得稳”更重要
一个实用的奖励函数可以返回四个部分的叠加,并且对碰撞做硬惩罚。下面是一个在 PyTorch 里用批处理的写法:
def weld_reward(next_err, action, prev_action, collision): # next_err: 动作执行后测得的参考轨迹偏差 # prev_err: 上一时刻偏差,这里简化为直接使用 next_err 与阈值比较 err_penalty = torch.norm(next_err, dim=-1) - 0.8 smooth_penalty = 0.01 * torch.sum((action - prev_action) ** 2, dim=-1) collision_penalty = 10.0 * collision reward = -err_penalty - smooth_penalty - collision_penalty return reward这里面用偏差变化量而不是绝对值,是为了让策略在接近参考轨迹时拿到正向奖励,在远离时拿到负向奖励,避免一个本来就偏移很大的初始状态导致 reward 全部是负的。平滑惩罚系数 0.01 不要太低,太低会让动作抖,焊枪末端抖动会直接影响熔池。碰撞惩罚 10.0 只是起始值,实际落地时我建议把它设成 50 以上,让策略在仿真里先学会安全,再学会焊接。
还可以加入一个“焊缝停留时间”奖励:如果策略在目标点附近停留超过设定帧数,就额外给一个小奖励,这能避免策略为了减少误差快速抖动,反而导致焊点位置偏移累积。
3.4 TD3 的 Actor 与 Critic 实现,以及训练参数
我倾向于用 TD3 而不是 PPO 来做机器人轨迹修正,因为动作空间是低维连续的,TD3 的确定策略梯度训练更快。Actor 输出确定性动作,Critic 输出 Q 值。
class TD3Actor(nn.Module): def __init__(self, state_dim, action_dim, max_action=0.05): super().__init__() self.net = nn.Sequential( nn.Linear(state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, action_dim), nn.Tanh() ) self.max_action = max_action def forward(self, state): # 输出限制在 [-max_action, max_action] return self.net(state) * self.max_actionTD3 的 Critic 是两个孪生 Q 网络,取 min 来抑制过估计。训练时最需要注意的是 reward scale,如果奖励里欧拉角和位置的数值量纲不统一,策略会忽略小维度的动作。我一般会把位置误差控制在米级、姿态误差转成弧度,并在状态输入里做归一化。
| 参数 | 值 | 说明 |
|---|---|---|
| replay buffer | 200k | 焊接过程数据宝贵,存多一些 |
| learning rate | 3e-4 | Actor 与 Critic 同 |
| discount factor | 0.99 | 焊接任务属于短 horizon,可改 0.95 |
| policy noise | 0.05 | 训练时加在动作上 |
| delay update | 2 | 每两个 critic 更新一次 actor |
| batch size | 128 | 正常 |
3.5 训练初期最常见的失败:动作爆掉和局部最优
焊接轨迹修正任务训练时如果 reward 一直不涨,先看 action 是否在合理范围。如果 Tanh 输出的动作被乘以一个大的 max_action,策略会从大探索开始,焊枪可能直接扎进工件。另一个常见失败是策略只会“往一个方向偏”,因为它发现朝固定方向动可以稍微降低平均误差。我处理这个问题的办法是在仿真里把初始偏差分布从 ±2mm 逐步扩大到 ±6mm,并随机翻转扫地方向。对于 TD3,还要注意 target policy smoothing 的噪声范围,太大会让 critic 训练不稳定,太小则起不到平滑作用。
4. 配准与强化学习结合时的闭环流程与联动部署
4.1 闭环中的三个层级:传感器、配准网络、轨迹修正策略
在实际焊接视觉引导系统里,我不会让强化学习直接输出关节角,而是把它放在配准网络之后、机器人控制器之前。整个闭环每次循环的流程是:3D 相机或线激光传感器取焊缝点云,点云经过体素滤波和区域提取后送入 PyTorch 配准网络得到工件实际位姿,与参考位姿相减得到位姿偏差,然后由 RL 策略把偏差映射成焊枪修正量,最后通过机器人控制器坐标下发。这个结构的好处是各模块可以独立调试。
从频率角度看,传感器、配准网络、RL 策略、机器人控制器各自的工作频率差一个数量级。传感器大约 10Hz,配准网络在 CPU 上跑大约 100ms,RL 策略 5ms,机器人控制循环 100Hz 到 1000Hz。所以实际下发的修正量频率不会高于传感器帧率,这时候要么用线激光只做局部轮廓配准提升到 50Hz,要么在控制器里做一次线性插值,把低频的修正量平滑成高频的增量。很多产线项目没做插值,导致机器人运动一顿一顿的,焊接质量反而更差。
4.2 误差如何在闭环里传播,以及策略如何容忍配准误差
配准网络的 RTE 是 0.5mm,但下游的强化学习策略训练时看到的输入是位姿偏差,不是原始点云,因此配准误差会被策略误认为真实工件误差。为了减小这种误差耦合,我会在状态向量里额外加入配准输出的点云重叠率作为置信度;重叠率低时,策略被允许做出更保守的修正。同时在训练强化学习策略时,对配准输出的偏差加一点随机噪声,让策略对 0.5mm 到 2mm 的配准波动都保持耐受。这个噪声叫做仿真偏置,很多项目里策略在仿真跑得很好、现场就崩,原因就是没有对配准置信度建模。
具体实现时,可以在每次仿真步进中这样加噪声:
pose_error = get_registered_error() noise = torch.randn_like(pose_error) * 0.001 # 1mm 标准差 state = build_state(pose_error + noise, obs_confidence)这里的 0.001 米即 1mm,刚好覆盖配准网络的 RTE 波动范围。如果现场配准误差更差,就把它放大到 2mm。策略只要在训练时见过不同大小的误差,部署时就不至于因为一个突发的飞溅点云产生剧烈修正。
4.3 一个真实可跑的联动控制循环,把配准和 RL 串起来
下面这段代码把配准网络和强化学习策略串起来,使用 PyTorch 的no_grad控制推理时内存占用:
while running: # 1. 获取当前帧点云 cloud = sensor.get_point_cloud() cloud = voxel_filter(cloud, voxel_size=0.002) # 2. 用配准网络估计工件位姿 src = sample_points(cad_model, 2048) tgt = sample_points(cloud, 2048) with torch.no_grad(): t_rel, q_rel = reg_net(src, tgt) R_rel = quaternion_to_matrix(q_rel) # 3. 计算与参考位姿的偏差 pose_error = compute_pose_error(t_rel, R_rel, ref_pose) # 4. 构建强化学习策略输入 state = torch.cat([ joint_pos, pose_error, local_feature, prev_action ], dim=-1) # 5. 策略输出焊接修正量 with torch.no_grad(): correction = actor(state, deterministic=True) # 6. 下发到机器人 controller.set_pose(ref_pose + correction) prev_action = correction这里每一步都是异步的。传感器大概 10Hz,配准网络如果用 CPU 推理需要 100ms 左右,RL 策略 5ms,所以真正下发给机器人的频率受传感器限制。如果我需要更高频控制,就把点云换成线激光的一小段轮廓,只对局部焊缝做配准,这样循环周期可以压到 10ms 以内。注意控制器下发的是位置增量,所以每次要从当前实际位姿推补偿,而不是直接设到一个绝对位姿。
4.4 配准精准但焊接仍然偏位时,检查这四个地方
第一,参考轨迹是否建立在 CAD 模型上,而不是建立在同一块工件的实测坐标上。第二,坐标系的传递:相机标定矩阵、机器人基座到工具坐标系、配准网络输出都在哪个坐标系。第三,强化学习策略的训练扰动范围和现场实际偏差范围是否一致。第四,机器人控制器里有没有速度前瞻和姿态平滑,如果平滑参数过大,动作即使正确也会被过滤掉。这四类问题里,坐标系问题占比最高,尤其是相机标定时把工件坐标系和工具坐标系混用一个变换矩阵,会导致配准结果完全不可用。
5. 部署现场要盯的验证指标和显存优化技巧
5.1 用四个指标判断配准与强化学习修正是否有效
现场评估时我不会只看配准的 RTE,还要看焊缝整体走势。上线前至少记录四个指标:配准成功率(连续 100 次,位姿误差小于 1°、1mm 的比例)、焊接轨迹平均误差(实际轨迹与目标轨迹的均值)、轨迹抖动率(修正量高频噪声方差)和单帧推理耗时。配准成功率盯的是视觉端,轨迹平均误差盯的是强化学习端,抖动率盯的是奖励函数里的平滑惩罚是否起作用。
| 指标 | 建议基准 | 说明 |
|---|---|---|
| 配准成功率 | ≥95% | 阈值 1°, 1mm |
| 轨迹平均误差 | ≤0.8mm | 超过则优先调奖励 |
| 轨迹抖动率 | <2% 动作方差 | 过大说明平滑惩罚太小 |
| 单帧配准耗时 | <120ms | CPU 推理,包含 ICP |
5.2 显存不足时的降级方案
工业现场的 GPU 不一定是 RTX 4090,很多工控机只有 6GB 显存。遇到显存不够,我一般先做三件事:把输入点云从 2048 降到 1024,并把 Chamfer 距离的 M 维改成用最远点采样;在损失矩阵计算中改用torch.cdist(..., compute_mode='donot_use_mm_for_euclid_dist')减少显存峰值;训练时使用torch.autocast混合精度,配准神经网络规模小,loss 缩放不需要额外处理。推理阶段则把模型转成 TorchScript 或 ONNX,去掉 BatchNorm 的统计分支后显存占用大概会下降 40%,推理速度也能从 100ms 降到 60ms 左右。
5.3 一个常被忽略但影响焊接效果的关键:点云坐标系的传递顺序
最后给出一个我在现场调参时验证过的技巧:配准网络输出的变换是“相机坐标系下的源点云到目标点云”,而强化学习策略需要的偏差是“机器人基座坐标系下的焊枪位姿偏差”。因此在把配准结果送入策略之前,要在外部矩阵T_base_to_cam的坐标系下做一次预变换,不能在网络输出的局部坐标系里直接减位姿。把这个变换矩阵放在主循环最前面,并且每次机器人末端运动之后都更新。
T_base_to_obj = T_base_to_cam @ T_cam_to_obj pose_error_in_base = decompose(T_base_to_obj)这段代码虽然简单,但很多项目焊偏 3 毫米以上,都是因为忽略了工具坐标系和基座坐标系的顺序。
本文还有配套的精品资源,点击获取