【C++音视频开发】视频篇 | RGB与YUV

简介: 【C++音视频开发】视频篇 | RGB与YUV

前言


       本专栏将不间断更新有关C++音视频开发的内容,其中有初级章、中级章与高级章的内容,包括但不限于音视频基础、FFmpeg实战、QT、流媒体客户端、流媒体服务器、WebRTC实战、Android NDK等等。是博主花了将近5000元购买的课程中的知识点,其中也掺杂着一些我的个人理解,希望能帮助大家和我一起入门音视频开发。


视频基础知识


1.RGB


1.1RGB的排列


       我们前面已经讲过了RGB色彩表示,这里我们重点讲解RGB的排列。


       通常图像像素是按照RGB顺序进行排列的,但有些图像处理要转换成其他排列顺序,比如我们经常使用的OpenCv就经常转换为BGR的排列方式。


1dc618a0ed9580ce8bfa6facb208c08f.png


        为什么需要转换为BGR这样子的排列呢?这其实是算法上的问题:


        OpenCV在1999年由Intel建立,当时主流的摄像头制造商和软件供应商提供的摄像头采集的图像的通道排列顺序为BGR,另外对于图片,位图BMP是最简单的,也是Windows显示图片的基本格式,其文件扩展名为*.BMP。在Windows下,任何格式的图片文件(包括视频播放)都要转化为位图才能显示出来,各种格式的图片文件也都是在位图格式的基础上采用不同的压缩算法生成的,值得注意的是位图BMP的格式就是BGR。正是基于BGR在当时被广泛使用,于是早期OpenCV开发者就选择BGR颜色格式,这也就成为了一种规范一直用到现在。


5d4c6812c8535adbb050f4ddf2e1bce8.png46a9d80a6e05e4e3b19d57a0ee70bcdf.png




        上图是关于各种类型色彩通道的信息表示,根据以往的知识来分析十分的简单,在此处就不多费口舌了。


2.YUV


2.1 YUV概念


       ▲ 与我们熟知的RGB类似,YUV也是一种颜色编码方式,但是它是指:亮度参量(Y:Luminance或Luma)和色度参量(UV:Chrominace或Chroma)分开进行表示的像素编码格式。


       ▲ 将Y与UV分开的好处是什么呢?这样子做可以避免互相干扰,而且没有UV信息一样可以显示完整的图像,因而解决了彩色电视与黑白电视的兼容问题;还可以降低色彩的采样率而不会对图像质量有太多的影响,降低了视屏信号传输时对频宽(带宽)的要求。


总结:为什么使用YUV? 显示器从黑白显示器演变过来的,为了和以前格式兼容以及YUV存储的数据比RGB要少很多。

     

1dc618a0ed9580ce8bfa6facb208c08f.png

        ▲ YUV是一种比较笼统的说法,针对它的具体排列方式,可以分为很多种具体的格式:


               - 打包(packed)格式:将每个像素点Y、U、V分量交叉排列并以像素点为单元连续存放在同一数组中,通常几个相邻的像素点组成一个宏像素(macro-pixel)


5d4c6812c8535adbb050f4ddf2e1bce8.png


               - 平面(planar)格式:使用三个数组分开连续的存放Y、U、V三个分量,即Y、U、V分别存放在各自的数组中。


46a9d80a6e05e4e3b19d57a0ee70bcdf.png


2.2 YUV采样表示法


        ▲ YUV采用A:B:C表示法来描述Y,U,V采样频率比例,下图中黑点表示采样像素点Y分量,空心圆表示采样像素点的UV分量。主要分为YUV 4:4:4 、YUV 4:2:2、YUV 4:2:0,这几种常用的类型。

1dc618a0ed9580ce8bfa6facb208c08f.png

       ★ 4:4:4 表示色度频道没有下采样,即一个Y分量对应着一个U分量和一个V分量。


       ★ 4:2:2 表示2:1的水平下采样,没有垂直下采样,即每两个Y分量共用一个U分量和一个V分量。


       ★ 4:2:0 表示2:1的水平下采样,2:1的垂直下采样,即每四个Y分量公用一个U分量和一个V分量。(应用最广泛的格式,播放器必须支持)

       重要:4:2:0并不意味着只有Y、Cb两个分量,而没有Cr分量。它实际指的是每行扫描线来说,只有一种色度分量,它以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0…以此类推


2.3 YUV数据存储


       ▲ 下面每一个分量数据存储在一个char(或byte)中为例描述YUV的数据存储方式。


               (1)- 4:4:4 格式


               (2)- 4:2:2 格式


               (3)- 4:2:0 格式


       2.3.1——4:4:4格式


5d4c6812c8535adbb050f4ddf2e1bce8.png


       2.3.2 ——4:2:2格式

46a9d80a6e05e4e3b19d57a0ee70bcdf.png

          2.3.3——4:2:0格式(应用最广泛的格式,播放器必须支持)


66ba272a0bfc97be54a5fa679e3d5482.png88b9988b40447cb37c7e3c492d49867f.png




3、RGB与YUV的转换


       RGB与YUV关系:RGB用于屏幕图像的展示,YUV用于采集与编码。


3、1 YUV转RGB:


公式:


20191105100746502.gif


代码:


#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
cv::Mat RGB2YUV(cv::Mat src, bool accelerate = false){
  CV_Assert(src.channels() == 3);
  cv::Mat dst(src.size(), CV_32FC3);  //这里最好要用浮点型,避免丧失精度
  cv::Vec3b rgb;
  int r = src.rows;
  int c = src.cols;
  for (int i = 0; i < r; ++i){
  for (int j = 0; j < c; ++j){
    rgb = src.at<cv::Vec3b>(i, j);
    int B = rgb[0]; int G = rgb[1]; int R = rgb[2];
    if (accelerate == false){
    dst.at<Vec3f>(i, j)[0] = R*0.299 + G*0.587 + B*0.114; //Y
    dst.at<Vec3f>(i, j)[1] = -0.169*R - 0.331*G + 0.500*B + 128; //U
    dst.at<Vec3f>(i, j)[2] = 0.500*R - 0.419*G - 0.081*B + 128;  //V
    }
    else{
    dst.at<Vec3f>(i, j)[0] = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8; //Y
    dst.at<Vec3f>(i, j)[1] = (-((R << 5) + (R << 3) + (R << 1)+ R) - ((G << 6) + (G << 4) + (G << 2)+G) + (B << 7) + 32768) >> 8; //U
    dst.at<Vec3f>(i, j)[2] = ((R << 7) - ((G << 6) + (G << 5) + (G << 3) + (G << 3) + G) - ((B << 4) + (B << 2) + B) + 32768 )>> 8; //V
    }
  }
  }
  dst.convertTo(dst, CV_8UC3);
  return dst;
}
cv::Mat YUV2RGB(cv::Mat src, bool accelerate = false){
  CV_Assert(src.channels() == 3);
  cv::Mat dst(src.size(), CV_32FC3); //这里一定要用浮点型,避免丧失精度
  cv::Vec3b yuv;
  int r = src.rows;
  int c = src.cols;
  for (int i = 0; i < r; ++i){
  for (int j = 0; j < c; ++j){
    yuv = src.at<cv::Vec3b>(i, j);
    int Y = yuv[0]; int U = yuv[1]; int V = yuv[2];
    U = U - 128;
    V = V - 128;
    if (accelerate == false){
    dst.at<Vec3f>(i, j)[0] = Y + 1.770*U;//B
    dst.at<Vec3f>(i, j)[1] = Y - 0.343*U - 0.714*V;//G
    dst.at<Vec3f>(i, j)[2] = Y + 1.403*V;//R
    }
    else{
    dst.at<Vec3f>(i, j)[0] = Y + U + ((U * 198) >> 8);
    dst.at<Vec3f>(i, j)[1] = Y -((U * 88) >> 8) - ((V * 183)>>8);
    dst.at<Vec3f>(i, j)[2] = Y + V + ( (V * 103) >> 8);
    }
  }
  }
  dst.convertTo(dst, CV_8UC3);
  return dst;
}
int main(){
  cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\4.JPG");
  if (src.empty()){
  return -1;
  }
  cv::Mat dst, dst1, dst2;
  opencv自带/
  double t2 = (double)cv::getTickCount(); //测时间
  cv::cvtColor(src, dst1, CV_RGB2YUV); //RGB2YUV
  t2 = (double)cv::getTickCount() - t2;
  double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
  std::cout << "Opencv_RGB2YUV=" << time2 << " ms. " << std::endl << std::endl;
  //RGB2YUV//
  double t1 = (double)cv::getTickCount(); //测时间
  dst = RGB2YUV(src,true); //RGB2YUV
  dst2 = YUV2RGB(dst,true); //YUV2BGR
  t1 = (double)cv::getTickCount() - t1;
  double time1 = (t1 *1000.) / ((double)cv::getTickFrequency());
  std::cout << "My_RGB2YUV=" << time1 << " ms. " << std::endl << std::endl;
  cv::namedWindow("src", CV_WINDOW_NORMAL);
  imshow("src", src);
  cv::namedWindow("My_RGB2YUV", CV_WINDOW_NORMAL);
  imshow("My_RGB2YUV", dst);
  cv::namedWindow("My_YUV2RGB", CV_WINDOW_NORMAL);
  imshow("My_YUV2RGB", dst2);
  cv::namedWindow("Opencv_RGB2YUV", CV_WINDOW_NORMAL);
  imshow("Opencv_RGB2YUV", dst1);
  cv::waitKey(0);
  return 0;
}


3、2 RGB24转YUV420P


公式:


20191105100746502.gif


#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
cv::Mat RGB2YUV(cv::Mat src, bool accelerate = false){
  CV_Assert(src.channels() == 3);
  cv::Mat dst(src.size(), CV_32FC3);  //这里最好要用浮点型,避免丧失精度
  cv::Vec3b rgb;
  int r = src.rows;
  int c = src.cols;
  for (int i = 0; i < r; ++i){
  for (int j = 0; j < c; ++j){
    rgb = src.at<cv::Vec3b>(i, j);
    int B = rgb[0]; int G = rgb[1]; int R = rgb[2];
    if (accelerate == false){
    dst.at<Vec3f>(i, j)[0] = R*0.299 + G*0.587 + B*0.114; //Y
    dst.at<Vec3f>(i, j)[1] = -0.169*R - 0.331*G + 0.500*B + 128; //U
    dst.at<Vec3f>(i, j)[2] = 0.500*R - 0.419*G - 0.081*B + 128;  //V
    }
    else{
    dst.at<Vec3f>(i, j)[0] = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8; //Y
    dst.at<Vec3f>(i, j)[1] = (-((R << 5) + (R << 3) + (R << 1)+ R) - ((G << 6) + (G << 4) + (G << 2)+G) + (B << 7) + 32768) >> 8; //U
    dst.at<Vec3f>(i, j)[2] = ((R << 7) - ((G << 6) + (G << 5) + (G << 3) + (G << 3) + G) - ((B << 4) + (B << 2) + B) + 32768 )>> 8; //V
    }
  }
  }
  dst.convertTo(dst, CV_8UC3);
  return dst;
}
cv::Mat YUV2RGB(cv::Mat src, bool accelerate = false){
  CV_Assert(src.channels() == 3);
  cv::Mat dst(src.size(), CV_32FC3); //这里一定要用浮点型,避免丧失精度
  cv::Vec3b yuv;
  int r = src.rows;
  int c = src.cols;
  for (int i = 0; i < r; ++i){
  for (int j = 0; j < c; ++j){
    yuv = src.at<cv::Vec3b>(i, j);
    int Y = yuv[0]; int U = yuv[1]; int V = yuv[2];
    U = U - 128;
    V = V - 128;
    if (accelerate == false){
    dst.at<Vec3f>(i, j)[0] = Y + 1.770*U;//B
    dst.at<Vec3f>(i, j)[1] = Y - 0.343*U - 0.714*V;//G
    dst.at<Vec3f>(i, j)[2] = Y + 1.403*V;//R
    }
    else{
    dst.at<Vec3f>(i, j)[0] = Y + U + ((U * 198) >> 8);
    dst.at<Vec3f>(i, j)[1] = Y -((U * 88) >> 8) - ((V * 183)>>8);
    dst.at<Vec3f>(i, j)[2] = Y + V + ( (V * 103) >> 8);
    }
  }
  }
  dst.convertTo(dst, CV_8UC3);
  return dst;
}
int main(){
  cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\4.JPG");
  if (src.empty()){
  return -1;
  }
  cv::Mat dst, dst1, dst2;
  opencv自带/
  double t2 = (double)cv::getTickCount(); //测时间
  cv::cvtColor(src, dst1, CV_RGB2YUV); //RGB2YUV
  t2 = (double)cv::getTickCount() - t2;
  double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
  std::cout << "Opencv_RGB2YUV=" << time2 << " ms. " << std::endl << std::endl;
  //RGB2YUV//
  double t1 = (double)cv::getTickCount(); //测时间
  dst = RGB2YUV(src,true); //RGB2YUV
  dst2 = YUV2RGB(dst,true); //YUV2BGR
  t1 = (double)cv::getTickCount() - t1;
  double time1 = (t1 *1000.) / ((double)cv::getTickFrequency());
  std::cout << "My_RGB2YUV=" << time1 << " ms. " << std::endl << std::endl;
  cv::namedWindow("src", CV_WINDOW_NORMAL);
  imshow("src", src);
  cv::namedWindow("My_RGB2YUV", CV_WINDOW_NORMAL);
  imshow("My_RGB2YUV", dst);
  cv::namedWindow("My_YUV2RGB", CV_WINDOW_NORMAL);
  imshow("My_YUV2RGB", dst2);
  cv::namedWindow("Opencv_RGB2YUV", CV_WINDOW_NORMAL);
  imshow("Opencv_RGB2YUV", dst1);
  cv::waitKey(0);
  return 0;
}
相关文章
|
2月前
|
机器学习/深度学习 算法 算法框架/工具
为什么使用C++进行机器学习开发
C++作为一种高性能语言,在某些性能要求极高或资源受限的场景下也具有非常重要的地位。C++的高效性和对底层硬件的控制能力,使其在大规模机器学习系统中发挥重要作用,尤其是当需要处理大数据或实时响应的系统时。
37 3
|
23天前
|
Rust 资源调度 安全
为什么使用 Rust over C++ 进行 IoT 解决方案开发
为什么使用 Rust over C++ 进行 IoT 解决方案开发
48 7
|
15天前
|
NoSQL API Redis
如何使用 C++ 开发 Redis 模块
如何使用 C++ 开发 Redis 模块
|
2月前
|
物联网 C# C语言
物联网开发中C、C++和C#哪个更好用
在物联网(IoT)开发中,C、C++和C#各有优缺点,适用场景不同。C语言性能高、资源占用低,适合内存和计算能力有限的嵌入式系统,但开发复杂度高,易出错。C++支持面向对象编程,性能优秀,适用于复杂应用,但学习曲线陡峭,编译时间长。C#易于学习,与.NET框架结合紧密,适合快速开发Windows应用,但性能略低,平台支持有限。选择语言需根据具体项目需求、复杂性和团队技术栈综合考虑。
|
2月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
98 1
|
3月前
|
C++
C++ Qt开发:QUdpSocket网络通信组件
QUdpSocket是Qt网络编程中一个非常有用的组件,它提供了在UDP协议下进行数据发送和接收的能力。通过简单的方法和信号,可以轻松实现基于UDP的网络通信。不过,需要注意的是,UDP协议本身不保证数据的可靠传输,因此在使用QUdpSocket时,可能需要在应用层实现一些机制来保证数据的完整性和顺序,或者选择在适用的场景下使用UDP协议。
122 2
|
4月前
|
NoSQL API Redis
c++开发redis module问题之为什么在使用RedisModule_GetApi之前要通过((void**)ctx)[0]这种方式获取其地址
c++开发redis module问题之为什么在使用RedisModule_GetApi之前要通过((void**)ctx)[0]这种方式获取其地址
|
4月前
|
Rust NoSQL API
c++开发redis module问题之如果在加载module时,该module没有执行权限,Redis会如何解决
c++开发redis module问题之如果在加载module时,该module没有执行权限,Redis会如何解决
|
2月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
134 0
|
4月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
【7月更文挑战第28天】在 Android 开发中, NDK 让 Java 与 C++ 混合编程成为可能, 从而提升应用性能。**为何选 NDK?** C++ 在执行效率与内存管理上优于 Java, 特别适合高性能需求场景。**环境搭建** 需 Android Studio 和 NDK, 工具如 CMake。**JNI** 构建 Java-C++ 交互, 通过声明 `native` 方法并在 C++ 中实现。**实战** 示例: 使用 C++ 计算斐波那契数列以提高效率。**总结** 混合编程增强性能, 但增加复杂性, 使用前需谨慎评估。
137 4