Android 相机预览需要注意的几点

3,131 阅读3分钟

Camera 预览方向

手机 Camera 的图像数据都是来自于摄像头硬件的图像传感器(Image Sensor),这个 Sensor 被固定到手机之后是有一个默认的取景方向的,这个方向如下图所示,坐标原点位于手机横放时的左上角:


由于手机屏幕可以 360 度旋转,为了保证用户无论怎么旋转手机都能看到“正确”的预览画面。Android 系统底层根据当前手机屏幕的方向对图像 Sensor 采集到的数据进行了旋转处理后才传输给显示系统。因此,你在打开 Camera 应用后,无论怎么旋转手机都能看到“正确”的画面。

Android 系统提供一个 API 来手动设置 Camera 的预览方向,叫 setDisplayOrientation。默认情况下这个值是0,与图像 Sensor 方向一致,所以对于横屏应用来说就不需要更改这个 Camera 预览方向。

但是,如果你的应用是竖屏应用,就必须通过这个 API 将 Camera 的预览方向旋转 90 度,让摄像头预览方向与手机屏幕方向保持一致,这样才会得到正确的预览画面。

当然要处理 Camera 方向不可能通过一个简单的 setDisplayOrientation(90) 就能搞定所有问题。比如前置摄像头的话预览要水平翻转才行,同样的情况下你可能要 setDisplayOrientation(270) 。具体的可参考官方文档的建议进行处理:

/**
 * If you want to make the camera image show in the same orientation as the display, you can use the following code.
 */
 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);
 }

真实的图像方向

当你点击拍照按钮,得到的图片方向不一定与画面中的预览方向一致,这是因为拍摄的照片是将图像 Sensor 采集到的数据直接存储到 SDCard上的,因此 Camera 的拍照方向与图像 Sensor 方向一致。

拍照得到的图片方向是与图像 Sensor 的方向一致的。 setDisplayOrientation这个 API 修改的仅仅是 Camera 的预览方向而已,并不会影响到 PreviewCallback 回调、生成的 JPEG 图片和录像文件的方向,这些数据的方向依然会跟图像 Sensor 的方向一致。

在Camera类的的PreviewCallback回调中,这个接口回调的是相机的真实图像,而不是预览图像,并且它是YUV格式的,所以,如果需要将PreviewCallback回调的图像转换成一个bitmap需要使用YuvImage。

@Override
public void onPreviewFrame(final byte[] data, Camera camera) {
    camera.setPreviewCallback(null);

    if (mCamera == null)
        return;

    Camera.Parameters parameters = camera.getParameters();
    int width = parameters.getPreviewSize().width;
    int height = parameters.getPreviewSize().height;

    YuvImage yuv = new YuvImage(data, parameters.getPreviewFormat(), width, height, null);

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    yuv.compressToJpeg(new Rect(0, 0, width, height), 50, out);
    byte[] bytes = out.toByteArray();
    final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    this.mCamera.setPreviewCallback(this);
}

上面说过,PreviewCallback回调图片方向跟 Sensor 的方向一致,所以需要进行旋转。

private byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight)
{
	byte [] yuv = new byte[imageWidth*imageHeight*3/2];
	// Rotate the Y luma
	int i = 0;
	for(int x = 0;x < imageWidth;x++)
	{
		for(int y = imageHeight-1;y >= 0;y--)
		{
			yuv[i] = data[y*imageWidth+x];
			i++;
		}
	}
	// Rotate the U and V color components
	i = imageWidth*imageHeight*3/2-1;
	for(int x = imageWidth-1;x > 0;x=x-2)
	{
		for(int y = 0;y < imageHeight/2;y++)
		{
			yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+x];
			i--;
			yuv[i] = data[(imageWidth*imageHeight)+(y*imageWidth)+(x-1)];
			i--;
		}
	}
	return yuv;
}


参考文章:

https://www.jianshu.com/p/7d88ec1347b6   

https://blog.csdn.net/illidantao/article/details/51366047    

https://www.51dev.com/android/8993    

https://stackoverflow.com/questions/14167976/rotate-an-yuv-byte-array-on-android   

https://www.cnblogs.com/cq-jiang/p/7823462.html   

https://blog.csdn.net/xx326664162/article/details/78534241