Android自定义相机自动对焦、定点对焦

在解决项目中相机某些机型无法自动对焦的问题时,在网上找到了一些资料,写下解决问题过程,以备查看。

Android相机实时自动对焦的完美实现
Android图像滤镜框架GPUImage从配置到应用
GPUImage for Android

Android Camera对焦相关


加速度控制器

当设备移动时,认定需要对焦,然后调用CameraFocusListener 接口的onFocus()方法。

/**
 * 加速度控制器  用来控制对焦
 * @author zuo
 * @date 2018/5/9 14:34
 */
public class SensorController implements SensorEventListener {
    private SensorManager mSensorManager;
    private Sensor mSensor;
    private static SensorController mInstance;
    private CameraFocusListener mCameraFocusListener;

    public static final int STATUS_NONE = 0;
    public static final int STATUS_STATIC = 1;
    public static final int STATUS_MOVE = 2;
    private int mX, mY, mZ;
    private int STATUE = STATUS_NONE;
    boolean canFocus = false;
    boolean canFocusIn = false;
    boolean isFocusing = false;
    Calendar mCalendar;
    private final double moveIs = 1.4;
    private long lastStaticStamp = 0;
    public static final int DELAY_DURATION = 500;

    private SensorController(Context context) {
        mSensorManager = (SensorManager) context.getSystemService(Activity.SENSOR_SERVICE);
        if (mSensorManager!=null){
            mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        }
        start();
    }

    public static SensorController getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new SensorController(context);
        }
        return mInstance;
    }

    public void setCameraFocusListener(CameraFocusListener mCameraFocusListener) {
        this.mCameraFocusListener = mCameraFocusListener;
    }

    public void start() {
        restParams();
        canFocus = true;
        mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    public void stop() {
        mSensorManager.unregisterListener(this, mSensor);
        canFocus = false;
    }


    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor == null) {
            return;
        }

        if (isFocusing) {
            restParams();
            return;
        }

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            int x = (int) event.values[0];
            int y = (int) event.values[1];
            int z = (int) event.values[2];
            mCalendar = Calendar.getInstance();
            long stamp = mCalendar.getTimeInMillis();
            int second = mCalendar.get(Calendar.SECOND);
            if (STATUE != STATUS_NONE) {
                int px = Math.abs(mX - x);
                int py = Math.abs(mY - y);
                int pz = Math.abs(mZ - z);
                double value = Math.sqrt(px * px + py * py + pz * pz);

                if (value > moveIs) {
                    STATUE = STATUS_MOVE;
                } else {
                    if (STATUE == STATUS_MOVE) {
                        lastStaticStamp = stamp;
                        canFocusIn = true;
                    }

                    if (canFocusIn) {
                        if (stamp - lastStaticStamp > DELAY_DURATION) {
                            //移动后静止一段时间,可以发生对焦行为
                            if (!isFocusing) {
                                canFocusIn = false;
//                                onCameraFocus();
                                if (mCameraFocusListener != null) {
                                    mCameraFocusListener.onFocus();
                                }
                            }
                        }
                    }

                    STATUE = STATUS_STATIC;
                }
            } else {
                lastStaticStamp = stamp;
                STATUE = STATUS_STATIC;
            }

            mX = x;
            mY = y;
            mZ = z;
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    private void restParams() {
        STATUE = STATUS_NONE;
        canFocusIn = false;
        mX = 0;
        mY = 0;
        mZ = 0;
    }

    /**
     * 对焦是否被锁定
     * @return
     */
    public boolean isFocusLocked() {
        return canFocus && isFocusing;
    }

    /**
     * 锁定对焦
     */
    public void lockFocus() {
        isFocusing = true;
    }

    /**
     * 解锁对焦
     */
    public void unlockFocus() {
        isFocusing = false;
    }

    public void restFocus() {
        isFocusing = false;
    }

    public interface CameraFocusListener {
        /**
         * 相机对焦中
         */
        void onFocus();
    }
}

自定义相机

1、初始化相机时,进行加速度监听

public Camera1(Activity activity, Callback callback, PreviewImpl preview) {
        super(callback, preview);
        this.mActivity = activity;
        preview.setCallback(new PreviewImpl.Callback() {

            @Override
            public void onSurfaceChanged() {
                if (mCamera != null) {
                    setUpPreview();
                    adjustCameraParameters();
                }
            }
        });
        sensorController = SensorController.getInstance(mActivity);
        sensorController.setCameraFocusListener(new SensorController.CameraFocusListener() {
            @Override
            public void onFocus() {
                if (mCamera != null) {
                    DisplayMetrics mDisplayMetrics = mActivity.getApplicationContext().getResources()
                            .getDisplayMetrics();
                    int mScreenWidth = mDisplayMetrics.widthPixels;
                    if (!sensorController.isFocusLocked()) {
                        if (newFocus(mScreenWidth / 2, mScreenWidth / 2)) {
                            sensorController.lockFocus();
                        }
                    }
                }
            }
        });
    }
      sensorController.start();

2、自动对焦代码

    private boolean isFocusing;

    private boolean newFocus(int x, int y) {
        //正在对焦时返回
        if (mCamera == null || isFocusing) {
            return false;
        }
        isFocusing = true;
        setMeteringRect(x, y);
        mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        mCamera.cancelAutoFocus(); // 先要取消掉进程中所有的聚焦功能
        try {
            mCamera.setParameters(mCameraParameters);
            mCamera.autoFocus(autoFocusCallback);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

设置感光区域,将手机屏幕点击点对应的感光矩形范围映射到相机的感光矩形坐标系

 /**
     * 设置感光区域
     * 需要将屏幕坐标映射到Rect对象对应的单元格矩形
     *
     * @param x
     * @param y
     */
    private void setMeteringRect(int x, int y) {
        if (mCameraParameters.getMaxNumMeteringAreas() > 0) {
            List<Camera.Area> areas = new ArrayList<Camera.Area>();
            Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);
            int left = rect.left * 2000 / CameraUtil.screenWidth - 1000;
            int top = rect.top * 2000 / CameraUtil.screenHeight - 1000;
            int right = rect.right * 2000 / CameraUtil.screenWidth - 1000;
            int bottom = rect.bottom * 2000 / CameraUtil.screenHeight - 1000;
            // 如果超出了(-1000,1000)到(1000, 1000)的范围,则会导致相机崩溃
            left = left < -1000 ? -1000 : left;
            top = top < -1000 ? -1000 : top;
            right = right > 1000 ? 1000 : right;
            bottom = bottom > 1000 ? 1000 : bottom;
            Rect area1 = new Rect(left, top, right, bottom);
            //只有一个感光区,直接设置权重为1000了
            areas.add(new Camera.Area(area1, 1000));
            mCameraParameters.setMeteringAreas(areas);
        }
    }

3、自动对焦回调事件

 private Handler mHandler = new Handler();
    private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {

        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //一秒之后才能再次对焦
                    isFocusing = false;
                    sensorController.unlockFocus();
                }
            }, 1000);
        }
    };

4、关闭监听事件

    /**
     * 关闭摄像头,关掉加速度监听
     *
     */
    @Override
    public void stop(boolean stopAll) {
        sensorController.stop();
        stopPreview(); 
        releaseCamera();
    }

测光和调焦

在某些摄像情景中,自动调焦和测光可能不能达到设计结果。从Android4.0(API Level 14)开始,你的Camera应用程序能够提供另外的控制允许应用程序或用户指定图像中特定区域用于进行调焦或光线级别的设置,并且把这些值传递给Camera硬件用于采集图片或视频。

测光和调焦区域的工作与其他Camera功能非常类似,你可以通过Camera.Parameters对象中的方法来控制它们。

1、给Camera设置两个测光区域

Camera.Area对象,包含两个参数:

  • Rect对象,它用于指定Camera预览窗口一块矩形区域(测光区域)
  • 一个权重值(weight),它告诉Camera这块指定区域应该给予的测光或调焦计算的重要性等级,权重大的优先级高,权重最高为1000
//获取相机实例
Camera.Parameters params = mCamera.getParameters();
//检查是否支持测光区域
if (params.getMaxNumMeteringAreas() > 0){ 

   List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
    //在图像的中心指定一个测光区域
   Rect areaRect1 = new Rect(-100, -100, 100, 100);    
    //设置权重为600,最高1000
   meteringAreas.add(new Camera.Area(areaRect1, 600));  
    //图像右上方的测光区域
   Rect areaRect2 = new Rect(800, -1000, 1000, -800);   
    //设置权重为400
   meteringAreas.add(new Camera.Area(areaRect2, 400));  
    //将测光区域设置给相机属性
   params.setMeteringAreas(meteringAreas);
}
mCamera.setParameters(params);

Rect对象,代表了一个2000x2000的单元格矩形,它的坐标对应Camera图像的位置关系可以参考下图,坐标(-1000,-1000)代表Camera图像的左上角,(1000,1000)代表Camera图像的右下角。


Camera感光矩阵坐标示意

2、感光区的计算

在上面设置感光区的setMeteringRect()方法中,为什么从手机屏幕坐标系映射到相机的感光矩形坐标系需要这样计算?

 Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);
 int left = rect.left * 2000 / CameraUtil.screenWidth - 1000;
 int top = rect.top * 2000 / CameraUtil.screenHeight - 1000;
 int right = rect.right * 2000 / CameraUtil.screenWidth - 1000;
 int bottom = rect.bottom * 2000 / CameraUtil.screenHeight - 1000;
  • 首先,我们把手机的屏幕坐标和Camera的感光矩阵坐标对应起来
    如图,我用黑色线条绘制了手机的屏幕坐标系,用红色线条绘制了Camera的感光矩阵坐标系,Camera的感光矩阵坐标系是一个2000x2000的单元格矩形,(0,0)单元格在中心,(-1000,-1000)代表Camera图像的左上角,(1000,1000)代表Camera图像的右下角。
    现在,我们点击了手机屏幕上的(x,y)这个点,并取这个点上下左右各100个单位的矩形作为要映射到Camera感光矩阵坐标系上的感光(对焦)区域,就是上面代码中的Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);,好了,我们要开始计算了。
手机屏幕坐标&相机感光矩阵
  • 计算,将手机屏幕坐标系上的矩形映射到Camera感光矩阵坐标系上
    矩形Rect对象入参的定义:Rect(int left, int top, int right, int bottom),下面我们就用rect.left、rect.top、rect.right、rect.bottom来表示该矩形在手机屏幕坐标上的数据,用△left 、△top 、△right 、△bottom表示该矩形在Camera感光矩阵坐标上的距离(长度),用left 、top 、right 、bottom表示该矩形在Camera感光矩阵坐标上的坐标,进行计算,
//1、建立等价式
rect.left / width =  △left / 2000  ,距离在两个坐标系上的长度比相同
left = △left - 1000 ,不管在第几象限-1000之后的数据都是他的坐标值

计算可得

△left = rect.left * 2000 / width 
left = △left - 1000 = rect.left * 2000 / width -1000

也就是该矩形在Camera感光矩阵坐标系上的 left 数值就等于 rect.left * 2000 / width -1000 ,width 就是手机屏幕的宽度,也就是上面代码中的 int left = rect.left * 2000 / CameraUtil.screenWidth - 1000;

同理:

 int top = rect.top * 2000 / CameraUtil.screenHeight - 1000;
 int right = rect.right * 2000 / CameraUtil.screenWidth - 1000;
 int bottom = rect.bottom * 2000 / CameraUtil.screenHeight - 1000;

好了,感光区的计算就是这样了!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容