从android相机的NV21格式中提取黑白图像

Posted

技术标签:

【中文标题】从android相机的NV21格式中提取黑白图像【英文标题】:Extract black and white image from android camera's NV21 format 【发布时间】:2011-07-13 10:39:47 【问题描述】:

我在 Google 上做了一些搜索,但找不到关于这种格式的足够信息。它是相机预览的默认格式。谁能建议有关它的良好信息来源以及如何从具有该格式的照片/预览图像中提取数据?更具体地说,我需要提取的黑白图像。

编辑:似乎该格式也称为 YCbCr 420 Semi Planar

【问题讨论】:

android API 级别 17 开始,最好的解决方案是使用内部 RenderScript - ScriptIntrinsicYuvToRGB。此 RenderScript 是内置的,因此您不必编写一行类似 C 的代码。有关详细信息,请参阅此问题:How to use ScriptIntrinsicYuvToRGB converting yuv to rgba 【参考方案1】:

我开发了以下代码来将 NV21 转换为 RGB,并且可以正常工作。

/**
 * Converts YUV420 NV21 to RGB8888
 * 
 * @param data byte array on YUV420 NV21 format.
 * @param width pixels width
 * @param height pixels height
 * @return a RGB8888 pixels int array. Where each int is a pixels ARGB. 
 */
public static int[] convertYUV420_NV21toRGB8888(byte [] data, int width, int height) 
    int size = width*height;
    int offset = size;
    int[] pixels = new int[size];
    int u, v, y1, y2, y3, y4;

    // i percorre os Y and the final pixels
    // k percorre os pixles U e V
    for(int i=0, k=0; i < size; i+=2, k+=2) 
        y1 = data[i  ]&0xff;
        y2 = data[i+1]&0xff;
        y3 = data[width+i  ]&0xff;
        y4 = data[width+i+1]&0xff;

        u = data[offset+k  ]&0xff;
        v = data[offset+k+1]&0xff;
        u = u-128;
        v = v-128;

        pixels[i  ] = convertYUVtoRGB(y1, u, v);
        pixels[i+1] = convertYUVtoRGB(y2, u, v);
        pixels[width+i  ] = convertYUVtoRGB(y3, u, v);
        pixels[width+i+1] = convertYUVtoRGB(y4, u, v);

        if (i!=0 && (i+2)%width==0)
            i+=width;
    

    return pixels;


private static int convertYUVtoRGB(int y, int u, int v) 
    int r,g,b;

    r = y + (int)(1.402f*v);
    g = y - (int)(0.344f*u +0.714f*v);
    b = y + (int)(1.772f*u);
    r = r>255? 255 : r<0 ? 0 : r;
    g = g>255? 255 : g<0 ? 0 : g;
    b = b>255? 255 : b<0 ? 0 : b;
    return 0xff000000 | (b<<16) | (g<<8) | r;

这张图有助于理解。

如果你只想灰度图像更容易。您可以丢弃所有 U 和 V 信息,而只获取 Y 信息。代码可以是这样的:

/**
 * Converts YUV420 NV21 to Y888 (RGB8888). The grayscale image still holds 3 bytes on the pixel.
 * 
 * @param pixels output array with the converted array o grayscale pixels
 * @param data byte array on YUV420 NV21 format.
 * @param width pixels width
 * @param height pixels height
 */
public static void applyGrayScale(int [] pixels, byte [] data, int width, int height) 
    int p;
    int size = width*height;
    for(int i = 0; i < size; i++) 
        p = data[i] & 0xFF;
        pixels[i] = 0xff000000 | p<<16 | p<<8 | p;
    

只需创建您的位图:

Bitmap bm = Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);

其中像素是您的 int [] 数组。

【讨论】:

@Derzu 你简直是摇滚!! \m/ 图形有错误。显示的字节布局是 NV12,而不是 NV21。唯一的区别是 UV 字节被翻转。 NV12:YYYYUV NV21:YYYYVU 这张图片对我帮助很大!奥布里加多! =) 我认为上面的答案是错误的。 fourcc.org/pixel-format/yuv-nv21 列出 NV21 有一个 V/U 平面,而不是如上图所示的 NV12 的 U/V。见***.com/questions/12469730/…另一个参考:linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/… 转换有错误:r = y + (int)1.402f*v;b = y + (int)1.772f*u;应该是r = y + (int)(1.402f*v);b = y + (int)(1.772f*u);否则float常量先转换成int再乘以vu。见this example【参考方案2】:

NV21 基本上是 YUV420,但不是 Y、U 和 V 具有独立平面的平面格式,NV21 有一个用于亮度的平面和第二个用于色度的平面。格式看起来像

YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY YYYYYYYYYYYYYYYYYYYYYYYYYYYY . . . .

VUVUVUVUVUVUVUVUVUVUVUVUVUVUVU VUVUVUVUVUVUVUVUVUVUVUVUVUVUVU . . . . .

【讨论】:

很好的解释【参考方案3】:

由于这种预览格式,我也很头疼。

我能找到的最好的是这些:

http://www.fourcc.org/yuv.php#NV21

http://v4l2spec.bytesex.org/spec/r5470.htm

似乎 Y 分量是您获得的数组中的第一个 width*height 字节。

更多信息链接:

http://msdn.microsoft.com/en-us/library/ms867704.aspx#yuvformats_yuvsampling

http://msdn.microsoft.com/en-us/library/ff538197(v=vs.85).aspx

希望这会有所帮助。

【讨论】:

【参考方案4】:

数据为YUV420格式。 如果您只对单色通道感兴趣,即“黑白”,那么这是您已经拥有的数据缓冲区的第一个 width x height 字节。Y 通道是第一个图像平面。就是灰度/强度/亮度等通道。

【讨论】:

【参考方案5】:

这里的代码只是提取灰度图像数据:

private int[] decodeGreyscale(byte[] nv21, int width, int height) 
    int pixelCount = width * height;
    int[] out = new int[pixelCount];
    for (int i = 0; i < pixelCount; ++i) 
        int luminance = nv21[i] & 0xFF;
        out[i] = Color.argb(0xFF, luminance, luminance, luminance);
    
    return out;

【讨论】:

Sam 如何从 nv21 中删除红色和蓝色。我想让我的图像变成绿色(夜视效果)。 @Nepster,我不确定我的头顶。您可以通过在上面的代码中进行此更改来使用亮度作为绿色值:out[i] = Color.argb(0xFF, 0, luminance, 0);。或者,也许您可​​以更改全彩色实现的代码以丢弃蓝色和红色信息。我认为您应该为此提出一个新的 *** 问题。 它适用于山姆。非常感谢。但是当我打开前置摄像头时。它垂直旋转图像。(上下)【参考方案6】:

当您只需要一个灰度相机预览时,您可以使用一个非常简单的 渲染脚本:

# pragma version(1)
# pragma rs java_package_name(com.example.name)
# pragma rs_fp_relaxed

rs_allocation gIn;   // Allocation filled with camera preview data (byte[])
int previewwidth;    // camera preview width (int)

// the parallel executed kernel
void root(uchar4 *v_out, uint32_t x,uint32_t y)
   uchar c = rsGetElementAt_uchar(gIn,x+y*previewwidth); 
   *v_out = (uchar4)c,c,c,255;

注意:这 比 ScriptIntrinsicYuvToRGB 快(以及下面的 ScriptIntrinsicColorMatrix 来做 RGBA-> 灰色),但是 它使用 API 11+ 运行(其中 Intrinsics 需要 Api 17+)。

【讨论】:

以上是关于从android相机的NV21格式中提取黑白图像的主要内容,如果未能解决你的问题,请参考以下文章

NV21 格式和奇数图像尺寸

我如何从 android 相机 onPreviewFrame 中知道 YUV 格式?

Android ImageReader 获取 NV21 格式?

如何使用Android中的Camera.Parameter在黑白模式下打开和捕捉相机图片

如何区分 imageReader 相机 API 2 中的 NV21 和 YV12 编码?

详解 YUV 格式(I420/YUV420/NV12/NV12/YUV422)