如何使用Android CameraX实现自动对焦

13

最近几个月,Android发布了一个新的API——CameraX。我正在尝试理解如何使相机自动对焦。

https://groups.google.com/a/android.com/forum/#!searchin/camerax-developers/auto$20focus|sort:date/camerax-developers/IQ3KZd8iOIY/LIbrRIqEBgAJ

这里有关于该主题的讨论,但几乎没有具体的文档。

https://github.com/android/camera-samples/tree/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic

这里也有基本的CameraX应用程序,但我找不到任何处理自动对焦的文件。

任何提示或指向文档的内容都很有帮助。另外,我对Android还比较新,所以上面的链接很可能有些我没注意到的东西。

7个回答

45

使用当前的CameraX 1.0.0 版本,有以下两种方法:

  1. 每隔 X 秒自动对焦:

 previewView.afterMeasured {
     val autoFocusPoint = SurfaceOrientedMeteringPointFactory(1f, 1f)
             .createPoint(.5f, .5f)
     try {
         val autoFocusAction = FocusMeteringAction.Builder(
             autoFocusPoint,
             FocusMeteringAction.FLAG_AF
         ).apply {
             //start auto-focusing after 2 seconds
             setAutoCancelDuration(2, TimeUnit.SECONDS)
         }.build()
         camera.cameraControl.startFocusAndMetering(autoFocusAction)
     } catch (e: CameraInfoUnavailableException) {
         Log.d("ERROR", "cannot access camera", e)
     }
 }
  • 焦点点击:

  •  previewView.afterMeasured {
         previewView.setOnTouchListener { _, event ->
             return@setOnTouchListener when (event.action) {
                 MotionEvent.ACTION_DOWN -> {
                     true
                 }
                 MotionEvent.ACTION_UP -> {
                     val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
                         previewView.width.toFloat(), previewView.height.toFloat()
                     )
                     val autoFocusPoint = factory.createPoint(event.x, event.y)
                     try {
                         camera.cameraControl.startFocusAndMetering(
                             FocusMeteringAction.Builder(
                                 autoFocusPoint,
                                 FocusMeteringAction.FLAG_AF
                             ).apply {
                                 //focus only when the user tap the preview
                                 disableAutoCancel()
                             }.build()
                         )
                     } catch (e: CameraInfoUnavailableException) {
                         Log.d("ERROR", "cannot access camera", e)
                     }
                     true
                 }
                 else -> false // Unhandled event.
             }
         }
     }
    

    afterMeasured扩展函数是一个简单的实用工具:(感谢ch271828n 改进它

    inline fun View.afterMeasured(crossinline block: () -> Unit) {
        if (measuredWidth > 0 && measuredHeight > 0) {
            block()
        } else {
            viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.GlobalLayoutListener {
                override fun onGlobalLayout() {
                    if (measuredWidth > 0 && measuredHeight > 0) {
                        viewTreeObserver.removeOnGlobalLayoutListener(this)
                        block()
                    } 
                }
            })
        }
    }
    

    可以使用Camera对象来获取

    val camera = cameraProvider.bindToLifecycle(
        this@Activity, cameraSelector, previewView //this is a PreviewView
    )
    

    2
    由于某些原因,这段代码在大多数设备上都能正常工作,但是其他一些设备却拒绝改变焦点,我找不到原因。 - Bencri
    1
    因为我们使用了 setAutoCancelDuration(X, TimeUnit.SECONDS),每隔 X 秒就会取消当前的对焦动作并重新开始(至少这是我从文档中理解的)。 如果您将 setAutoCancel 替换为 disableAutoCancel(),则相机在打开时将进行对焦,然后再也不会调整对焦了。 - MatPag
    1
    此外,根据 SurfaceOrientedMeteringPointFactory 构造函数的文档,您可以使用标准化坐标,即最大值等于 1.0 的坐标。在这种简单情况下,当我们想要根据中心点调整焦点时,我们甚至可以更简单地准备我们的测光点(不需要 PreviewView):val autoFocusPoint = SurfaceOrientedMeteringPointFactory(1f, 1f).createPoint(.5f, .5f) - Andrei K.
    2
    嗨,afterMeasured有一个bug:经常它的回调函数从未被调用。这里是一个修复方法:https://dev59.com/TlMH5IYBdhLWcg3w0DS9#70304116 - ch271828n
    1
    @ch271828n回答已更新,谢谢。 - MatPag
    显示剩余9条评论

    5
    只需指出,如果要在PreviewView中使“轻触对焦”功能正常工作,您需要使用 DisplayOrientedMeteringPointFactory 。否则,您将获得混乱的坐标。
    val factory = DisplayOrientedMeteringPointFactory(activity.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat())
    

    其余部分请参考MatPag的答案。


    3

    一些安卓设备存在一个问题,就是使用CameraX时摄像头无法自动对焦。CameraX团队已经意识到了这个问题,并且正在通过一个内部工单进行跟踪。希望他们能尽快解决这个问题。


    我已经添加了一个答案到这个问题中。 - MatPag
    这个工单有公共链接吗? - Bencri

    1
    使用当前的1.0.0-rc031.0.0-alpha22构件。
    此解决方案假定相机已经设置好,包括bindToLifecycle。之后,我们需要检查previewView的streamState是否为STREAMING,然后再尝试对相机进行对焦。
     previewView.getPreviewStreamState().observe(getActivity(), value -> {
            if (value.equals(STREAMING)) {
                setUpCameraAutoFocus();
            }
        });
    
    private void setUpCameraAutoFocus() {
        final float x =  previewView.getX() + previewView.getWidth() / 2f;
        final float y =  previewView.getY() + previewView.getHeight() / 2f;
    
      MeteringPointFactory pointFactory = previewView.getMeteringPointFactory();
      float afPointWidth = 1.0f / 6.0f;  // 1/6 total area
      float aePointWidth = afPointWidth * 1.5f;
      MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
      MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
      ListenableFuture<FocusMeteringResult> future = cameraControl.startFocusAndMetering(
              new FocusMeteringAction.Builder(afPoint,
                      FocusMeteringAction.FLAG_AF).addPoint(aePoint,
                      FocusMeteringAction.FLAG_AE).build());
      Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
        @Override
        public void onSuccess(@Nullable FocusMeteringResult result) {
        }
    
        @Override
        public void onFailure(Throwable t) {
          // Throw the unexpected error.
          throw new RuntimeException(t);
        }
      }, CameraXExecutors.directExecutor());
    }
    

    1

    afterMeasured函数在最高票答案中有一个严重的bug: 经常出现回调从未被调用的情况。

    非常简单的修复方法:

    inline fun View.afterMeasured(crossinline block: () -> Unit) {
        if (measuredWidth > 0 && measuredHeight > 0) {
            block()
        } else {
            viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
                override fun onGlobalLayout() {
                    if (measuredWidth > 0 && measuredHeight > 0) {
                        viewTreeObserver.removeOnGlobalLayoutListener(this)
                        block()
                    }
                }
            })
        }
    }
    

    说明:我在一个生产中的应用程序中观察到,有时视图已经被测量并且没有UI更改,因此onGlobalLayout稍后将永远不会被调用。然后afterMeasured的回调也将永远不会被调用,因此摄像头未初始化。


    1

    您可以在此处找到有关焦点的文档,因为它已添加在“1.0.0-alpha05”中。 https://developer.android.com/jetpack/androidx/releases/camera#camera2-core-1.0.0-alpha05

    基本上,您需要在视图上设置一个触摸监听器并获取所点击的位置。

    private boolean onTouchToFocus(View viewA, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_UP:
                        return focus(event);
                    break;
                default:
                    // Unhandled event.
                    return false;
            }
            return true;
        }

    将此位置转换为点。

    private boolean focus(MotionEvent event) {
            final float x = (event != null) ? event.getX() : getView().getX() + getView().getWidth() / 2f;
            final float y = (event != null) ? event.getY() : getView().getY() + getView().getHeight() / 2f;
    
            TextureViewMeteringPointFactory pointFactory = new TextureViewMeteringPointFactory(textureView);
            float afPointWidth = 1.0f / 6.0f;  // 1/6 total area
            float aePointWidth = afPointWidth * 1.5f;
            MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth, 1.0f);
            MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth, 1.0f);
    
               try {
                CameraX.getCameraControl(lensFacing).startFocusAndMetering(
                    FocusMeteringAction.Builder.from(afPoint, FocusMeteringAction.MeteringMode.AF_ONLY)
                                               .addPoint(aePoint, FocusMeteringAction.MeteringMode.AE_ONLY)
                                               .build());
            } catch (CameraInfoUnavailableException e) {
                Log.d(TAG, "cannot access camera", e);
            }
    
            return true;
        }


    我回答了这个问题。触摸对焦不是自动对焦。 - Blue

    0

    我遇到了同样的问题,然后我设置了这个解决方案(即使它看起来相当愚蠢)。

    val displayMetrics = resources.displayMetrics
    val factory = SurfaceOrientedMeteringPointFactory(
        displayMetrics.widthPixels.toFloat(),
        displayMetrics.heightPixels.toFloat()
    )
    val point = factory.createPoint(
        displayMetrics.widthPixels / 2f,
        displayMetrics.heightPixels / 2f
    )
    val action = FocusMeteringAction
                .Builder(point, FocusMeteringAction.FLAG_AF)
                .build()
    
    try {
        camera = cameraProvider.bindToLifecycle(
            lifecycleOwner,
            cameraSelector,
            preview,
            imageAnalyzer
        )
        GlobalScope.launch(Dispatchers.Default) {
            while (workflowModel.isCameraLive) {
                camera?.cameraControl?.startFocusAndMetering(action)?
                delay(3000)
            }
        }
    } catch (e: Exception) {
        Log.e(mTag, "Use case binding failed", e)
    }
    

    基本上,我在一个while循环中每3秒重新启动对焦操作。

    isCameraLive是我在viewModel中存储的布尔变量,当我启动相机时,我将其设置为true,并通过调用cameraProvider.unbindAll()停止相机时将其设置为false


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接