1.Android sdk中的Camera
在Android Studio中敲下Camera,会给出两个提示类:
- android.graphics.Camera ,一个照相机实例可以被用于计算3D变换,生成一个可以被使用的Matrix矩阵,一个实例,用在画布上。
- android.hardware.Camera,用于设置图像捕获设置、开始/停止预览、快照图片以及检索用于视频编码的帧。这个类是相机服务的客户端,它管理实际的相机硬件。
今天要讲的是相机管理类android.hardware.Camera,但在API 21+之后该类已被废弃,新的相机管理类在 android.hardware.camera2 包下,所以本文会覆盖camera和camera2两种API。
2.Camera开发中遇到的一些疑问
- 拍摄的照片为什么会旋转90度
- 预览的图像被拉伸
- 预览时返回的图像帧数据为什么不能正常解析成bitmap
针对以上的问题我们从camera成像说起,camera其实应该叫image sensor。
image sensor有两大类CMOS和CCD,手机中常常使用的是价格低廉和整合性高的CMOS,sensor的成像原理是景物通过镜头(LENS)生成的光学图像投射到图像传感器(Sensor)表面上,然后转为模拟的电信号,经过A/D(模数转换)转换后变为数字图像信号,再送到数字信号处理芯片(DSP)中加工处理,再通过IO接口传输到CPU中处理,通过LCD 就可以看到图像了。
sensor一般的输出格式:
1)YUV sensor
YUV sensor输出的Data格式为YUV,也是使用android.hardware.Camera进行预览返回预览帧的常见格式。
2)Raw sensor
Raw Sensor输出的Data格式为原始未经处理的Raw,图像感应器将捕捉到的光源信号转化为数字信号的原始数据。RAW文件是一种记录了数码相机传感器的原始信息,同时记录了由相机拍摄所产生的一些原数据(如ISO的设置、快门速度、光圈值、白平衡等)的文件,在camera2中可查看相机是否支持raw格式并获取raw数据。
从sensor输出格式可以知道预览时的帧数据为什么不能简单生成bitmap,因为bitmap创建需要的是argb格式,而预览帧数据一般是YUV格式。
image sensor的成像扫描方向是固定的,但image sensor安装在手机的成像方向不一定和手机自然方向(一般为竖屏)一致
上图是常见的后置摄像头与手机自然方向的对比,image sensor 顺时针旋转90度就和手机自然方向一致,这就是为什么我们要给camera设置90度才能竖屏预览。
但90度不是定势,所以实际编码中可以获取image sensor的方向和phone display的方向进行计算得出正确的旋转值。
来看看Android官方给出的建议计算方案
值得注意的是,以上操作只是让预览中显示的图像正常,对于预览返回帧数据和拍照取得的数据并不会真正改变其方向,如果要得到和预览图像一致的照片还需要对图像数据进行额外的旋转操作,但如果只需要拍照图像,则不需要对数据进行旋转,使用camera.getParameters().setRotation(rotation)可以直接得到正确方向的jpeg图像,对于rotation值的计算官方给出的算法在setRotation方法注释中,要特别注意的是这个值只改变jpeg方向,并不能改变预览帧YUV数据方向,在camera2中的使用会更直观一点,后面再讲。
预览时图像显示为什么被拉伸? 这和image sensor返回的预览图像大小有关,只有预览图的大小和显示预览视图大小相近相等时才最自然。
一台手机image sensor支持的属性设置都是有固定值的,绝大多数时候拍摄的图片好不好看取决于image sensor的硬件成本,成像不好,再如何p图也枉然。
3.android.hardware.Camera实例
以上通过SurfaceTexture+GLSurfaceView进行六种不同滤镜效果预览。关于SurfaceView、TextureView、GLSurfaceView、SurfaceTexture的区别可以看这篇文章。
通常大家拍照只要点击一下就好,但是现在的手机越来越贵,硬件越来越好有些可以媲美相机效果,但是喜欢摄影的人都喜欢自己控制相机设置不同的效果进行拍照,一键拍照显得有点傻瓜式了,拍出来的效果都是千遍一律,随着手机硬件的提升,手机拍照的可控性就越来越专业。
一键拍照为什么拍的还可以呢?对于软件层面来说其实关系不大,随便写的Camera的小demo拍出的照片一样好看,这是因为Camera默认情况下使用的是3A模式。
3A就是Auto Exposure(AE 自动曝光) 、Auto Focus(AF自动对焦) 、Auto White Balance(AWB自动白平衡),一个合格的image sensor默认条件下的成像就是这样。
如何调节image sensor参数拍出不同的效果,还需要了解手机上的image sensor支持什么样的参数,对于image sensor来说接收的参数非常严格,对于不支持的参数会直接抛出异常。
下面是荣耀8支持的可调节参数值:
不同手机支持参数不一样,对于预览大小来说尤其重要,设置的不合理,那么我们看见的图像就可能被拉伸,所以在设置预览大小之前,必须计算出和当前显示画面大小高宽比例最接近且高考值最相近的预览Size。
懂摄影的经常说这张照片曝光过度,这张照片曝光不足,专业照相机都有两个按键+EV/-EV,就是通过加/减曝光补偿来弥补当前环境在曝光的缺陷。
在荣耀8上最大和最小的曝光补偿如下图:
从上图可见不同的曝光补偿值对于成像来说可见的细节不一样。
如何使用android.hardware.Camera进行预览拍照相信大家都知道,这样的文章很多,这里不赘述,但是对于预览帧的返回数据相信大家还是有点疑问的,这里再讲一讲YUV。
什么是YUV?
YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。
YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,关于其详细原理,可以通过网上其它文章了解,这里我想强调的是如何根据其采样格式来从码流中还原每个像素点的YUV值,因为只有正确地还原了每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来。
用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。
先记住下面这段话,以后提取每个像素的YUV分量会用到。
YUV 4:4:4采样,每一个Y对应一组UV分量。
YUV 4:2:2采样,每两个Y共用一组UV分量。
YUV 4:2:0采样,每四个Y共用一组UV分量。
YUV格式有两大类:planar和packed。
对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
对于packed的YUV格式,每个像素点的Y,U,V是连续交替存储的。
对于预览帧来说在Android API <=20的版本中返回的YUV格式通常只支持两种格式ImageFormat.NV21和ImageFormat.YV12,它们的区别就是存储方式不一样。
通过camera.getParameters().getPreviewFormat();可以获取预览帧的数据格式,一般来说返回的是ImageFormat.NV21
通过camera.getParameters().getSupportedPreviewFormats();可以获取支持的预览帧数据格式列表,通过camera.getParameters().setPreviewFormat(ImageFormat.YV12);可以设置预览帧格式,
不同手机返回支持格式可能不同,在荣耀8上支持ImageFormat.NV21和ImageFormat.YV12两种。
一般来说不需要开发者自己编写YUV转RGB,android.graphics.YuvImage类支持ImageFormat.NV21和ImageFormat.YUY2转换成jpeg格式,但通过了解YUV格式的采样规则、存储格式、转成RGB转换公式,可以很方便的编写转换算法。
转换公式可参考微软信息网站。
4.camera2实例
最近看见摩托罗拉发布的z3手机中相机支持微动摄影的文章,还是很有意思的一个功能,因为camera2预览返回的数据处理起来比较方便,就写了一个类似微动摄影的demo
camera2相比camera1对应用程序来说提供了更全面的API,提供了更接近于底层相机的功能,重新设计的API更大的提高了对相机子系统的控制能力
在Android 8.0以下,camera1和camera2的相机架构
camera1中CameraService运行在mediaserver进程中,虽然camera2在5.0就存在,但CameraServide依然存在于mediaserver进程中,在Android7.0+系统中CameraService才作为一个独立的系统服务进程存在。
看下应用层的使用流程。
使用camera2进行预览拍照最好的例子可查看官方demo。
先看两个最关键的问题
1、怎么设置预览/拍照图像参数?
2、从哪里拿预览/拍照的图像数据?
第一点,camera1的参数设置都在camera.getParameters()中,camera2在哪里呢?在CaptureRequest.Builder中,比如设置对焦和曝光模式:
前面讲过camera的3A模式,这里看参数命名就直接明了了,更多参数详解可以看3A 模式和状态转换。
另外值得注意的是camera2相比camera1支持的数据格式、预览大小等都有很大的差异,比如在荣耀8上使用camera1进行预览获取的最大预览大小是1920x1080, 拍照支持最大size是3968x2240, 使用camera2的最大预览尺寸为3968x2240,拍照最大支持3968x2240,camera2可支持的预览size更加丰富,使用camera2获取的Size根据格式的不同返回不同, 如果不支持输出的格式则返回null,使用方式如下;
第二点,camera2中预览和拍照都没有直接的图像数据回调,它引入了ImageReader类,在createCaptureSession时可传入surface数组入参,通过CaptureRequest.Builder.addTarget添加多个surface接收图像数据:
SurfaceView、TextureView等呈现的surface数据不能被直接访问,而ImageReader类允许应用程序直接访问呈现到surface的图像数据,使用方式如下:
在camera1中支持预览返回格式有ImageFormat.NV21和ImageFormat.YV12,但是camera2中通常仅开放ImageFormat.JPEG、ImageFormat.RAW_SENSOR和ImageFormat.YUV_420_888,其他格式通常是私有的,不可读取,尤其不支持ImageFormat.NV21。
本例的实现就是通过锁定对焦,预览计时3秒,从ImageReader的OnImageAvailableListener中获取可用Image放入list,解析list生成bitmap,生成原始GIF,自定义view创建画布,获得画布上用户选取绘制像素点,根据像素点列表,获取第一帧之后的每一帧相关像素,循环替换第一帧选取区域像素点,生成微动gif,核心代码如下:
5.总结
从camera2和camera1的使用体验来说,新的api带来了更多的可能性,但厂商定制手机五花八门,同样版本的手机也不一定都支持同样的功能,所以做相机开发需要考虑到相当多的 兼容特性。
从Android API出发,需要考虑camera使用版本、预览view使用类,从设备出发,需要考虑相机支持特性、支持数据格式等,谷歌官方给出的一份兼容方案如下:
参考资料:
http://lib.csdn.net/article/embeddeddevelopment/67528
http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.htm