How to convert 8 bits Grayscale image to NV12 (limited range) color space using IPP

2.4k Views Asked by At

Video encoders like Intel® Media SDK do not accept 8 bits Grayscale image as input format.
8 bits Grayscale format applies one byte per pixel in range [0, 255].

8 bits YUV format in the context of the question applies YCbCr (BT.601 or BT.709).
Although there is a full range YUV standard, the commonly used format is "limited range" YUV, where range of Y is [16, 235] and range of U,V is [16, 240].

NV12 format is the common input format in this case.
NV12 format is YUV 4:2:0 format ordered in memory with a Y plane first, followed by packed chroma samples in interleaved UV plane:
YYYYYY
YYYYYY
UVUVUV

enter image description here

The Grayscale image will be referred as "I plane":
IIIIII
IIIIII

enter image description here

Setting the UV plane is simple: Set all U,V elements to 128 value.

But what about the Y plane?

In case of full range YUV, we can simply put "I plane" as Y plane (i.e Y = I).

In case of "limited" YUV format, a transformation is required:
Setting R=G=B in the conversion formula results: Y = round(I*0.859 + 16).

What is the efficient way to do the above conversion using IPP?

1

There are 1 best solutions below

0
On

I am adding an answer to my own question.
I hope to see a better answer...

I found a solution using two IPP functions:

I selected functions that uses fixed point math, for better performance.

  • Fixed point implementation of 0.859 scaling is performed by expanding, scaling and shifting. Example: b = (a*scale + (1<<7)) >> 8; [When scale = (0.859)*2^8].
    val parameter to ippsMulC_8u_Sfs set to round(0.859*2^8) = 220.
    scaleFactor parameter to ippsMulC_8u_Sfs set to 8 (divide the scaled result by 2^8).

Code sample:

void GrayscaleToNV12(const unsigned char I[],
                     int image_width,
                     int image_height,
                     unsigned char J[])
{
    IppStatus ipp_status;
    const int image_size = image_width*image_height;

    unsigned char *UV = &J[image_size]; //In NV12 format, UV plane starts below Y.

    const Ipp8u expanded_scaling = (Ipp8u)(0.859 * 256.0 + 0.5);

    //J[x] = (expanded_scaling * I[x] + 128u) >> 8u;
    ipp_status = ippsMulC_8u_Sfs(I,                 //const Ipp8u* pSrc,
                                 expanded_scaling,  //Ipp8u val,
                                 J,                 //Ipp8u* pDst,
                                 image_size,        //int len,
                                 8);                //int scaleFactor);

    //Check ipp_status, and handle errors...

    //J[x] += 16;
    //ippsAddC_8u_ISfs is deprecated, I used it to keep the code simple.
    ipp_status = ippsAddC_8u_ISfs(16,           //Ipp8u val, 
                                  J,            //Ipp8u* pSrcDst, 
                                  image_size,   //int len, 
                                  0);           //int scaleFactor);

    //Check ipp_status, and handle errors...

    //2. Fill all UV plane with 128 value - "gray color".
    memset(UV, 128, image_width*image_height/2);
}

Out of topic note:
There is a way to mark a video stream as "full range" (where Y range is [0, 255] instead of [16, 235], and U,V range is also [0, 255]).
Using the "full range" standard allows placing I in place of Y (i.e Y = I).

Marking the stream as "full range" using Intel Media SDK, is possible (but not well documented).
Marking H.264 stream as "full range" requires to add pointer to mfxExtBuffer **ExtParam list (in structure mfxVideoParam):
A pointer to structure of type mfxExtVideoSignalInfo should be added with the following values:

typedef struct {
    mfxExtBuffer Header; //MFX_EXTBUFF_VIDEO_SIGNAL_INFO and sizeof(mfxExtVideoSignalInfo)
    mfxU16 VideoFormat; //Most likely 5 ("Unspecified video format")
    mfxU16 VideoFullRange; //1 (video_full_range_flag is equal to 1)
    mfxU16 ColourDescriptionPresent; //0 (description_present_flag equal to 0)
    mfxU16 ColourPrimaries; //0 (no affect when ColourDescriptionPresent = 0)
    mfxU16 TransferCharacteristics; //0 (no affect when ColourDescriptionPresent = 0)
    mfxU16 MatrixCoefficients; //0 (no affect when ColourDescriptionPresent = 0)
} mfxExtVideoSignalInfo; 

VideoFullRange = 1 is the only relevant parameter of setting "full range" video, but we must fill the entire structure.