三星Galaxy A54 5G(Android14)Camera2 YUV转RGBA偏绿问题求助
三星Galaxy A54 5G YUV420转RGBA偏色修复及HDRViewFinder代码迁移方案
一、YUV转RGBA偏色问题解决
三星A54 5G的Camera2输出YUV420格式存在非标准实现,主要是UV通道顺序反转或色彩空间不匹配导致白色/灰色区域偏绿/黄,华为等设备无此问题是因为遵循标准NV21/YV12规范。
1. 先确认YUV子格式
通过Image对象的Planes信息判断具体格式:
Image.Plane[] planes = image.getPlanes(); // 检查UV平面的像素步长,YV12的U/V平面像素步长均为1,NV21则是2 boolean isYV12 = planes[1].getPixelStride() == 1 && planes[2].getPixelStride() == 1;
三星A54大概率输出YV12,而多数转换工具默认按NV21处理,直接导致UV错位偏色。
2. 针对性修复方案
方案1:交换UV通道后再转换
手动调换U和V缓冲区数据,适配YV12转NV21的场景:
Image.Plane[] planes = image.getPlanes(); ByteBuffer yBuf = planes[0].getBuffer(); ByteBuffer uBuf = planes[1].getBuffer(); ByteBuffer vBuf = planes[2].getBuffer(); // 保存U通道数据 byte[] uData = new byte[uBuf.remaining()]; uBuf.get(uData); uBuf.clear(); // 将V通道数据写入U缓冲区 byte[] vData = new byte[vBuf.remaining()]; vBuf.get(vData); uBuf.put(vData); vBuf.clear(); // 将原U通道数据写入V缓冲区 vBuf.put(uData); // 之后用原有转换工具(如ScriptIntrinsicYuvToRGB)处理调整后的Planes
方案2:强制指定色彩转换矩阵
部分三星设备默认使用BT.2020色彩空间,而转换工具默认用BT.709,需手动替换转换矩阵:
// BT.2020到sRGB的转换矩阵 float[] colorMatrix = { 1.6605f, -0.1246f, -0.0182f, 0, -0.5876f, 1.3808f, 0.0466f, 0, -0.0729f, -0.2562f, 1.0416f, 0, 0, 0, 0, 1 }; // 在RenderScript转换中应用矩阵 RenderScript rs = RenderScript.create(context); ScriptIntrinsicYuvToRGB yuvConverter = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)); yuvConverter.setInput(Allocation.createFromByteBuffer(rs, yBuf, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT)); yuvConverter.setColorMatrix(colorMatrix); Allocation rgbAlloc = Allocation.createTyped(rs, Type.createXY(rs, Element.U8_4(rs), width, height)); yuvConverter.forEach(rgbAlloc); Bitmap outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); rgbAlloc.copyTo(outputBitmap);
方案3:修正官方YuvToRgbConverter
修改Camera2示例中的YuvToRgbConverter,增加YV12格式判断:
public void convert(Image image, Bitmap output) { Image.Plane[] planes = image.getPlanes(); boolean isYV12 = planes[1].getPixelStride() == 1 && planes[2].getPixelStride() == 1; if (isYV12) { // 交换U和V平面 Image.Plane tempPlane = planes[1]; planes[1] = planes[2]; planes[2] = tempPlane; } // 执行原转换逻辑 // ...(保留原YuvToRgbConverter的代码) }
二、HDRViewFinder的hdrmerge改写为原生Android代码
完全可以实现,核心是复刻RenderScript版的多帧HDR合成逻辑,以下是简化的原生Java实现:
1. 核心合成逻辑
public class HdrFrameMerger { // 多帧YUV融合,exposureWeights为各帧的曝光权重(暗帧权重高,亮帧权重低) public byte[] mergeHdrFrames(List<byte[]> yuvFrames, List<Float> exposureWeights, int width, int height) { int ySize = width * height; int uvSize = ySize / 2; byte[] mergedYuv = new byte[ySize + uvSize]; // 融合Y通道(高光压制、阴影提升) for (int i = 0; i < ySize; i++) { float weightedSum = 0f; float weightTotal = 0f; for (int frameIdx = 0; frameIdx < yuvFrames.size(); frameIdx++) { byte yByte = yuvFrames.get(frameIdx)[i]; int yValue = yByte & 0xFF; // 模拟原RenderScript的权重分配:亮像素降低亮帧权重,暗像素提升暗帧权重 float pixelWeight = exposureWeights.get(frameIdx) * (yValue < 128 ? 1.5f : 0.5f); weightedSum += yValue * pixelWeight; weightTotal += pixelWeight; } mergedYuv[i] = (byte) Math.min(Math.max(weightedSum / weightTotal, 0), 255); } // 融合UV通道(简化处理,实际需和Y通道对齐) for (int i = ySize; i < mergedYuv.length; i++) { float weightedSum = 0f; float weightTotal = 0f; for (int frameIdx = 0; frameIdx < yuvFrames.size(); frameIdx++) { byte uvByte = yuvFrames.get(frameIdx)[i]; int uvValue = uvByte & 0xFF; float pixelWeight = exposureWeights.get(frameIdx); weightedSum += uvValue * pixelWeight; weightTotal += pixelWeight; } mergedYuv[i] = (byte) Math.min(Math.max(weightedSum / weightTotal, 0), 255); } return mergedYuv; } // 可选:帧对齐(基于特征点匹配,复杂场景可引入OpenCV简化开发) public List<byte[]> alignFrames(List<byte[]> yuvFrames, int width, int height) { // 此处实现简单的平移对齐,针对轻微抖动场景 // ... return yuvFrames; } }
2. 注意事项
- 若要和原RenderScript效果完全一致,需严格复刻其像素权重计算逻辑和帧对齐算法,原hdrmerge会根据像素亮度动态调整单帧权重,避免高光过曝或阴影过暗。
- 合成计算量较大,务必放在
AsyncTask或Coroutine(Kotlin)后台线程执行,避免阻塞UI。
内容的提问来源于stack exchange,提问作者Ladislav
相关产品推荐
相关产品推荐

