Android自定義相機(jī)自動對焦逞敷、定點對焦

在解決項目中相機(jī)某些機(jī)型無法自動對焦的問題時,在網(wǎng)上找到了一些資料蚯涮,寫下解決問題過程坯临,以備查看。

Android相機(jī)實時自動對焦的完美實現(xiàn)
Android圖像濾鏡框架GPUImage從配置到應(yīng)用
GPUImage for Android

Android Camera對焦相關(guān)


加速度控制器

當(dāng)設(shè)備移動時恋昼,認(rèn)定需要對焦看靠,然后調(diào)用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) {
                            //移動后靜止一段時間液肌,可以發(fā)生對焦行為
                            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 {
        /**
         * 相機(jī)對焦中
         */
        void onFocus();
    }
}

自定義相機(jī)

1挟炬、初始化相機(jī)時,進(jìn)行加速度監(jiān)聽

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(); // 先要取消掉進(jìn)程中所有的聚焦功能
        try {
            mCamera.setParameters(mCameraParameters);
            mCamera.autoFocus(autoFocusCallback);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

設(shè)置感光區(qū)域谤祖,將手機(jī)屏幕點擊點對應(yīng)的感光矩形范圍映射到相機(jī)的感光矩形坐標(biāo)系

 /**
     * 設(shè)置感光區(qū)域
     * 需要將屏幕坐標(biāo)映射到Rect對象對應(yīng)的單元格矩形
     *
     * @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)的范圍,則會導(dǎo)致相機(jī)崩潰
            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);
            //只有一個感光區(qū)老速,直接設(shè)置權(quán)重為1000了
            areas.add(new Camera.Area(area1, 1000));
            mCameraParameters.setMeteringAreas(areas);
        }
    }

3粥喜、自動對焦回調(diào)事件

 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、關(guān)閉監(jiān)聽事件

    /**
     * 關(guān)閉攝像頭,關(guān)掉加速度監(jiān)聽
     *
     */
    @Override
    public void stop(boolean stopAll) {
        sensorController.stop();
        stopPreview(); 
        releaseCamera();
    }

測光和調(diào)焦

在某些攝像情景中橘券,自動調(diào)焦和測光可能不能達(dá)到設(shè)計結(jié)果额湘。從Android4.0(API Level 14)開始,你的Camera應(yīng)用程序能夠提供另外的控制允許應(yīng)用程序或用戶指定圖像中特定區(qū)域用于進(jìn)行調(diào)焦或光線級別的設(shè)置旁舰,并且把這些值傳遞給Camera硬件用于采集圖片或視頻锋华。

測光和調(diào)焦區(qū)域的工作與其他Camera功能非常類似,你可以通過Camera.Parameters對象中的方法來控制它們箭窜。

1毯焕、給Camera設(shè)置兩個測光區(qū)域

Camera.Area對象,包含兩個參數(shù):

  • Rect對象磺樱,它用于指定Camera預(yù)覽窗口一塊矩形區(qū)域(測光區(qū)域)
  • 一個權(quán)重值(weight)纳猫,它告訴Camera這塊指定區(qū)域應(yīng)該給予的測光或調(diào)焦計算的重要性等級,權(quán)重大的優(yōu)先級高竹捉,權(quán)重最高為1000
//獲取相機(jī)實例
Camera.Parameters params = mCamera.getParameters();
//檢查是否支持測光區(qū)域
if (params.getMaxNumMeteringAreas() > 0){ 

   List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
    //在圖像的中心指定一個測光區(qū)域
   Rect areaRect1 = new Rect(-100, -100, 100, 100);    
    //設(shè)置權(quán)重為600芜辕,最高1000
   meteringAreas.add(new Camera.Area(areaRect1, 600));  
    //圖像右上方的測光區(qū)域
   Rect areaRect2 = new Rect(800, -1000, 1000, -800);   
    //設(shè)置權(quán)重為400
   meteringAreas.add(new Camera.Area(areaRect2, 400));  
    //將測光區(qū)域設(shè)置給相機(jī)屬性
   params.setMeteringAreas(meteringAreas);
}
mCamera.setParameters(params);

Rect對象,代表了一個2000x2000的單元格矩形活孩,它的坐標(biāo)對應(yīng)Camera圖像的位置關(guān)系可以參考下圖物遇,坐標(biāo)(-1000,-1000)代表Camera圖像的左上角憾儒,(1000,1000)代表Camera圖像的右下角询兴。


Camera感光矩陣坐標(biāo)示意

2、感光區(qū)的計算

在上面設(shè)置感光區(qū)的setMeteringRect()方法中起趾,為什么從手機(jī)屏幕坐標(biāo)系映射到相機(jī)的感光矩形坐標(biāo)系需要這樣計算诗舰?

 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;
  • 首先,我們把手機(jī)的屏幕坐標(biāo)和Camera的感光矩陣坐標(biāo)對應(yīng)起來
    如圖训裆,我用黑色線條繪制了手機(jī)的屏幕坐標(biāo)系眶根,用紅色線條繪制了Camera的感光矩陣坐標(biāo)系,Camera的感光矩陣坐標(biāo)系是一個2000x2000的單元格矩形边琉,(0,0)單元格在中心属百,(-1000,-1000)代表Camera圖像的左上角变姨,(1000,1000)代表Camera圖像的右下角族扰。
    現(xiàn)在,我們點擊了手機(jī)屏幕上的(x,y)這個點定欧,并取這個點上下左右各100個單位的矩形作為要映射到Camera感光矩陣坐標(biāo)系上的感光(對焦)區(qū)域渔呵,就是上面代碼中的Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);,好了砍鸠,我們要開始計算了扩氢。
手機(jī)屏幕坐標(biāo)&相機(jī)感光矩陣
  • 計算,將手機(jī)屏幕坐標(biāo)系上的矩形映射到Camera感光矩陣坐標(biāo)系上
    矩形Rect對象入?yún)⒌亩x:Rect(int left, int top, int right, int bottom)爷辱,下面我們就用rect.left录豺、rect.top、rect.right饭弓、rect.bottom來表示該矩形在手機(jī)屏幕坐標(biāo)上的數(shù)據(jù)巩检,用△left 、△top 示启、△right 兢哭、△bottom表示該矩形在Camera感光矩陣坐標(biāo)上的距離(長度),用left 夫嗓、top 迟螺、right 、bottom表示該矩形在Camera感光矩陣坐標(biāo)上的坐標(biāo)舍咖,進(jìn)行計算矩父,
//1、建立等價式
rect.left / width =  △left / 2000  排霉,距離在兩個坐標(biāo)系上的長度比相同
left = △left - 1000 窍株,不管在第幾象限-1000之后的數(shù)據(jù)都是他的坐標(biāo)值

計算可得

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

也就是該矩形在Camera感光矩陣坐標(biāo)系上的 left 數(shù)值就等于 rect.left * 2000 / width -1000 ,width 就是手機(jī)屏幕的寬度,也就是上面代碼中的 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;

好了球订,感光區(qū)的計算就是這樣了后裸!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市冒滩,隨后出現(xiàn)的幾起案子微驶,更是在濱河造成了極大的恐慌,老刑警劉巖开睡,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件因苹,死亡現(xiàn)場離奇詭異,居然都是意外死亡篇恒,警方通過查閱死者的電腦和手機(jī)扶檐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胁艰,“玉大人蘸秘,你說我怎么就攤上這事』茸拢” “怎么了醋虏?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哮翘。 經(jīng)常有香客問我颈嚼,道長,這世上最難降的妖魔是什么饭寺? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任阻课,我火速辦了婚禮,結(jié)果婚禮上艰匙,老公的妹妹穿的比我還像新娘限煞。我一直安慰自己,他們只是感情好员凝,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布署驻。 她就那樣靜靜地躺著,像睡著了一般健霹。 火紅的嫁衣襯著肌膚如雪旺上。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天糖埋,我揣著相機(jī)與錄音宣吱,去河邊找鬼。 笑死瞳别,一個胖子當(dāng)著我的面吹牛征候,可吹牛的內(nèi)容都是我干的杭攻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼疤坝,長吁一口氣:“原來是場噩夢啊……” “哼兆解!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卒煞,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叼架,沒想到半個月后畔裕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡乖订,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年扮饶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乍构。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡甜无,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哥遮,到底是詐尸還是另有隱情岂丘,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布眠饮,位于F島的核電站奥帘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仪召。R本人自食惡果不足惜寨蹋,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一已旧、第九天 我趴在偏房一處隱蔽的房頂上張望运褪。 院中可真熱鬧,春花似錦吐句、人聲如沸嗦枢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春涕蜂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背有鹿。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工穿肄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留矢否,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓社牲,卻偏偏與公主長得像违寿,于是被迫代替她去往敵國和親搞莺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容