CameraX图像分析的ImageProxy大小和PreviewView大小不同。

8
我正在尝试使用Firebase的MLKit结合Camerax进行人脸检测。我试图让Image analysis的imageproxy大小与PreviewView的大小相匹配,但是遇到了困难。对于Image analysis和PreviewView,我都将setTargetResolution()设置为PreviewView的宽度和高度。然而,在分析器中检查Imageproxy的大小时,它给出的宽度是1920,高度是1080。我的PreviewView宽度为1080,高度为2042。当我在Image analysis的setTargetResolution()中交换宽度和高度时,我得到的imageproxy的宽度和高度都为1088。我的预览视图也被锁定为纵向模式。
最终,我需要将原始的imageproxy数据和人脸点数据传递到AR代码中。因此,仅缩放绘制人脸点的图形叠加层将无法解决我的问题。
问:如果在caramax库中没有修复这个问题的方法,如何缩放从分析器返回的imageproxy以匹配previewview?
我正在使用Java和最新的Camerax库:
def camerax_version = "1.0.0-beta08"

嗨,你可以看看我在这里提供的解决方案 https://dev59.com/SlIG5IYBdhLWcg3wqTfe#67348548。它应该有助于实现所见即所得的效果。 - Alex F.
2个回答

9

确保预览和图像分析使用相同的输出分辨率非常困难,因为不同的设备支持不同的分辨率,并且图像分析在其输出的最大分辨率上有一个硬性限制(如文档中所述)。

为了使从图像分析帧到UI / PreviewView的坐标转换更加容易,您可以设置预览和ImageAnalysis使用相同的长宽比,例如AspectRatio.RATIO_4_3,以及PreviewView(例如通过将其包装在ConstraintLayout中,并在其宽度/高度比上设置约束)。通过这种方式,将检测到的面部的坐标从分析器映射到UI变得更加直观,您可以在此示例中查看。

另外,您可以使用CameraX的ViewPort API,我相信它仍处于实验阶段。它允许为一组用例定义视野范围,从而使它们的输出匹配并具有所见即所得效果。您可以在这里找到其使用示例。对于您的情况,您需要编写类似于以下内容。

Preview preview = ...
preview.setSurfaceProvider(previewView.getSurfaceProvider());

ImageAnalysis imageAnalysis = ...
imageAnalysis.setAnalyzer(...);

ViewPort viewPort = preview.getViewPort();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
                .setViewPort(viewPort)
                .addUseCase(preview)
                .addUseCase(imageAnalysis)
                .build();

cameraProvider.bindToLifecycle(
                lifecycleOwner,
                cameraSelector,
                usecaseGroup);

在这种情况下,分析器接收到的每个ImageProxy都将包含与PreviewView显示匹配的裁剪矩形。因此,您只需要裁剪图像,然后将其传递给人脸检测器。

嗨@Husayn,感谢您的回答。我尝试将setTargetResolution更改为预览、分析和imageCapture的setTargetAspectRatio。然而,我从分析器获取的imageproxy大小为960(W)和540(H)。此外,我一开始不想使用setTargetAspectRatio的原因是,捕获的图像比预览小。但是,使用setTargetResolution时我没有这个问题。我的prviewView是整个屏幕。 - Rocky666
2
由于以下原因,我无法尝试viewport方法:1)我找不到 previewView 的getSurfaceProvider()。相反,我有 previewView.createSurfaceProvider()。2)我无法将getViewPort()分配给preview,因为getViewPort()不存在。不确定我在这里做错了什么。 - Rocky666
更新:使用CameraX Beta09,我能够实现第二个视口方法。然而结果没有改变。仍然在分析器中获取1920作为宽度和1080作为图像代理高度。 - Rocky666
是的,viewPort API在最新版本(从今天早上)中可用。您仍将获得相同大小的图像(因此在您的情况下为1920x1080),但图像的裁剪矩形将与PreviewView显示的内容匹配。因此,您需要裁剪图像,然后将其传递给人脸检测器。 - Husayn Hakeem
抱歉回复晚了。是的,预览裁剪矩形确实与分析器中的图像代理尺寸匹配。当我在预览上使用getViewPortCropRect()函数时,一切都变得清晰明了。我还意识到预览视图尺寸与预览尺寸不匹配。感谢您的支持。我将接受这个答案作为正确的答案。 - Rocky666

1

这个答案是基于@Husayn的答案得出的。我添加了相关的示例代码部分。

Camerax图像预览和分析的大小因各种原因而异(例如设备特定的显示大小/硬件/相机或应用程序特定的视图和处理)。然而,有选项将处理图像大小和结果xy坐标映射到预览大小和预览xy坐标。

在布局中使用DimensionRatio 3:4设置预览和分析叠加层的布局,

示例:

<androidx.camera.view.PreviewView
  android:id="@+id/view_finder"
  android:layout_width="match_parent"
  android:layout_height="0dp"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintDimensionRatio="3:4"
  app:layout_constraintTop_toTopOf="parent"/>

<com.loa.sepanex.scanner.view.GraphicOverlay
  android:id="@+id/graphic_overlay"
  android:layout_width="match_parent"
  android:layout_height="0dp"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintDimensionRatio="3:4"
  app:layout_constraintTop_toTopOf="parent"/>

设置预览和分析用例,使用AspectRatio.RATIO_4_3。

示例:

viewFinder = view.findViewById(R.id.view_finder)
graphicOverlay = view.findViewById(R.id.graphic_overlay)
//...
preview = Preview.Builder()
          .setTargetAspectRatio(AspectRatio.RATIO_4_3)
          .setTargetRotation(rotation)
          .build()

imageAnalyzer = ImageAnalysis.Builder()
                .setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .setTargetRotation(rotation)
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, ImageAnalysis.Analyzer { 
                     image ->
                        //val rotationDegrees = image.imageInfo.rotationDegrees
                        try {
                            val mediaImage: Image? = image.image
                            if (mediaImage != null) {
                                val imageForFaceDetectionProcess = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees())
                                //...
                            }
                         }
                      }
                  }


定义比例和翻译API,以获取分析图像的xy坐标到预览xy坐标的映射,如下所示。
            val preview = viewFinder.getChildAt(0)
            var previewWidth = preview.width * preview.scaleX
            var previewHeight = preview.height * preview.scaleY
            val rotation = preview.display.rotation
            if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
                val temp = previewWidth
                previewWidth = previewHeight
                previewHeight = temp
            }
            val isImageFlipped = lensFacing == CameraSelector.LENS_FACING_FRONT
            val rotationDegrees: Int = imageProxy.getImageInfo().getRotationDegrees()
            if (rotationDegrees == 0 || rotationDegrees == 180) {
                graphicOverlay!!.setImageSourceInfo(
                        imageProxy.getWidth(), imageProxy.getHeight(), isImageFlipped)
            } else {
                graphicOverlay!!.setImageSourceInfo(
                        imageProxy.getHeight(), imageProxy.getWidth(), isImageFlipped)
            }
    :::
    :::
    float viewAspectRatio = (float) previewWidth / previewHeight;
    float imageAspectRatio = (float) imageWidth / imageHeight;
    postScaleWidthOffset = 0;
    postScaleHeightOffset = 0;

    if (viewAspectRatio > imageAspectRatio) {
        // The image needs to be vertically cropped to be displayed in this view.
        scaleFactor = (float) previewWidth / imageWidth;
        postScaleHeightOffset = ((float) previewWidth / imageAspectRatio - previewHeight) / 2;
    } else {
        // The image needs to be horizontally cropped to be displayed in this view.
        scaleFactor = (float) previewHeight / imageHeight;
        postScaleWidthOffset = ((float) previewHeight * imageAspectRatio - previewWidth) / 2;
    }
    transformationMatrix.reset();
    transformationMatrix.setScale(scaleFactor, scaleFactor);
    transformationMatrix.postTranslate(-postScaleWidthOffset, -postScaleHeightOffset);
    if (isImageFlipped) {
        transformationMatrix.postScale(-1f, 1f, previewWidth / 2f, previewHeight / 2f);
    }
    :::
    :::
    public float scale(float imagePixel) {
        return imagePixel * overlay.scaleFactor;
    }
    public float translateX(float x) {
        if (overlay.isImageFlipped) {
            return overlay.getWidth() - (scale(x) - overlay.postScaleWidthOffset);
        } else {
            return scale(x) - overlay.postScaleWidthOffset;
        }
    }
    public float translateY(float y) {
        return scale(y) - overlay.postScaleHeightOffset;
    }

使用translateX和translateY方法将基于图像的数据绘制分析结果到预览中。
示例:
        for (FaceContour contour : face.getAllContours()) {
            for (PointF point : contour.getPoints()) {
                canvas.drawCircle(translateX(point.x), translateY(point.y), FACE_POSITION_RADIUS, facePositionPaint);
            }
        }

3
你的示例代码非常难以阅读。 - Chad Bingham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接