Reputation: 1448
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.
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.
Upvotes: 16
Views: 22397
Reputation: 44813
With the current CameraX 1.0.0
, you can proceed in this 2 ways:
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)
}
}
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
)
Upvotes: 50
Reputation: 17567
The afterMeasured
function in highest voted answer has a serious bug: Frequently its callback is never called.
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. Then the afterMeasured
's callback will never be called, so the camera is not initialized.
Upvotes: 2
Reputation: 121
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());
}
Upvotes: 1
Reputation: 39
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.
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()
.
Upvotes: 0
Reputation: 298
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.
Upvotes: 6
Reputation: 1448
There is an issue with some android devices where the camera's aren't auto-focusing with CameraX. The CameraX team is aware of it and are tracking it with an internal ticket and hopefully will have a fix soon.
Upvotes: 3
Reputation: 11
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
Basically you have to set a touch listener on your view and grab the clicked 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
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;
}
Upvotes: 1