【问题标题】:How to auto-focus with Android CameraX如何使用 Android CameraX 自动对焦
【发布时间】:2019-09-29 23:05:38
【问题描述】:

最近几个月,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 还很陌生,所以很可能我遗漏了一些使上述链接更有用的东西。

【问题讨论】:

    标签: android android-studio kotlin android-camerax


    【解决方案1】:

    使用当前的 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)
           }
       }
      
    2. 专注于点击:

       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 提供improving it

    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
    )
    

    【讨论】:

    • 出于某种原因,这段代码在大多数设备上运行良好,但其他一些只是拒绝改变焦点,我找不到原因。
    • 因为我们使用setAutoCancelDuration(X, TimeUnit.SECONDS) 每X 秒取消当前焦点操作并重新启动它(至少这是我从文档中理解的)。如果将 setAutoCancel 替换为disableAutoCancel(),则相机打开时会对焦,然后再也不会调焦
    • 此外,根据SurfaceOrientedMeteringPointFactory 构造函数的文档,您可以使用归一化坐标,即最大值等于 1.0。在这种简单的情况下,当我们想根据中心点调整焦点时,我们可以更简单地准备测光点(不用PreviewView):val autoFocusPoint = SurfaceOrientedMeteringPointFactory(1f, 1f).createPoint(.5f, .5f)
    • 您好,afterMeasured 有一个错误:它的回调通常不会被调用。这是一个修复:stackoverflow.com/a/70304116/4619958
    • @ch271828n 答案已更新,谢谢
    【解决方案2】:

    请指出,要让“点击聚焦”与 PreviewView 一起使用,您需要使用 DisplayOrientedMeteringPointFactory。否则你会弄乱坐标。

    val factory = DisplayOrientedMeteringPointFactory(activity.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat())
    

    其余的使用 MatPag 的答案。

    【讨论】:

      【解决方案3】:

      某些 Android 设备存在相机无法使用 CameraX 自动对焦的问题。 CameraX 团队意识到了这一点,并正在使用内部故障单对其进行跟踪,希望能尽快修复。

      【讨论】:

      • 我已经为此添加了答案
      • 这张票有公开链接吗?
      【解决方案4】:

      您可以在此处找到有关 Focus 的文档,因为它是在“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;
          }

      【讨论】:

      • 我添加了对问题的答案。触摸对焦不是自动对焦。
      【解决方案5】:

      使用当前的 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());
      }
      

      【讨论】:

        【解决方案6】:

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

        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

        【讨论】:

          【解决方案7】:

          highest voted answer 中的afterMeasured 函数有一个严重的错误:它的回调经常从不调用

          非常简单的解决方法:

          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的回调永远不会被调用,所以相机没有初始化。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-12-19
            • 2022-01-12
            • 1970-01-01
            • 1970-01-01
            • 2022-01-05
            • 1970-01-01
            相关资源
            最近更新 更多