Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to auto-focus with Android CameraX

Android has released a new API camerax in recent months. 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

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

Here is also the basic camerax app but I couldn't find any file dealing with the auto focusing.

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.

like image 507
Blue Avatar asked Sep 29 '19 23:09

Blue


3 Answers

With the current CameraX 1.0.0, you can proceed in this 2 ways:

  1. Auto-focus every X seconds:

     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: (thanks ch271828n for 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()
                } 
            }
        })
    }
}

A Camera object can be obtained with

val camera = cameraProvider.bindToLifecycle(
    this@Activity, cameraSelector, previewView //this is a PreviewView
)
like image 181
MatPag Avatar answered Oct 13 '22 05:10

MatPag


Just point out, to get the "Tap to focus" working with PreviewView, you need to use 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.

like image 42
Bonco Avatar answered Oct 13 '22 05:10

Bonco


With current 1.0.0-rc03 and 1.0.0-alpha22 artifacts

This solution assumes that camera is already setup including bindToLifecycle. After that we need to check whether previewView streamState is STREAMING before trying to focus the camera

 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());
}
like image 30
Anshul mittal Avatar answered Oct 13 '22 03:10

Anshul mittal