从 YUV_420_888 图像获取 RGB 值

Getting RGB values from YUV_420_888 image

我正在尝试裁剪相机预览 YUV_420_888 图像的中心方形部分。我想在处理每一帧时获取 RGB 值,而不是转换为位图然后获取值。

这是我目前拥有的,但我似乎无法获得有效图像。我想我检索的 YUV 值不正确。

planes: plane0 pixelStride:1 rowStride:1280 height:720 capacity:921600
planes: plane1 pixelStride:2 rowStride:1280 height:719 capacity:460799
planes: plane2 pixelStride:2 rowStride:1280 height:719 capacity:460799

_

fun processImage(image: Image) {
    val imageSize = 224
    val subImageWidth = imageSize/2
    val subImageHeight = imageSize/2

    val bb = ByteBuffer.allocateDirect(imageSize * imageSize * 3 * 4)
    bb.order(ByteOrder.nativeOrder())

    val plane0 = image.planes[0]
    val plane1 = image.planes[1]
    val plane2 = image.planes[2]

    val width = plane0.rowStride
    val height = plane0.buffer.capacity() / plane0.rowStride

    //Documentation says that plane 1&2 will have the same pixel stride and row stride
    val plane12Width = plane1.rowStride / plane1.pixelStride

    val xOffset = (width - subImageWidth) / 2
    val yOffset = (height - subImageHeight) / 2

    for (y in 0 until imageSize) {
        for (x in 0 until imageSize) {
            val yb = plane0.buffer[(x + xOffset) + width * (y + yOffset)]
            val ub = plane1.buffer[(x + xOffset)/2 + plane12Width * (y + yOffset)]
            val vb = plane2.buffer[(x + xOffset)/2 + plane12Width * (y + yOffset)]

            val rgb = yuvToRGB(yb, ub, vb)


            bb.putFloat(rgb[0])
            bb.putFloat(rgb[1])
            bb.putFloat(rgb[2])
        }
    }
    imageView.setBitmap(getOutputImage(bb))
}

fun yuvToRGB(y: Float, u: Float, v: Float): FloatArray {
    val rgb = FloatArray(3)

    val rTemp = ((y - 16) * 1.164 + (v - 128) * 1.596).toFloat()
    val gTemp = ((y - 16) * 1.164 - (u - 128) * 0.392 - (v - 128) * 0.813).toFloat()
    val bTemp = ((y - 16) * 1.164 + (u - 128) * 2.017).toFloat()

    if (rTemp > 255f) {
        rgb[0] = 255f
    } else if (rTemp < 0f) {
        rgb[0] = 0f
    } else {
        rgb[0] = rTemp
    }

    if (gTemp > 255f) {
        rgb[1] = 255f
    } else if (gTemp < 0f) {
        rgb[1] = 0f
    } else {
        rgb[1] = gTemp
    }

    if (bTemp > 255f) {
        rgb[2] = 255f
    } else if (bTemp < 0f) {
        rgb[2] = 0f
    } else {
        rgb[2] = bTemp
    }

    return rgb
}

private fun getOutputImage(output: ByteBuffer): Bitmap {
    val imageSize = 224
    output.rewind() // Rewind the output buffer after running.

    val bitmap = Bitmap.createBitmap(imageSize, imageSize, Bitmap.Config.ARGB_8888)
    val pixels = IntArray(imageSize * imageSize) // Set your expected output's height and width
    for (i in 0 until imageSize * imageSize) {
        val a = 0xFF
        val r: Float = output.float
        val g: Float = output.float
        val b: Float = output.float
        pixels[i] = a shl 24 or (r.toInt() shl 16) or (g.toInt() shl 8) or b.toInt()
    }
    bitmap.setPixels(pixels, 0, imageSize, 0, 0, imageSize, imageSize)

    return bitmap
}

修复 #1:

你想确保你击中了颜色平面的正确区域,对于 UV 值:

val ub = plane1.buffer[((x + xOffset)/2) * 2 + plane1.rowStride * ((y + yOffset)/2)].toFloat()
val vb = plane1.buffer[((x + xOffset)/2) * 2 + 1 + plane1.rowStride * ((y + yOffset)/2)].toFloat()

我有意将 plane1 用于两者作为一种 hack。对于您的特定设备,这些 Images 会出现 interleaved 颜色平面。 plane1plane2 本质上 相同的数组(一些注意事项)。它们都 "point" 到 RAM 中的同一区域;它们有 99.9% 重叠。它们包含这样的值:"U/V/U/V/U/V",这就是为什么 pixelStride 是两个。我认为如果你这样做也会有效:

val ub = plane1.buffer[((x + xOffset)/2) * 2 + plane1.rowStride * ((y + yOffset)/2)].toFloat()
val vb = plane2.buffer[((x + xOffset)/2) * 2 + plane2.rowStride * ((y + yOffset)/2)].toFloat()

...因为显然 ByteBufferDirect 接口已经抽象出 plane2plane1 右侧 1 个字节开始的事实(在本机内存中)。 (如果您在本机端从 JNI 执行此操作,情况看起来会略有不同。)

((x + xOffset)/2)*2 更正是为了解释这种交错,pixelStride((y + yOffset)/2) 校正是为了说明在 4:2:0 子采样过程中每隔一个像素行都会被跳过这一事实。 (每隔 也被跳过,但是因为每个像素由彼此相邻的 2 个字节 (U,V) 组成,我们需要使用 * 2. 整数数学;我们试图得到一个偶数,所以参考偏移量总是在 "U").

修复 #2:

在这个地区:

val r: Float = output.float * 255
val g: Float = output.float * 255
val b: Float = output.float * 255

...您不想乘以 255,因为您的值已经在 0..255 范围内。

修复 #3:

如您所述,数组值被解释为 "signed";您的转换算法期望它是无符号的,因此最好将它们设置在 0..255.

范围内

建议改进

如果 yuvToRGB() 对输入和输出都使用整型数据类型而不是 floats,效率会更高。