Image Reality-Image Format Conversion

Posted by Lee-Bartlett on Mon, 05 Aug 2019 12:34:23 +0200

When using some image processing SDK, we often encounter some limitations of image format. For example, we get RGB data from camera stream, but when using some SDK, we find that SDK can not directly support this kind of data. We may need further conversion to invoke SDK. Therefore, in order to facilitate the majority of developers, this paper provides some common image conversion methods.

#### This paper first introduces some functions that may be used in downconversion, including the interchange functions of RGB and YUV, some formulas that are more popular on the Internet, and alignment functions that are often used repeatedly.

// RGB, YUV Interchange
int rgbToY(int r, int g, int b) {
    return (((66 * r + 129 * g + 25 * b + 128) >> 8) + 16);
}

int rgbToU(int r, int g, int b) {

    return (((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128);
}

int rgbToV(int r, int g, int b) {
    return (((112 * r - 94 * g - 18 * b + 128) >> 8) + 128);
}

int yuvToR(int y, int u, int v) {
    return (int) ((y & 0xFF) + 1.4075 * ((v & 0xFF) - 128));
}

int yuvToG(int y, int u, int v) {
    return (int) ((y & 0xFF) - 0.3455 * ((u & 0xFF) - 128) - 0.7169 * ((v & 0xFF) - 128));
}

int yuvToB(int y, int u, int v) {
    return (int) ((y & 0xFF) + 1.779 * ((u & 0xFF) - 128));
}
// Reserve the low octave of int
int alignIntToByte(int c) {
    return c & 0xFF;
}

1. Converting RGBA32 to BGR24

For example, for 4x2 images, the RGBA32 format is as follows:

R1 G1 B1 A1   R2 G2 B2 A2   R3 G3 B3 A3   R4 G4 B4 A4

R5 G5 B5 A5   R6 G6 B6 A6   R7 G7 B7 A7  R8 G8 B8 A8

If it needs to be translated into BGR24, the content will become:

B1 G1 R1   B2 G2 R2   B3 G3 R3   B4 G4 R4

B5 G5 R5   B6 G6 R6   B7 G7 R7   B8 G8 R8

The content of BGR24 is a group of three bytes, and the content of RGBA32 is a group of four bytes. Therefore, for the first group of RGBA32(R1 G1 B1 A1) and the first group of BGR24(B1 G1 R1), the corresponding relationship is as follows:

bgr24[0] = Rgba32[2];
bgr24[1] = Rgba32[1];
bgr24[2] = Rgba32[0];

The corresponding conversion code:

void rgba32ToBgr24(char *rgba32, char *bgr24, int width, int height) {
    int groupNum = width * height;
    int bgr24Index = 0;
    int rgba32Index = 0;
    for (int i = 0; i < groupNum; i++) {
        *(bgr24 + bgr24Index) = *(rgba32 + rgba32Index + 2);
        *(bgr24 + bgr24Index + 1) = *(rgba32 + rgba32Index + 1);
        *(bgr24 + bgr24Index + 2) = *(rgba32 + rgba32Index);
        bgr24Index += 3;
        rgba32Index += 4;
    }
}

2.BGR24 to NV21

Each group of BGR data in BGR24 corresponds to a pixel, while NV21 is a YUV data. Each Y of YUV data will form a pixel with corresponding U and V. The common relationship of NV21 is that every four Y (e.g. four Y with subscripts of 0,1,width,width+1) will share a set of U and V (corresponding subscripts: U:width x height+1,V:width x h h) 8). So for Y, we need to calculate the corresponding Y value of each group of BGR; but for NV21 U and V, the size of U and V is only half of Y, so we can calculate a group of U and V when both width and height subscripts are even.

void bgr24ToNv21(char *bgr24, char *nv21, int width, int height) {
    int yIndex = 0;
    int uvIndex = width * height;
    int bgrIndex = 0;
    int nv21Length = width * height * 3 / 2;
    for (int j = 0; j < height; ++j) {
        for (int i = 0; i < width; ++i) {
            int b = bgr24[bgrIndex++];
            int g = bgr24[bgrIndex++];
            int r = bgr24[bgrIndex++];
            b = alignIntToByte(b);
            g = alignIntToByte(g);
            r = alignIntToByte(r);
            int y = rgbToY(r, g, b);
            nv21[yIndex++] = static_cast<char>(alignIntToByte(y));
            if ((j & 1) == 0 && ((bgrIndex >> 2) & 1) == 0 && uvIndex < nv21Length - 2) {
                int u = rgbToU(r, g, b);
                int v = rgbToV(r, g, b);
                nv21[uvIndex++] = static_cast<char>(alignIntToByte(v));
                nv21[uvIndex++] = static_cast<char>(alignIntToByte(u));
            }
        }
    }
}

3. Conversion of NV21 to BGR24

This is the inverse process described above. When BGR24 is converted to NV21, the width subscript and height subscript are even numbers before U and V are calculated. If NV21 is converted to BGR24, then we need to reuse each set of U and V once.

void nv21ToBgr24(char *nv21, char *bgr24, int width, int height) {
    int bgrLineSize = width * 3;
    //bgr data subscripts for even rows
    int evenLineBgrIndex = 0;
    //bgr data subscripts for odd rows
    int oddLineBgrIndex = bgrLineSize;
    //The leftmost subscript of the current row y
    int yLineStart = 0;
    //Subscript of uv data
    int uvIndex = width * height;
    //Because of the shared relationship of NV21, conversion is done every 2 rows.
    for (int i = 0; i < height; i += 2) {
        for (int widthOffset = 0; widthOffset < width; widthOffset++) {
            char v = nv21[uvIndex];
            char u = nv21[uvIndex + 1];
            char yEven = nv21[yLineStart + widthOffset];
            char yOdd = nv21[yLineStart + width + widthOffset];
            int r, g, b;
            //Even row YUV to RGB
            r = alignIntToByte(yuvToR(yEven, u, v));
            g = alignIntToByte(yuvToG(yEven, u, v));
            b = alignIntToByte(yuvToB(yEven, u, v));

            bgr24[evenLineBgrIndex++] = (char) b;
            bgr24[evenLineBgrIndex++] = (char) g;
            bgr24[evenLineBgrIndex++] = (char) r;

            //Odd line YUV to RGB
            r = alignIntToByte(yuvToR(yOdd, u, v));
            g = alignIntToByte(yuvToG(yOdd, u, v));
            b = alignIntToByte(yuvToB(yOdd, u, v));

            bgr24[oddLineBgrIndex++] = (char) b;
            bgr24[oddLineBgrIndex++] = (char) g;
            bgr24[oddLineBgrIndex++] = (char) r;
            //Increase the uv subscript by 1 for every two y
            if ((widthOffset & 1) == 1) {
                uvIndex += 2;
            }
        }
        //Since width* has been increased three times in the inner loop, only one additional line is needed in the outer loop
        evenLineBgrIndex += bgrLineSize;
        oddLineBgrIndex += bgrLineSize;
        //y add 2 lines
        yLineStart += (width << 1);
    }
}

4. Exchange of NV12 and NV21

NV21 and NV12 are just different data locations of U and V, only need to replace the location of U and V. Therefore, the code that converts NV21 to NV12 is also suitable for converting NV12 to NV21. Refer to the following code:

void nv21ToNv12(char *nv21, char *nv12, int width, int height) {
    int ySize = width * height;
    int totalSize = width * height * 3 / 2;
//Replicate Y
    memcpy(nv12, nv21, ySize);
//UV Interchange
    for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
        *(nv12 + uvIndex) = *(nv21 + uvIndex + 1);
        *(nv12 + uvIndex + 1) = *(nv21 + uvIndex);
    }
}

5. NV21 to YV12

The process of converting NV21 to YV12 is mainly to change the cross-sorting of its UV data into continuous sorting. Refer to the following code:

void nv21ToYv12(char *nv21, char *yv12, int width, int height) {
    int ySize = width * height;
    int totalSize = width * height * 3 / 2;
    int i420UIndex = ySize;
    int i420VIndex = ySize * 5 / 4;
    //Copy y Y
    memcpy(yv12, nv21, ySize);
    //Duplicate uv
    for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
        *(yv12 + i420UIndex++) = *(nv21 + uvIndex);
        *(yv12 + i420VIndex++) = *(nv21 + uvIndex + 1);
    }
}

6. YUYV to NV12

In YUYV format, two Y share a set of U and V, while NV12 is four Y share a set of U and V. Therefore, this is a process of YUV422 to YUV420. Half of U and V need to be discarded. It should be noted that each group of YUYV is two pixels. Refer to the following code:

void yuyvToNv12(char *yuyv, char *nv12, int width, int height) {
    int ySize = width * height;
    int lineDataSize = width * 2;
    char *nv12UV = nv12 + ySize;
    for (int i = 0; i < height; i++, yuyv += lineDataSize) {
        if ((i & 1) == 0) {
            for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
                //Copy Y
                *nv12++ = *(yuyv + lineOffset);
                *nv12++ = *(yuyv + lineOffset + 2);
                //Copy UV
                *nv12UV++ = *(yuyv + lineOffset + 1);
                *nv12UV++ = *(yuyv + lineOffset + 3);
            }
        } else {
            for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
                //Copy Y
                *nv12++ = *(yuyv + lineOffset);
                *nv12++ = *(yuyv + lineOffset + 2);
            }
        }
    }
}

7. Interchange between I420 and YV12

I420 and YV12 are just different data locations between U and V, so the code for converting I420 to YV12 is also applicable for converting YV12 to I420. Refer to the following code:

void i420ToYv12(char *i420, char *yv12, int width, int height) {
    int ySize = width * height;
    int uSize = ySize / 4;
    int vSize = uSize;
    //Replicate Y
    memcpy(yv12, i420, ySize);
    //UV Interchange
    memcpy(yv12 + ySize + uSize, i420 + ySize, uSize);
    memcpy(yv12 + ySize, i420 + ySize + vSize, vSize);
}

8. I420 to YUYV

Compared with YUYV and I420, the U and V of I420 are only half of YUYV. This is a process of YUV420 to YUV422. The defective data can only be compensated by multiplexing U and V.

void i420ToYuyv(char *i420, char *yuyv, int width, int height) {
    int yuyvLineSize = width * 2;
    int i420YIndex = 0;
    int i420UIndex = width * height;
    int i420VIndex = width * height * 5 / 4;
    int yuyvLineStart = 0;
    for (int i = 0; i < height; i += 2) {
        for (int lineOffset = 0; lineOffset < yuyvLineSize; lineOffset += 4) {
            char u = *(i420 + i420UIndex++);
            char v = *(i420 + i420VIndex++);

            //Even row data assignment
            int yuyvOffset = yuyvLineStart + lineOffset;
            *(yuyv + yuyvOffset) = *(i420 + i420YIndex);
            *(yuyv + yuyvOffset + 1) = u;
            *(yuyv + yuyvOffset + 2) = *(i420 + i420YIndex + 1);
            *(yuyv + yuyvOffset + 3) = v;

            //Odd row data assignment
            int yuyvNextLineOffset = yuyvLineStart + yuyvLineSize + lineOffset;
            *(yuyv + yuyvNextLineOffset) = *(i420 + i420YIndex + width);
            *(yuyv + yuyvNextLineOffset + 1) = u;
            *(yuyv + yuyvNextLineOffset + 2) = *(i420 + i420YIndex + width + 1);
            *(yuyv + yuyvNextLineOffset + 3) = v;

            i420YIndex += 2;
        }
        i420YIndex += width;
        yuyvLineStart += (width << 2);
    }
}

9. I420 to NV12

Converting I420 to NV12 is to change the continuous U and V to cross-storage. The sample code is as follows

void i420ToNv12(char *i420, char *nv12, int width, int height) {
    int ySize = width * height;
    int totalSize = width * height * 3 / 2;
    int i420UIndex = ySize;
    int i420VIndex = ySize * 5 / 4;
    //Copy y Y
    memcpy(nv12, i420, ySize);
    //Duplicate uv
    for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
        *(nv12 + uvIndex) = *(i420 + i420UIndex++);
        *(nv12 + uvIndex + 1) = *(i420 + i420VIndex++);
    }
}

10. I420 to NV21

Converting I420 to NV12 is to change the successive U and V to cross storage and replace the positions of U and V. The sample code is as follows

void i420ToNv21(char *i420, char *nv21, int width, int height) {
    int ySize = width * height;
    int totalSize = width * height * 3 / 2;
    int i420UIndex = ySize;
    int i420VIndex = ySize * 5 / 4;
    //Copy y Y
    memcpy(nv21, i420, ySize);
    //Duplicate uv
    for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
        *(nv21 + uvIndex + 1) = *(i420 + i420UIndex++);
        *(nv21 + uvIndex) = *(i420 + i420VIndex++);
    }
}

Topics: SDK