将同事的送我的仙人掌转换成 3D 模型

2021 WWDC,Apple 在 RealityKit 框架下引入了新 API:PhotogrammetrySession,通过这个 API,可以从不同角度拍摄物体,并且基于这些相片,生成一个可以用于 AR/VR 显示的 3D 模型。

最近我也收到一个来自同事的礼物,她家的仙人掌掉落下来的小仙人掌。于是我萌生了这个将仙人掌转换成 3D 模型的想法。

开始,拍照

Apple 对于这个新 API 提供了非常多的相关代码和文章。其中包括用于拍摄的应用的 源代码

通过 Xcode 将这段源代码编译使其应用运行在 iPhone 上,可以对前期拍摄起到非常大的帮助作用。这个应用的主要功能是每隔一段时间自动拍摄一张相片,并且会记录相片的景深信息(注:必须使用至少拥有两个镜头的 iPhone 才可以使用这个应用,因为至少两个镜头才可以支持记录景深信息)。

在这个应用自动帮助我们拍摄的同时,我们只需要围绕着物体,缓慢移动镜头,完整的记录物体各个角度下的样子。关于拍照还有很多注意事项,比如最好拍摄 20 至 200 张相片,当前拍摄的物体的相片与上一张相片最好有 70% 以上的重合度,尽量在光线均匀的环境下拍摄,不要拍摄透明或者强烈反光的物体之类的。更多详细注意事项可以看《Capturing Photographs for RealityKit Object Capture》。

下图分别是 Apple 官方推荐的拍摄场景和我的拍摄场景。

Apple 官方将物体放在一个会旋转的平台上,并且用上文中提及的应用自动拍照。我将仙人掌放在一个普通的凳子上,然后右手举着相机,尽可能保持不动,左手从底部托着等凳子,缓慢的手动旋转。

拍摄完成之后将相片导入 Mac,然后就开始处理这些相片了。

Show Me the Code

这里需要用到 Apple 提供的另一段相关的 源代码,这段代码将在 Mac 运行,通过调用本文的主角 API PhotogrammetrySession,在 Mac 上将所拍摄的物体生成为 3D 模型。

在开始使用 PhotogrammetrySession 之前,需要先创建一个 PhotogrammetrySession.Request,这个 request 需要指定两个参数:URL 和 PhotogrammetrySession.Request.Detail。他们分别表示最终输出 3D 模型的绝对地址和 3D 模型的精细程度,地址必须以 .usdz 后缀结尾,因为这是 3D 模型的格式名字,而精细程度则会影响 3D 模型的大小。

有了这个 request 之后就可以创建 PhotogrammetrySession,在创建这个 session 时,也需要一个 URL 参数,用来表示用于生成 3D 模型的图片所在文件夹的绝对地址:

let outputUrl = URL(fileURLWithPath: "/Users/jake/Downloads/Cactus.usdz")
var request = PhotogrammetrySession.Request.modelFile(url: outputUrl, detail: .full)

let inputFolderUrl = URL(fileURLWithPath: "/Users/jake/Downloads/Cactus Images")
guard let session = try PhotogrammetrySession(input: inputFolderUrl) else { return } 

在创建 session 时还可以加入 PhotogrammetrySession.Configuration 参数:

let config = Configuration()
config.featureSensitivity = .high
config.sampleOrdering = .sequential
config.isObjectMaskingEnabled = true

guard let session = try PhotogrammetrySession(input: inputFolderUrl, configuration:config) else { return } 

其中 featureSensitivity 表示处理模型时的细致程度,sampleOrdering 表示模型的相片是否连续,可以用于提高处理速度,isObjectMaskingEnabled 好像表示是否将模型与模型所在的背景区隔开,应该也可以提高处理速度。但在我实际使用中,我发现这几个参数无论如何调整似乎都没什么差别,也有可能是因为这个 API 还处于测试版的原因。

最后调用 session 的 process(requests: [PhotogrammetrySession.Request]) 方法,然后等待制作完成就可以啦。这里可以一次性处理多个 requset,就可以同时生成多个模型,节约时间。

session.process(requests: [request])

async {
    do {
        for try await output in session.outputs {
            switch output {
                case .processingComplete:

                case .requestError(let request, let error):

                case .requestComplete(let request, let result):

                case .requestProgress(let request, let fractionComplete):

...

十几分钟之后

最后得到的模型在 Mac 上显示如下:

我用 iPhone 12 mini 一共给这个仙人掌拍了一百多张相片,使用的电脑是 2020 款 M1 芯片最低配金色 MacBook Air,按照 Apple 官方的说法,使用英特尔芯片的 MacBook 处理速度会更慢。我试着生成了好几种不同精度的模型,发现处理速度只跟相片数量有关,相片数量越多,处理时间越长,与模型精细程度等无关。

也许用带激光雷达的 iPhone 或者 iPad 拍摄相片,制作出来的 3D 模型效果会更好。

最后

在得到仙人掌的 3D 模型之后,通过使用 QLPreviewController 就可以很方便的在手机上显示出来:

override func viewDidAppear(_ animated: Bool) {
    let previewController = QLPreviewController()
    previewController.dataSource = self
    present(previewController, animated: true, completion: nil)
}

func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 
    return 1 
}

func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
    guard let path = Bundle.main.path(forResource: "Cactus", ofType: "usdz") else { 
        fatalError("Couldn't find the supported input file.") 
    }

    let url = URL(fileURLWithPath: path)
    return url as QLPreviewItem
}

效果如图所示:

看起来还不错,但 Apple 官方的样例才是惊为天人,可以从「AR Quick Look」下载并使用 Apple 官方模型。