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 图像格式保存函数, 得到了图像文件, 肉眼观察验证了正确性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/959695.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我谈区域偏心率

偏心率的数学定义 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》P312 区域的拟合椭圆看这里。 Rafael Gonzalez的二阶中心矩的表达不说人话。 我认为半长轴和半短轴不等于特征值&#xff0c;而是特征值的根号。…

ansible自动化运维实战--软件包管理模块、服务模块、文件模块和收集模块setup(4)

文章目录 一、软件包管理模块1.1、功能1.2、常用参数1.3、示例 二、服务模块2.1、功能2.2、服务模块常用参数2.3、示例 三、文件与目录模块3.1、file功能3.2、常用参数3.3、示例 四、收集模块-setup4.1、setup功能4.2、示例 一、软件包管理模块 1.1、功能 Ansible 提供了多种…

Elasticsearch 性能测试工具 Loadgen 之 004——高级用法示例

在性能测试中&#xff0c;能够灵活地模拟不同的应用场景是至关重要的。 Loadgen 提供了多种高级用法&#xff0c;帮助用户更好地评估系统在不同负载下的表现。 本文将介绍如何使用 Loadgen 模拟批量摄取、限制客户端负载以及限制总请求数。 一、模拟批量摄取 在实际应用中&…

将点云转换为 3D 网格:Python 指南

3D 数据的世界往往是一个碎片化的景观。 存在点云&#xff0c;其细节丰富&#xff0c;但缺乏表面信息。 有3D 网格&#xff0c;它明确地定义表面&#xff0c;但创建起来通常很复杂。 将点云转换为网格弥补了这一差距并开启了许多可能性&#xff0c;从真实模拟到 3D 数字环境…

智能电动汽车系列 --- 智能汽车向车载软件转型

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

不解释快上车

聊一聊 最近有小伙伴问我有小红书图片和短视频下载的软件吗&#xff0c;我心想&#xff0c;下载那上面的图片和视频做什么&#xff1f;也许是自己没有这方面的需求&#xff0c;不了解。 不过话又说回来&#xff0c;有些很多下载器可能作者没有持续的维护&#xff0c;所以可能…

FPGA实现任意角度视频旋转(完结)视频任意角度旋转实现

本文主要介绍如何基于FPGA实现视频的任意角度旋转&#xff0c;关于视频180度实时旋转、90/270度视频无裁剪旋转&#xff0c;请见本专栏前面的文章&#xff0c;旋转效果示意图如下&#xff1a; 为了实时对比旋转效果&#xff0c;采用分屏显示进行处理&#xff0c;左边代表旋转…

[JavaScript] 面向对象编程

JavaScript 是一种多范式语言&#xff0c;既支持函数式编程&#xff0c;也支持面向对象编程。在 ES6 引入 class 语法后&#xff0c;面向对象编程在 JavaScript 中变得更加易于理解和使用。以下将详细讲解 JavaScript 中的类&#xff08;class&#xff09;、构造函数&#xff0…

20250121在Ubuntu20.04.6下使用Linux_Upgrade_Tool工具给荣品的PRO-RK3566开发板刷机

sudo upgrade_tool uf update.img 20250121在Ubuntu20.04.6下使用Linux_Upgrade_Tool工具给荣品的PRO-RK3566开发板刷机 2025/1/21 11:54 百度&#xff1a;ubuntu RK3566 刷机 firefly rk3566 ubuntu upgrade_tool烧写详解 https://wiki.t-firefly.com/Core-3566JD4/03-upgrad…

基础项目——扫雷(c++)

目录 前言一、环境配置二、基础框架三、关闭事件四、资源加载五、初始地图六、常量定义七、地图随机八、点击排雷九、格子类化十、 地图类化十一、 接口优化十二、 文件拆分十三、游戏重开 前言 各位小伙伴们&#xff0c;这期我们一起学习出贪吃蛇以外另一个基础的项目——扫雷…

【动态规划】落花人独立,微雨燕双飞 - 8. 01背包问题

本篇博客给大家带来的是01背包问题之动态规划解法技巧. &#x1f40e;文章专栏: 动态规划 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心要快乐顺便…

游戏steam_api64.dll文件缺失怎么办?无法找到指定的模块的解决方法

在使用Steam平台运行游戏时&#xff0c;有时会遇到“steam_api64.dll文件缺失&#xff0c;无法找到指定的模块”的错误提示。这个问题通常是由于该文件被误删、病毒感染、系统更新不兼容或游戏安装不完整等原因造成的。以下是一些有效的解决方法&#xff0c;帮助你解决steam_ap…

Linux学习笔记——网络管理命令

一、网络基础知识 TCP/IP四层模型 以太网地址&#xff08;MAC地址&#xff09;&#xff1a; 段16进制数据 IP地址&#xff1a; 子网掩码&#xff1a; 二、接口管命令 ip命令&#xff1a;字符终端&#xff0c;立即生效&#xff0c;重启配置会丢失 nmcli命令&#xff1a;字符…

在 Windows 系统上,将 Ubuntu 从 C 盘 迁移到 D 盘

在 Windows 系统上&#xff0c;如果你使用的是 WSL&#xff08;Windows Subsystem for Linux&#xff09;并安装了 Ubuntu&#xff0c;你可以将 Ubuntu 从 C 盘 迁移到 D 盘。迁移过程涉及导出当前的 Ubuntu 发行版&#xff0c;然后将其导入到 D 盘的目标目录。以下是详细的步骤…

simulink入门学习01

文章目录 1.基本学习方法2.图形环境--模块和参数3.激活菜单---添加到模型3.1输入选项3.2添加到模型3.3更改运算3.4验证要求 4.乘以特定值--Gain模块4.1引入gain模块4.2更改增益参数4.3接入系统4.4大胆尝试 1.基本学习方法 今天突然想要学习这个simulink的相关知识&#xff0c;…

Linux的基本指令(上)

1.ls指令 语法&#xff1a;ls [选项] [目录或文件] 功能&#xff1a;对于⽬录&#xff0c;该命令列出该⽬录下的所有⼦⽬录与⽂件。对于⽂件&#xff0c;将列出⽂件名以及其他信息。 常用选项&#xff1a; -a 列出⽬录下的所有⽂件&#xff0c;包括以 . 开头的隐含⽂件。 -d 将…

一文详解Filter类源码和应用

背景 在日常开发中&#xff0c;经常会有需要统一对请求做一些处理&#xff0c;常见的比如记录日志、权限安全控制、响应处理等。此时&#xff0c;ServletApi中的Filter类&#xff0c;就可以很方便的实现上述效果。 Filter类 是一个接口&#xff0c;属于 Java Servlet API 的一部…

【算法】数论基础——唯一分解定理(算术基本定理)python

目录 定义进入正题热身训练实战演练扩展衍生判断一个数是否为完全平方数举一反三总结 定义 唯一分解定理&#xff1a;也叫做算数基本定理: 任意一个大于1的整数N&#xff0c;都可以唯一分解为若干个质数的乘积 换句话说&#xff0c;任何大于1的整数n可以表示为&#xff1a; 例如…

互联网医院成品|互联网医院开发|互联网医院搭建

在数字化医疗蓬勃发展的当下&#xff0c;互联网医院系统已成为医疗服务体系中至关重要的组成部分。它打破了传统医疗服务在时间和空间上的限制&#xff0c;为患者提供了更加便捷、高效的医疗服务。而一套完善的互联网医院系统&#xff0c;有几个功能是不能缺少的。 在线问诊功能…

Go的内存逃逸

Go的内存逃逸 内存逃逸是 Go 语言中一个重要的概念&#xff0c;指的是本应分配在栈上的变量被分配到了堆上。栈上的变量在函数结束后会自动回收&#xff0c;而堆上的变量需要通过垃圾回收&#xff08;GC&#xff09;来管理&#xff0c;因此内存逃逸会增加 GC 的压力&#xff0…