简体   繁体   English

如何使用 Android CameraX 自动对焦

[英]How to auto-focus with Android CameraX

Android has released a new API camerax in recent months. Android 最近几个月发布了新的 API camerax。 I'm trying to understand how to get auto-focusing for the camera to work.我试图了解如何让相机自动对焦。

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

Here is a discussion on the topic but there is almost no specific documentation on it.这是关于该主题的讨论,但几乎没有具体的文档。

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

Here is also the basic camerax app but I couldn't find any file dealing with the auto focusing.这也是基本的camerax应用程序,但我找不到任何处理自动对焦的文件。

Any tips or points to documentation is helpful.任何有关文档的提示或要点都是有帮助的。 Also I'm fairly new to android so its very possible I'm missing something that makes the above links more useful.此外,我对 android 还很陌生,所以我很可能遗漏了一些使上述链接更有用的东西。

With the current CameraX 1.0.0 , you can proceed in this 2 ways:使用当前的 CameraX 1.0.0 ,您可以通过以下两种方式进行:

  1. Auto-focus every X seconds:每 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. Focus on-tap:专注于点击:

     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 extension function is a simple utility: afterMeasured扩展 function 是一个简单的实用程序:

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

A Camera object can be obtained with一个Camera object可以用

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

There is an issue with some android devices where the camera's aren't auto-focusing with CameraX.某些 android 设备存在相机无法使用 CameraX 自动对焦的问题。 The CameraX team is aware of it and are tracking it with an internal ticket and hopefully will have a fix soon. CameraX 团队意识到了这一点,并正在通过内部票证对其进行跟踪,并希望很快能得到修复。

Just point out, to get the "Tap to focus" working with PreviewView, you need to use DisplayOrientedMeteringPointFactory .只需指出,要使“点击聚焦”与 PreviewView 一起使用,您需要使用DisplayOrientedMeteringPointFactory Otherwise you'll get messed up coordinates.否则你会弄乱坐标。

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

For the rest use the MatPag's answer.对于 rest 使用 MatPag 的答案。

You can find the doc here about Focus as it was added in "1.0.0-alpha05" https://developer.android.com/jetpack/androidx/releases/camera#camera2-core-1.0.0-alpha05您可以在此处找到有关 Focus 的文档,因为它已添加到“1.0.0-alpha05” https://developer.android.com/jetpack/androidx/releases/camera#camera2-core-1.0.0-alpha05

Basically you have to set a touch listener on your view and grab the clicked position基本上你必须在你的视图上设置一个触摸监听器并抓住点击的 position

 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; }

And translate this position into point并将这个 position 翻译成点

 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; }

With current 1.0.0-rc03 and 1.0.0-alpha22 artifacts使用当前1.0.0-rc031.0.0-alpha22工件

This solution assumes that camera is already setup including bindToLifecycle .此解决方案假定已设置相机,包括bindToLifecycle After that we need to check whether previewView streamState is STREAMING before trying to focus the camera之后,我们需要在尝试对焦相机之前检查 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());
}

I ran into the same issue and I set up this solution (even if it looks pretty dumb).我遇到了同样的问题,我设置了这个解决方案(即使它看起来很愚蠢)。

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)
}

Basically, I restart the focusing action every 3s in a while loop.基本上,我在一段while循环中每 3 秒重新启动一次聚焦动作。

isCameraLive is a boolean variable I store in my viewModel and I set true when I start the camera and false when I stop it by calling cameraProvider.unbindAll() . isCameraLive是一个 boolean 变量,我存储在我的 viewModel 中,当我启动相机时设置为true ,当我通过调用cameraProvider.unbindAll()停止它时设置为false

The afterMeasured function in highest voted answer has a serious bug: Frequently its callback is never called .最高投票答案中的afterMeasured function 有一个严重的错误:它的回调通常不会被调用

The very simple fix:非常简单的修复:

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()
                }
            }
        })
    }
}

Explanation: I have observed (in an app in production) that, sometimes the view is already measured and no ui changes so onGlobalLayout will never be called later.说明:我观察到(在生产中的应用程序中),有时视图已经测量并且没有 ui 更改,因此以后永远不会调用onGlobalLayout Then the afterMeasured 's callback will never be called, so the camera is not initialized.那么afterMeasured的回调将永远不会被调用,因此相机不会被初始化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM