Android: Camera相机开发详解(上) —— 知识储备

android.jpg

前言

  • 上一篇文章介绍了如何调用系统相机进行拍照裁剪等功能,一般情况下这些已经能满足我们的需求了。但是在有些场景和特殊需求下,比如要进行人脸检测、要不间断地抓取多张照片等等,那就需要使用原生Camera来进行开发啦

  • 这里并不打算讲如何用代码去实现,而是先给小伙们介绍相关的知识点,等对这些知识有了大致了解后在动手去写,这样既能有目的的去写又能加深对知识点的理解

  • 本篇文章主要给大家讲解进行Camera开发需要用到的类和方法,以及在开发过程中遇到的方向问题的分析


进行Camra开发主要用到了以下两个类:

  1. Camera
  2. SurfaceView (当然也可以是TextureView,本文我们使用SurfaceView)

这两者的关系如下图:

图一、关系类比.png

一、 SurfaceView 、Surface 、 SurfaceHolder

关系图

图二、SurfaceView.jpg

Surface

什么是Surface?源码中是这样描述的:


 * Handle onto a raw buffer that is being managed by the screen compositor.
 *
 * <p>A Surface is generally created by or from a consumer of image buffers (such as a
 * {@link android.graphics.SurfaceTexture}, {@link android.media.MediaRecorder}, or
 * {@link android.renderscript.Allocation}), and is handed to some kind of producer (such as
 * {@link android.opengl.EGL14#eglCreateWindowSurface(android.opengl.EGLDisplay,android.opengl.EGLConfig,java.lang.Object,int[],int) OpenGL},
 * {@link android.media.MediaPlayer#setSurface MediaPlayer}, or
 * {@link android.hardware.camera2.CameraDevice#createCaptureSession CameraDevice}) to draw
 * into.</p>

简单翻译一下,大概意思就是:

Surfaces是用来处理屏幕显示内容合成器所管理的原始缓存区的工具。它通常由图像缓冲区的消费者来创建(如:SurfaceTexture,MediaRecorder),然后被移交给生产者(如:MediaPlayer)或者是显示到其上(如:CameraDevice)

SurfaceHolder

源码描述:

 * Abstract interface to someone holding a display surface.  Allows you to
 * control the surface size and format, edit the pixels in the surface, and
 * monitor changes to the surface.  This interface is typically available
 * through the {@link SurfaceView} class.

简单翻译一下:

一个抽象接口,给持有surface的对象使用。它可以控制surface的大小和格式,编辑surface中的像素,以及监听surface的变化。这个接口通常通过SurfaceView类获得

SurfaceHolder中有一个Callbcak接口,它有3个回调方法

  • surfaceCreated(SurfaceHolder holder)
    surface第一次创建时回调

  • surfaceChanged(SurfaceHolder holder, int format, int width,
    int height)
    surface变化的时候回调(格式/大小)

  • surfaceDestroyed(SurfaceHolder holder)
    surface销毁的时候回调

这个回调接口就是源码中所提到的 “monitor changes to the surface”(监听surface的变化),我们后面会用到

SurfaceView

源码描述:

 * Provides a dedicated drawing surface embedded inside of a view hierarchy.
 * You can control the format of this surface and, if you like, its size; the
 * SurfaceView takes care of placing the surface at the correct location on the
 * screen

大致翻译一下:

SurfaceView提供了嵌入视图层级中的专用surface。你可以控制surface的格式或大小。SurfaceView负责把surface显示在屏幕的正确位置

SurfaceView继承自View,其中有两个成员变量,一个是Surface对象,一个是SuraceHolder对象。(请参考上面第二幅关系图)

  • SurfaceView把Surface显示在屏幕上
  • SurfaceView通过SuraceHolder告诉我们Surface的状态(创建、变化、销毁)
  • 通过getHolder()方法获得当前SurfaceView的SuraceHolder对象,然后就可以对SuraceHolder对象添加回调来监听Surface的状态

SurfaceView小结

  1. SurfaceView是一个view对象,用于在屏幕上显示相机的预览画面
  2. SurfaceView中有两个对象,Surface和SuraceHolder。 我们通过SuraceHolder中的回调可以知道Surface的状态(创建、变化、销毁)
  3. 通过getHolder()方法获得当前SurfaceView的SuraceHolder对象

二、Camera

概览图

Camera类中主要的内部类和接口,如下图:

图三、Camera内部类和接口.jpg

Camera类中有很多内部类和方法,下面分别对这些类和方法做介绍:

Camera类中的内部类

CameraInfo

CameraInfo类用来描述相机信息,通过Camera类中getCameraInfo(int cameraId, CameraInfo cameraInfo)方法获得,主要包括以下两个成员变量:

  • facing
    facing 代表相机的方向,它的值只能是CAMERA_FACING_BACK(后置摄像头) 或者CAMERA_FACING_FRONT(前置摄像头)。

CAMERA_FACING_BACK 和 CAMERA_FACING_FRONT 是CameraInfo类中的静态变量

  • orientation
    这个就比较有意思了,也比较重要,源码中是这样描述的:
         * <p>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.</p>
         *
         * <p>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.</p>

翻译一下,大概意思是:

orientation是相机采集图片的角度。这个值是相机所采集的图片需要顺时针旋转至自然方向的角度值。它必须是0,90,180或270中的一个。
举个栗子:
假如你自然地竖着拿着手机(就是自拍时候的样子...),后置摄像头的传感器在手机里是水平方向的,你现在看着手机,如果传感器的顶部在自然方向上手机屏幕的右边(此时,手机是竖屏,传感器是横屏),那么这个orientation的值就是90。 如果前置摄像头的传感器顶部在手机屏幕右边,那么这个值就是270.

  • setDisplayOrientation
    源码描述如下:
     * Set the clockwise rotation of preview display in degrees. This affects
     * the preview frames and the picture displayed after snapshot. This method
     * is useful for portrait mode applications. Note that preview display of
     * front-facing cameras is flipped horizontally before the rotation, that
     * is, the image is reflected along the central vertical axis of the camera
     * sensor. So the users can see themselves as looking into a mirror.

翻译一下:

设置预览画面顺时针旋转的角度。这个方法会影响预览图像和拍照后显示的照片。这个方法对竖屏应用非常有用。
注意,前置摄像头在进行角度旋转之前,图像会进行一个水平的镜像翻转。
所以用户在看预览图像的时候就像照镜子,看到的是现实的水平方向的镜像。

注:setDisplayOrientation(int degrees)是Camea类中的一个方法,之所以穿插在这里来讲,是为了和上面提到的orientation做一个统一讲解,因为这两个都涉及到了方向问题

看了上面的介绍是不是有点懵逼。。。没关系,下面给小伙伴们详细介绍一下Android手机上几个方向的概念以及在使用Camera过程中会遇到的方向问题:

注:如果你是第一次使用Camera的话,首先要了解以下几点:

  1. 相机图像数据都是来自于相机硬件的图像传感器(Image Sensor),这个Sensor被固定到手机之后是有一个默认的取景方向,且不会改变
  2. 相机在预览的时候是有一个预览方向的,可以通过setDisplayOrientation()设置
  3. 相机所采集的照片也是有一个方向的(就是上面刚刚提到的orientation),这个方向与预览时的方向互不相干

  • 屏幕坐标: 在Android系统中,屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向

  • 自然方向:每个设备都有一个自然方向,手机和平板的自然方向不同。手机的自然方向是portrait(竖屏),平板的自然方向是landscape(横屏)

  • 图像传感器(Image Sensor)方向:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向

图四、传感器方向.png
  • 相机的预览方向:将图像传感器捕获的图像,显示在屏幕上的方向。在默认情况下,与图像传感器方向一致。在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器方向一致
图五、相机预览方向.png
  • 相机采集的图像方向
    相机采集图像后需要进行顺时针旋转的角度,即上面介绍的orientation的值:
图六、采集的图像方向.png

这里先做个小结:

  • 绝大部分安卓手机中图像传感器方向是横向的,且不能改变,所以orientation是90或是270,也就是说,当点击拍照后保存图片的时候,需要对图片做旋转处理,使其为"自然方向"。 (可能存在一些特殊的定制或是能外接摄像头的安卓机,他们的orientation会是0或者180)

  • 通过setDisplayOrientation方法设置预览方向,使预览画面为"自然方向"。前置摄像头在进行角度旋转之前,图像会进行一个水平的镜像翻转,所以用户在看预览图像的时候就像照镜子一样。

这些都是些理论,在下篇文章中,会通过实例代码一步步地验证。

好,我们接着看Camera类中的内部类:

Size

图片大小,里面包含两个变量:width和height(图片的宽和高)

Parameters

Parameters是相机服务设置,不同的相机可能是不相同的。比如相机所支持的图片大小,对焦模式等等。下面介绍一下这个类中常用的方法

  • getSupportedPreviewSizes()
    获得相机支持的预览图片大小,返回值是一个List<Size>数组

  • setPreviewSize(int width, int height)
    设置相机预览图片的大小

  • getSupportedPreviewFormats()
    获得相机支持的图片预览格式,所有的相机都支持ImageFormat.NV21
    更多的图片格式可以自行百度或是查看ImageFormat类

  • setPreviewFormat(int pixel_format)
    设置预览图片的格式

  • getSupportedPictureSizes()
    获得相机支持的采集的图片大小(即拍照后保存的图片的大小)

  • setPictureSize(int width, int height)
    设置保存的图片的大小

  • getSupportedPictureFormats()
    获得相机支持的图片格式

  • setPictureFormat(int pixel_format)
    设置保存的图片的格式

  • getSupportedFocusModes()
    获得相机支持的对焦模式

  • setFocusMode(String value)
    设置相机的对焦模式

  • getMaxNumDetectedFaces()
    返回当前相机所支持的最大的人脸检测个数
    比如,我的手机是vivo x9,后置摄像头所支持最大的人脸检测个数是10,也就是说当画面中人脸数超过10个的时候,只能检测到10张人脸

PreviewCallback

PreviewCallback是一个抽象接口

  • void onPreviewFrame(byte[] data, Camera camera)
    通过onPreviewFrame方法来获取到相机预览的数据,第一个参数data,就是相机预览到的原始数据。

这些预览到的原始数据是非常有用的,比如我们可以保存下来当做一张照片,还有很多第三方的人脸检测及静默活体检测的sdk,都需要我们把相机预览的数据实时地传递过去。

Face

Face类用来描述通过Camera的人脸检测功能检测到的人脸信息

  • rect
    rect 是一个Rect对象,它所表示的就是检测到的人脸的区域。

注意:这个Rect对象中的坐标系并不是安卓屏幕的坐标系,需要进行转换后才能使用,具体会在后面实现人脸检测功能的时候详细介绍

  • score
    检测到的人脸的可信度,范围是1 到100

  • leftEye
    leftEye 是一个Point对象,表示检测到的左眼的位置坐标

  • rightEye
    rightEye是一个Point对象,表示检测到的右眼的位置坐标

  • mouth
    mouth是一个Point对象,表示检测到的嘴的位置坐标

leftEye ,rightEye和mouth这3个人脸中关键点,并不是所有相机都支持的,如果相机不支持的话,这3个的值为null

FaceDetectionListener

这是一个抽象接口,当开始人脸检测时开始回调

  • onFaceDetection(Face[] faces, Camera camera)
    第一参数代表检测到的人脸,是一个Face数组(画面内可能存在多张人脸)

Camera类中的方法

  • getNumberOfCameras()
    返回当前设备上可用的摄像头个数

  • open()
    使用当前设备上第一个后置摄像头创建一个Camera对象。如果当前设备没有后置摄像头,则返回值为null

  • open(int cameraId)
    使用传入id所表示的摄像头创建一个Camera对象,如果id所表示的摄像头已经被打开,则会抛出异常

设备上每一个物理摄像都是有一个id的,id从0开始,到getNumberOfCameras() - 1 结束

例如,一般的手机上都有前后两个摄像头,那么后置摄像头id就是0,前置摄像头id就是1

  • getCameraInfo(int cameraId, CameraInfo cameraInfo)
    返回指定id所表示的摄像头的信息

  • setDisplayOrientation(int degrees)
    设置相机预览画面旋转的角度
    上面已经讲过,见图五

  • setPreviewDisplay(SurfaceHolder holder)
    设置一个Surface对象用来实时预览
    我们看一下源码:

  public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
        if (holder != null) {
            setPreviewSurface(holder.getSurface());
        } else {
            setPreviewSurface((Surface)null);
        }
    }
     
 public native final void setPreviewSurface(Surface surface) throws IOException;

可以看到 ,这个方法调用了setPreviewSurface(Surface surface),传入的surface对象是holder.getSurface()
那么holder.getSurface()所表示的surface是什么呢?
在SurfaceView的源码中(1088行)找到了答案:

        @Override
        public Surface getSurface() {
            return mSurface;
        }

holder.getSurface()所返回的surface对象就是SurfaceView中的mSurface对象! 这三者的关系见图二

  • setPreviewCallback(PreviewCallback cb)
    设置相机预览数据的回调,参数是一个PreviewCallback对象,上面在介绍内部类的时候已讲过

  • getParameters()
    返回当前相机的参数信息,返回值是一个Parameters对象

  • setParameters(Parameters params)
    设置当前相机的参数信息

  • startPreview()
    开始预览
    调用此方法之前,如果没有setPreviewDisplay(SurfaceHolder) 或 setPreviewTexture(SurfaceTexture)的话,是没有效果的

  • stopPreview()
    停止预览

  • startFaceDetection()
    开始人脸检测
    这个方法必须在开始预览之后调用

  • stopFaceDetection()
    停止人脸检测

  • setFaceDetectionListener(FaceDetectionListener listener)
    设置人脸检测监听回调

  • release()
    释放相机

三、总结

  1. Camera负责采集数据和各种操作,SurfaceView负责把Camera采集到的数据实时地显示在屏幕上

  2. 我们通过SuraceHolder中的回调可以监听Surface的状态(创建、变化、销毁)

  3. 相机图像数据都是来自于相机硬件的图像传感器这个方向是不能改变的

  4. 相机在预览的时候是有一个预览方向的,可以通过setDisplayOrientation()设置

  5. 前置摄像头在进行角度旋转之前,图像会进行一个水平的镜像翻转,所以用户在看预览图像的时候就像照镜子一样

  6. 相机所采集的照片也是有一个方向的,这个方向与预览时的方向互不相干

  7. 我们可以通过setParameters(Parameters params)设置当前相机的参数信息,比如 保存的图片大小,对焦模式等等

  8. 在关闭页面 或者 打开其他摄像头之前,一定要先调用release()方法释放当前相机资源

好,到这里关于Camera开发所需要的知识点已经介绍完了。看了这么多枯燥的知识点是不是已经双手痒痒,迫不及待动手撸代码了?在下篇文章中,我将会带着小伙们一块,从零开始实现自己的Camera相机!

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 136,093评论 17 580
  • 上一篇介绍了如何使用系统相机简单、快速的进行拍照,本篇将介绍如何使用框架提供的API直接控制摄像机硬件。 你还在为...
    Xiao_Mai阅读 4,027评论 3 16
  • 以下内容翻译来源,Google官方开发文档:https://developer.android.google.cn...
    肱二头肌的孤单阅读 3,052评论 4 47
  • 今天是2017年10月17日,农历,8月28。今天是一个特别的日子,是个重要的日子。早晨天还是阴沉沉的,但已经没有...
    星之梦lyx阅读 31评论 0 0
  • 宏观新闻: 1)从知情人士处获悉,“全市场目前1700亿左右的养老金到账规模,社保基金理事会自行管理约600亿规模...
    赢家说_4d25阅读 25评论 0 0