如何使用 Android CameraX 自动对焦
How to auto-focus with Android CameraX
Android 最近几个月发布了新的 API camerax。我正在尝试了解如何让相机自动对焦。
https://groups.google.com/a/android.com/forum/#!searchin/camerax-developers/auto$20焦点|排序:date/camerax-developers/IQ3KZd8iOIY/LIbrRIqEBgAJ
这里有关于该主题的讨论,但几乎没有关于它的具体文档。
这也是基本的 camerax 应用程序,但我找不到任何处理自动对焦的文件。
文档的任何提示或要点都会有所帮助。另外,我对 android 还很陌生,所以很可能我遗漏了一些使上述链接更有用的东西。
您可以在此处找到有关 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;
}
某些 android 设备存在问题,其中相机无法使用 CameraX 自动对焦。 CameraX 团队已意识到这一点,并正在通过内部票证对其进行跟踪,希望尽快修复。
使用当前的 CameraX 1.0.0
,您可以通过以下两种方式进行操作:
每 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
)
请指出,要使“点击聚焦”与 PreviewView 一起使用,您需要使用
DisplayOrientedMeteringPointFactory。否则你会弄乱坐标。
val factory = DisplayOrientedMeteringPointFactory(activity.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat())
其余的请使用 MatPag 的答案。
我 运行 遇到了同样的问题,我设置了这个解决方案(即使它看起来很愚蠢)。
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
。
当前 1.0.0-rc03
和 1.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());
}
中的 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
的回调将永远不会被调用,所以相机不会被初始化。
Android 最近几个月发布了新的 API camerax。我正在尝试了解如何让相机自动对焦。
https://groups.google.com/a/android.com/forum/#!searchin/camerax-developers/auto$20焦点|排序:date/camerax-developers/IQ3KZd8iOIY/LIbrRIqEBgAJ
这里有关于该主题的讨论,但几乎没有关于它的具体文档。
这也是基本的 camerax 应用程序,但我找不到任何处理自动对焦的文件。
文档的任何提示或要点都会有所帮助。另外,我对 android 还很陌生,所以很可能我遗漏了一些使上述链接更有用的东西。
您可以在此处找到有关 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;
}
某些 android 设备存在问题,其中相机无法使用 CameraX 自动对焦。 CameraX 团队已意识到这一点,并正在通过内部票证对其进行跟踪,希望尽快修复。
使用当前的 CameraX 1.0.0
,您可以通过以下两种方式进行操作:
每 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
)
请指出,要使“点击聚焦”与 PreviewView 一起使用,您需要使用 DisplayOrientedMeteringPointFactory。否则你会弄乱坐标。
val factory = DisplayOrientedMeteringPointFactory(activity.display, camera.cameraInfo, previewView.width.toFloat(), previewView.height.toFloat())
其余的请使用 MatPag 的答案。
我 运行 遇到了同样的问题,我设置了这个解决方案(即使它看起来很愚蠢)。
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
。
当前 1.0.0-rc03
和 1.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());
}
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
的回调将永远不会被调用,所以相机不会被初始化。