in development

making YUV conversion a little faster

As part of my next Android project, I’ve been working with the hardware camera. All implementations of the Android stack are required to support the NV21 format, so I’m going with that. For some reason that I’m sure makes perfect sense, there isn’t an Android API for converting between YUV and RGB formats, so you have to roll your own. This routine I dug out of Stack Overflow works fine.

public static void YUV_NV21_TO_RGB(int[] argb, byte[] yuv, int width, int height) {
    final int frameSize = width * height;

    final int ii = 0;
    final int ij = 0;
    final int di = +1;
    final int dj = +1;

    int a = 0;
    for (int i = 0, ci = ii; i > 1) * width + (cj & ~1) + 0]));
            int u = (0xff & ((int) yuv[frameSize + (ci >> 1) * width + (cj & ~1) + 1]));
            y = y  255 ? 255 : r);
            g = g  255 ? 255 : g);
            b = b  255 ? 255 : b);

            argb[a++] = 0xff000000 | (r 

My only issue is that I'm using it in a preview callback--converting and using the image data in real time--and it's not fully optimized for that. The frame rate drops from 15fps to 8-10fps.
The major culprit appears to be the floating point conversions between the YUV and RGB planes.

int r = (int) (1.164f * (y - 16) + 1.596f * (v - 128));
int g = (int) (1.164f * (y - 16) - 0.813f * (v - 128) - 0.391f * (u - 128));
int b = (int) (1.164f * (y - 16) + 2.018f * (u - 128));

Fortunately, there's a simple fix. Scaled integers to the rescue!

int a0 = 1192 * (y - 16);
int a1 = 1634 * (v - 128);
int a2 = 832 * (v - 128);
int a3 = 400 * (u - 128);
int a4 = 2066 * (u - 128);
	            
int r = (a0 + a1) >> 10;
int g = (a0 - a2 - a3) >> 10;
int b = (a0 + a4) >> 10;

I've multiplied each floating point constant by 1024 and truncated it. Now I can add/subtract the scaled integers, and apply a bit shift right to "divide" each result by 1024. With this change, the frame rate is back up to 15fps.

  1. Hi Chris,
    Search all over the net, finally find the YUV to RGB solution in your article. Thanks.
    But after the RGB byte array is generated, how would I use the RGB byte array to generate an IplImage? Could you please give some advice?

    Thanks.

    • Hi David,

      Unfortunately, I’m not familiar with OpenCV, so I can’t give you much help with an IplImage. (I wrote the original code for an Android app.) The RGB format used here is ARGB8888–with 8 bits apiece to store red, green, blue, and alpha channels–but I’m not sure what that translates into for OpenCV. Sorry!

  2. Hi Chris,
    Thanks for your reply.
    I’ve tried out a solution. In open, the RGB is encoded as BGR, so just change the below line of code,

    argb[a++] = 0xff000000 | (b << 16) | (g << 8) | r;

    Then

    int[] bgrData = new int[previewWidth * previewHeight];
    ImageUtils.YUV_NV21_TO_BGR(bgrData, data,
    previewWidth, previewHeight);
    IplImage bgrImage = IplImage.create(previewWidth, previewHeight,
    opencv_core.IPL_DEPTH_8U, 4);
    bgrImage.getIntBuffer().put(bgrData);

    Maybe this is helpful to others.

    Cheers.

      • Hi Chris,

        What could be the best way to display a better resolution when the device can output a resolution of 1024*1080. I have the following code that works very well in the android but the resolution is 640 * 480

        int width=0;
        int height=0;

        width = IMG_WIDTH;
        height = IMG_HEIGHT;

        int frameSize =width*height*2;

        int i;

        if((!rgb || !ybuf)){
        return;
        }
        int *lrgb = NULL;
        int *lybuf = NULL;

        lrgb = &rgb[0];
        lybuf = &ybuf[0];

        if(yuv_tbl_ready==0){
        for(i=0 ; i<256 ; i++){
        y1192_tbl[i] = 1192*(i-16);
        if(y1192_tbl[i]<0){
        y1192_tbl[i]=0;
        }

        v1634_tbl[i] = 1634*(i-128);
        v833_tbl[i] = 833*(i-128);
        u400_tbl[i] = 400*(i-128);
        u2066_tbl[i] = 2066*(i-128);
        }
        yuv_tbl_ready=1;
        }

        for(i=0 ; i>10;
        int g1 = (y1192_1 – v833_tbl[v] – u400_tbl[u])>>10;
        int b1 = (y1192_1 + u2066_tbl[u])>>10;

        int y1192_2=y1192_tbl[y2];
        int r2 = (y1192_2 + v1634_tbl[v])>>10;
        int g2 = (y1192_2 – v833_tbl[v] – u400_tbl[u])>>10;
        int b2 = (y1192_2 + u2066_tbl[u])>>10;

        r1 = r1>255 ? 255 : r1255 ? 255 : g1255 ? 255 : b1255 ? 255 : r2255 ? 255 : g2255 ? 255 : b2<0 ? 0 : b2;

        *lrgb++ = 0xff000000 | b1<<16 | g1<<8 | r1;
        *lrgb++ = 0xff000000 | b2<<16 | g2<<8 | r2;

        if(lybuf!=NULL){
        *lybuf++ = y1;
        *lybuf++ = y2;
        }
        }

        • Hi Eric,

          I’m not certain what the code you’ve posted is intended to do. You seem to be building up a table of constants, then there’s this line “for(i=0 ; i>10;” which seems to be cut off…? The code I’ve posted should be resolution independent. Sorry if I’m not getting the point!

          Chris

Comments are closed.