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 毫秒。
我一直在考虑转换从 onPreviewFrame() 获得的 NV21 byte[]。我在论坛和 google 中搜索了各种解决方案。我尝试过 RenderScripts 和其他一些代码示例。他们中的一些给我一张带有黄色色调的图像,一些给我一张红色和蓝色翻转的图像(在我将它翻转回代码后,我得到黄色调),一些给我整个图像的奇怪颜色特征(几乎像底片),有些给我一张灰度图像,有些给我一张太暗的图像,你真的什么都看不出来。
因为我是输入问题的人,我意识到我一定是房间里的白痴所以我们将从
我需要一个有效的 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 毫秒。