如何在Swift中将LiDAR深度图保存为LAS/LAZ文件?
从iOS 17.2的LiDAR深度图生成LAS/LAZ点云文件
你当前的代码只能保存2D深度图为TIFF,但LAS/LAZ是带空间三维坐标的点云格式,需要先把每个像素的深度值结合AR相机的参数转换成世界坐标系下的3D点,再写入LAS/LAZ格式。以下是具体实现步骤和代码:
核心原理
ARFrame的sceneDepth提供的是每个像素对应的深度(单位:米),但这些数据没有空间位置信息。需要通过:
- AR相机的内参(intrinsics):将像素坐标转换为相机坐标系下的3D点
- AR相机的外参(transform):将相机坐标系下的点转换为世界坐标系下的点
实现步骤
1. 获取深度像素数据与相机参数
首先从ARFrame中提取深度图的原始像素数据,同时获取相机的内参和位姿:
func session(_ session: ARSession, didUpdate frame: ARFrame) { guard let sceneDepth = frame.sceneDepth, let camera = frame.camera else { return } // 限制保存次数 guard savedImagesCount < 3 else { return } // 获取深度图的CVBuffer let depthBuffer = sceneDepth.depthMap CVPixelBufferLockBaseAddress(depthBuffer, .readOnly) defer { CVPixelBufferUnlockBaseAddress(depthBuffer, .readOnly) } // 获取深度数据的指针与格式(iOS的sceneDepth是Float32类型,单位米) let width = CVPixelBufferGetWidth(depthBuffer) let height = CVPixelBufferGetHeight(depthBuffer) let bytesPerRow = CVPixelBufferGetBytesPerRow(depthBuffer) let depthData = unsafeBitCast(CVPixelBufferGetBaseAddress(depthBuffer), to: UnsafeMutablePointer<Float32>.self) // 相机内参(焦距、主点坐标) let intrinsics = camera.intrinsics let fx = intrinsics[0][0] let fy = intrinsics[1][1] let cx = intrinsics[2][0] let cy = intrinsics[2][1] // 相机位姿(将相机坐标系点转换为世界坐标系) let cameraTransform = camera.transform
2. 遍历像素生成3D点云
遍历每个有效深度像素,计算对应的世界坐标:
var pointCloud: [SIMD3<Float>] = [] for y in 0..<height { for x in 0..<width { let index = y * (bytesPerRow / MemoryLayout<Float32>.stride) + x let depth = depthData[index] // 过滤无效深度值(深度为0或NaN时跳过) guard depth > 0, !depth.isNaN else { continue } // 相机坐标系下的3D点计算 let xCam = (Float(x) - cx) * depth / fx let yCam = (Float(y) - cy) * depth / fy let zCam = depth let cameraSpacePoint = SIMD3<Float>(xCam, yCam, zCam) // 转换为世界坐标系 let worldSpacePoint = cameraTransform * SIMD4<Float>(cameraSpacePoint, 1.0) pointCloud.append(SIMD3<Float>(worldSpacePoint.x, worldSpacePoint.y, worldSpacePoint.z)) } }
3. 写入LAS/LAZ文件
iOS上可以使用LASlib(C++库,需通过CocoaPods或手动集成)来处理LAS/LAZ格式。以下是用LASlib写入LAS文件的示例:
// 保存LAS文件到Documents目录 let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let lasFileURL = documentsDir.appendingPathComponent("pointCloud.las") // 初始化LAS写入器 let lasWriter = LASwriter() lasWriter.open(lasFileURL.path) // 设置LAS文件头部信息 var header = LASheader() header.point_count = UInt32(pointCloud.count) // 设置坐标范围(可选,优化点云查看器加载) if let minPoint = pointCloud.min(by: { $0.x < $1.x }), let maxPoint = pointCloud.max(by: { $0.x < $1.x }) { header.min_x = minPoint.x header.max_x = maxPoint.x header.min_y = minPoint.y header.max_y = maxPoint.y header.min_z = minPoint.z header.max_z = maxPoint.z } lasWriter.setHeader(&header) // 写入每个点 for point in pointCloud { var lasPoint = LASpoint() lasPoint.setCoordinates(x: point.x, y: point.y, z: point.z) lasWriter.writePoint(&lasPoint) } // 关闭写入器 lasWriter.close() print("LAS点云保存至:\(lasFileURL)") savedImagesCount += 1 }
注意事项
- LAZ压缩:如果需要生成LAZ文件,只需将LASwriter的打开路径后缀改为
.laz,LASlib会自动处理压缩(需确保库编译时启用了LASzip支持)。 - 性能优化:遍历所有像素可能会占用较多资源,建议在后台队列处理,避免阻塞主线程。
- 深度数据有效性:sceneDepth的深度值可能存在无效区域(如过远/过近的物体),需过滤0或NaN值。
内容的提问来源于stack exchange,提问作者Javide
相关产品推荐
相关产品推荐

