ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

双目视觉三维重建全流程:从标定、NCC匹配到点云配准

双目视觉三维重建全流程:从标定、NCC匹配到点云配准 简介一份关于双目视觉与三维人脸重建的技术文档适合计算机视觉初学者、人脸识别与三维建模相关开发者阅读用于理解如何从双目图像序列恢复人脸三维点云。内容围绕完整重建流程展开先由双目系统采集左右人脸图像再用GrabCut分割出人脸以缩小立体匹配范围继而通过NCC区域匹配算法计算视差图并生成三维点云最后利用SIFT特征提取与匹配实现不同角度人脸粗配准提升重建精度。文档同时涉及摄像机标定、极线校正、金字塔模型及图割能量函数等关键细节来自《现代电子技术》2015年期刊作者为江南大学物联网工程学院研究者适合作为课题参考或课堂拓展资料。包体仅含1个docx文件共20KB目前已有172人学习小巧精炼便于直接阅读。1. 双目人脸重建的完整链路从两张照片到一坨点云人脸三维重建这块做的人不少但大多是拿 Kinect、RealSense 这类深度相机直接怼脸点云是现成的。这篇论文不一样的地方在于它用的是普通双目摄像头就是两台罗技 pro9000 那种级别的 USB 相机拍完左右两张图走完整条算法流水线自己把三维点云算出来。整条链路是 GrabCut 分割人脸 → NCC 区域匹配求视差 → 视差转点云 → SIFT 特征映射到三维点云做粗配准思路非常干净也是今天做双目点云获取仍然在沿用的技术骨架。适合正在做双目相机标定、3D 点云获取、三维人脸重建相关项目的人读。这篇笔记我会把它拆成能直接复现的流程把参数、坑点、为什么这么做讲透你照着搭一遍差不多就能跑通自己的版本。2. 成像端准备双目标定、极线校正与 GrabCut 分割策略2.1 双目系统搭建光轴距离为什么越短越好论文原文写得很直白希望两个相机的光轴距离越短越好。这个约束不是随便拍的它直接决定后面立体匹配能不能用简化的平移模型。两个摄像头平行摆放的时候场景中同一个物点在左右图像中的位置差异近似等于一个纯平移关系。如果光轴距离大了左右视角差别过大对应点之间的区域就不能再看成简单的平移NCC 窗口匹配的假设就开始失效视差图会出现大量误匹配。因此做双目系统的第一步是先固定好基线论文用的方案是标定板 立体标定参数数值/做法相机型号罗技 pro9000普通 USB 摄像头标定板黑白棋盘格标定方式左右相机分别标定内参再做立体标定求相对外参立体标定误差0.377 542论文给出的重投影误差光轴关系尽量平行基线尽量短标定的输出包括左右相机内参焦距、主点、畸变系数和相对外参旋转矩阵 R、平移向量 T。这些参数后面的深度公式都要用特别是焦距 f 和基线长度 b这两个值直接影响点云尺度单位错了后面全白做。我一般会在标定完成之后做一次验证把棋盘格放在离相机 50cm、100cm、150cm 的位置各拍几张对比标定算出来的距离和实际距离。偏差在毫米级说明标定可以用偏差大到离谱先查是不是棋盘格角点检测出了问题。2.2 极线校正把二维搜索降成一维立体匹配最耗时的部分是搜索空间。如果直接在整幅右图上搜索左图像素的对应点复杂度是二维的。极线校正的作用是让同名点落在同一行上这样搜索就变成了一维的横向搜索论文原文说的将原来二维的搜索变成了一维的搜索指的就是这一步。操作上OpenCV 里对应的是stereoRectify和initUndistortRectifyMap这几个函数。通过标定得到的左右相机内外参计算出两个新的映射关系把左右图像重投影到同一个平面上让极线水平对齐。校正前左右图像的同名点一般在斜着的极线上校正后所有极线都平行于水平方向。这一步建议做可视化验证画出若干条水平线看左右图像上的棋盘格角点、人脸眼睛嘴角等特征点是否落在同一条水平线上。如果发现某些行对不齐多半是标定误差偏大需要重新采标定板的图。2.3 GrabCut 分割用 mask 缩短匹配范围GrabCut 在论文里的角色不是花活而是很实在的一步人脸只占整幅图像的一部分如果全图都做立体匹配计算量浪费在背景上而且背景区域墙面、桌面纹理少、匹配噪点多反而容易干扰视差结果。先用 GrabCut 把人脸从背景里抠出来后面的匹配只在人脸区域计算。GrabCut 的交互方式很轻在目标区域画一个矩形框框外自动识别为背景框内是未知区域算法用高斯混合模型GMM迭代估计前景/背景的颜色概率分布最终输出一个 mask。论文里 mask 的约定是mask 值含义0背景立体匹配跳过不搜索对应点1前景人脸只在右视图中搜索匹配点这里有个值得注意的点GrabCut 分别在左视图和右视图上各做一次。用左视图的 mask 作为匹配的开关右视图 mask 则配合匹配区域的筛选。左右脸的分割结果不一致没关系因为以左视图为主右视图只是候选区。常见做法是先用cv2.grabCut跑一轮拿到初始 mask再用cv2.compare或直接遍历 mask 矩阵生成一个布尔掩膜传给匹配函数。注意 GrabCut 的迭代次数会影响边缘质量迭代太少头发边缘容易被切成背景迭代太多计算慢但边缘会更贴合。论文没有给出具体迭代次数OpenCV 实现默认是 5 次我一般先从 5 次开始边缘效果不满意再往上加。3. 视差图计算NCC 区域匹配与金字塔模型的参数细节3.1 NCC 相似度度量窗口、均值与三个约束拿到校正后的左右视图和 mask接下来就进入立体匹配阶段。论文用的是区域匹配算法 NCC归一化互相关而不是 SGM 或 BM原因很朴素人脸区域纹理相对均匀NCC 对光照差异有一定的鲁棒性而且实现简单、参数直观适合在低纹理场景下做区域匹配。NCC 的计算公式核心是一个滑动窗口内左右图像灰度序列的相关系数。窗口大小直接决定匹配质量窗口大小效果7×7 以下噪声大误匹配多尤其嘴角、眼窝这些低纹理区域9×9 到 15×15论文实践中的常用区间平滑效果好边缘略糊21×21 以上视差图平滑但细节全丢人脸轮廓变胖论文虽然没有直接给出窗口尺寸但提到了一个关键约束在 5×5 的窗口中要确保超过一半的邻居和中心点的视差值差不大于 1 个像素。这是平滑约束的工程实现。我建议窗口主尺寸先用 11×11平滑约束检查用 5×5 子窗口来算这样视差图既不碎也不会过度平滑。NCC 的三个约束条件对应到代码里就是三道检查def ncc_match_with_constraints(left_img, right_img, left_mask, max_disp, win_size11): 基于 NCC 的区域匹配带论文的三个约束条件 left_mask: GrabCut 输出的前景 mask, 1 表示需要计算视差 max_disp: 最大视差搜索范围像素 h, w left_img.shape disparity np.zeros((h, w), dtypenp.float32) half win_size // 2 for y in range(half, h - half): for x in range(half, w - half): if left_mask[y, x] 0: continue # 背景区域跳过不搜索匹配点 # 提取左图窗口和窗口灰度均值 left_win left_img[y - half:y half 1, x - half:x half 1] left_win left_win.astype(np.float32) mean_left left_win.mean() norm_left left_win - mean_left best_score -1.0 best_disp 0 # 只搜索右图中同一条极线上的点视差范围 [0, max_disp] for d in range(0, max_disp): if x - d - half 0: break right_win right_img[y - half:y half 1, x - d - half:x - d half 1] right_win right_win.astype(np.float32) mean_right right_win.mean() norm_right right_win - mean_right # NCC 相关系数 numerator (norm_left * norm_right).sum() denominator np.sqrt((norm_left ** 2).sum() * (norm_right ** 2).sum()) if denominator 0: continue score numerator / denominator if score best_score: best_score score best_disp d disparity[y, x] best_disp return disparity这段代码演示的是最朴素的 NCC 实现思路直接双层循环肯定慢实际用 OpenCV 的cv2.ximgproc.createDisparityWPM或者矩阵化计算会快得多但意思到位了每个前景像素在右图极线上一字排开搜索NCC 分数最高的就是对应点。论文里提到的唯一性约束要求做双向搜索从左边找右边分数最高的点再从右边找左边分数最高的点两次指向彼此才保留候选匹配。这个检查建议保留因为单向搜索在低纹理区域几乎必然出现一个左图点对应多个右图候选的状况。3.2 金字塔模型的三层结构与视差预测直接在高分辨率原图上做上面的循环640×480 的图像假设 max_disp 是 60每个像素要算 60 次窗口 NCC算到天荒地老。论文的金字塔模型解决的就是这个问题。金字塔取 N3 层层级分辨率作用第 2 层顶层160×120粗匹配直接在全范围搜视差第 1 层中间层320×240由顶层视差指导搜小范围第 0 层底层640×480原图仅在上一层视差附近微调匹配顺序是从顶层往下走。第 2 层算出的视差图映射到第 1 层时每个位置的初始视差是上一层的 2 倍因为分辨率翻倍了然后在2*d - t到2*d t这个范围内搜索t是允许的视差波动量。论文没有给出t的具体值实践中的常见做法是t 2或t 3像素等于把搜索范围从 60 个像素直接缩减到 57 个像素计算量降了一个数量级。这里有一个容易搞反的细节金字塔层数增加不会提高视差的理论精度上限它提高的是计算效率顺带通过上层粗结果给下层一个合理的初始值减少跳到局部极小值的概率。换句话说金字塔不是用来提精度的是用来让你敢把max_disp设大而不怕跑太久。3.3 深度转点云d bf / disparity 的参数单位陷阱视差图和三维点云之间的桥梁是深度公式。论文里的式子是物体的深度和基线长度b、焦距f、视差IL - IR之间的关系。换算成工程写法就是def disparity_to_depth(disparity, focal_length_px, baseline_mm): 视差图转深度图 focal_length_px: 以像素为单位的焦距由相机标定内参得到 baseline_mm: 左右相机光心之间的距离单位 mm 返回深度图单位与 baseline 保持一致 # 避免除零视差为 0 或负数的像素视为无效点 valid disparity 0.1 depth np.zeros_like(disparity, dtypenp.float32) depth[valid] (focal_length_px * baseline_mm) / disparity[valid] return depth三个变量的单位必须配套这是最容易翻车的地方focal_length_px是标定得到的fx单位是像素不是毫米。有的镜头参数表给的是物理焦距 3.6mm直接拿这个算就完了必须乘上传感器像素密度才等于像素焦距。baseline_mm是双目光心之间的距离用标定得到的平移向量T的模长算单位取决于你标定时用的棋盘格真实尺寸的单位。我习惯用 mm。depth的输出单位和baseline_mm一致。如果基线标成 60mm深度就是 600mm 表示 0.6 米。拿到深度图之后配合左图像的像素坐标和相机内参反投影成三维点def backproject_to_pointcloud(depth, fx, fy, cx, cy, mask): 深度图反投影生成三维点云 fx, fy, cx, cy: 左相机内参 mask: 有效点标记, 1 表示保留 h, w depth.shape u, v np.meshgrid(np.arange(w), np.arange(h)) # 像素坐标转相机坐标 x (u - cx) * depth / fx y (v - cy) * depth / fy z depth # 按 mask 过滤掉背景和无效深度 points np.stack([x[mask 0], y[mask 0], z[mask 0]], axis1) return points到这一步你就有了第一份人脸三维点云。这份点云是在左相机坐标系下的面和身体朝向取决于拍摄角度。如果你只拍了正脸点云就是正脸视角的一块面片接下来要做的是从其他角度补拍然后配准。4. 不同视角点云配准SIFT 特征映射与四元数粗配准4.1 从 2D 特征到 3D 对应SIFT 特征点坐标映射多视角人脸重建的思路是拍一个角度的左右图像对生成一份点云转动人脸或相机再拍一对生成第二份点云。两份点云不在同一个坐标系需要找一个旋转矩阵R和平移向量T把它们对齐。论文的处理方式很有技巧不在三维点云上直接找特征点而是回到二维图像上用 SIFT 提取特征点。原因是二维图像纹理信息丰富SIFT 对旋转、尺度变化有很强的鲁棒性而三维点云一旦缺少颜色信息找特征就难了。操作上SIFT 特征匹配的是两组数据中的左视图或两组数据中的右视图。比如你拍了角度 A 和角度 B 两组双目图就用角度 A 的左视图和角度 B 的左视图做 SIFT 匹配。SIFT 特征点坐标匹配成功后查一下这些坐标在点云里面对应的深度值就能得到三维对应点对。代码思路import cv2 def sift_feature_mapping(left_img1, left_img2, points3d1, points3d2): 在两组左视图上用 SIFT 提取特征并匹配 把匹配点坐标映射到各自的三维点云上 points3d1, points3d2: 通过表查找的方式记录每个像素坐标对应的三维点 # 创建 SIFT 检测器 sift cv2.SIFT_create(nfeatures1500, contrastThreshold0.04) # 检测特征点和描述子 kp1, des1 sift.detectAndCompute(left_img1, None) kp2, des2 sift.detectAndCompute(left_img2, None) # FLANN 匹配器做最近邻匹配 FLANN_INDEX_KDTREE 1 index_params dict(algorithmFLANN_INDEX_KDTREE, trees5) search_params dict(checks50) flann cv2.FlannBasedMatcher(index_params, search_params) matches flann.knnMatch(des1, des2, k2) # 用 Lowes ratio test 保留可靠匹配 good_matches [] for m, n in matches: if m.distance 0.75 * n.distance: good_matches.append(m) # 映射到三维点云 src_pts_3d [] dst_pts_3d [] for match in good_matches: pt1 kp1[match.queryIdx].pt # 左图一的特征点像素坐标 pt2 kp2[match.trainIdx].pt # 左图二的特征点像素坐标 # 用整像素坐标查点云表得到对应的三维坐标 # 注意这里的查找表需要根据点云的生成方式预先构建 p3d1 points3d1.get(str(int(pt1[1])) _ str(int(pt1[0]))) p3d2 points3d2.get(str(int(pt2[1])) _ str(int(pt2[0]))) if p3d1 is not None and p3d2 is not None: src_pts_3d.append(p3d1) dst_pts_3d.append(p3d2) return np.array(src_pts_3d), np.array(dst_pts_3d)这段代码里有一个值得注意的点像素坐标的整数值直接查表可能找不到精确对应的三维点因为反投影之后的点云坐标对应的是每个整数像素通常能对上。但如果 SIFT 特征点落在像素之间亚像素坐标建议取最近的整数像素或者对点云做一次 KD-Tree 最近邻搜索取半径内的点做加权平均。4.2 RANSAC 剔除误匹配阈值怎么设SIFT 匹配结果中难免有误匹配直接把这堆带噪的对应点对丢给配准算法旋转矩阵就算出来也会歪。论文里用 RANSAC 消除误匹配这是一个标准的做法。RANSAC 在三维配准场景下的思路是随机挑 3 对不共线对应点求一个变换然后用这个变换验证所有对应点统计满足阈值的点数迭代多次保留内点数最多的模型。# 用 OpenCV 的 estimateAffine3D 配合 RANSAC # 或者用自定义的 RANSAC 循环更可控 def ransac_3d_transform(src_pts, dst_pts, threshold_mm3.0, max_iter1000): RANSAC 估计三维刚体变换 src_pts - dst_pts threshold_mm: 重投影误差阈值单位与点云坐标一致 best_T None best_inliers 0 n src_pts.shape[0] for _ in range(max_iter): # 随机取 3 对点求变换最小配置 idx np.random.choice(n, 3, replaceFalse) # 用 Kabsch / Horn 四元数法求变换矩阵 T solve_rigid_transform(src_pts[idx], dst_pts[idx]) # 变换所有点并统计内点数 transformed apply_transform(T, src_pts) errors np.linalg.norm(transformed - dst_pts, axis1) inliers np.sum(errors threshold_mm) if inliers best_inliers: best_inliers inliers best_T T # 用所有内点重新估计一次提高精度 return refine_with_inliers(src_pts, dst_pts, best_T, threshold_mm)阈值threshold_mm怎么定取决于你点云的噪声水平。如果你的深度误差在 ±3mm 左右阈值设 3mm 比较合理如果标定做得好可以收紧到 1.5mm。太紧会把有效对应点全筛掉太松又会让误匹配混进来论文里粗配准阶段建议稍微宽松一点因为后面还有 ICP 精配准兜底。4.3 四元数法求解旋转和平移从协方差矩阵到最大特征向量求解刚体变换也就是已知一组对应三维点对A和B求R和T使得B ≈ R*A T。论文用的是四元数法也叫 Horn 方法。步骤拆开看第一步计算两组点云的质心def solve_rigid_transform(src_pts, dst_pts): 四元数法Horns method求解刚体变换 src_pts: 源点云 Nx3 dst_pts: 目标点云 Nx3 返回旋转矩阵 R(3x3) 和平移向量 t(3x1) # 1. 计算质心 cA src_pts.mean(axis0) cB dst_pts.mean(axis0) # 去质心 A_centered src_pts - cA B_centered dst_pts - cB # 2. 计算协方差矩阵 H A_centered^T * B_centered H A_centered.T B_centered # 3. 构造 4x4 对称矩阵 Q用于四元数求解 # Q [[trace(H), Δ^T], # [Δ, H H^T - trace(H)*I]] delta np.array([H[1, 2] - H[2, 1], H[2, 0] - H[0, 2], H[0, 1] - H[1, 0]]) trace_H np.trace(H) Q np.zeros((4, 4)) Q[0, 0] trace_H Q[0, 1:] delta Q[1:, 0] delta Q[1:, 1:] H H.T - trace_H * np.eye(3) # 4. 最大特征值对应的特征向量即是最佳旋转四元数 eigenvalues, eigenvectors np.linalg.eigh(Q) q eigenvectors[:, np.argmax(eigenvalues)] # [q0, q1, q2, q3] # 四元数转旋转矩阵 q0, q1, q2, q3 q R np.array([ [1 - 2*(q2*q2 q3*q3), 2*(q1*q2 - q0*q3), 2*(q1*q3 q0*q2)], [2*(q1*q2 q0*q3), 1 - 2*(q1*q1 q3*q3), 2*(q2*q3 - q0*q1)], [2*(q1*q3 - q0*q2), 2*(q2*q3 q0*q1), 1 - 2*(q1*q1 q2*q2)] ]) # 平移向量由质心关系确定 t cB - R cA return R, t代码逻辑对应论文的完整推导质心去中心化 → 协方差矩阵H→ 构造对称矩阵Q→ 求最大特征值对应的特征向量。这里的Q矩阵的特征向量就是旋转四元数最大特征值对应的那个方向是使对应点残差最小的旋转。平移向量不用搜索质心相减得到。粗配准到这里就结束了。注意论文明确提到这只是粗配准为后续 ICP 精配准打基础。粗配准允许有毫米级到厘米级的误差关键是把两个视角的点云姿态大致转到同一个方向防止 ICP 陷入局部极小值。5. 避坑指南复现这套流程最容易翻车的五处细节5.1 GrabCut 分割边缘锯齿导致视差图空洞现象分割出来的人脸 mask 边缘参差不齐按 mask 跳过背景之后视差图在人脸边缘出现一圈空洞脸部点云缺了一块边缘。原因GrabCut 对头发丝、耳朵这些弱边缘区域的分割结果不稳定mask 边缘的像素被误判为背景导致匹配程序直接跳过这些位置。解决在拿到 GrabCut 的 mask 之后先做一次形态学处理用腐蚀去掉边缘的孤立噪声点再用膨胀把边缘补回来。更稳妥的做法是只用 mask 的内部区域做完整匹配边缘往外扩 35 个像素的带状区域采用放宽的 NCC 阈值。从那以后我每次跑 GrabCut 分割都会在拿到 mask 后多写两行cv2.erode和cv2.dilate省得后面查空洞查得头大。5.2 NCC 窗口太大导致视差图出现块状伪影现象视差图整体平滑了但人脸轮廓变糊眼睛、嘴巴周围出现方块状的同质区域像打了一层马赛克。原因NCC 窗口开到 21×21 甚至更大窗口内包含了太多深度不连续的像素比如眼窝边缘相关计算把这些深度差异平均掉了导致视差被平滑到同一个值。解决窗口大小和人脸尺寸要匹配。人脸在 640×480 图像里约占 200×300 像素时11×11 到 15×15 是最稳的区间。如果用了金字塔模型底层窗口可以比顶层小一号因为底层已经有上层视差做引导搜索范围小不需要靠大窗口提高信噪比。另外一个辅助手段是加论文提到的 5×5 窗口平滑约束检查强制保留视差细节。5.3 标定误差看着很小重建出来的人脸却是歪的现象OpenCV 标定输出的重投影误差在 0.3 到 0.5 像素之间看起来质量不错但生成的深度图有明显的系统性倾斜人脸像被斜切了一刀。原因标定的重投影误差反映的是角点检测和参数拟合的自洽性不代表双目外参的真实精度。如果两个摄像头的光轴没有严格做到平行或者左右相机拍的标定板图太少、覆盖角度不够立体标定算出来的R和T会有偏差极线校正之后图像仍然有残余的垂直视差。解决标定板采集至少要 1520 对图覆盖画面的九个方位四个角、四条边中点、中心并且要保证标定板在每个位置都有倾斜角度不能只拍正对相机的姿势。标定完成后专门用人脸图像检查极线对齐效果在左右图上画同一水平线观察眼睛和嘴是否在同一高度。歪得明显就重新标定别硬联调。5.4 SIFT RANSAC 之后仍然存在误匹配对现象RANSAC 跑完之后点云粗配准结果看起来大致对着了但嘴角或眼角附近有几个点明显飘了精细对齐后这些区域翘起来。原因论文原文也承认了这个问题——用 RANSAC 手动消除误匹配还是会存在误匹配的现象。RANSAC 的剔噪能力取决于错误的匹配对是否符合随机分布如果误匹配点恰好聚集在某个局部区域比如同一块低纹理区域它们可能被当成一致的模型混过 RANSAC 的筛选。解决在 RANSAC 之前额外加一道过滤统计所有 SIFT 匹配点对之间的欧氏距离三维距离计算中位数把距离超过中位数 1.5 倍的匹配对直接丢掉。论文在结尾给出了这个方向以匹配点间的欧氏距离中值的一定范围作为依据或拟合高斯分布取分布的置信区间。我在实现时用的是中位数 MAD绝对中位差的组合过滤效果比单纯调 RANSAC 阈值更稳定。5.5 金字塔上层视差误差在下层被指数放大现象顶层160×120算出的视差在某些区域错了 1 个像素逐层映射到最底层时某些区域出现持续的 48 像素的视差偏移深度图出现台阶状断层。原因逐层映射时视差会乘以 2误差也随之放大。顶层误差 1 像素第 1 层变成 2 像素第 0 层变成 4 像素。如果t值设得太小底层搜索范围兜不住这个放大后的误差错误就对不上号了。解决顶层匹配优先保证准确率而不是覆盖率。一种做法是把顶层的max_disp设大一点同时把 NCC 的阈值提高只保留高置信度的匹配点不确定的区域留空不强行给初值。另一种做法是底层搜索的t值从 3 放宽到 5代价是搜索范围大了 60%但对整体运行时间影响有限因为只在前景区域搜索。两个方法可以组合使用论文里的t值预留的就是这个弹性空间。6. 粗配准之后ICP 精配准收敛性验证与三个实用习惯粗配准拿到的是一个大致对齐的姿态误差通常在几个毫米到一两厘米之间。论文明确说粗配准为精细点云配准做好铺垫这个铺垫对象就是 ICP。我自己在复现时会在四元数粗配准之后紧接 ICP并设置一个收敛性验证流程。ICP 的标准做法是用 KD-Tree 找最近邻点对以对应点距离平方和为目标函数迭代优化R和T。初始值直接用粗配准的R、T这样 ICP 不会因为初始姿态偏差太大而陷入局部极值。from scipy.spatial import KDTree def icp_refine(src_pts, dst_pts, init_R, init_t, max_iter50, tolerance1e-6): ICP 精配准, 以粗配准结果为初始值 src_pts: 源点云待对齐dst_pts: 目标点云参考 # 用粗配准结果做初始变换 R init_R t init_t src_transformed (R src_pts.T).T t tree KDTree(dst_pts) prev_error float(inf) for i in range(max_iter): # 最近邻搜索 distances, indices tree.query(src_transformed) # 剔除距离过远的点离群点 valid distances np.percentile(distances, 85) # 用有效对应点求新变换 R_new, t_new solve_rigid_transform(src_transformed[valid], dst_pts[indices[valid]]) # 更新 src_transformed (R_new src_transformed.T).T t_new R R_new R # 累积旋转 t t_new R_new t mean_error distances[valid].mean() if abs(prev_error - mean_error) tolerance: break prev_error mean_error return R, t, src_transformed验证收敛有三个手段第一看迭代过程中平均对应距离是否单调下降并进入平台期如果不降反升多半是初始粗配准姿态错了或者对应点搜索半径太大第二对齐后点数比即在某个距离阈值内找到对应点的点数占总点数的比例粗配准好时这个比例应该在 70% 以上低于 50% 要回查 SIFT 匹配质量第三做视觉检查把两张点云叠加渲染观察鼻尖、眼窝、下颌线是否重合椒盐噪声式的离散点通常是 ICP 没有完全收敛的迹象。从那以后我做多视角点云配准都强制先走一遍粗配准验证——把 SIFT 匹配对可视化画出来再跑 ICP最后用重叠率指标复核。这步看起来笨重但能避免在错误姿态上反复调参是这套流程里性价比最高的习惯。希望帮到你。本文还有配套的精品资源点击获取
返回列表