引言
最近做一套基于Camera2 API的相机应用,发现有关Camera2 手动点击区域对焦的文章少之又少,无奈痛苦研究了一天手动区域对焦,终于达到理想效果。
展示一下效果:
有关Camera2 API的基本使用请自行Google,本文基于研究Camera2点击对焦实现基础之上,如有不足,欢迎指出。
手动对焦基本原理
我们都应该知道,手机改变焦距是通过分析摄像头拍摄二维图,并经过算法根据对焦区域来测量光度情况进行对焦。因此,Camera2中也提供了该对焦方式的API,我们只需提供基于Camera2所需的对焦区域(Rect)即可。
Camera2通过不同的接口,给应用层提供了这方面的信息:
SENSOR_INFO_PIXEL_ARRAY_SIZE表示的是摄像头成像区域所使用的内存大小。
SENSOR_INFO_ACTIVE_ARRAY_SIZE表示真正接收光线的区域,因此成像的区域是该参数指定的区域,当然该矩形区域的坐标系基于SENSOR_INFO_PIXEL_ARRAY_SIZE。
SCALER_CROP_REGION表示最终的输出内容是基于SENSOR_INFO_ACTIVE_ARRAY_SIZE裁剪的部分,而该值指定裁剪的区域。
上图灰色区域可以理解为通过 CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE拿到的区域,这个区域就是我们摄像头拍摄照片的最大分辨率(比如我的三星s8,最大分辨率为4032*3024)。
蓝色区域是通过CaptureRequest.SCALER_CROP_REGION 拿到的部分,该部分经测试与*SENSOR_INFO_PIXEL_ARRAY_SIZE *拿到的区域大小是一样的。
橘黄色区域和绿色区域分别代表我们的应用界面裁剪区域。橘黄色一般为竖屏,绿色一般为横屏。这个两个部分的大小其实就是我们在设定相机预览界面时的大小,这个可以根据需要来设定。但这个区域显示在手机屏幕的时候是等比缩放的。所以我们在处理手指的点击区域时,我们需要根据预览界面相对于相机取景最大分辨率进行调整触摸坐标位置。
开始对焦
原理介绍完了,开始切入正题
首先我们需要知道,手动对焦是通过CaptureRequest.Builder的CONTROL_AF_REGIONS(控制对焦区域)和CONTROL_AE_REGIONS(控制曝光区域)来控制的。上代码(Kotlin):
mPreViewBuilder.apply{
set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(MeteringRectangle(rect, 1000)))
set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(MeteringRectangle(rect, 1000)))
set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START)
}
可以见得手动对焦关键在于如何获取上述代码中的rect变量。也就是相对于整个相机最大分辨率,手指的点击区域。继续上代码(在这里我们暂只考虑竖屏情况,横屏情况参考注释):
/**
* 获取点击区域
* @param x:手指触摸点x坐标
* @param y: 手指触摸点y坐标
*/
private fun getFocusRect( x: Int, y: Int): Rect {
val screenW = FontDisplayUtil.getScreenWidth()//获取屏幕长度
val screenH = FontDisplayUtil.getScreenHeight()//获取屏幕宽度
//因为获取的SCALER_CROP_REGION是宽大于高的,也就是默认横屏模式,竖屏模式需要对调width和height
var realPreviewWidth = mPreviewSize.height
var realPreviewHeight = mPreviewSize.width
//根据预览像素与拍照最大像素的比例,调整手指点击的对焦区域的位置
val focusX = realPreviewWidth.toFloat() / screenW * x
val focusY = realPreviewHeight.toFloat() / screenH * y
LogUtil.i("focusX=$focusX,focusY=$focusY")
//获取SCALER_CROP_REGION,也就是拍照最大像素的Rect
val totalPicSize = mPreViewBuilder!!.get(CaptureRequest.SCALER_CROP_REGION)
LogUtil.i("camera pic area size=$totalShowSize")
//计算出摄像头剪裁区域偏移量
val cutDx = (totalPicSize.height() - mPreviewSize.height) / 2
LogUtil.i("cutDx=$cutDx")
//我们默认使用10dp的大小,也就是默认的对焦区域长宽是10dp,这个数值可以根据需要调节
val width = FontDisplayUtil.dip2px(10f)
val height = FontDisplayUtil.dip2px(10f)
//返回最终对焦区域Rect
return Rect(focusY.toInt(), focusX.toInt() + cutDx, (focusY + height).toInt(), (focusX + cutDx + width).toInt())
}
其中cutDx变量是屏幕比例相对于摄像头剪裁区域的偏移量。因为在我的项目中屏幕是固定竖屏的,也就是说我的屏幕高度与SCALER_CROP_REGION获取的最大高度时相等的,所以只需要考虑宽度的调整。可以参考下图整理一下思路...
至此,点击对焦的大部分工作均已完成。不要忘了在设置完mPreViewBuilder的手动对焦模式之后,使用setRepeatingRequest方法来实现画面的连贯加载。
本文参考:https://www.jianshu.com/p/76225ac72b56
如有不足,欢迎指出,谢谢。