Camerax 图像分析:将图像转换为 bytearray 或 ByteBuffer

Posted

技术标签:

【中文标题】Camerax 图像分析:将图像转换为 bytearray 或 ByteBuffer【英文标题】:Camerax image analysis: Convert image to bytearray or ByteBuffer 【发布时间】:2020-11-24 11:54:40 【问题描述】:

我做了很多阅读尝试了很多不同的方法。 CameraX 正在生成 yuv_420_888 格式的 Image 对象并将其提供给 ImageAnalysis。

但是,无法将其转换为字节缓冲区以进行缩放、转换为位图和运行检测操作。我尝试了以下和许多其他建议的技术。

Converting ImageProxy to Bitmap

所有这些都创建了灰度(即使在使用了所有 3 个平面之后)和一些叠加颜色阴影图像。它有时还会在帧之间产生故障输出,我无法找出原因。

获取简单字节数组以便稍后转换为位图的正确方法是什么?

还有如何引起cameraX作者的注意?

【问题讨论】:

你试过github.com/owahltinez/camerax-tflite/blob/master/app/src/main/…吗? 【参考方案1】:

您只需要使用imageProxy.image?.toBitmap() 转换imageProxy 然后将bitmap 转换为bytearray 如下:

这是一个例子:

private fun takePhoto() 

    camera_capture_button.isEnabled = false

    // Get a stable reference of the modifiable image capture use case
    val imageCapture = imageCapture ?: return

    imageCapture.takePicture(
        ContextCompat.getMainExecutor(this),
        object : ImageCapture.OnImageCapturedCallback() 

            @SuppressLint("UnsafeExperimentalUsageError")
            override fun onCaptureSuccess(imageProxy: ImageProxy) 
                val bitmapImage = imageProxy.image?.toBitmap()
                   
                val stream = ByteArrayOutputStream()
                bitmapImage.compress(Bitmap.CompressFormat.PNG, 90, stream)
                val image = stream.toByteArray()
                    
                
            

            override fun onError(exception: ImageCaptureException) 
                super.onError(exception)
            
        )

【讨论】:

【参考方案2】:
 fun imageProxyToByteArray(image: ImageProxy): ByteArray 
            val yuvBytes = ByteArray(image.width * (image.height + image.height / 2))
            val yPlane = image.planes[0].buffer
            val uPlane = image.planes[1].buffer
            val vPlane = image.planes[2].buffer

            yPlane.get(yuvBytes, 0, image.width * image.height)

            val chromaRowStride = image.planes[1].rowStride
            val chromaRowPadding = chromaRowStride - image.width / 2

            var offset = image.width * image.height
            if (chromaRowPadding == 0) 

                uPlane.get(yuvBytes, offset, image.width * image.height / 4)
                offset += image.width * image.height / 4
                vPlane.get(yuvBytes, offset, image.width * image.height / 4)
             else 
                for (i in 0 until image.height / 2) 
                    uPlane.get(yuvBytes, offset, image.width / 2)
                    offset += image.width / 2
                    if (i < image.height / 2 - 2) 
                        uPlane.position(uPlane.position() + chromaRowPadding)
                    
                
                for (i in 0 until image.height / 2) 
                    vPlane.get(yuvBytes, offset, image.width / 2)
                    offset += image.width / 2
                    if (i < image.height / 2 - 1) 
                        vPlane.position(vPlane.position() + chromaRowPadding)
                    
                
            

            return yuvBytes
        

【讨论】:

社区鼓励在代码中添加解释,而不是纯粹基于代码的答案(参见here)。【参考方案3】:

您可以使用从 Mlkit Pose Detection 中提取的此类。

Mlkit pose detection: BitmapUtils.java

object ImageProxyUtils 

fun getByteArray(image: ImageProxy): ByteArray? 
    image.image?.let 
        val nv21Buffer = yuv420ThreePlanesToNV21(
            it.planes, image.width, image.height
        )

        return ByteArray(nv21Buffer.remaining()).apply 
            nv21Buffer.get(this)
        
    

    return null


private fun yuv420ThreePlanesToNV21(
    yuv420888planes: Array<Plane>,
    width: Int,
    height: Int
): ByteBuffer 
    val imageSize = width * height
    val out = ByteArray(imageSize + 2 * (imageSize / 4))
    if (areUVPlanesNV21(yuv420888planes, width, height)) 

        yuv420888planes[0].buffer[out, 0, imageSize]
        val uBuffer = yuv420888planes[1].buffer
        val vBuffer = yuv420888planes[2].buffer
        vBuffer[out, imageSize, 1]
        uBuffer[out, imageSize + 1, 2 * imageSize / 4 - 1]
     else 
        unpackPlane(yuv420888planes[0], width, height, out, 0, 1)
        unpackPlane(yuv420888planes[1], width, height, out, imageSize + 1, 2)
        unpackPlane(yuv420888planes[2], width, height, out, imageSize, 2)
    
    return ByteBuffer.wrap(out)


private fun areUVPlanesNV21(planes: Array<Plane>, width: Int, height: Int): Boolean 
    val imageSize = width * height
    val uBuffer = planes[1].buffer
    val vBuffer = planes[2].buffer

    val vBufferPosition = vBuffer.position()
    val uBufferLimit = uBuffer.limit()

    vBuffer.position(vBufferPosition + 1)
    uBuffer.limit(uBufferLimit - 1)

    val areNV21 =
        vBuffer.remaining() == 2 * imageSize / 4 - 2 && vBuffer.compareTo(uBuffer) == 0

    vBuffer.position(vBufferPosition)
    uBuffer.limit(uBufferLimit)
    return areNV21


private fun unpackPlane(
    plane: Plane,
    width: Int,
    height: Int,
    out: ByteArray,
    offset: Int,
    pixelStride: Int
) 
    val buffer = plane.buffer
    buffer.rewind()
    val numRow = (buffer.limit() + plane.rowStride - 1) / plane.rowStride
    if (numRow == 0) 
        return
    
    val scaleFactor = height / numRow
    val numCol = width / scaleFactor

    var outputPos = offset
    var rowStart = 0
    for (row in 0 until numRow) 
        var inputPos = rowStart
        for (col in 0 until numCol) 
            out[outputPos] = buffer[inputPos]
            outputPos += pixelStride
            inputPos += plane.pixelStride
        
        rowStart += plane.rowStride
    

【讨论】:

以上是关于Camerax 图像分析:将图像转换为 bytearray 或 ByteBuffer的主要内容,如果未能解决你的问题,请参考以下文章

Camerax ImageCapture 传递给 Chaquopy 太慢了

Android Camera App 使用 CameraX 将图像保存为 YUV_420_888 格式

如何使用 CameraX 拍摄多张图像?

为 CameraX ImageAnalysis 进行 YUV 到 RGB 的转换

PostgreSQL - 如何将 Base64 图像字符串插入 BYTEA 列?

如何解决CameraX闪屏问题