将ImageProxy转换为位图

Pet*_*der 6 android android-camerax

因此,我想探索新的Google的Camera API- CameraX。我想做的是每秒从相机提要中获取一张图像,然后将其传递到接受位图的功能中,以进行机器学习。

我阅读了有关Camera XImage Analyzer 的文档:

图像分析用例为您的应用提供了CPU可访问的图像,以执行图像处理,计算机视觉或机器学习推断。该应用程序实现在每个框架上运行的Analyzer方法。

..这基本上是我需要的。因此,我实现了这个图像分析器,如下所示:

imageAnalysis.setAnalyzer { image: ImageProxy, _: Int ->
    viewModel.onAnalyzeImage(image)
}
Run Code Online (Sandbox Code Playgroud)

我得到的是image: ImageProxy。我该如何将其转移ImageProxyBitmap

我试图这样解决:

fun decodeBitmap(image: ImageProxy): Bitmap? {
    val buffer = image.planes[0].buffer
    val bytes = ByteArray(buffer.capacity()).also { buffer.get(it) }
    return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
Run Code Online (Sandbox Code Playgroud)

但是它返回null-因为decodeByteArray没有收到有效的(?)位图字节。有任何想法吗?

Ahw*_*war 17

我需要用 Java编写Mike A 的代码,所以我转换了它。

您可以先使用 Java 将 ImageProxy 转换为 Image

Image image = imageProxy.getImage();
Run Code Online (Sandbox Code Playgroud)

然后您可以使用转换为 Java 的上层函数将图像转换为位图

Image image = imageProxy.getImage();
Run Code Online (Sandbox Code Playgroud)

本答案的权利由Mike A保留

  • 您可能需要修复 compressToJpeg 行以将常量替换为 (0, 0, yuvImage.getWidth(), yuvImage.getHeight()) (2认同)
  • 这是行不通的。大多数设备的图像都会失真。(继续之前检查位图预览)。 (2认同)

art*_*art 12

这种转换还有另一种实现。首先YUV_420_888被转换为NV21,然后RenderScript被用于转换为位图(因此预期更effecient)。此外,它考虑了更正确的像素步幅。它也来自官方的 android 相机示例存储库。

如果有人不想处理RenderScript和同步这里是修改后的代码:

fun ImageProxy.toBitmap(): Bitmap? {
    val nv21 = yuv420888ToNv21(this)
    val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, null)
    return yuvImage.toBitmap()
}

private fun YuvImage.toBitmap(): Bitmap? {
    val out = ByteArrayOutputStream()
    if (!compressToJpeg(Rect(0, 0, width, height), 100, out))
        return null
    val imageBytes: ByteArray = out.toByteArray()
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}

private fun yuv420888ToNv21(image: ImageProxy): ByteArray {
    val pixelCount = image.cropRect.width() * image.cropRect.height()
    val pixelSizeBits = ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)
    val outputBuffer = ByteArray(pixelCount * pixelSizeBits / 8)
    imageToByteBuffer(image, outputBuffer, pixelCount)
    return outputBuffer
}

private fun imageToByteBuffer(image: ImageProxy, outputBuffer: ByteArray, pixelCount: Int) {
    assert(image.format == ImageFormat.YUV_420_888)

    val imageCrop = image.cropRect
    val imagePlanes = image.planes

    imagePlanes.forEachIndexed { planeIndex, plane ->
        // How many values are read in input for each output value written
        // Only the Y plane has a value for every pixel, U and V have half the resolution i.e.
        //
        // Y Plane            U Plane    V Plane
        // ===============    =======    =======
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y    U U U U    V V V V
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        val outputStride: Int

        // The index in the output buffer the next value will be written at
        // For Y it's zero, for U and V we start at the end of Y and interleave them i.e.
        //
        // First chunk        Second chunk
        // ===============    ===============
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y    V U V U V U V U
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        // Y Y Y Y Y Y Y Y
        var outputOffset: Int

        when (planeIndex) {
            0 -> {
                outputStride = 1
                outputOffset = 0
            }
            1 -> {
                outputStride = 2
                // For NV21 format, U is in odd-numbered indices
                outputOffset = pixelCount + 1
            }
            2 -> {
                outputStride = 2
                // For NV21 format, V is in even-numbered indices
                outputOffset = pixelCount
            }
            else -> {
                // Image contains more than 3 planes, something strange is going on
                return@forEachIndexed
            }
        }

        val planeBuffer = plane.buffer
        val rowStride = plane.rowStride
        val pixelStride = plane.pixelStride

        // We have to divide the width and height by two if it's not the Y plane
        val planeCrop = if (planeIndex == 0) {
            imageCrop
        } else {
            Rect(
                    imageCrop.left / 2,
                    imageCrop.top / 2,
                    imageCrop.right / 2,
                    imageCrop.bottom / 2
            )
        }

        val planeWidth = planeCrop.width()
        val planeHeight = planeCrop.height()

        // Intermediate buffer used to store the bytes of each row
        val rowBuffer = ByteArray(plane.rowStride)

        // Size of each row in bytes
        val rowLength = if (pixelStride == 1 && outputStride == 1) {
            planeWidth
        } else {
            // Take into account that the stride may include data from pixels other than this
            // particular plane and row, and that could be between pixels and not after every
            // pixel:
            //
            // |---- Pixel stride ----|                    Row ends here --> |
            // | Pixel 1 | Other Data | Pixel 2 | Other Data | ... | Pixel N |
            //
            // We need to get (N-1) * (pixel stride bytes) per row + 1 byte for the last pixel
            (planeWidth - 1) * pixelStride + 1
        }

        for (row in 0 until planeHeight) {
            // Move buffer position to the beginning of this row
            planeBuffer.position(
                    (row + planeCrop.top) * rowStride + planeCrop.left * pixelStride)

            if (pixelStride == 1 && outputStride == 1) {
                // When there is a single stride value for pixel and output, we can just copy
                // the entire row in a single step
                planeBuffer.get(outputBuffer, outputOffset, rowLength)
                outputOffset += rowLength
            } else {
                // When either pixel or output have a stride > 1 we must copy pixel by pixel
                planeBuffer.get(rowBuffer, 0, rowLength)
                for (col in 0 until planeWidth) {
                    outputBuffer[outputOffset] = rowBuffer[col * pixelStride]
                    outputOffset += outputStride
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记。OpenCV android SDK 中有类似的转换。

  • 这对 2022 年的我有用。 (2认同)

Mik*_*ail 9

为任何在使用Mike A 转换器(尤其是在小米设备上)尝试转换高分辨率(1080p 及更高)图像时出现绿色乱码/故障位图的人提供解决方案。故障示例:

在此输入图像描述

尝试来自MLKit 示例的 Google 转换器:https://github.com/googlesamples/mlkit/blob/master/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/BitmapUtils .java

要使其正常工作,您还需要添加以下内容: https: //github.com/googlesamples/mlkit/blob/master/android/vision-quickstart/app/src/main/java/com/google/mlkit/vision/demo/框架元数据.java

然后就BitmapUtils.getBitmap(imageProxy)

在 Poco X3 NFC 上使用 3200x2400 图像进行测试。


Mik*_*e A 7

您将需要检查,image.format以查看是否为ImageFormat.YUV_420_888。如果是这样,则可以使用此扩展名将图像转换为位图:

fun Image.toBitmap(): Bitmap {
    val yBuffer = planes[0].buffer // Y
    val uBuffer = planes[1].buffer // U
    val vBuffer = planes[2].buffer // V

    val ySize = yBuffer.remaining()
    val uSize = uBuffer.remaining()
    val vSize = vBuffer.remaining()

    val nv21 = ByteArray(ySize + uSize + vSize)

    //U and V are swapped
    yBuffer.get(nv21, 0, ySize)
    vBuffer.get(nv21, ySize, vSize)
    uBuffer.get(nv21, ySize + vSize, uSize)

    val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null)
    val out = ByteArrayOutputStream()
    yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
    val imageBytes = out.toByteArray()
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
Run Code Online (Sandbox Code Playgroud)

这对我有用。

  • 通过上述逻辑,我得到了乱码的绿色图像。有解决问题吗? (10认同)
  • 我已经尝试过了,这实际上是我创建位图的唯一方法。但是 - 现在它无法识别我的图像中的任何文本。之前,当我通过 fromMediaImage 通过检测器时,它确实识别了文本。知道为什么吗?我尝试将生成的位图显示到“ImageView”,但它只是乱码。只是一堆绿线。 (2认同)
  • 我也得到乱码图像。有人修好了吗?该解决方案在小米A2上根本不起作用。 (2认同)

dar*_*win 6

从 image.getPlanes() 访问缓冲区时,我遇到了 ArrayIndexOutOfBoundsException。以下函数可以毫无例外地将 ImageProxy 转换为 Bitmap。

爪哇

private Bitmap convertImageProxyToBitmap(ImageProxy image) {
        ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer();
        byteBuffer.rewind();
        byte[] bytes = new byte[byteBuffer.capacity()];
        byteBuffer.get(bytes);
        byte[] clonedBytes = bytes.clone();
        return BitmapFactory.decodeByteArray(clonedBytes, 0, clonedBytes.length);
    }
Run Code Online (Sandbox Code Playgroud)

Kotlin 扩展函数

fun ImageProxy.convertImageProxyToBitmap(): Bitmap {
        val buffer = planes[0].buffer
        buffer.rewind()
        val bytes = ByteArray(buffer.capacity())
        buffer.get(bytes)
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
    }
Run Code Online (Sandbox Code Playgroud)

  • 对于那些正在考虑为什么有不同的解决方案有效的人:使用 imageProxy.getFormat() 检查 ImageProxy 的格式。如果您的格式是35,您可以使用@Mike A解决方案,如果您的格式是256,您可以使用@darwin解决方案。最后,显然,每种格式都需要不同的转换过程。@Mike A 和 @Ahwar 解决方案适用于 YUV_420_888。图像格式:https://developer.android.com/reference/android/graphics/ImageFormat#JPEG (4认同)
  • @BCJuan 是对的。我尝试过,记住格式并使用 Mike A、Ahwar 和 darwin 的算法,它对我有用。谢谢你们。 (2认同)