如何将ARKit世界空间的LiDAR顶点正确投影到segmentationBuffer像素坐标?
ARKit LiDAR顶点与Segmentation Buffer坐标对齐问题解决
问题解答
1. 如何正确对齐投影,匹配Segmentation Buffer像素坐标?
核心是对齐相机图像坐标系与segmentationBuffer坐标系,步骤如下:
- 确保世界空间→相机空间的变换正确(你的代码中
worldToCamera的计算是对的) - 用相机内参投影到图像平面后,先将坐标归一化到0-1范围
- 根据
ARFrame.imageOrientation调整归一化坐标,匹配segmentationBuffer的方向(它和相机原始图像方向一致,而非屏幕显示方向) - 最后将归一化坐标映射到segmentationBuffer的实际像素尺寸
2. 是否应直接使用相机内参/外参进行投影?
是的,必须直接使用:
- 外参(
worldToCamera)负责将世界坐标转换为相机空间坐标,ARKit的世界空间与相机空间均为右手坐标系,z轴向前,你的变换逻辑正确 - 内参(
fx/fy/cx/cy)是基于相机传感器原始分辨率的投影参数,投影后需归一化再适配segmentationBuffer的分辨率
3. SegmentationBuffer的参考系差异?
segmentationBuffer使用图像缓冲区坐标系:
- 原点在左上角,x轴向右,y轴向下,方向与
ARFrame.capturedImage完全一致(绑定设备物理方向,而非屏幕显示方向) - 分辨率通常低于相机原始图像(比如iPhone 14 Pro的segmentationBuffer为1920x1080,相机原始分辨率为4032x3024)
- 后置相机场景下无需镜像处理,仅前置相机需要额外镜像
4. displayTransform的正确用法?
displayTransform(for:orientation:viewportSize:)是用来将相机图像归一化坐标转换为屏幕显示坐标的,仅适用于将投影点渲染到屏幕视图的场景。如果目标是匹配segmentationBuffer(属于图像缓冲区),则不应该使用这个方法,转而用ARFrame.imageOrientation调整坐标。
修正后的代码
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) { guard let frame = session.currentFrame else { return } updateProjection(for: anchors, using: frame) } func updateProjection(for anchors: [ARAnchor], using frame: ARFrame) { guard let segmentationBuffer = frame.segmentationBuffer else { return } // 相机内参与原始图像分辨率 let imageResolution = frame.camera.imageResolution let intrinsics = frame.camera.intrinsics let fx = intrinsics[0, 0] let fy = intrinsics[1, 1] let cx = intrinsics[0, 2] let cy = intrinsics[1, 2] // 世界→相机空间变换 let worldToCamera = frame.camera.transform.inverse // SegmentationBuffer尺寸 let segWidth = CVPixelBufferGetWidth(segmentationBuffer) let segHeight = CVPixelBufferGetHeight(segmentationBuffer) // 相机图像方向(与segmentationBuffer方向一致) let imageOrientation = frame.imageOrientation for anchor in anchors { guard let vertices = perAnchorRecord[anchor.identifier]?.vertices else { continue } for vertexWorld in vertices { // 1. 世界空间→相机空间 let vertexCam4 = simd_mul(worldToCamera, SIMD4<Float>(vertexWorld, 1)) let vertexCam = vertexCam4.xyz guard vertexCam.z > 0 else { continue } // 仅处理相机前方的点 // 2. 相机空间→图像平面投影(基于原始分辨率) let u = fx * (vertexCam.x / vertexCam.z) + cx let v = fy * (vertexCam.y / vertexCam.z) + cy // 3. 转换为归一化坐标(0-1范围) var normalizedPoint = CGPoint( x: CGFloat(u / Float(imageResolution.width)), y: CGFloat(v / Float(imageResolution.height)) ) // 4. 根据图像方向调整坐标,匹配segmentationBuffer normalizedPoint = adjustNormalizedPoint(normalizedPoint, for: imageOrientation) // 5. 映射到segmentationBuffer像素坐标 let segPoint = CGPoint( x: normalizedPoint.x * CGFloat(segWidth), y: normalizedPoint.y * CGFloat(segHeight) ) // 此时segPoint即为segmentationBuffer中对应的像素坐标 // 可在此处读取掩码值进行过滤/可视化 } } } // 辅助函数:根据图像方向调整归一化坐标 private func adjustNormalizedPoint(_ point: CGPoint, for orientation: UIImageOrientation) -> CGPoint { var adjustedPoint = point switch orientation { case .left: adjustedPoint = CGPoint(x: point.y, y: 1 - point.x) case .right: adjustedPoint = CGPoint(x: 1 - point.y, y: point.x) case .down: adjustedPoint = CGPoint(x: 1 - point.x, y: 1 - point.y) default: // .up break } return adjustedPoint }
内容的提问来源于stack exchange,提问作者Bejota
相关产品推荐
相关产品推荐

