【转】[Android编程心得] Camera(OpenCV)自动对焦和触摸对焦的实现

参考
http://*.com/questions/18460647/android-setfocusarea-and-auto-focus

http://blog.csdn.net/candycat1992/article/details/21617741

 

写在前面

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

实现

 
以OpenCV的JavaCameraView为例,首先需要定制自己的Camera,主要代码如下:
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import org.opencv.android.JavaCameraView;
  4. import android.R.integer;
  5. import android.content.Context;
  6. import android.graphics.Rect;
  7. import android.graphics.RectF;
  8. import android.hardware.Camera;
  9. import android.hardware.Camera.AutoFocusCallback;
  10. import android.util.AttributeSet;
  11. import android.view.MotionEvent;
  12. import android.widget.Toast;
  13. public class MTCameraView extends JavaCameraView implements AutoFocusCallback {
  14. public MTCameraView(Context context, int attrs) {
  15. super(context, attrs);
  16. // TODO Auto-generated constructor stub
  17. }
  18. public List<Camera.Size> getResolutionList() {
  19. return  mCamera.getParameters().getSupportedPreviewSizes();
  20. }
  21. public Camera.Size getResolution() {
  22. Camera.Parameters params = mCamera.getParameters();
  23. Camera.Size s = params.getPreviewSize();
  24. return s;
  25. }
  26. public void setResolution(Camera.Size resolution) {
  27. disconnectCamera();
  28. connectCamera((int)resolution.width, (int)resolution.height);
  29. }
  30. public void focusOnTouch(MotionEvent event) {
  31. Rect focusRect = calculateTapArea(event.getRawX(), event.getRawY(), 1f);
  32. Rect meteringRect = calculateTapArea(event.getRawX(), event.getRawY(), 1.5f);
  33. Camera.Parameters parameters = mCamera.getParameters();
  34. parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
  35. if (parameters.getMaxNumFocusAreas() > 0) {
  36. List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
  37. focusAreas.add(new Camera.Area(focusRect, 1000));
  38. parameters.setFocusAreas(focusAreas);
  39. }
  40. if (parameters.getMaxNumMeteringAreas() > 0) {
  41. List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
  42. meteringAreas.add(new Camera.Area(meteringRect, 1000));
  43. parameters.setMeteringAreas(meteringAreas);
  44. }
  45. mCamera.setParameters(parameters);
  46. mCamera.autoFocus(this);
  47. }
  48. /**
  49. * Convert touch position x:y to {@link Camera.Area} position -1000:-1000 to 1000:1000.
  50. */
  51. private Rect calculateTapArea(float x, float y, float coefficient) {
  52. float focusAreaSize = 300;
  53. int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
  54. int centerX = (int) (x / getResolution().width * 2000 - 1000);
  55. int centerY = (int) (y / getResolution().height * 2000 - 1000);
  56. int left = clamp(centerX - areaSize / 2, -1000, 1000);
  57. int right = clamp(left + areaSize, -1000, 1000);
  58. int top = clamp(centerY - areaSize / 2, -1000, 1000);
  59. int bottom = clamp(top + areaSize, -1000, 1000);
  60. return new Rect(left, top, right, bottom);
  61. }
  62. private int clamp(int x, int min, int max) {
  63. if (x > max) {
  64. return max;
  65. }
  66. if (x < min) {
  67. return min;
  68. }
  69. return x;
  70. }
  71. public void setFocusMode (Context item, int type){
  72. Camera.Parameters params = mCamera.getParameters();
  73. List<String> FocusModes = params.getSupportedFocusModes();
  74. switch (type){
  75. case 0:
  76. if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO))
  77. params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
  78. else
  79. Toast.makeText(item, "Auto Mode not supported", Toast.LENGTH_SHORT).show();
  80. break;
  81. case 1:
  82. if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
  83. params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
  84. else
  85. Toast.makeText(item, "Continuous Mode not supported", Toast.LENGTH_SHORT).show();
  86. break;
  87. case 2:
  88. if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_EDOF))
  89. params.setFocusMode(Camera.Parameters.FOCUS_MODE_EDOF);
  90. else
  91. Toast.makeText(item, "EDOF Mode not supported", Toast.LENGTH_SHORT).show();
  92. break;
  93. case 3:
  94. if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_FIXED))
  95. params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
  96. else
  97. Toast.makeText(item, "Fixed Mode not supported", Toast.LENGTH_SHORT).show();
  98. break;
  99. case 4:
  100. if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_INFINITY))
  101. params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
  102. else
  103. Toast.makeText(item, "Infinity Mode not supported", Toast.LENGTH_SHORT).show();
  104. break;
  105. case 5:
  106. if (FocusModes.contains(Camera.Parameters.FOCUS_MODE_MACRO))
  107. params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
  108. else
  109. Toast.makeText(item, "Macro Mode not supported", Toast.LENGTH_SHORT).show();
  110. break;
  111. }
  112. mCamera.setParameters(params);
  113. }
  114. public void setFlashMode (Context item, int type){
  115. Camera.Parameters params = mCamera.getParameters();
  116. List<String> FlashModes = params.getSupportedFlashModes();
  117. switch (type){
  118. case 0:
  119. if (FlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO))
  120. params.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
  121. else
  122. Toast.makeText(item, "Auto Mode not supported", Toast.LENGTH_SHORT).show();
  123. break;
  124. case 1:
  125. if (FlashModes.contains(Camera.Parameters.FLASH_MODE_OFF))
  126. params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
  127. else
  128. Toast.makeText(item, "Off Mode not supported", Toast.LENGTH_SHORT).show();
  129. break;
  130. case 2:
  131. if (FlashModes.contains(Camera.Parameters.FLASH_MODE_ON))
  132. params.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
  133. else
  134. Toast.makeText(item, "On Mode not supported", Toast.LENGTH_SHORT).show();
  135. break;
  136. case 3:
  137. if (FlashModes.contains(Camera.Parameters.FLASH_MODE_RED_EYE))
  138. params.setFlashMode(Camera.Parameters.FLASH_MODE_RED_EYE);
  139. else
  140. Toast.makeText(item, "Red Eye Mode not supported", Toast.LENGTH_SHORT).show();
  141. break;
  142. case 4:
  143. if (FlashModes.contains(Camera.Parameters.FLASH_MODE_TORCH))
  144. params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
  145. else
  146. Toast.makeText(item, "Torch Mode not supported", Toast.LENGTH_SHORT).show();
  147. break;
  148. }
  149. mCamera.setParameters(params);
  150. }
  151. @Override
  152. public void onAutoFocus(boolean arg0, Camera arg1) {
  153. }
  154. }

在MainActivity中需要初始化MTCamera,并且实现OnTouchListener接口,以便在触摸屏幕时可以调用onTouch函数。其中主要代码如下:

  1. private MTCameraView mOpenCvCameraView;
  2. public void init() {
  3. mOpenCvCameraView = new MTCameraView(this, -1);
  4. mOpenCvCameraView.setCvCameraViewListener(this);
  5. mOpenCvCameraView.setFocusable(true);
  6. mOpenCvCameraView.setOnTouchListener(MainActivity.this);
  7. mOpenCvCameraView.enableView();
  8. FrameLayout frame = new FrameLayout(this);
  9. frame.addView(mOpenCvCameraView);
  10. setContentView(frame);
  11. }
  12. @Override
  13. public boolean onTouch(View arg0, MotionEvent arg1) {
  14. // TODO Auto-generated method stub
  15. mOpenCvCameraView.focusOnTouch(arg1);
  16. return true;
  17. }

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中添加菜单栏的代码,以便进行选择。代码如下:
  1. private List<Camera.Size> mResolutionList;
  2. private MenuItem[] mResolutionMenuItems;
  3. private MenuItem[] mFocusListItems;
  4. private MenuItem[] mFlashListItems;
  5. private SubMenu mResolutionMenu;
  6. private SubMenu mFocusMenu;
  7. private SubMenu mFlashMenu;
  8. @Override
  9. public boolean onCreateOptionsMenu(Menu menu) {
  10. Log.i(TAG, "called onCreateOptionsMenu");
  11. List<String> mFocusList = new LinkedList<String>();
  12. int idx =0;
  13. mFocusMenu = menu.addSubMenu("Focus");
  14. mFocusList.add("Auto");
  15. mFocusList.add("Continuous Video");
  16. mFocusList.add("EDOF");
  17. mFocusList.add("Fixed");
  18. mFocusList.add("Infinity");
  19. mFocusList.add("Makro");
  20. mFocusList.add("Continuous Picture");
  21. mFocusListItems = new MenuItem[mFocusList.size()];
  22. ListIterator<String> FocusItr = mFocusList.listIterator();
  23. while(FocusItr.hasNext()){
  24. // add the element to the mDetectorMenu submenu
  25. String element = FocusItr.next();
  26. mFocusListItems[idx] = mFocusMenu.add(2,idx,Menu.NONE,element);
  27. idx++;
  28. }
  29. List<String> mFlashList = new LinkedList<String>();
  30. idx = 0;
  31. mFlashMenu = menu.addSubMenu("Flash");
  32. mFlashList.add("Auto");
  33. mFlashList.add("Off");
  34. mFlashList.add("On");
  35. mFlashList.add("Red-Eye");
  36. mFlashList.add("Torch");
  37. mFlashListItems = new MenuItem[mFlashList.size()];
  38. ListIterator<String> FlashItr = mFlashList.listIterator();
  39. while(FlashItr.hasNext()){
  40. // add the element to the mDetectorMenu submenu
  41. String element = FlashItr.next();
  42. mFlashListItems[idx] = mFlashMenu.add(3,idx,Menu.NONE,element);
  43. idx++;
  44. }
  45. mResolutionMenu = menu.addSubMenu("Resolution");
  46. mResolutionList = mOpenCvCameraView.getResolutionList();
  47. mResolutionMenuItems = new MenuItem[mResolutionList.size()];
  48. ListIterator<Camera.Size> resolutionItr = mResolutionList.listIterator();
  49. idx = 0;
  50. while(resolutionItr.hasNext()) {
  51. Camera.Size element = resolutionItr.next();
  52. mResolutionMenuItems[idx] = mResolutionMenu.add(1, idx, Menu.NONE,
  53. Integer.valueOf((int) element.width).toString() + "x" + Integer.valueOf((int) element.height).toString());
  54. idx++;
  55. }
  56. return true;
  57. }
  58. public boolean onOptionsItemSelected(MenuItem item) {
  59. Log.i(TAG, "called onOptionsItemSelected; selected item: " + item);
  60. if (item.getGroupId() == 1)
  61. {
  62. int id = item.getItemId();
  63. Camera.Size resolution = mResolutionList.get(id);
  64. mOpenCvCameraView.setResolution(resolution);
  65. resolution = mOpenCvCameraView.getResolution();
  66. String caption = Integer.valueOf((int) resolution.width).toString() + "x" + Integer.valueOf((int) resolution.height).toString();
  67. Toast.makeText(this, caption, Toast.LENGTH_SHORT).show();
  68. }
  69. else if (item.getGroupId()==2){
  70. int focusType = item.getItemId();
  71. mOpenCvCameraView.setFocusMode(this, focusType);
  72. }
  73. else if (item.getGroupId()==3){
  74. int flashType = item.getItemId();
  75. mOpenCvCameraView.setFlashMode(this, flashType);
  76. }
  77. return true;
  78. }

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

 
 
 

参考

 
上一篇:.Net 平台下的互联网架构新思考


下一篇:Android编程心得-ListView的Item高亮显示的办法