作者:HJDaryl ,感谢作者的分享。原文链接:https://www.jianshu.com/p/067889611ae7
当我们谈论方向的时候,实际上都是相对于某一个 0° 方向的角度,这个 0° 方向被称作自然方向,例如人站立的时候就是自然方向,你总不会认为一个人要倒立的时候才是自然方向吧,而接下来我们要谈论的设备方向就有的自然方向的定义。
设备方向指的是硬件设备在空间中的方向与其自然方向的顺时针夹角。这里提到的自然方向指的就是我们手持一个设备的时候最习惯的方向,比如手机我们习惯竖着拿,而平板我们则习惯横着拿,所以通常情况下手机的自然方向就是竖着的时候,平板的自然方向就是横着的时候。
以手机为例,我们可以有以下四个比较常见的设备方向:
当我们把手机垂直放置且屏幕朝向我们的时候,设备方向为 0°,即设备自然方向。
当我们把手机向右横放且屏幕朝向我们的时候,设备方向为 90°
当我们把手机倒着放置且屏幕朝向我们的时候,设备方向为 180°
当我们把手机向左横放且屏幕朝向我们的时候,设备方向为 270°
了解了设备方向的概念之后,我们可以通过 OrientationEventListener 监听设备的方向,进而判断设备当前是否处于自然方向,当设备的方向发生变化的时候会回调 OrientationEventListener.onOrientationChanged(int) 方法,传给我们一个 0° 到 359° 的方向值,其中 0° 就代表设备处于自然方向。
局部坐标系
所谓的局部坐标系指的是当设备处于自然方向时,相对于设备屏幕的坐标系,该坐标系是固定不变的,不会因为设备方向的变化而改变,下图是基于手机的局部坐标系示意图:
局部坐标系 x 轴是当手机处于自然方向时,和手机屏幕平行且指向右边的坐标轴。y 轴是当手机处于自然方向时,和手机屏幕平行且指向上方的坐标轴。z 轴是当手机处于自然方向时,和手机屏幕垂直且指向屏幕外面的坐标轴。为了进一步解释【坐标系是固定不变的,不会因为设备方向的变化而改变】的概念,这里举个例子,当我们把手机向右横放且屏幕朝向我们的时候,此时设备方向为 90°,局部坐标系相对于手机屏幕是保持不变的,所以 y 轴正方向指向右边,x 轴正方向指向下方,z 轴正方向还是指向屏幕外面,如下图所示:
设备方向 90°
显示方向
显示方向指的是屏幕上显示画面与局部坐标系 y 轴的顺时针夹角。
为了更清楚的说明这个概念,我们举一个例子,假设我们将手机向右横放看电影,此时画面是朝上的,如下图所示:
屏幕方向
从上图来看,手机向右横放会导致设备方向变成了 90°,但是显示方向却是 270°,因为它是相对局部坐标系 y 轴的顺时针夹角,所以跟设备方向没有任何关系。如果把图中的设备换成是平板,结果就不一样了,因为平板横放的时候就是它的设备自然方向,y 轴朝上,屏幕画面显示的方向和 y 轴的夹角是 0°,设备方向也是 0°。
总结一下,设备方向是相对于其现实空间中自然方向的角度,而显示方向是相对局部坐标系的角度。
摄像头传感器方向
摄像头传感器方向指的是传感器采集到的画面方向经过顺时针旋转多少度之后才能和局部坐标系的 y 轴正方向一致,也就是通过 CameraCharacteristics.SENSOR_ORIENTATION 获取到的值。
例如 orientation 为 90° 时,意味我们将摄像头采集到的画面顺时针旋转 90° 之后,画面的方向就和局部坐标系的 y 轴正方向一致,换个说法就是原始画面的方向和 y 轴的夹角是逆时针 90°。
最后我们要考虑一个特殊情况,就是前置摄像头的画面是做了镜像处理的,也就是所谓的前置镜像操作,这个情况下, orientation 的值并不是实际我们要旋转的角度,我们需要取它的镜像值才是我们真正要旋转的角度,例如 orientation 为 270°,实际我们要旋转的角度是 90°。
注意:摄像头传感器方向在不同的手机上可能不一样,大部分手机都是 90°,也有小部分是 0° 的,所以我们要通过 CameraCharacteristics.SENSOR_ORIENTATION 去判断方向,而不是假设所有设备的摄像头传感器方向都是 90°。
矫正图像数据的方向
介绍完几个方向的概念之后,我们就来说下如何校正相机的预览画面。我们会举几个例子,由简到繁逐步说明预览画面校正过程中要注意的事项。
首先我们要知道的是摄像头传感器方向只有 0°、90°、180°、270° 四个可选值,并且这些值是相对于局部坐标系 的 y 轴定义出来的,现在假设一个相机 APP 的画面在手机上是竖屏显示,也就是显示方向是 0° ,并且假设摄像头传感器的方向是 90°,如果我们没有校正画面的话,则显示的画面如下图所示(忽略画面变形):
很明显,上面显示的画面内容方向是错误的,里面的人物应该是垂直向上显示才对,所以我们应该吧摄像头采集到的画面顺时针旋转 90°,才能得到正确的显示结果,如下图所示:
上面的例子是建立在我们的显示方向是 0° 的时候,如果我们要求显示方向是 90°,也就是手机向左横放的时候画面才是正的,并且假设摄像头传感器的方向还是 90°,如果我们没有校正画面的话,则显示的画面如下图所示(忽略画面变形):
此时,我们知道传感器的方向是 90°,如果我们将传感器采集到的画面顺时针旋转 90° 显然是无法得到正确的画面,因为它是相对于局部坐标系 y 轴的角度,而不是实际显示方向,所以在做画面校正的时候我们还要把实际显示方向也考虑进去,这里实际显示方向是 90°,所以我们应该把传感器采集到的画面顺时针旋转 180°(摄像头传感器方向 + 实际显示方向) 才能得到正确的画面,显示的画面如下图所示(忽略画面变形):
总结一下,在校正画面方向的时候要同时考虑两个因素,即摄像头传感器方向和显示方向。接下来我们要回到我们的相机应用里,看看通过代码是如何实现预览画面方向校正的。
如果你有自己看过 Camera 的官方 API 文档,你会发现官方已经给我们写好了一个同时考虑显示方向和摄像头传感器方向的方法,我把它翻译成 Kotlin 语法:
private fun getDisplayRotation(cameraCharacteristics: CameraCharacteristics): Int {
val rotation = windowManager.defaultDisplay.rotation
val degrees = when (rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> 0
}
val sensorOrientation = cameraCharacteristics[CameraCharacteristics.SENSOR_ORIENTATION]!!
return if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) {
(360 - (sensorOrientation + degrees) % 360) % 360
} else {
(sensorOrientation - degrees + 360) % 360
}
}
如果你已经完全理解前面介绍的那些角度的概念,那你应该很容易就能理解上面这段代码,实际上就是通过 WindowManager 获取当前的显示方向,然后再参照摄像头传感器方向以及是否是前后置,最后计算出我们实际要旋转的角度。
推荐阅读:
“点击阅读原文,可以扫码加入知识星球”
评论 (0)