Android中如何将Bitmap正确转换为YUV420格式?
Android Bitmap转YUV420(YUV420P/NV21)正确实现方法
现有代码的核心问题
- 错误将Bitmap的RGB像素字节当作YUV420SP数据处理,跳过了RGB到YUV的色彩空间转换,导致后续操作基于错误数据源。
convertYUV420SPtoYUV420P函数逻辑不匹配输入数据,因为输入根本不是YUV420SP格式。
方法一:手动RGB转YUV420P(逐像素计算)
YUV420P格式为:Y分量(width*height字节)→ U分量((width*height)/4字节)→ V分量((width*height)/4字节)。采用BT.601色彩转换标准(Android通用):
fun bitmapToYuv420P(bitmap: Bitmap): ByteArray { val width = bitmap.width val height = bitmap.height // YUV420要求宽高为偶数,做对齐处理 val alignedWidth = if (width % 2 != 0) width - 1 else width val alignedHeight = if (height % 2 != 0) height - 1 else height val pixels = IntArray(alignedWidth * alignedHeight) bitmap.getPixels(pixels, 0, alignedWidth, 0, 0, alignedWidth, alignedHeight) val yuvSize = alignedWidth * alignedHeight * 3 / 2 val yuvArray = ByteArray(yuvSize) val ySize = alignedWidth * alignedHeight val uvSize = ySize / 4 var yIndex = 0 var uIndex = ySize var vIndex = ySize + uvSize for (y in 0 until alignedHeight) { for (x in 0 until alignedWidth) { val pixel = pixels[y * alignedWidth + x] val r = (pixel shr 16) and 0xFF val g = (pixel shr 8) and 0xFF val b = pixel and 0xFF // 计算Y分量 val yVal = (0.299 * r + 0.587 * g + 0.114 * b).toInt() yuvArray[yIndex++] = yVal.coerceIn(0, 255).toByte() // 每2x2像素采样一组U、V分量(YUV420的4:2:0采样规则) if (y % 2 == 0 && x % 2 == 0) { val uVal = (-0.14713 * r - 0.28886 * g + 0.436 * b + 128).toInt() val vVal = (0.615 * r - 0.51499 * g - 0.10001 * b + 128).toInt() yuvArray[uIndex++] = uVal.coerceIn(0, 255).toByte() yuvArray[vIndex++] = vVal.coerceIn(0, 255).toByte() } } } return yuvArray }
方法二:RenderScript高效实现(适合大分辨率Bitmap)
RenderScript是Android高性能计算框架,图像转换效率远高于手动循环,推荐用于大尺寸Bitmap转换。
步骤1:启用RenderScript
在模块级build.gradle中添加配置:
android { defaultConfig { renderscriptTargetApi 21 renderscriptSupportModeEnabled true } }
步骤2:实现转换函数
默认输出NV21格式(Android相机常用YUV420变体,Y后UV交错排列):
import android.renderscript.Allocation import android.renderscript.Element import android.renderscript.RenderScript import android.renderscript.ScriptIntrinsicYuvToRGB import android.renderscript.Type fun bitmapToYuv420NV21(rs: RenderScript, bitmap: Bitmap): ByteArray { val width = bitmap.width val height = bitmap.height val alignedWidth = if (width % 2 != 0) width - 1 else width val alignedHeight = if (height % 2 != 0) height - 1 else height // 创建Bitmap输入Allocation val inputAlloc = Allocation.createFromBitmap( rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT ) // 创建YUV输出Allocation(NV21格式尺寸为width*height*3/2) val outputType = Type.Builder(rs, Element.U8(rs)) .setX(alignedWidth * alignedHeight * 3 / 2) .create() val outputAlloc = Allocation.createTyped(rs, outputType) // 执行RGB转YUV转换 val yuvScript = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)) yuvScript.setInput(inputAlloc) yuvScript.forEach(outputAlloc) // 提取转换后的字节数组 val yuvArray = ByteArray(outputAlloc.bytesSize) outputAlloc.copyTo(yuvArray) // 释放资源 inputAlloc.destroy() outputAlloc.destroy() yuvScript.destroy() return yuvArray }
如果需要YUV420P格式,可在得到NV21后将UV分量分离重组,逻辑简单且性能影响极小。
内容的提问来源于stack exchange,提问作者Mustafa Maden
相关产品推荐
相关产品推荐

