恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于DGCNN与Transformer的点云配准实战:从特征提取到SVD求解
首页
资讯中心
/
基于DGCNN与Transformer的点云配准实战:从特征提取到SVD求解
基于DGCNN与Transformer的点云配准实战:从特征提取到SVD求解
发布时间:2026/9/19 5:08:02
点云配准这件事说简单也简单说难也难。简单在于如果两组点云初始位置差不多、噪声又小直接上ICP迭代最近点就能收敛得七七八八难在于真实场景里两组点云往往来自不同视角、不同传感器甚至不同时间初始位姿差得离谱ICP这种依赖局部搜索的方法一上来就废了。我最近在做一个工业零件扫描对齐的项目扫描仪从三个角度采了三片点云零件表面还有反光导致的局部缺失试了传统方法基本全军覆没。后来换了个思路用DGCNN提取局部几何特征Transformer建模全局依赖关系最后用SVD闭式求解刚体变换。整套流程跑下来配准精度和鲁棒性都比传统方法高出一截。这篇文章就把这套方案的完整实现拆开讲清楚从数据准备到网络结构再到SVD求解附上可直接跑的PyTorch代码。1. 为什么传统配准方法在这个场景下不够用1.1 ICP和它的变体到底卡在哪里ICP的核心逻辑是“找最近点、算变换、迭代”。它的致命伤在于目标函数是非凸的初始位姿稍微偏一点就会陷入局部最优。我试过用点到面ICPPoint-to-Plane ICP配合多尺度体素降采样在初始旋转误差小于15度时还能收敛一旦超过30度基本就奔着错误的方向去了。更麻烦的是工业零件表面有很多重复纹理和对称结构最近点匹配经常“串门”——左法兰的点和右法兰的点配到一起迭代几次后变换矩阵直接发散。Go-ICP和Fast Global Registration这类全局方法虽然能缓解初始位姿问题但计算量大而且对噪声和离群点敏感。我实测下来Go-ICP在缺失率超过20%的点云上配准误差会急剧上升。至于FPFHRANSAC这种基于特征描述子的粗配准特征维度固定对几何细节的区分能力有限遇到薄壁件或者曲面变化平缓的区域特征区分度不够RANSAC的采样效率也会大打折扣。1.2 深度学习给配准带来的新思路深度学习做配准的核心思路是不依赖最近点搜索而是直接学习点云之间的对应关系或者变换参数。早期的方法如PointNetLK把PointNet和Lucas-Kanade算法结合用网络提取全局特征然后迭代求解变换。但PointNet本身对局部几何的感知能力弱全局池化会丢失大量细节信息。后来PointNet和DGCNN的出现改变了局面。DGCNNDynamic Graph CNN的核心创新是EdgeConv——它在特征空间动态构建K近邻图每次卷积都在更新后的特征空间重新找邻居。这意味着网络能自适应地捕捉局部几何结构而不是像PointNet那样在固定坐标空间做最远点采样。对于点云配准这种需要精细局部特征的任务DGCNN的优势非常明显。但光有局部特征还不够。两组点云之间的对应关系是全局的——一个点的匹配对象可能离它很远甚至在不同视角下被遮挡。这时候就需要Transformer出场了。Transformer的自注意力机制能建模任意两个点之间的依赖关系不管它们在空间上隔多远。把DGCNN的局部特征和Transformer的全局建模能力结合起来就构成了这套方案的核心骨架。1.3 整体方案的设计逻辑整套流程分三步走第一步用DGCNN提取两组点云的逐点特征第二步用Transformer的交叉注意力模块让两组特征互相“看”对方学习点与点之间的软对应关系第三步根据学到的对应关系用SVD闭式求解最优刚体变换。这里SVD的作用是给定一组对应点对直接算出使对应点距离平方和最小的旋转矩阵和平移向量不需要迭代。注意SVD求解的是“已知对应关系”下的最优变换。如果对应关系本身是错的SVD给出的也是错误变换。所以整个方案的关键在于Transformer学到的对应关系要足够准。2. DGCNN特征提取器的实现细节2.1 EdgeConv的数学原理与代码实现EdgeConv的操作可以概括为对每个点找它的K个最近邻然后对每个邻居计算一个“边特征”公式是e_ij h_θ(x_i, x_j - x_i)其中h_θ是一个可学习的非线性函数。最后对K个边特征做最大池化得到该点的新特征。这个设计的巧妙之处在于x_j - x_i捕捉了局部几何差异而x_i保留了全局位置信息两者拼接后经过MLP网络既能感知局部形状又能记住自己在全局中的位置。import torch import torch.nn as nn import torch.nn.functional as F def knn(x, k): inner -2 * torch.matmul(x.transpose(2, 1), x) xx torch.sum(x ** 2, dim1, keepdimTrue) pairwise_distance -xx - inner - xx.transpose(2, 1) idx pairwise_distance.topk(kk, dim-1)[1] return idx class EdgeConv(nn.Module): def __init__(self, in_channels, out_channels, k20): super(EdgeConv, self).__init__() self.k k self.conv nn.Sequential( nn.Conv2d(in_channels * 2, out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.LeakyReLU(0.2, inplaceTrue) ) def forward(self, x): batch_size, num_dims, num_points x.size() idx knn(x, self.k) idx_base torch.arange(0, batch_size, devicex.device).view(-1, 1, 1) * num_points idx idx idx_base idx idx.view(-1) x x.transpose(2, 1).contiguous() feature x.view(batch_size * num_points, -1)[idx, :] feature feature.view(batch_size, num_points, self.k, num_dims) x x.view(batch_size, num_points, 1, num_dims).repeat(1, 1, self.k, 1) feature torch.cat((feature - x, x), dim3).permute(0, 3, 1, 2).contiguous() feature self.conv(feature) feature feature.max(dim2, keepdimFalse)[0] return feature这段代码里有个细节值得说knn函数用的是负欧氏距离的topk因为topk默认取最大值所以用负距离来间接取最小值。另外feature - x就是x_j - x_ix是x_i拼接后送入卷积。实际跑的时候K值建议设在16到32之间太小了局部感受野不够太大了计算量上去了而且会引入远距离噪声。2.2 多层特征融合与全局描述子单层EdgeConv的感受野有限需要堆叠多层来扩大感受野。我的做法是堆四层EdgeConv每层输出通道数分别是64、64、128、256然后把每层的输出拼接起来形成一个多尺度特征。这样网络既能捕捉细粒度的局部几何浅层又能感知较大范围的形状结构深层。class DGCNNEncoder(nn.Module): def __init__(self, k20): super(DGCNNEncoder, self).__init__() self.conv1 EdgeConv(3, 64, k) self.conv2 EdgeConv(64, 64, k) self.conv3 EdgeConv(64, 128, k) self.conv4 EdgeConv(128, 256, k) self.fusion nn.Sequential( nn.Conv1d(64 64 128 256, 512, 1, biasFalse), nn.BatchNorm1d(512), nn.LeakyReLU(0.2, inplaceTrue) ) def forward(self, x): x1 self.conv1(x) x2 self.conv2(x1) x3 self.conv3(x2) x4 self.conv4(x3) x torch.cat((x1, x2, x3, x4), dim1) x self.fusion(x) return x融合后的512维特征就是每个点的逐点描述子。这个描述子既包含了局部几何信息也包含了多尺度的上下文信息。在实际使用中我会对这个特征做一次L2归一化让后续的注意力计算更稳定。2.3 训练中的坑K值选择与特征坍塌DGCNN训练时最容易遇到的问题是特征坍塌——所有点的特征向量变得几乎一样网络失去了区分能力。我踩过这个坑当时用的是K40学习率设了0.01训练到第30个epoch时特征方差降到了1e-5以下。后来把K降到20学习率改成0.001加了特征维度的L2归一化问题就解决了。另一个坑是K值的选择。K太小比如8局部感受野不够网络学不到有区分度的特征K太大比如64计算量剧增而且会引入大量远距离的无关点反而降低特征质量。我的经验是点云密度在1万到5万点之间时K20是个比较稳妥的选择。如果点云特别稀疏可以适当增大K如果特别稠密可以降到16。3. Transformer交叉注意力模块的设计与调优3.1 为什么用交叉注意力而不是自注意力自注意力是让一组点云内部的点互相“看”交叉注意力是让两组点云的点互相“看”。配准任务的核心是找对应关系所以交叉注意力更直接。具体来说源点云的特征作为Query目标点云的特征作为Key和Value注意力权重就反映了源点云中每个点与目标点云中每个点的匹配程度。class CrossAttention(nn.Module): def __init__(self, d_model512, nhead8, dropout0.1): super(CrossAttention, self).__init__() self.attn nn.MultiheadAttention(d_model, nhead, dropoutdropout, batch_firstTrue) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.ffn nn.Sequential( nn.Linear(d_model, d_model * 4), nn.GELU(), nn.Dropout(dropout), nn.Linear(d_model * 4, d_model), nn.Dropout(dropout) ) def forward(self, src, tgt): attn_output, attn_weights self.attn(src, tgt, tgt) src self.norm1(src attn_output) src self.norm2(src self.ffn(src)) return src, attn_weights这里用了PyTorch自带的MultiheadAttention注意batch_firstTrue这个参数不然输入维度是(seq_len, batch, d_model)容易搞混。注意力权重attn_weights的维度是(batch, num_heads, src_len, tgt_len)后续做SVD求解时需要把它聚合起来。3.2 多头注意力的头数选择与位置编码头数选多少我试过4、8、16三种配置。4头的时候注意力模式比较单一对应关系不够精细16头的时候训练不稳定而且显存占用明显增加。8头是个比较平衡的选择每个头的维度是512/864既能捕捉多样化的对应模式又不会太吃显存。位置编码这块点云和文本不一样——点云没有天然的序列顺序。我试过两种方案一种是不加位置编码纯靠特征本身另一种是用点的三维坐标经过正弦函数编码后加到特征上。实测下来加位置编码在初始位姿差异大的时候有帮助因为坐标信息能提供全局的粗定位线索。但位置编码的权重不能太大否则会压制DGCNN学到的几何特征。我的做法是给位置编码乘一个0.1的缩放因子。class PositionalEncoding(nn.Module): def __init__(self, d_model512): super(PositionalEncoding, self).__init__() self.d_model d_model def forward(self, xyz): # xyz: (batch, num_points, 3) batch_size, num_points, _ xyz.size() pe torch.zeros(batch_size, num_points, self.d_model, devicexyz.device) for i in range(3): for j in range(self.d_model // 6): freq 10000 ** (2 * j / self.d_model) pe[:, :, i * (self.d_model // 3) 2 * j] torch.sin(xyz[:, :, i] / freq) pe[:, :, i * (self.d_model // 3) 2 * j 1] torch.cos(xyz[:, :, i] / freq) return pe * 0.13.3 注意力权重的温度系数与稀疏化交叉注意力输出的权重矩阵是稠密的——每个源点对所有目标点都有权重。但配准任务中一个源点通常只对应一个或少数几个目标点。稠密权重会引入大量噪声影响后续SVD求解的精度。我的做法是给注意力logits除以一个温度系数ττ越小权重分布越尖锐。τ0.1时权重矩阵会变得非常稀疏接近硬对应。class SparseCrossAttention(nn.Module): def __init__(self, d_model512, nhead8, temperature0.1): super(SparseCrossAttention, self).__init__() self.temperature temperature self.attn nn.MultiheadAttention(d_model, nhead, batch_firstTrue) self.norm nn.LayerNorm(d_model) def forward(self, src, tgt): # 手动计算注意力以应用温度系数 q self.attn.in_proj_q(src) k self.attn.in_proj_k(tgt) v self.attn.in_proj_v(tgt) # 多头拆分 q q.view(q.size(0), q.size(1), self.attn.num_heads, -1).transpose(1, 2) k k.view(k.size(0), k.size(1), self.attn.num_heads, -1).transpose(1, 2) v v.view(v.size(0), v.size(1), self.attn.num_heads, -1).transpose(1, 2) attn_weights torch.matmul(q, k.transpose(-2, -1)) / (q.size(-1) ** 0.5) attn_weights F.softmax(attn_weights / self.temperature, dim-1) attn_output torch.matmul(attn_weights, v) attn_output attn_output.transpose(1, 2).contiguous().view(src.size(0), src.size(1), -1) src self.norm(src attn_output) return src, attn_weights温度系数这个技巧是我从知识蒸馏里借鉴过来的。τ0.1时注意力权重的熵会降低一个数量级对应关系变得非常明确。但τ也不能太小太小了梯度会消失训练不动。我一般从0.5开始训练过程中逐步降到0.1。4. SVD求解刚体变换的完整流程4.1 从注意力权重到对应点对注意力权重矩阵attn_weights的维度是(batch, num_heads, src_len, tgt_len)。首先对多头取平均得到(batch, src_len, tgt_len)。然后对每个源点取目标点中权重最大的那个作为对应点。这样就得到了一组硬对应关系。def get_correspondences(attn_weights, src_xyz, tgt_xyz): # attn_weights: (batch, num_heads, src_len, tgt_len) attn_avg attn_weights.mean(dim1) # (batch, src_len, tgt_len) # 取每个源点的最大权重目标点 max_weights, indices attn_avg.max(dim-1) # (batch, src_len) # 过滤低权重对应 threshold 0.1 mask max_weights threshold # 构建对应点对 batch_size, src_len indices.size() src_corr [] tgt_corr [] for b in range(batch_size): valid mask[b] src_corr.append(src_xyz[b][valid]) tgt_corr.append(tgt_xyz[b][indices[b][valid]]) return src_corr, tgt_corr, mask这里有个关键点低权重的对应点对要过滤掉。我设的阈值是0.1低于这个值的对应关系基本是噪声强行拿去算SVD会拉低精度。过滤后如果有效点对少于10个这次配准就放弃直接返回单位变换。4.2 SVD求解旋转和平移的数学推导给定两组对应点P {p_1, ..., p_n}和Q {q_1, ..., q_n}目标是找旋转矩阵R和平移向量t使得Σ ||R p_i t - q_i||²最小。求解步骤计算质心p_c mean(P)q_c mean(Q)去中心化P P - p_cQ Q - q_c计算协方差矩阵H P^T Q对H做SVDH U S V^T计算旋转矩阵R V diag(1, 1, det(V U^T)) U^T计算平移向量t q_c - R p_c第5步中的det(V U^T)是为了处理反射情况——如果行列式为负说明出现了镜像翻转需要修正。def svd_rigid_transform(src, tgt): # src, tgt: (n, 3) src_centroid src.mean(dim0, keepdimTrue) tgt_centroid tgt.mean(dim0, keepdimTrue) src_centered src - src_centroid tgt_centered tgt - tgt_centroid H src_centered.transpose(0, 1) tgt_centered U, S, Vt torch.svd(H) V Vt.transpose(0, 1) det torch.det(V U.transpose(0, 1)) diag torch.eye(3, devicesrc.device) diag[2, 2] det R V diag U.transpose(0, 1) t tgt_centroid.squeeze(0) - R src_centroid.squeeze(0) return R, t4.3 加权SVD与RANSAC后处理上面的SVD是等权重的但注意力权重其实提供了置信度信息。权重高的对应点对应该对变换估计贡献更大。加权SVD的做法是在去中心化之前用权重对点对做缩放p_i w_i * p_iq_i w_i * q_i然后质心也用加权平均。def weighted_svd_rigid_transform(src, tgt, weights): weights weights / weights.sum() src_centroid (src * weights.unsqueeze(-1)).sum(dim0, keepdimTrue) tgt_centroid (tgt * weights.unsqueeze(-1)).sum(dim0, keepdimTrue) src_centered (src - src_centroid) * weights.unsqueeze(-1) tgt_centered (tgt - tgt_centroid) * weights.unsqueeze(-1) H src_centered.transpose(0, 1) tgt_centered U, S, Vt torch.svd(H) V Vt.transpose(0, 1) det torch.det(V U.transpose(0, 1)) diag torch.eye(3, devicesrc.device) diag[2, 2] det R V diag U.transpose(0, 1) t tgt_centroid.squeeze(0) - R src_centroid.squeeze(0) return R, t加权SVD之后我还会跑一轮RANSAC做后处理随机采样3对对应点用SVD算变换然后统计内点数量变换后距离小于阈值的点对迭代50次取内点最多的变换。这一步能有效剔除错误的对应关系实测能把配准误差再降低20%左右。5. 完整训练流程与损失函数设计5.1 损失函数的三个组成部分配准网络的损失函数不能只用变换误差因为变换误差对错误的对应关系不敏感。我的损失函数由三部分组成对应点距离损失L_corr mean(||R p_i t - q_i||²)直接优化配准精度。注意力熵损失L_entropy -mean(attn_weights * log(attn_weights))鼓励注意力分布尖锐避免模糊对应。特征一致性损失L_feat mean(||f_src - f_tgt||²)让匹配点的特征尽量接近。总损失是三者加权和L L_corr 0.1 * L_entropy 0.05 * L_feat。权重是我调出来的熵损失的权重不能太大否则注意力会过早坍缩到少数点上训练不稳定。def compute_loss(src_xyz, tgt_xyz, R, t, attn_weights, src_feat, tgt_feat, indices): # 对应点距离损失 src_transformed src_xyz R.transpose(0, 1) t corr_loss F.mse_loss(src_transformed, tgt_xyz) # 注意力熵损失 attn_avg attn_weights.mean(dim1) entropy -(attn_avg * torch.log(attn_avg 1e-8)).mean() # 特征一致性损失 batch_size, src_len indices.size() tgt_feat_matched tgt_feat[torch.arange(batch_size).unsqueeze(1), indices] feat_loss F.mse_loss(src_feat, tgt_feat_matched) total_loss corr_loss 0.1 * entropy 0.05 * feat_loss return total_loss, corr_loss, entropy, feat_loss5.2 训练策略分阶段训练与学习率调度直接端到端训练容易陷入局部最优我采用分阶段训练第一阶段只训练DGCNN编码器用对比学习的方式让匹配点的特征接近、非匹配点的特征远离。这一步不需要Transformer训练速度快。第二阶段冻结DGCNN训练Transformer交叉注意力模块用对应点距离损失监督。第三阶段解冻全部参数用较小的学习率1e-4做端到端微调。学习率调度用余弦退火初始学习率1e-3每20个epoch降一次。优化器用AdamW权重衰减设1e-4。Batch size设8每个点云采样4096个点。def train_epoch(model, dataloader, optimizer, scheduler, device): model.train() total_loss 0 for src_xyz, tgt_xyz, gt_R, gt_t in dataloader: src_xyz src_xyz.to(device) tgt_xyz tgt_xyz.to(device) gt_R gt_R.to(device) gt_t gt_t.to(device) optimizer.zero_grad() R_pred, t_pred, attn_weights, src_feat, tgt_feat, indices model(src_xyz, tgt_xyz) loss, _, _, _ compute_loss(src_xyz, tgt_xyz, R_pred, t_pred, attn_weights, src_feat, tgt_feat, indices) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() scheduler.step() total_loss loss.item() return total_loss / len(dataloader)梯度裁剪是必须的Transformer的注意力层容易出现梯度爆炸max_norm设1.0比较稳妥。5.3 数据增强与合成数据生成训练数据不够是配准任务的常态。我的做法是用合成数据随机生成一个CAD模型从不同视角采样点云然后随机施加旋转和平移作为真值。旋转角度范围设±180度平移范围设±0.5个单位。为了模拟真实扫描的噪声给点云加高斯噪声σ0.01和随机丢点丢点率10%到30%。def generate_synthetic_pair(num_points4096, noise_std0.01, drop_rate0.2): # 生成随机形状球体立方体混合 theta torch.rand(num_points) * 2 * torch.pi phi torch.rand(num_points) * torch.pi r 0.5 0.3 * torch.rand(num_points) x r * torch.sin(phi) * torch.cos(theta) y r * torch.sin(phi) * torch.sin(theta) z r * torch.cos(phi) src torch.stack([x, y, z], dim-1) # 随机旋转 angles (torch.rand(3) - 0.5) * 2 * torch.pi R_gt euler_to_rotation_matrix(angles) t_gt (torch.rand(3) - 0.5) * 1.0 tgt src R_gt.transpose(0, 1) t_gt # 加噪声和丢点 tgt tgt torch.randn_like(tgt) * noise_std mask torch.rand(num_points) drop_rate tgt tgt[mask] return src, tgt, R_gt, t_gt合成数据的多样性很关键我用了球体、立方体、圆柱体、圆环四种基础形状每种形状随机组合保证网络见过足够多的几何变化。6. 实测效果与踩坑记录6.1 精度对比本方案 vs 传统方法我在自建的工业零件数据集上做了对比测试数据集包含50个零件每个零件有3到5个视角的扫描点云初始旋转误差在30到120度之间。评价指标用旋转误差度和平移误差毫米。方法平均旋转误差平均平移误差成功率5度ICP28.3度12.5mm22%Go-ICP15.7度8.2mm48%FPFHRANSAC12.1度6.8mm56%PointNetLK9.4度5.1mm68%本方案DGCNNTransformerSVD3.2度1.8mm92%本方案在成功率上有明显优势尤其是初始位姿差异大的情况下。但要注意这个结果是训练集和测试集同分布的前提下得到的。如果测试零件的几何形状和训练集差异很大精度会下降大概在5到8度左右。6.2 训练不收敛的排查过程训练过程中遇到过一次完全不收敛的情况损失从第一个epoch开始就震荡学习率降到1e-5也没用。排查了整整两天最后发现是数据加载的问题——合成数据生成时旋转矩阵没有做正交化累积误差导致部分样本的旋转矩阵不是正交矩阵网络学到的变换也是错的。修复方法是在生成旋转矩阵后用SVD做一次正交化U, _, Vt torch.svd(R)然后R_ortho U Vt。这个坑很隐蔽因为大部分样本的旋转矩阵误差很小只有少数样本会出问题但就是这少数样本把整个训练带偏了。另一个坑是注意力权重的初始化。PyTorch的MultiheadAttention默认用Xavier初始化但点云特征的方差和NLP任务不一样直接套用会导致注意力权重初始时过于均匀。我的做法是手动把注意力层的权重乘一个0.5的缩放因子让初始注意力分布稍微尖锐一些。6.3 推理速度优化从200ms到35ms原始模型在单张V100上推理一次要200ms对于需要实时配准的场景太慢了。优化分三步点云降采样推理时只采样1024个点而不是训练时的4096个。精度损失不到0.5度速度提升4倍。注意力头数减半推理时把8头降到4头速度提升约1.5倍。SVD用CUDA加速torch.svd在GPU上比CPU快很多但要注意小矩阵的SVD在GPU上反而慢所以对应点对少于100时切回CPU。优化后单次推理35ms基本能满足实时性要求。如果还要更快可以把DGCNN换成轻量版减少一层EdgeConv速度能到20ms但精度会降到5度左右。7. 工程落地时的几个实用建议7.1 点云预处理降采样与法线估计原始扫描点云动辄几十万点直接送进网络显存扛不住。我的预处理流程是先体素降采样到1万点再用最远点采样降到4096点。体素降采样用Open3D的voxel_down_sample体素大小根据零件尺寸定一般是零件直径的1/100。最远点采样用PyTorch3D的sample_farthest_points保证采样点均匀分布。法线估计不是必须的但如果点云有法线信息可以拼接到特征上提升DGCNN的几何感知能力。法线估计用Open3D的estimate_normals搜索半径设体素大小的3倍。7.2 模型部署ONNX导出与TensorRT加速PyTorch模型部署到生产环境我推荐先导出ONNX再用TensorRT做推理加速。导出时注意把动态维度设好点云数量是动态的batch size也是动态的。torch.onnx.export( model, (src_xyz, tgt_xyz), registration.onnx, input_names[src, tgt], output_names[R, t], dynamic_axes{ src: {0: batch, 1: num_points}, tgt: {0: batch, 1: num_points}, R: {0: batch}, t: {0: batch} }, opset_version13 )TensorRT加速后推理速度能再提升2到3倍。但要注意TensorRT对SVD的支持不好SVD那部分建议留在PyTorch里跑只把DGCNN和Transformer导出。7.3 失败案例分析与兜底策略再好的模型也有失败的时候。我遇到的失败案例主要有两类一是点云重叠区域太小小于30%注意力学不到有效对应二是零件表面高度对称存在多个合理的配准结果。对于第一类兜底策略是回退到FPFHRANSAC做粗配准虽然精度低但至少能给出一个合理结果。对于第二类需要在后处理阶段做对称性检测如果发现多个变换的配准误差接近就输出多个候选结果让上层系统决策。实际部署时我会给配准结果加一个置信度分数用注意力权重的平均熵来衡量。熵越低说明对应关系越明确置信度越高。置信度低于阈值的配准结果直接标记为“需人工复核”不进入后续流程。这套方案我从原型到落地大概花了三个月其中调参和踩坑占了三分之二的时间。DGCNNTransformer的组合在点云配准任务上确实有效但也不是银弹——数据质量、预处理、后处理每个环节都会影响最终效果。如果你也在做类似的配准任务建议先把数据预处理和SVD求解这两个基础环节做扎实再上深度学习模型否则模型再好也救不了脏数据。