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(支持的分辨率)。对焦模式和视频模式中提供了几种常见的模式供选择,代码会判断当前设备是否支持该模式。而分辨率菜单栏会显示出当前设备支持的所有分辨率种类。



    参考



  • 相关阅读:
    sql2slack alash3al 开源的又个轻量级工具
    pgspider fetchq 扩展docker镜像
    godns 集成coredns 的demo
    godns 简单dnsmasq 的dns 替换方案
    aviary.sh 一个基于bash的分布式配置管理工具
    使用coredns 的template plugin实现一个xip 服务
    nginx 代理 coredns dns 服务
    基于nginx proxy dns server
    几个不错的geodns server
    spring boot rest api 最好添加servlet.context-path
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314703.html
Copyright © 2011-2022 走看看