安卓开发中经常有需要使用摄像头的应用场景,对于初次接触的同学摄像头的方向是一个比较难弄清楚的概念,开发时很容易处理不当,本文将详述该部分内容帮助理解。
一、摄像头捕获的图像
先看一个简单的场景,打开手机的后置摄像头拍摄,摄像头捕获的图像帧数据可通过Camera.PreviewCallback回调中获取,也就是摄像头的输出数据,
void onPreviewFrame(byte[] data, Camera camera);
这里我们先忽略屏幕上的预览,只关注摄像头的输出。如果把它保存为图片或直接显示出来,可以看到图像和原始画面相比逆时针旋转了90度。
而我们如果同样使用iPhone手机拍摄,输出的结果是一个正向的图片。
二、摄像头的正向
为什么输出的图像相比原始画面旋转了90度?因为设备的摄像头存在一个“正向角度”,什么是摄像头的正向?
通俗一点讲,设备相当于人的身体,眼睛相当于摄像头,眼睛把接收到的画面反馈给大脑处理,相当于摄像头把接收到的数据给应用程序处理。人眼能判断出我们头顶向上的方向是我们视觉上的正向,而后置摄像头判断的正向并不是手机物理屏幕向上的方向,而是物理屏幕右侧的方向。我们想象一下,如果人眼是这个摄像头,它认为右侧才是我们的视觉正向,那我们看到的东西是不是都是旋转90度的?这样就比较好理解了。
上图是手机在竖直和水平方向摄像头“看”到的画面。
固定设备,指定的摄像头,正向角度固定的(0/90/180/270),和屏幕旋转、横竖屏切换无关,一般都在屏幕的右侧(但不排除某些厂商修改成别的)。
这个角度在代码中可通过Camera.CameraInfo
的orientation获取,官方文档也有解释:
The orientation of the camera image. The value is the angle that the camera image needs to be rotated clockwise so it shows correctly on the display in its natural orientation. It should be 0, 90, 180, or 270.
For example, suppose a device has a naturally tall screen. The back-facing camera sensor is mounted in landscape. You are looking at the screen. If the top side of the camera sensor is aligned with the right edge of the screen in natural orientation, the value should be 90. If the top side of a front-facing camera sensor is aligned with the right of the screen, the value should be 270.
意思就是输出的图片需要顺时针旋转多少度,才能在自然方向上正确显示。这里的自然方向就是以标题栏左上角为原点的屏幕渲染坐标系,图片旋转后,把它放到渲染坐标系中,能和原始画面一样正常显示。
上图红点代表了图片的坐标原点,蓝点则代表屏幕渲染坐标的原点。只有做了旋转处理,渲染到屏幕上的预览图像才是正确的(和原始画面一样),而这个旋转的角度,就是orientation的值。
注意,正向始终在物理屏幕的右侧(想象音量键那边有一个正向箭头),orientation就等于从摄像头的角度(想象成人眼)看,从物理设备的正上方向(想象听筒位置有个箭头),需要顺时针旋转多少度才能到正向的箭头。所以,根据这个想象一下前后摄像头的区别,这个值后置摄像头是90,前置摄像头是270。
iPhone的摄像头正向就是物理设备的正上方,对应的正向角度是0,所以输出的图像是正向的。
三、如何正确地预览图像
其实不预览应用程序也能正确获取到摄像头的输入,但是一般应用打开摄像头后都会在屏幕上显示当前拍摄到的画面,这是用户的基本的使用体验。正确的预览图像就是让摄像头输出的图像能够正确的在屏幕上显示给用户。
如上节所述,摄像头采集到的图像按照orientation旋转和渲染坐标系对齐即可,这样就能正确显示图像了。不过这是在屏幕方向锁定的情况下,就是渲染坐标系始终在物理屏幕的左上方。
如果我们打开了设备陀螺仪(锁屏),屏幕可以在四个方向上切换,对应渲染的坐标原点(蓝点)会在物理屏幕的四个角上切换。
切换方向的角度可以通过activity.getWindowManager().getDefaultDisplay().getRotation();
获取,和前后摄像头无关:
这个角度可以理解为,以物理设备左上角为原点的渲染坐标系(听筒左边的角点),需要顺时针旋转多少度,才能变成当前的渲染坐标系。打开陀螺仪后,无论手机怎么旋转,当前的渲染坐标系永远以绝对左上角为原点(视觉左上方角点)。这个角度恰好和物理旋转的角度相反。
我们只要根据当前的切换角度+摄像头正向角度正确地设置显示角度就行了,官方文档也有现成的适配代码,详见setDisplayOrientation。
注意,setDisplayOrientation只会对预览显示的图像有影响,并不会影响onPreviewFrame回调的数据。
public static void setCameraDisplayOrientation(Activity activity,
int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
如果还不理解这段代码的意思,看下这张图就明白了:
蓝点是渲染的坐标原点,红点是输出图片的原点。每一次旋转图片的原点就会变换到绝对位置的左上角,setDisplayOrientation要设置的值就是图像要顺时针旋转的角度,使图片能在渲染坐标系中正确显示。从图中也能看出来,它就是第二列的箭头需要顺时针旋转到第一列箭头的角度。