Camera2 对焦模式

2,470 阅读3分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

一、对焦模式

1、对焦模式介绍 常用的四种

public static final String FOCUS_MODE_AUTO = "auto";
public static final String FOCUS_MODE_FIXED = "fixed"; 
public static final String FOCUS_MODE_CONTINUOUS_VIDEO = "continuous-video";
public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";

FOCUS_MODE_AUTO:自动对焦,这个只会触发一次对焦,并且是需要在预览开启后,调用autoFocus接口后才会触发, 像触点对焦、和拍照对焦都可以用到该模式;

FOCUS_MODE_FIXED:定焦,有些摄像头本身不支持对焦;

FOCUS_MODE_CONTINUOUS_VIDEO:录像的时候,可以采用该模式,会持续对焦,设置parameter参数后就会生效;

FOCUS_MODE_CONTINUOUS_PICTURE :拍照的时候,可以采用该模式,会持续对焦,设置parameter参数后就会生效, 对焦速度相对 FOCUS_MODE_CONTINUOUS_VIDEO 会快点;

2、设置对焦模式

mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, int focusMode);

二、实现

1. 操作摄像头进行对焦

mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[] {new MeteringRectangle(rect, 1000)});
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {new MeteringRectangle(rect, 1000)}); 
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START); mPreviewRequest = mPreviewRequestBuilder.build();
try 
{ mCaptureSession.setRepeatingRequest(mPreviewRequest, mAfCaptureCallback, mBackgroundHandler); }
catch (CameraAccessException e) 
{ Log.e(TAG, "setRepeatingRequest failed, " + e.getMessage()); }

2. 对焦完成恢复正常的预览模式

if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState)
{ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); 
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); mPreviewRequest = mPreviewRequestBuilder.build();
try 
{ mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler); }
catch (CameraAccessException e) 
{ Log.e(TAG, "setRepeatingRequest failed, errMsg: " + e.getMessage()); } }

3. 计算自动对焦区域

CONTROL_AF_REGIONS 的参数是指定用来测量对焦的区域,摄像头设备使用这个区域来测量光度情况进行对焦,因此该区域坐标系基于摄像头成像区域的坐标系。那么app接收到的图像数据与成像区域的关系如何呢?

手机摄像头成像区域是固定的,因此系统对于app中需要采集不同分辨率的需求做法是:将成像区域按照需要的分辨率比例进行居中裁剪,然后缩放到对应的尺寸。

  • 一般app拿到摄像头来的数据之后,会将摄像头进行合适的旋转,然后根据屏幕比例居中裁剪图像数据,再进行显示。所以我们先将屏幕上面点击区域的坐标转化为app收到的图像区域的坐标:

// 先取相对于view上面的坐标

double x = event.getX(), y = event.getY(), tmp;

// 取出来的图像如果有旋转角度的话,则需要将宽高交换下

int realPreviewWidth = mPreviewSize.width, realPreviewHeight = mPreviewSize.height;

if (90 == mDisplayRotate || 270 == mDisplayRotate)

{ realPreviewWidth = mPreviewSize.height; realPreviewHeight = mPreviewSize.width; }

// 计算摄像头取出的图像相对于view放大了多少,以及有多少偏移

double imgScale = 1.0, verticalOffset = 0, horizontalOffset = 0;

if (realPreviewHeight * viewWidth > realPreviewWidth * viewHeight)

{ imgScale = viewWidth * 1.0 / realPreviewWidth; verticalOffset = (realPreviewHeight - viewHeight / imgScale) / 2; }

else { imgScale = viewHeight * 1.0 / realPreviewHeight; horizontalOffset = (realPreviewWidth - viewWidth / imgScale) / 2; }

// 将点击的坐标转换为图像上的坐标

x = x / imgScale + horizontalOffset; y = y / imgScale + verticalOffset;

if (90 == mDisplayRotate) { tmp = x; x = y; y = mPreviewSize.height - tmp; }

else if (270 == mDisplayRotate) { tmp = x; x = mPreviewSize.width - y; y = tmp; }

  • app取到的图像是按照裁剪区域(crop region)按照预览尺寸的比例进行居中裁剪的,所以需要计算app取到的图像相对于裁剪区域进行了多少缩放,以及有多少位移:

// 计算取到的图像相对于裁剪区域的缩放系数,以及位移

Rect cropRegion = mPreviewRequest.get(CaptureRequest.SCALER_CROP_REGION); 
if (null == cropRegion)
{ Log.e(TAG, "can't get crop region"); cropRegion = mActiveArraySize; } int cropWidth = cropRegion.width(), cropHeight = cropRegion.height(); 
if (mPreviewSize.height * cropWidth > mPreviewSize.width * cropHeight) 
{ imgScale = cropHeight * 1.0 / mPreviewSize.height; verticalOffset = 0; horizontalOffset = (cropWidth - imgScale * mPreviewSize.width) / 2; }
else { imgScale = cropWidth * 1.0 / mPreviewSize.width; horizontalOffset = 0; verticalOffset = (cropHeight - imgScale * mPreviewSize.height) / 2; }
  • 将点击区域相对于app取到的图像坐标,转化为相对于成像区域的坐标:

// 将点击区域相对于图像的坐标,转化为相对于成像区域的坐标 x = x * imgScale + horizontalOffset + cropRegion.left; y = y * imgScale + verticalOffset + cropRegion.top;

  • 按照对焦区域为成像区域的0.1倍计算对焦的矩形:
double tapAreaRatio = 0.1; Rect rect = new Rect(); 
rect.left = clamp((int) (x - tapAreaRatio / 2 * cropRegion.width()), 0, cropRegion.width()); 
rect.right = clamp((int) (x + tapAreaRatio / 2 * cropRegion.width()), 0, cropRegion.width()); 
rect.top = clamp((int) (y - tapAreaRatio / 2 * cropRegion.height()), 0, cropRegion.height()); 
rect.bottom = clamp((int) (y + tapAreaRatio / 2 * cropRegion.height()), 0, cropRegion.height());