【问题标题】:CameraX Image analysis's imageproxy size and PreviewView size are not the sameCameraX图像分析的imageproxy大小和PreviewView大小不一样
【发布时间】:2022-01-24 05:25:37
【问题描述】:

我正在尝试使用 Firebase 的 MLKit 通过 Camerax 进行人脸检测。我很难让图像分析的图像代理大小与 PreviewView 的大小相匹配。对于图像分析和 PreviewView,我将 setTargetResolution() 设置为 PreviewView 的宽度和高度。但是,当我在分析器中检查 Imageproxy 的大小时,它给我的宽度是 1920,高度是 1080。我的 PreviewView 宽度为 1080,高度为 2042。当我在 setTargetResolution() 中交换宽度和高度以进行图像分析时,imageproxy 中的宽度和高度都为 1088。我的预览视图也锁定为纵向模式。

最终,我需要将原始图像代理数据和面部点数据输入 AR 代码。因此,仅放大绘制面部点的图形叠加层对我不起作用。

问:如果在 camerax 库中无法解决此问题,如何缩放从分析器返回的图像代理以匹配预览视图?

我正在使用 Java 和最新的 Camerax 库:

def camerax_version = "1.0.0-beta08"

【问题讨论】:

标签: android image analysis android-camerax


【解决方案1】:

很难确保预览和图像分析用例具有相同的输出分辨率,因为不同的设备支持不同的分辨率,并且图像分析对其输出的最大分辨率有硬性限制(如 the documentation 中所述)。

为了使图像分析帧和 UI/PreviewView 的坐标之间的转换更容易,您可以将 preview 和 ImageAnalysis 设置为使用相同的纵横比,例如 AspectRatio.RATIO_4_3 以及 PreviewView(通过将其包裹在里面一个 ConstraintLayout 例如,并设置其宽度/高度比的约束)。这样,检测到的人脸坐标从分析器映射到 UI 变得更加直接,您可以在 this sample 中查看。

或者,您可以使用 CameraX 的 ViewPort API,我相信它仍然是实验性的。它允许为一组用例定义一个视野,从而使它们的输出匹配并具有所见即所得。您可以找到其用法示例here。对于你的情况,你会写这样的东西。

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 显示的匹配的 crop rect。因此,您只需裁剪图像,然后将其传递给人脸检测器。

【讨论】:

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

这个答案来自@Husayn 的答案。我已经添加了相关的示例代码部分。

用于预览和分析的相机图像尺寸因各种原因而异(例如特定于设备的显示尺寸/硬件/相机或特定于应用的视图和处理) 但是,有一些选项可以将处理图像大小和生成的 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())
                                //...
                            }
                         }
                      }
                  }

定义 scale 和 traslate 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);
            }
        }

【讨论】:

  • 您的示例代码非常难以阅读。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-30
  • 2015-10-04
  • 1970-01-01
  • 2019-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多