Touch Focus对焦区域计算
- 一、摘要
- 二、算法
- 三、计算方法
-
- 3.1 网上常用方法
- 3.2 最佳计算方法
-
- 构造`CoordinateTransformer.java`类
- 具体计算
- 3.3 官方touch focus区域计算源码
一、摘要
本章介绍点击对焦区域计算的几种方式,包括如下几部分:
- 算法解析
- 网上常用方法
- 最佳计算方法
- 官方源码
相关文章:
- Camera1 对焦(一) UI坐标系和相机坐标系
- 【Android Camera1】Camera1 对焦(三) 对焦功能标准化流程
- Camera1 Parameters参数详解(二)—— 3A算法 (对焦、曝光、白平衡)
- Android AF Work Flow安卓自动对焦工作流程
二、算法
这里回顾一下算法如下
- 获取点击对应的Point(x, y)
- Point(x, y)根据相机的orient方向,来判断是否进行x, y互换【后置从UI到相机参考系需要逆时针旋转-90度】
- Point(x, y) 从(0, 0, mPreviewWidth, mPreviewHeight) => (-1000, -1000, 1000, 1000)
- Point(x, y) [camera] 根据AspectRatio和区域Size => Rect[Camera]
点击对焦区域的计算结果和如下几个因素相关:
Point(x,y)
:触摸点的坐标isFront
:相机是否前后置mPreviewUIWidth, mPreviewUIHeight
:UI Sizeorientation
:CameraInfo.orientationcurZoomValue
: 当前缩放值:在未锁放下可不考虑isFocus
:用于区分是计算对焦区域还是计算曝光区域weight
:区域的权重 :可默认1000aspectRatio
:宽高比:可不考虑直接返回正方形的rect
三、计算方法
3.1 网上常用方法
如果从网上搜,则大概率会搜到如下函数代码,但是它只适合一部分的应用场景。
calculateTapArea
3.2 最佳计算方法
分为2部分,具体通过Matrix类进行坐标转换
- 构造
CoordinateTransformer.java
类。 - 具体计算
构造CoordinateTransformer.java
类
// ui坐标系转换到相机坐标系的Matrix
private final Matrix mTransformMatrix;
// 相机的范围RectF
private RectF mCamera1RectF;
// ui坐标范围
private int mPreviewUIWidth,mPreviewUIHeight;/** *@isCameraFront : 相机是否前置、前置需要做一次水平翻转 *@rotateDegree:matrix旋转角度 为相机的rotation *@mCamera1RectF:camera1坐标系 *@previewRect:ui坐标系 *@mTransformMatrix transform Matrix **/
public Camera2CoordinateTransformer(boolean isCameraFront, int rotateDegree, int mPreviewUIWidth,int mPreviewUIHeight) {
this.mPreviewUIWidth = mPreviewUIWidth;this.mPreviewUIHeight = mPreviewUIHeight;mCamera1RectF = new RectF(-1000, -1000, 1000, 1000);RectF previewRect = new RectF(0,0,mPreviewUIWidth,mPreviewUIHeight);mTransformMatrix = previewToCameraTransform(isCameraFront, rotateDegree, previewRect);
}//核心方法
private Matrix previewToCameraTransform(boolean mirrorX, int sensorOrientation,RectF previewRect) {
Matrix transform = new Matrix();//前置的话需要镜像,scaleX进行反转transform.setScale(mirrorX ? -1 : 1, 1);//ui的坐标系和相机的坐标系有夹角,需要做rotate处理//如后置,这里传入的是90度,ui坐标系转换为相机坐标系就需要顺时针旋转90度 即postRotate(-90)transform.postRotate(-sensorOrientation);// 先在previewRect添加旋转操作transform.mapRect(previewRect);// Map preview coordinates to driver coordinatesMatrix fill = new Matrix();fill.setRectToRect(previewRect, mDriverRectF, Matrix.ScaleToFit.FILL);// fill做坐标转换映射到镜像和旋转transform.setConcat(fill, transform);// finally get transform matrixreturn transform;
}/*** ui坐标系下的rect转换到相机坐标系的rect* Transform a rectangle in preview view space into a new rectangle in* camera view space.* @param source the rectangle in preview view space* @return the rectangle in camera view space.*/
private RectF toCameraCoorRectF(RectF source) {
RectF result = new RectF();mPreviewToCameraTransform.mapRect(result, source);return result;
}//调用获取Camera.Area
//曝光区域要比对焦区域大。这里默认设置1000
public Camera.Area getArea(float x, float y,boolean isFocusArea) {
if (isFocusArea) {
return calcTapArea(x,y,mPreviewUIWidth / 6f,1000);} else {
return calcTapArea(x,y,mPreviewUIWidth / 3f,1000);}
}private Rect toFocusRect(RectF rectF) {
Rect mFocusRect = new Rect();mFocusRect.left = Math.round(rectF.left);mFocusRect.top = Math.round(rectF.top);mFocusRect.right = Math.round(rectF.right);mFocusRect.bottom = Math.round(rectF.bottom);return mFocusRect;
}private Camera.Area calcTapArea(float currentX, float currentY, float areaSize,int weight) {
float left = currentX - areaSize / 2f;float top = currentY - areaSize / 2f;RectF rect = new RectF(left, top, left + areaSize, top + areaSize);return new Camera.Area(toRect(toCameraCoorRectF(rectF)), weight));
}
具体计算
handleFocus
方法
public void handleTouchFocus(float x, float y){
//判读如果不支持FOCUS_MODE_AUTO 就返回,注意要try catchif(! supportFocusMode(Camera.Parameters.FOCUS_MODE_AUTO)){
return;}//先取消cancelAutoFocustry {
mCamera.cancelAutoFocus();} catch (Exception e) {
}//注意处理zoomValue情况if (curZoomValue > 1.0f) {
//zoomX - centerX = (sorceX - centerX)*zoomValue;//sourceX = (zoomX - centerX)/zoomValue+ centerX;//这里的x是zoom之后的x坐标x = (x- previewUIWidth / 2f) / curZoomValue + previewUIWidth / 2f;y = (y - previewUIHeight / 2f) / curZoomValue + previewUIHeight / 2f;}//CoordinateTransformer类方法Camera.Area focusArea = transformer.getArea(x, y, true);Camera.Area meterArea = transformer.getArea(x, y, false);...if(supportFocusArea){
updateParameters...}if(supportMeterArea){
updateParameters...}
}
分析
- 判断1:是否支持
FOCUS_MODE_AUTO
,touch focus
需要FOCUS_MODE_AUTO
模式,像前置就不支持;- 判断2:是否支持supportFocusArea和supportMeterArea,具体可参考Camera1 Parameters参数详解(二)—— 3A算法 (对焦、曝光、白平衡)
- touch focus之前需要
mCamera.cancelAutoFocus();
- 判断,设置,更新参数需要
try-catch
- zoom Value处理,这里需要特别注意zoom放大后的坐标计算。因为不管缩放多大,最终都是以
zoom = 1.0f
的坐标系参考
3.3 官方touch focus区域计算源码
packages/apps/LegacyCamera/src/com/android/camera/FocusManager.java
public boolean onTouch(MotionEvent e) {
if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) return false;// Let users be able to cancel previous touch focus.if ((mFocusArea != null) && (mState == STATE_FOCUSING ||mState == STATE_SUCCESS || mState == STATE_FAIL)) {
cancelAutoFocus();}// Initialize variables.int x = Math.round(e.getX());int y = Math.round(e.getY());int focusWidth = mFocusIndicatorRotateLayout.getWidth();int focusHeight = mFocusIndicatorRotateLayout.getHeight();int previewWidth = mPreviewFrame.getWidth();int previewHeight = mPreviewFrame.getHeight();if (mFocusArea == null) {
mFocusArea = new ArrayList<Area>();mFocusArea.add(new Area(new Rect(), 1));mMeteringArea = new ArrayList<Area>();mMeteringArea.add(new Area(new Rect(), 1));}// Convert the coordinates to driver format.// AE area is bigger because exposure is sensitive and// easy to over- or underexposure if area is too small.calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,mFocusArea.get(0).rect);calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,mMeteringArea.get(0).rect);// Set the focus area and metering area.mListener.setFocusParameters();if (mFocusAreaSupported && (e.getAction() == MotionEvent.ACTION_UP)) {
autoFocus();} else {
// Just show the indicator in all other cases.updateFocusUI();// Reset the metering area in 3 seconds.mHandler.removeMessages(RESET_TOUCH_FOCUS);mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);}return true;
}public void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,int x, int y, int previewWidth, int previewHeight, Rect rect) {
int areaWidth = (int)(focusWidth * areaMultiple);int areaHeight = (int)(focusHeight * areaMultiple);int left = Util.clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);int top = Util.clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);mMatrix.mapRect(rectF);Util.rectFToRect(rectF, rect);
}
分析
1.可以看到onTouch
方法里调用calculateTapArea
计算对焦区域值
2.calculateTapArea
中,最终通过mMatrix.mapRect(rectF);把ui坐标系映射成为相机坐标系
public void initialize(View focusIndicatorRotate, View previewFrame,FaceView faceView, Listener listener, boolean mirror, int displayOrientation) {
mFocusIndicatorRotateLayout = focusIndicatorRotate;mFocusIndicator = (FocusIndicatorView) focusIndicatorRotate.findViewById(R.id.focus_indicator);mPreviewFrame = previewFrame;mFaceView = faceView;mListener = listener;Matrix matrix = new Matrix();Util.prepareMatrix(matrix, mirror, displayOrientation,previewFrame.getWidth(), previewFrame.getHeight());// In face detection, the matrix converts the driver coordinates to UI// coordinates. In tap focus, the inverted matrix converts the UI// coordinates to driver coordinates.matrix.invert(mMatrix);if (mParameters != null) {
mInitialized = true;} else {
Log.e(TAG, "mParameters is not initialized.");}
}public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,int viewWidth, int viewHeight) {
// Need mirror for front camera.matrix.setScale(mirror ? -1 : 1, 1);// This is the value for android.hardware.Camera.setDisplayOrientation.matrix.postRotate(displayOrientation);// Camera driver coordinates range from (-1000, -1000) to (1000, 1000).// UI coordinates range from (0, 0) to (width, height).matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
}
分析:
- 这里计算逻辑是计算从相机坐标系到ui坐标系 然后matrix.invert(mMatrix); 取返就得到ui坐标系映射到相机坐标系。
【3.2】
中是直接计算ui坐标系到相机坐标系。- 相机坐标系到ui坐标系先处理镜像,然后postRotate(displayOrientation) invert之后正好对应
【3.2】
中postRotate(-rotateDegree)。最后把from (-1000, -1000) to (1000, 1000)范围映射成为(0, 0) to (width, height)。
综上官方的计算过程为
- 在
FocusManager
里先prepareMatrix为相机坐标系->ui坐标系。 - 然后通过Matrix.invert得到ui坐标系-> 相机坐标系。
- 最后在
onTouch
函数里calculateTapArea -> mMatrix.mapRect(rectF) -> Util.rectFToRect(rectF, rect);从ui坐标系下到RectF得到相机坐标系下到Rect。