从 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 颜色平面。 plane1
和 plane2
是 本质上 相同的数组(一些注意事项)。它们都 "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
接口已经抽象出 plane2
从 plane1
右侧 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
,效率会更高。
我正在尝试裁剪相机预览 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 颜色平面。 plane1
和 plane2
是 本质上 相同的数组(一些注意事项)。它们都 "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
接口已经抽象出 plane2
从 plane1
右侧 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
,效率会更高。