在 iOS 11 ARKit 中获取相机视野

Get camera field of view in iOS 11 ARKit

我正在使用 ARKit 中的 ARSCNView 来实时显示来自 iPad 上的摄像头的视频。我的 ARSCNView 对象设置与 Xcode 的增强现实应用程序模板完全相同。我想知道有没有办法得到相机的视野?

@IBOutlet var sceneView: ARSCNView!

func start() {
    sceneView.delegate = self
    sceneView.session.run(ARWorldTrackingConfiguration())
    // Retrieve camera FOV here
}

这里有几种方法,需要提防可能的错误开始。

⚠️ ARKit + SceneKit(不正确)

如果您已经通过 SceneKit (ARSCNView) 使用 ARKit,您可能会认为 ARKit 会自动更新 SceneKit 相机(视图的 pointOfView's camera)以匹配增强现实套件。这是正确的。

但是ARKit直接直接设置了SCNCameraprojectionTransform. When you work with geometric properties of SCNCamera like zNear and zFar and fieldOfView, SceneKit derives a projection matrix for use in rendering. But if you set projectionTransform,没有数学可以恢复near/far和xFov/yFov的值,所以对应的SCNCamera 属性无效。也就是说,sceneView.pointOfView.camera.fieldOfView 和类似的 APIs 总是 return ARKit 应用 的虚假结果。

那么,您可以做什么呢?继续阅读...

投影矩阵

AR 会话不断地出售 ARFrame objects through its delegate, or you can request the currentFrame from it. Each frame has an ARCamera attached that describes the imaging parameters, one of which is a projectionMatrix 这取决于视野。 (还有前面提到的SceneKitprojectionTransform,也是同一个矩阵。)

标准 3D 投影矩阵包括基于垂直视野和纵横比的缩放项。具体来说,矩阵如下所示:

[ xScale    0     0    0  ]   xScale = yScale * aspectRatio (width/height)
[   0    yScale   0    0  ]   yScale = 1 / tan(yFov/2)
[   0       0    nf1  nf2 ]   nf1 and nf2 relate to near and far clip planes,
[   0       0     -1   0  ]     so aren't relevant to field of view

所以你应该可以通过求解yScale方程得到yFov

let projection = session.currentFrame!.camera.projectionMatrix
let yScale = projection[1,1]
let yFov = 2 * atan(1/yScale) // in radians
let yFovDegrees = yFov * 180/Float.pi

对于水平视野,您可以乘以纵横比(具体来说,width/height 比率):

let imageResolution = session.currentFrame!.camera.imageResolution
let xFov = yFov * Float(imageResolution.width / imageResolution.height)

Note: Here, "horizontal" and "vertical" are with respect to the camera image, which is natively in landscape orientation regardless of how your device or AR view UI are oriented.

不过,如果你仔细观察,你可能会注意到这里 xFov/yFov 之间的纵横比(以及 imageResolution 的纵横比)不一定匹配您的设备屏幕(尤其是 iPhone X)或您在其中绘制 AR 内容的视图。那是因为您已经测量了相机图像 的 FOV 角度 ,而不是应用程序的 AR 视图。别担心,也有一个 API...

带视口的投影矩阵

ARCamera 提供两个 API 用于获取投影矩阵。除了我们刚刚看过的那个,还有 projectionMatrix(for:viewportSize:zNear:zFar:),它考虑了外观。如果您不想匹配相机的 FOV,而是匹配 ARSCNViewARSKView(或者 Unity 或 Unreal,可能是?)渲染您的 AR 场景的 FOV,请使用它,传递设备方向和看你的看法。然后做所有与上面相同的数学运算:

let imageResolution = session.currentFrame!.camera.imageResolution
let viewSize = sceneView.bounds.size
let projection = session.currentFrame!.camera.projectionMatrix(for: .portrait,
    viewportSize: viewSize, zNear: zNear, zFar: zFar)
let yScale = projection[1,1] // = 1/tan(fovy/2)
let yFovDegrees = 2 * atan(1/yScale) * 180/Float.pi
let xFovDegrees = yFovDegrees * Float(viewSize.height / viewSize.width)

您为 zNearzFar 传递的内容无关紧要,因为我们没有使用依赖于此的矩阵部分。 (您可能仍需要确保 zNear < zFarzNear != zFar != 0。)

Note: Now the height/width are based on your view (or rather, the attributes of your view that you pass to projectionMatrix(for:...)). In this example, yFov is vertical with respect to the UI because the orientation is portrait, so you multiply by the height/width aspect ratio to get xFov. If you're in landscape, you multiply by width/height instead.

相机内部函数

细心的观察者可能已经注意到,上面的计算忽略了部分投影矩阵。那是因为 definition of FOV angle 是相机的光学 属性,与 3D 投影无关,所以整个投影矩阵是您可能不需要的中间结果。

ARCamera 还公开了一个 intrinsics matrix that describes optical properties of the camera. The first and second values along the diagonal in this matrix are the horizontal and vertical focal length of a single pixel in the camera image. If you have focal length and image width/height, you can compute FOV per the definition of FOV angle:

let imageResolution = session.currentFrame!.camera.imageResolution
let intrinsics = session.currentFrame!.camera.intrinsics
let xFovDegrees = 2 * atan(Float(imageResolution.width)/(2 * intrinsics[0,0])) * 180/Float.pi
let yFovDegrees = 2 * atan(Float(imageResolution.height)/(2 * intrinsics[1,1])) * 180/Float.pi

Note: Like the version that uses projectionMatrix, this is based on the size and always-landscape orientation of the camera image, not the device screen or the view you're displaying AR content in. If you need something based on the viewport instead, scroll back up to "Projection Matrix with Viewport".

或者你可以这样做:

sceneView.pointOfView?.camera?.fieldOfView