Android 上的 NV21 位图、非常暗的图像、灰度或黄色色调?

NV21 to Bitmap on Android, Very dark image, grayscale, or yellow tint?

我一直在考虑转换从 onPreviewFrame() 获得的 NV21 byte[]。我在论坛和 google 中搜索了各种解决方案。我尝试过 RenderScripts 和其他一些代码示例。他们中的一些给我一张带有黄色色调的图像,一些给我一张红色和蓝色翻转的图像(在我将它翻转回代码后,我得到黄色调),一些给我整个图像的奇怪颜色特征(几乎像底片),有些给我一张灰度图像,有些给我一张太暗的图像,你真的什么都看不出来。

因为我是输入问题的人,我意识到我一定是房间里的白痴所以我们将从 开始。这个特殊的解决方案给了我一个非常暗的图像,但我还不够酷,无法发表评论。有没有人尝试过这个解决方案或者有一个可以生成与原始 NV21 格式质量相同的图像?

我需要一个有效的 ARGB 字节 [] 或一个有效的位图,我可以修改我的项目来处理其中任何一个。仅供参考,我已经尝试过这些(以及其他一些实际上只是这些的副本):

One solution I tried
Another solution I tried

我能够通过使用代码 here 获得一种不同的方法(之前链接的方法)。但那是 Red/Blue 颜色翻转。所以,我只是重新排列了 U 线和 V 线,一切正常。不过,这不如 RenderScript 快。如果有一个正常运行的 RenderScript 就好了。这是代码:

static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
    final int frameSize = width * height;

    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (0xff & ((int) yuv420sp[yp])) - 16;
            if (y < 0) y = 0;
            if ((i & 1) == 0) {
                u = (0xff & yuv420sp[uvp++]) - 128; //Just changed the order
                v = (0xff & yuv420sp[uvp++]) - 128; //It was originally v then u
            }

            int y1192 = 1192 * y;
            int r = (y1192 + 1634 * v);
            int g = (y1192 - 833 * v - 400 * u);
            int b = (y1192 + 2066 * u);

            if (r < 0) r = 0; else if (r > 262143) r = 262143;
            if (g < 0) g = 0; else if (g > 262143) g = 262143;
            if (b < 0) b = 0; else if (b > 262143) b = 262143;

            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
        }
    }
}

谁有没有色调和/或翻转问题的 RenderScript?

如果您尝试将 YUV 从相机转换为位图,您可以尝试以下操作:

// import android.renderscript.*
// RenderScript mRS;
// ScriptIntrinsicYuvToRGB mYuvToRGB;
// Allocation yuvPreviewAlloc;
// Allocation rgbOutputAlloc;

// Create RenderScript context, ScriptIntrinsicYuvToRGB and Allocations and keep reusing them.
if (NotInitialized) {
    mRS = RenderScript.create(this).
    mYuvToRGB = ScriptIntrinsicYuvToRGB.create(mRS, Element.YUV(mRS));    

    // Create a RS Allocation to hold NV21 data.
    Type.Builder tYuv = new Type.Builder(mRS, Element.YUV(mRS));
    tYuv.setX(width).setY(height).setYuvFormat(android.graphics.ImageFormat.NV21);
    yuvPreviewAlloc = Allocation.createTyped(mRS, tYuv.create(), Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT);

    // Create a RS Allocation to hold RGBA data.
    Type.Builder tRgb = new Type.Builder(mRS, Element.RGBA_8888(mRS));
    tRgb.setX(width).tRgb(height);
    rgbOutputAlloc = Allocation.createTyped(mRS, tRgb.create(), Allocation.USAGE_SCRIPT);

    // Set input of ScriptIntrinsicYuvToRGB
    mYuvToRGB.setInput(yuvPreviewAlloc);
}

// Use rsPreviewSurface as one of the output surface from Camera API.
// You can refer to https://github.com/googlesamples/android-HdrViewfinder/blob/master/Application/src/main/java/com/example/android/hdrviewfinder/HdrViewfinderActivity.java#L504
Surface rsPreviewSurface = yuvPreviewAlloc.getSurface();
...

// Whenever a new frame is available
// Update the yuv Allocation with a new Camera buffer without any copy.
// You can refer to https://github.com/googlesamples/android-HdrViewfinder/blob/master/Application/src/main/java/com/example/android/hdrviewfinder/ViewfinderProcessor.java#L109
yuvPreviewAlloc.ioReceive();
// The actual Yuv to Rgb conversion.
mYuvToRGB.forEach(rgbOutputAlloc);

// Copy the rgb Allocation to a Bitmap.
rgbOutputAlloc.copyTo(mBitmap);

// continue processing mBitmap.
...

使用 ScriptIntrinsics 时,我强烈建议至少更新到 JellyBean 4.3 或更高版本 (API18)。事情比 JB 4.2 更容易使用 (API 17).

ScriptIntrinsicYuvToRGB 并不像看起来那么复杂。 特别是你不需要 Type.Builder 对象。 相机预览格式必须为NV21 !

在 onCreate()... 方法中创建 RenderScript 对象和 Intrinsic:

mRS = RenderScript.create(this);
mYuvToRGB = ScriptIntrinsicYuvToRGB.create(mRS, Element.U8_4(mRS));    

用你的 cameraPreviewWidth 和 cameraPreviewHeight 计算 相机数据字节数组的长度:

int yuvDatalength = cameraPreviewWidth*cameraPreviewHeight*3/2 ;  // this is 12 bit per pixel

您需要一个位图用于输出:

mBitmap = Bitmap.createBitmap(cameraPreviewWidth, cameraPreviewHeight, Bitmap.Config.ARGB_8888);

然后创建输入和输出分配(这里是 API18+ 中的更改)

yuvPreviewAlloc = Allocation.createSized(mRS, Element.U8(mRS),  yuvDatalength);

rgbOutputAlloc =  Allocation.createFromBitmap(mRS, mBitmap);  // this simple !

并将脚本输入设置为输入分配

mYuvToRGB.setInput(yuvPreviewAlloc);  // this has to be done only once !

在相机循环中(每当有新帧可用时),将 NV21 字节数组(data[])复制到 yuvPreviewAlloc,执行脚本并将结果复制到位图:

yuvPreviewAlloc.copyFrom(data);  // or yuvPreviewAlloc.copyFromUnchecked(data);

mYuvToRGB.forEach(rgbOutputAlloc);

rgbOutputAlloc.copyTo(mBitmap);

例如:在 Nexus 7(2013,JellyBean 4.3)上,全高清 (1920x1080) 相机预览转换大约需要 7 毫秒。