naivecv的设计与实现(3): NV12到RGB的转换

准备 NV12 图像

在 github 搜索关键字 “YUVViewer", 找到样例文件:

https://github.com/LiuYinChina/YUVViewer/blob/master/Output/720X576-NV12.yuv

它是二进制文件,没有文件头信息,只有像素内容, 排布方式: 先 Y 平面,再 U V 交错的平面:

Y Y Y Y  .... Y Y
Y Y Y Y  .... Y Y
...
U V U V  ...  U V

读取 NV12 图像

以二进制文件形式读取 .yuv 文件。

基于之前实现的 pgm 图像读写功能,将 Y 平面内容写入 .pgm 文件:

void test_nv12_to_rgb()
{
    const char* image_path = "/Users/zz/work/YUVViewer/Output/720X576-NV12.yuv";
    int width = 720;
    int height = 576;
    FILE* fin = fopen(image_path, "rb");
    const int buf_size = width * height * 3 / 2;
    uint8_t* buffer = (uint8_t*) malloc(buf_size);
    fread(buffer, buf_size, 1, fin);

    savePGM("test.pgm", width, height, buffer);
    free(buffer);
    fclose(fin);
}

在这里插入图片描述

3. 遍历 UV 平面,并转换得到 RGB

对于 UV 平面, 可以看做2通道,宽度、高度都是Y平面的一半:

Y Y Y Y Y Y Y Y
Y Y Y Y Y Y Y Y
U V U V U V U V

遍历 UV 平面,每个 UV 元素, 对应到4个Y: 2行Y,2列Y:

Y Y
Y Y
U V

每一组 YUV 可以算出一个 RGB, 一共有4组:

y00:

Y _
_ _
U V

y01

_ Y
_ _
U V

y10:

_ _
Y _
U V

y11:

_ _
_ Y
U V

对应的 C++ 代码如下,实现整张图的 NV12 到 RGB 的转换:


void test_nv12_to_rgb()
{
     const char* image_path = "/Users/zz/work/YUVViewer/Output/720X576-NV12.yuv";
     int width = 720;
     int height = 576;

//    const char* image_path = "/Users/zz/data/0_45_1280x720.NV12";
//    int width = 1280;
//    int height = 720;

    FILE* fin = fopen(image_path, "rb");
    const int buf_size = width * height * 3 / 2;
    uint8_t* buffer = (uint8_t*) malloc(buf_size);
    fread(buffer, buf_size, 1, fin);

    NCV_Image rgbImg;
    rgbImg.format = NCV_PIXFMT_RGB;
    rgbImg.height = height;
    rgbImg.width = width;
    rgbImg.pitch[0] = width * 3;
    rgbImg.plane[0] = (uint8_t*) malloc(height * width * 3);

    savePGM("test.pgm", width, height, buffer);

    YuvToRgb_Converter_v2 converter;

    uint8_t* y_plane = buffer;
    uint8_t* uv_plane = buffer + height * width;
    uint8_t* rgb = rgbImg.plane[0];
    for (int i = 0; i < height/2; i++)
    {
        for (int j = 0; j < width/2; j++)
        {
            uint8_t v = uv_plane[i * width + 2 * j];
            uint8_t u = uv_plane[i * width + 2 * j + 1];

            int si = i * 2;
            int sj = j * 2;

            uint8_t y, r, g, b;

            /// y00
            y = y_plane[si * width + sj];

            // B = 1.164(Y - 16)                   + 2.018(U - 128)
            // G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
            // R = 1.164(Y - 16) + 1.596(V - 128)
            
            r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
            g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
            b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );

            // R = Y + 1.403V'
            // G = Y - 0.344U' - 0.714V'
            // B = Y + 1.770U'

            // uint8_t r = NCV_CLAMP( (1.164 * (y - 16)) + (2.018 * (v - 128)), 0, 255);
            // uint8_t g = NCV_CLAMP( (1.164 * (y - 16)) - (0.813 * (u - 128)) - (0.391 * (v - 128)), 0, 255);
            // uint8_t b = NCV_CLAMP( (1.164 * (y - 16)) + (1.596 * (u - 128)), 0, 255);

            // uint8_t r = NCV_CLAMP( y + (1.370705 * (v-128)), 0, 255);
            // uint8_t g = NCV_CLAMP( y - (0.698001 * (v-128)) - (0.337633 * (u-128)), 0, 255);
            // uint8_t b = NCV_CLAMP( y + (1.732446 * (u-128)), 0, 255);

            // uint8_t r = y + 1.400*(v-128);
            // uint8_t g = y - 0.343*(u-128) - 0.711*(v-128);
            // uint8_t b = y + 1.765*(u-128);

            // uint8_t r = converter.get_r(y, u, v);
            // uint8_t g = converter.get_g(y, u, v);
            // uint8_t b = converter.get_b(y, u, v);

            rgb[si * width * 3 + sj * 3 + 0] = r;
            rgb[si * width * 3 + sj * 3 + 1] = g;
            rgb[si * width * 3 + sj * 3 + 2] = b;

            

            /// y01
            y = y_plane[(si + 1) * width + sj];

            r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
            g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
            b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );

            rgb[(si + 1) * width * 3 + sj * 3 + 0] = r;
            rgb[(si + 1) * width * 3 + sj * 3 + 1] = g;
            rgb[(si + 1) * width * 3 + sj * 3 + 2] = b;


            /// y10

            y = y_plane[si * width + sj + 1];

            r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
            g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
            b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );

            rgb[si * width * 3 + (sj + 1) * 3 + 0] = r;
            rgb[si * width * 3 + (sj + 1) * 3 + 1] = g;
            rgb[si * width * 3 + (sj + 1) * 3 + 2] = b;


            /// y11

            y = y_plane[(si + 1) * width + sj + 1];

            r = NCV_CLAMP( 1.164 * (y - 16) + 2.018 * (u - 128), 0, 255 );
            g = NCV_CLAMP( 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128), 0, 255);
            b = NCV_CLAMP( 1.164 * (y - 16) + 1.596 * (v - 128), 0, 255 );

            rgb[(si + 1) * width * 3 + (sj + 1) * 3 + 0] = r;
            rgb[(si + 1) * width * 3 + (sj + 1) * 3 + 1] = g;
            rgb[(si + 1) * width * 3 + (sj + 1) * 3 + 2] = b;
        }
    }

    savePPM("test.ppm", width, height, rgb);

    free(buffer);
    free(rgb);

    fclose(fin);
}

在这里插入图片描述

4. 整理和总结

通过搜索github,得到了用于测试的 NV12 图像。

基于对 NV12 图像格式的理解,用C语言读取了 NV12 图像内容。

遍历 NV12 的 UV 平面,并得到对应的 Y 平面的像素点, 从而得到了一组 RGB 像素值; 由于每个 UV 是被 2x2 的 4个Y共享的,因此, 先获取剩余的3个 Y, 然后算出三组 RGB 像素值。 这样一来就写入了 dst rgb 图像的 2x2 像素区域,进而完成了整个 NV12 到 RGB 的图像格式转换。

基于先前的 pgm 和 ppm 图像格式保存函数, 得到了图像文件, 肉眼观察验证了正确性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值