我正在尝试裁剪相机预览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
用作这两种技巧。在您的特定设备上,这些Images
与interleaved色彩平面一起出现。 plane1
和plane2
本质上是相同的数组(有些警告)。它们都“指向” 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二次采样过程中,每隔一个像素行都会被跳过。 (其他所有column也会被跳过,但是由于每个像素都由相邻的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:
如您所述,数组值被解释为“有符号”;您的转换算法希望此符号是无符号的,因此最好将它们的范围设置为0..255。
推荐的改进
[yuvToRGB()
如果对输入和输出使用整数数据类型而不是floats
,效率会更高。