在iOS 11 ARKit中获取相机视野
我正在使用ARKit的ARSCNView在iPad上实时显示来自摄像头的video。 我将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
camera
)以匹配ARKit使用的投影变换。 这是对的。
但是,ARKit直接设置了SCNCamera
的projectionTransform
。 当您使用SCNCamera
几何属性(如zNear
和zFar
以及fieldOfView
,SceneKit会导出用于渲染的投影矩阵。 但是如果直接设置projectionTransform
,则没有可以恢复near / far和xFov / yFov值的数学运算,因此相应的SCNCamera
属性无效。 也就是说, sceneView.pointOfView.camera.fieldOfView
和类似的API总是返回ARKit应用程序的伪造结果 。
那么,你能做些什么呢? 继续阅读……
投影矩阵
AR会话通过其委托不断地ARFrame
对象,或者您可以从中请求currentFrame
。 每个框架都附有一个描述成像参数的ARCamera
,其中一个是依赖于视野的projectionMatrix
。 (还有前面提到的SceneKit projectionTransform
,它是相同的矩阵。)
标准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
对于水平视野,您可以乘以纵横比(具体地说,宽高比):
let imageResolution = session.currentFrame!.camera.imageResolution let xFov = yFov * Float(imageResolution.width / imageResolution.height)
注意:此处,“水平”和“垂直”是相对于相机图像,无论您的设备或AR视图UI如何定向,相机图像本身都是横向方向。
但是,如果仔细观察,您可能会注意到xFov
/ yFov
此处的宽高比(以及yFov
的宽高比)不一定与设备屏幕的宽高比(尤其是在iPhone X上)或您正在绘制的视图相匹配AR内容。这是因为您已经测量了摄像机图像的FOV角度,而不是应用程序AR视图的FOV角度。 别担心,这也有一个API ……
带视口的投影矩阵
ARCamera
提供了两个用于获取投影矩阵的API。 除了我们刚刚过去的那个,还有projectionMatrix(for:viewportSize:zNear:zFar:)
,它将演示考虑在内。 如果你不想匹配相机的FOV,但想知道ARSCNView
或ARSKView
(或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)
你为zNear
和zFar
传递的zFar
并不重要,因为我们没有使用依赖于它的矩阵部分。 (您可能仍需要确保zNear < zFar
和zNear != zFar != 0
)
注意:现在高度/宽度基于您的视图(或者更确切地说,是您传递给
projectionMatrix(for:...)
视图属性projectionMatrix(for:...)
)。 在此示例中,yFov
相对于UI是垂直的,因为方向是portrait
,因此您乘以高度/宽度纵横比以获得xFov
。 如果你在风景中,则乘以宽度/高度。
相机内在
敏锐的观察者可能已经注意到上述计算忽略了投影矩阵的部分。 这是因为FOV角度的定义是相机的光学属性,与3D投影无关,因此整个投影矩阵是您可能不需要的中间结果。
ARCamera
还公开了一个描述相机光学特性的intrinsics
矩阵。 该矩阵中沿对角线的第一和第二值是摄像机图像中单个像素的水平和垂直焦距 。 如果您有焦距和图像宽度/高度,则可以根据FOV角度的定义计算FOV:
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
注意:与使用
projectionMatrix
的版本一样,这是基于摄像机图像的大小和始终横向方向,而不是设备屏幕或您正在显示AR内容的视图。如果您需要基于视口的内容,向上滚动到“带视口的投影矩阵”。
或者你可以这样做:
sceneView.pointOfView?.camera?.fieldOfView