zoukankan      html  css  js  c++  java
  • [Android编程心得] Camera(OpenCV)自动对焦和触摸对焦的实现


    写在前面


    最近在从零开始写一个移动端的AR系统,坑实在是太多了!!!整个项目使用了OpenCV第三方库,但对于摄像机来说,和原生Camera的方法基本相同。



    实现


    以OpenCV的JavaCameraView为例,首先需要定制自己的Camera,主要代码如下:
    import java.util.ArrayList;
    import java.util.List;
    
    import org.opencv.android.JavaCameraView;
    
    import android.R.integer;
    import android.content.Context;
    import android.graphics.Rect;
    import android.graphics.RectF;
    import android.hardware.Camera;
    import android.hardware.Camera.AutoFocusCallback;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.widget.Toast;
    
    public class MTCameraView extends JavaCameraView implements AutoFocusCallback {
    
    	public MTCameraView(Context context, int attrs) {
    		super(context, attrs);
    		// TODO Auto-generated constructor stub
    	}
    
    	public List<Camera.Size> getResolutionList() {      
    	    return  mCamera.getParameters().getSupportedPreviewSizes();      
    	}
    	
    	public Camera.Size getResolution() {
    	    Camera.Parameters params = mCamera.getParameters(); 
    	    Camera.Size s = params.getPreviewSize();
    	    return s;
    	}
    	
    	public void setResolution(Camera.Size resolution) {
    	    disconnectCamera();
    	    connectCamera((int)resolution.width, (int)resolution.height);       
    	}
    	
    	public void focusOnTouch(MotionEvent event) {
            Rect focusRect = calculateTapArea(event.getRawX(), event.getRawY(), 1f);
            Rect meteringRect = calculateTapArea(event.getRawX(), event.getRawY(), 1.5f);
    
            Camera.Parameters parameters = mCamera.getParameters();
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            
            if (parameters.getMaxNumFocusAreas() > 0) {
            	List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
            	focusAreas.add(new Camera.Area(focusRect, 1000));
            
            	parameters.setFocusAreas(focusAreas);
            }
    
            if (parameters.getMaxNumMeteringAreas() > 0) {
            	List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
            	meteringAreas.add(new Camera.Area(meteringRect, 1000));
            	
                parameters.setMeteringAreas(meteringAreas);
            }
    
            mCamera.setParameters(parameters);
            mCamera.autoFocus(this);
    	}
    	
    	/**
    	 * Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
    	 */
    	private Rect calculateTapArea(float x, float y, float coefficient) {
    	    float focusAreaSize = 300;
    	    int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
    
    	    int centerX = (int) (x / getResolution().width * 2000 - 1000);
    	    int centerY = (int) (y / getResolution().height * 2000 - 1000);
    
    	    int left = clamp(centerX - areaSize / 2, -1000, 1000);
    	    int right = clamp(left + areaSize, -1000, 1000);
    	    int top = clamp(centerY - areaSize / 2, -1000, 1000);
    	    int bottom = clamp(top + areaSize, -1000, 1000);
    
    	    return new Rect(left, top, right, bottom);
            }
    
    	private int clamp(int x, int min, int max) {
    	    if (x > max) {
    	        return max;
    	    }
    	    if (x < min) {
    	        return min;
    	    }
    	    return x;
    	}
    	
    	public void setFocusMode (Context item, int type){
    	    Camera.Parameters params = mCamera.getParameters(); 
    	    List<String> FocusModes = params.getSupportedFocusModes();
    
    	    switch (type){
    	    case 0:
    	        if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO))
    	            params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
    	        else 
    	            Toast.makeText(item, "Auto Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;
    	    case 1:         
    	        if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
    	            params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
    	        else
    	            Toast.makeText(item, "Continuous Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;
    	    case 2:         
    	        if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_EDOF))
    	            params.setFocusMode(Camera.Parameters.FOCUS_MODE_EDOF);
    	        else
    	            Toast.makeText(item, "EDOF Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;
    	    case 3:
    	        if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_FIXED))
    	            params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
    	        else
    	            Toast.makeText(item, "Fixed Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;
    	    case 4:
    	        if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_INFINITY))
    	            params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
    	        else
    	            Toast.makeText(item, "Infinity Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;
    	    case 5:
    	        if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_MACRO))
    	            params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
    	        else
    	            Toast.makeText(item, "Macro Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;      
    	    }
    
    	    mCamera.setParameters(params);
    	}
    	
    	public void setFlashMode (Context item, int type){
    	    Camera.Parameters params = mCamera.getParameters();
    	    List<String> FlashModes = params.getSupportedFlashModes();
    
    	    switch (type){
    	    case 0:
    	        if (FlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO))
    	            params.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
    	        else
    	            Toast.makeText(item, "Auto Mode not supported", Toast.LENGTH_SHORT).show();
    	        break;
    	    case 1:
    	        if (FlashModes.contains(Camera.Parameters.FLASH_MODE_OFF))
    	            params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
    	        else
    	            Toast.makeText(item, "Off Mode not supported", Toast.LENGTH_SHORT).show();          
    	        break;
    	    case 2:
    	        if (FlashModes.contains(Camera.Parameters.FLASH_MODE_ON))
    	            params.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
    	        else
    	            Toast.makeText(item, "On Mode not supported", Toast.LENGTH_SHORT).show();       
    	        break;
    	    case 3:
    	        if (FlashModes.contains(Camera.Parameters.FLASH_MODE_RED_EYE))
    	            params.setFlashMode(Camera.Parameters.FLASH_MODE_RED_EYE);
    	        else
    	            Toast.makeText(item, "Red Eye Mode not supported", Toast.LENGTH_SHORT).show();          
    	        break;
    	    case 4:
    	        if (FlashModes.contains(Camera.Parameters.FLASH_MODE_TORCH))
    	            params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
    	        else
    	            Toast.makeText(item, "Torch Mode not supported", Toast.LENGTH_SHORT).show();        
    	        break;
    	    }
    
    	    mCamera.setParameters(params);
    	}
    
    	@Override
    	public void onAutoFocus(boolean arg0, Camera arg1) {
    		 
    	}
    }
    

    在MainActivity中需要初始化MTCamera,并且实现OnTouchListener接口,以便在触摸屏幕时可以调用onTouch函数。其中主要代码如下:
    	private MTCameraView mOpenCvCameraView;
    
    	public void init() {
    		mOpenCvCameraView = new MTCameraView(this, -1);
    		mOpenCvCameraView.setCvCameraViewListener(this);
    		mOpenCvCameraView.setFocusable(true);
    		mOpenCvCameraView.setOnTouchListener(MainActivity.this);
    		mOpenCvCameraView.enableView();
    		
    		FrameLayout frame = new FrameLayout(this);
    		frame.addView(mOpenCvCameraView);
    		
    		setContentView(frame);
    	     }
    
    	@Override
    	public boolean onTouch(View arg0, MotionEvent arg1) {
    		// TODO Auto-generated method stub
    		mOpenCvCameraView.focusOnTouch(arg1);
    		return true;
    	}

    init()函数是自定义的初始化函数,可以在onCreate时使用。由于这里需要使用OpenCV库,所以本项目是在加载完OpenCV库并判断成功后才调用init()函数的。


    解释


    在发生触摸事件时,MainActivity由于实现了OnTouchListener接口,因此会调用重写的onTouch函数,并把它的第二个参数MotionEvent传递给MTCamera,以便定位触摸位置。

    MTCamera的focusOnTouch函数继续工作。它首先根据触摸位置计算对焦和测光(metering)区域的大小(通过calculateTapArea函数),然后创建新的Camera.Parameters,并设置摄像机的对焦模式为Auto。

    然后,它分别判断该设备的相机是否支持设置对焦区域和测光区域,如果支持就分别为parameters设置之前计算好的聚焦和测光区域。

    最后,让Camera自动对焦。


    • calculateTapArea函数

      这个函数主要实现从屏幕坐标系到对焦坐标系的转换。由MotionEvent.getRowX()得到的是以屏幕坐标系(即屏幕左上角为原点,右下角为你的当前屏幕分辨率,单位是一个像素)为准的坐标,而
      setFocusAreas接受的List<Area>中的每一个Area的范围是(-1000,-1000)到(1000, 1000),也就是说屏幕中心为原点,左上角为(-1000,-1000),右下角为(1000,1000)。注意,如果超出这个范围的话,会报setParemeters failed的错误哦!除此之外,我们还提前定义了一个对焦框(测光框)的大小,并且接受一个参数(第三个参数coefficient)作为百分比进行调节。


    至此完成了触摸对焦的功能。

    但是,可以发现MTCamera里还有很大部分代码,主要是两个函数setFocusMode和setFlashMode。这两个函数,主要是因为在项目中我的图像经常是模糊的,但不知道系统支持那么对焦模式。这时,就可以使用这两个函数进行测试。这还需要在MainActivity中添加菜单栏的代码,以便进行选择。代码如下:
        private List<Camera.Size> mResolutionList;
    
        private MenuItem[] mResolutionMenuItems;
        private MenuItem[] mFocusListItems;
        private MenuItem[] mFlashListItems;
    
        private SubMenu mResolutionMenu;
        private SubMenu mFocusMenu;
        private SubMenu mFlashMenu;
        
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            Log.i(TAG, "called onCreateOptionsMenu");
            
            List<String> mFocusList = new LinkedList<String>();
    	    int idx =0;
    
    	    mFocusMenu = menu.addSubMenu("Focus");
    
    	    mFocusList.add("Auto");
    	    mFocusList.add("Continuous Video");
    	    mFocusList.add("EDOF");
    	    mFocusList.add("Fixed");
    	    mFocusList.add("Infinity");
    	    mFocusList.add("Makro");
    	    mFocusList.add("Continuous Picture");
    
    	    mFocusListItems = new MenuItem[mFocusList.size()];
    
    	    ListIterator<String> FocusItr = mFocusList.listIterator();
    	    while(FocusItr.hasNext()){
    	        // add the element to the mDetectorMenu submenu
    	        String element = FocusItr.next();
    	        mFocusListItems[idx] = mFocusMenu.add(2,idx,Menu.NONE,element);
    	        idx++;
    	    }
    
    	    List<String> mFlashList = new LinkedList<String>();
    	    idx = 0;
    
    	    mFlashMenu = menu.addSubMenu("Flash");
    
    	    mFlashList.add("Auto");
    	    mFlashList.add("Off");
    	    mFlashList.add("On");
    	    mFlashList.add("Red-Eye");
    	    mFlashList.add("Torch");
    
    	    mFlashListItems = new MenuItem[mFlashList.size()];
    
    	    ListIterator<String> FlashItr = mFlashList.listIterator();
    	    while(FlashItr.hasNext()){
    	        // add the element to the mDetectorMenu submenu
    	        String element = FlashItr.next();
    	        mFlashListItems[idx] = mFlashMenu.add(3,idx,Menu.NONE,element);
    	        idx++;
    	    }
    
    	    mResolutionMenu = menu.addSubMenu("Resolution");
    	    mResolutionList = mOpenCvCameraView.getResolutionList();
    	    mResolutionMenuItems = new MenuItem[mResolutionList.size()];
    
    	    ListIterator<Camera.Size> resolutionItr = mResolutionList.listIterator();
    	    idx = 0;
    	    while(resolutionItr.hasNext()) {
    	        Camera.Size element = resolutionItr.next();
    	        mResolutionMenuItems[idx] = mResolutionMenu.add(1, idx, Menu.NONE,
    	                Integer.valueOf((int) element.width).toString() + "x" + Integer.valueOf((int) element.height).toString());
    	        idx++;
    	     }
    
    	    return true;
        }
        
        public boolean onOptionsItemSelected(MenuItem item) {
            Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
    
            if (item.getGroupId() == 1)
    	    {
    	        int id = item.getItemId();
    	        Camera.Size resolution = mResolutionList.get(id);
    	        mOpenCvCameraView.setResolution(resolution);
    	        resolution = mOpenCvCameraView.getResolution();
    	        String caption = Integer.valueOf((int) resolution.width).toString() + "x" + Integer.valueOf((int) resolution.height).toString();
    	        Toast.makeText(this, caption, Toast.LENGTH_SHORT).show();
    	    } 
    	    else if (item.getGroupId()==2){
    
    	       int focusType = item.getItemId();
    
    	       mOpenCvCameraView.setFocusMode(this, focusType);
    	    }
    	    else if (item.getGroupId()==3){
    
    	       int flashType = item.getItemId();
    
    	       mOpenCvCameraView.setFlashMode(this, flashType);
    	    }
    
            return true;
        }

    这样运行后,点击菜单就可以看见有三个菜篮列表:Focus(对焦模式),Flash(视频模式),Resolution(支持的分辨率)。对焦模式和视频模式中提供了几种常见的模式供选择,代码会判断当前设备是否支持该模式。而分辨率菜单栏会显示出当前设备支持的所有分辨率种类。



    参考



  • 相关阅读:
    KMP
    图论知识,博客
    POJ 2318/2398 叉积性质
    CF821 E. Okabe and El Psy Kongroo 矩阵快速幂
    CF821 D. Okabe and City 图 最短路
    CF821 C. Okabe and Boxes 栈模拟
    CF821 A. Okabe and Future Gadget Laboratory 水
    Atcoder arc077 D
    Atcoder #017 agc017 D.Game on Tree 树上NIM 博弈
    Atcoder #017 agc017 B.Moderate Differences 思维
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314703.html
Copyright © 2011-2022 走看看