Extract black and white image from android camera's NV21 format

Ethan picture Ethan · Mar 11, 2011 · Viewed 30.5k times · Source

I have done some google-ing around and couldn't find enough information about this format. It is the default format for camera preview. Can anyone suggest good sources of information about it and how to extract data from a photo/preview image with that format? To be more specific, I need the black and white image extracted.

EDIT: Seems like that format is also called YCbCr 420 Semi Planar

Answer

Derzu picture Derzu · Oct 3, 2012

I developed the following code to convert the NV21 to RGB, and it is working.

/**
 * 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;
}

This image helps to understand. YUV420 NV21

If you wanna just grayscale image is easer. You can discard all the U and V info, and take just the Y info. The code would can be like this:

/**
 * 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;
    }
}

To create your Bitmap just:

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

Where pixels is your int [] array.