Получение значений RGB из изображения YUV_420_888 - PullRequest
0 голосов
/ 05 февраля 2020

Я пытаюсь обрезать центральную квадратную часть изображения 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
}

center crop

1 Ответ

2 голосов
/ 06 февраля 2020

Fix # 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 для обоих как своего рода хак. С вашим конкретным устройством эти Images выходят с чередующимися цветными плоскостями. plane1 и plane2 являются по существу одним и тем же массивом (некоторые предостережения). Они оба «указывают» на одну и ту же область в ОЗУ; они перекрываются на 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 абстрагировал тот факт, что plane2 запускает 1 байт справа от plane1 ( в родной памяти). (Все выглядело бы немного иначе, если бы вы делали это на нативной стороне, от JNI.)

Исправление ((x + xOffset)/2)*2 предназначено для учета этого чередования, а pixelStride. Поправка ((y + yOffset)/2) учитывает тот факт, что все остальные строки пикселей пропускаются в процессе субдискретизации 4: 2: 0. (Каждый второй столбец также пропускается, но поскольку каждый пиксель состоит из 2 байтов (U, V) рядом друг с другом, нам необходимо удвоить смещение обратно с помощью * 2. Integer math ; мы пытаемся сделать четное число, поэтому смещение ссылки всегда находится на «U»).

Fix # 2:

В этой области:

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

... вы не хотите умножать на 255, потому что ваши значения уже находятся в диапазоне 0..255.

Fix # 3:

Как вы заметили, значения массива интерпретируются как "подписанные"; Ваш алгоритм преобразования ожидает, что это будет без знака, поэтому лучше иметь их в диапазоне 0..255.

Рекомендуемое улучшение

Было бы более эффективно, если yuvToRGB() использовал интегральный тип данных для ввода и вывода вместо floats.

...