返回

ARKit LiDAR 扫描导出带纹理 OBJ 模型(JPG+MTL)

IOS

从 ARKit LiDAR 扫描导出带纹理(JPG + MTL) 的 OBJ 模型

在使用 ARKit 和 RealityKit 进行物体扫描时,可以轻松地从 ARMeshAnchors 生成 OBJ 文件。但通常情况下,导出后的模型缺少纹理(JPG + MTL),导致模型看起来单调、缺乏真实感。

问题成因

根本原因在于,ARKit 的 ARMeshAnchor 主要提供的是物体的几何信息(顶点、面等),而不直接包含纹理数据。当前的代码将网格转换为MDLAsset并导出.obj, 缺少将场景的视觉外观捕获为纹理(通常是 JPG 图像)并将其与 OBJ 模型关联(通过 MTL 文件)的步骤。

解决方案

要解决这个问题,需要手动实现纹理生成和 MTL 文件的创建。 下面给出几种思路和具体实现方式:

1. 利用 ARFramecapturedImage

这是最直接的方法。ARFrame 提供了当前相机捕获的原始图像,可以直接将其保存为 JPG 文件。

原理:

每次 ARFrame 更新时,capturedImage 属性都包含了相机看到的图像数据。 我们可以直接把它转成图片。

步骤:

  1. 获取 ARFrame 中的 capturedImage
  2. CVPixelBuffer (capturedImage 的类型) 转换为 UIImage
  3. UIImage 保存为 JPG 文件。
  4. 生成mtl文件。

代码示例:

//  ... (之前的 exportScannedObject 函数保持不变) ...

func saveCapturedImage(frame: ARFrame) -> URL? {
    let pixelBuffer = frame.capturedImage
    let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
    let context = CIContext()

    guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil }
    let uiImage = UIImage(cgImage: cgImage)

    let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let imageUrl = directory.appendingPathComponent("texture.jpg")

    do {
        try uiImage.jpegData(compressionQuality: 1.0)?.write(to: imageUrl)
        return imageUrl
    } catch {
        print("Failed to save image: \(error)")
        return nil
    }
}

private func exportScannedObject() {
      // ... 其他代码 ...
    guard let frame = arView.session.currentFrame else {return}

      if let meshAnchors = frame.anchors.compactMap({ $0 as? ARMeshAnchor }),
         let asset = convertToAsset(meshAnchors: meshAnchors) {
          do {
              let objURL = try export(asset: asset)
              //保存图片
              if let imageURL = saveCapturedImage(frame: frame){
                 //生成mtl文件
                  createMTLFile(objFileName: "scaned", textureFileName: "texture", directory: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!)
              }
              showScanPreview(objURL) //展示obj
          } catch {
              print("export error")
          }
      }
}

func createMTLFile(objFileName: String, textureFileName: String, directory:URL) {
      let mtlText = """
      newmtl material_0
      Ka 1.000000 1.000000 1.000000
      Kd 1.000000 1.000000 1.000000
      Ks 0.000000 0.000000 0.000000
      Ns 0.000000
      map_Kd \(textureFileName).jpg
      """

    let mtlURL = directory.appendingPathComponent("\(objFileName).mtl")

      do {
          try mtlText.write(to: mtlURL, atomically: true, encoding: .utf8)
      } catch {
          print("Failed to write MTL file: \(error)")
      }
}

注意事项:

  • 这种方法得到的纹理只是一个视角的图像,可能会导致模型从不同角度看时出现拉伸或变形。
  • compressionQuality 参数可以调整 JPG 图像的压缩质量(0.0 表示最大压缩,1.0 表示不压缩)。
  • 上面的代码,只做了单张图片的导出, 如需要多张图片导出, 请自行编写代码循环处理。

2. UV 展开与纹理映射(Texture Mapping)

这是更专业、更准确的方法,但实现起来也更复杂。它涉及 UV 展开和纹理映射两个关键概念。

原理:

  • UV 展开: 将三维模型的表面展开成一个二维平面,就像把地球仪展开成世界地图一样。每个顶点都会有一个对应的 UV 坐标(取值范围通常是 0 到 1),用来确定纹理图像上的哪个像素应该应用到这个顶点上。
  • 纹理映射: 将一个或多个二维图像(纹理)“贴”到三维模型上的过程。

步骤:

  1. UV 展开:ARMeshAnchor 生成的网格计算 UV 坐标。这是一个复杂的过程,通常需要借助第三方库或算法。
  2. 投影相机图像:ARFrame 中的 capturedImage 按照 UV 坐标投影到展开的纹理上。
  3. 保存纹理: 将投影后的纹理保存为 JPG 文件。
  4. 创建 MTL 文件: 用于告诉渲染引擎哪个obj, 使用哪个 JPG 作为纹理。

进阶技巧:

由于 iOS 上进行 UV 展开较为复杂, 可以利用一些开源或者商业 3D 软件,如 Blender,来处理。步骤如下:

  1. ARMeshAnchor 导出的 OBJ 文件导入 Blender。
  2. 在 Blender 中进行 UV 展开,并手动调整 UV 布局,使其尽量不重叠、不拉伸。
  3. 将多个视角的 capturedImage 作为纹理投影到模型上。在 Blender 中,你可以使用相机投影修改器(Camera Project modifier)或者纹理绘制工具(Texture Paint)来完成这一步。
  4. 烘焙纹理(Bake Texture):将投影后的纹理合并成一张最终的纹理图像。
  5. 导出 OBJ、MTL 和 JPG 文件。

注意: 这是一个手动且费力的过程。如果你的应用场景只是为了扫描物体的材质和贴图,可以使用市面上的三维扫描软件。

3. 使用第三方库或服务

有些第三方库或服务可以简化纹理生成的过程。比如:

  • RealityKit (仅限 macOS): 如果你在 macOS 上开发,可以考虑使用 RealityKit 的 PhotogrammetrySession。它可以从一系列图像中生成带纹理的三维模型。 但是,PhotogrammetrySession 并不适用于 iOS 平台.
  • 第三方服务 : 使用提供LiDAR扫描并生成带纹理模型的服务(可能有费用)。

步骤:

  1. 参照相应库或服务的文档进行操作。
  2. 获取包含 OBJ,MTL,和 JPG 文件的结果。

提示 : 搜索相关例如“iOS LiDAR texture mapping SDK" 来获取解决方案.

MTL 文件创建(通用)

无论你选择哪种方法生成纹理,都需要创建一个 MTL 文件来将 OBJ 模型与纹理关联起来。

MTL 文件是一个纯文本文件,其中定义了材质的属性,包括漫反射颜色(Kd)、环境光颜色(Ka)、高光颜色(Ks)、光泽度(Ns)以及纹理贴图(map_Kd)等。

确保mtl 文件里map_Kd的相对路径指向纹理图像。

安全建议

  • 用户隐私: 当你使用相机捕捉图像时,要确保你遵守相关的隐私法规,并在必要时征得用户的同意。
  • 文件存储: 如果你将扫描的模型或纹理保存在本地,请注意文件存储的安全性,避免被未经授权的访问。
  • 敏感数据: 不要在纹理图中存储敏感信息, 尽量将扫描的内容限制在应用所需的范围内.