FFmpeg入门详解之121:颜色空间转换RGB和YUV的原理与实战

Posted 福优学苑@音视频+流媒体

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg入门详解之121:颜色空间转换RGB和YUV的原理与实战相关的知识,希望对你有一定的参考价值。

5.颜色空间转换RGB和YUV的原理与实战

三种颜色空间模型:RGB、YUV、HSV

一、概述

颜色通常用三个独立的属性来描述,三个独立变量综合作用,自然就构成一个空间坐标,这就是颜色空间。

但被描述的颜色对象本身是客观的,不同颜色空间只是从不同的角度去衡量同一个对象。颜色空间按照基本机构可以分为两大类:基色颜色空间和色、亮分离颜色空间。前者典型的是RGB,后者包括YUV和HSV等等。

二、RGB颜色空间

1、计算机色彩显示器和彩色电视机显示色彩的原理一样,都是采用R、G、B相加混色的原理,通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示。

2、在RGB颜色空间中,任意色光F都可以用R、G、B三色不同分量的相加混合而成:F=r[R]+r[G]+r[B]。RGB色彩空间还可以用一个三维的立方体来描述。当三基色分量都为0(最弱)时混合为黑色光;当三基色都为k(最大,值由存储空间决定)时混合为白色光。

3、RGB色彩空间根据每个分量在计算机中占用的存储字节数分为如下几种类型:

(1)RGB555

RGB555是一种16位的RGB格式,各分量都用5位表示,剩下的一位不用。

高字节 -> 低字节

XRRRRRGGGGGBBBBB

(2)RGB565

RGB565也是一种16位的RGB格式,但是R占用5位,G占用6位,B占用5位。

(3)RGB24

RGB24是一种24位的RGB格式,各分量占用8位,取值范围为0-255。

(4)RGB32

RGB24是一种32位的RGB格式,各分量占用8位,剩下的8位作Alpha通道或者不用。

4、RGB色彩空间采用物理三基色表示,因而物理意义很清楚,适合彩色显象管工作。然而这一体制并不适应人的视觉特点。因而,产生了其它不同的色彩空间表示法。

三、YUV颜色空间

1、YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法。在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄影机进行取像,然后把取得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和两个色差总共三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V信号分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。

2、YUV主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异。

3、YUV和RGB互相转换的公式如下(RGB取值范围均为0-255)︰

 Y = 0.299R + 0.587G + 0.114B[3:6:1]

 U = -0.147R - 0.289G + 0.436B

 V = 0.615R - 0.515G - 0.100B

 R = Y + 1.14V

 G = Y - 0.39U - 0.58V

 B = Y + 2.03U

四、HSV颜色空间

1、HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法。HSV即色相(Hue)、饱和度(Saturation)、明度(Value),又称HSB(B即Brightness)。色相是色彩的基本属性,就是平常说的颜色的名称,如红色、黄色等。饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。明度(V),取0-max(计算机中HSV取值范围和存储的长度有关)。HSV颜色空间可以用一个圆锥空间模型来描述。圆锥的顶点处,V=0,H和S无定义,代表黑色。圆锥的顶面中心处V=max,S=0,H无定义,代表白色。

2、RGB颜色空间中,三种颜色分量的取值与所生成的颜色之间的联系并不直观。而HSV颜色空间,更类似于人类感觉颜色的方式,封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”

3、RGB和HSV转换

(1)从RGB到HSV

设max等于r、g和b中的最大者,min为最小者。对应的HSV空间中的(h,s,v)值为:

h在0到360°之间,s在0到100%之间,v在0到max之间。

(2)从HSV到RGB

理解颜色空间YUV和RGB:傻傻分不清

1 什么是RGB

对RGB,并不陌生,从初中开始接触的色光的三原色,告诉我们我们可以看到的光可以由这三种颜色按一定的比例去混合得到;后来在html以及android开发中设置元素/控件的颜色时,可以通过一串数字,得到某个特定的颜色。这就是RGB的应用。

RGB 模型是目前常用的一种彩色信息表达方式,它使用红、绿、蓝三原色的亮度来定量表示颜色。该模型也称为加色混色模型,是以RGB三色光互相叠加来实现混色的方法,因而适合于显示器等发光体的显示。

RGB 颜色模型可以看做三维直角坐标颜色系统中的一个单位正方体。任何一种颜色在RGB 颜色空间中都可以用三维空间中的一个点来表示。在RGB 颜色空间上,当任何一个基色的亮度值为零时,即在原点处,就显示为黑色。当三种基色都达到最高亮度时,就表现为白色。在连接黑色与白色的对角线上,是亮度等量的三基色混合而成的灰色,该线称为灰色线。

RGB简介

RGB色彩模式是工业界的一种颜色标准,是通过对红、绿、蓝三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。

RGB16格式

RGB16数据格式主要有二种:RGB565和RGB555。

RGB565

每个像素用16比特位表示,占2个字节,RGB分量分别使用5位、6位、5位。

RGB555

每个像素用16比特位表示,占2个字节,RGB分量都使用5位(最高位不用)。

RGB24格式

RGB24图像每个像素用8比特位表示,占1个字节,

注意:在内存中RGB各分量的排列顺序为:BGR BGR BGR ......。

RGB32格式

RGB32图像每个像素用32比特位表示,占4个字节,R,G,B分量分别用8个bit表示,存储顺序为B,G,R,最后8个字节保留。

注意:在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA ......。

ARGB32

本质就是带alpha通道的RGB24,与RGB32的区别在与,保留的8个bit用来表示透明,也就是alpha的值。

在内存中的分量排列顺序如下:

2 什么是BGR

与RGB类似,只是存储时B位与R位的位置进行调换。

3 什么是YCbCr

Y表示亮度,CbCr表示色度。在DVD中,色度信号被存储成Cb和Cr(C代表颜色,b代表蓝色,r代表红色)

怎么表示颜色,可以看下面这幅坐标图:

Y要如何表示亮度呢,下面是Y在不同的情况下的表现:

因此可以这样理解,同样是使用三个数来表示某个像素点的颜色,但是这三个数的意义变了,与RGB相比。然后接下来是各种yuv的衍生物。

The scope of the terms Y′UV, YUV, YCbCr, YPbPr, etc., is sometimes ambiguous and overlapping. Historically, the terms YUV and Y′UV were used for a specific analog encoding of color information in television systems, while YCbCr was used for digital encoding of color information suited for video and still-image compression and transmission such as MPEG and JPEG. Today, the term YUV is commonly used in the computer industry to describe file-formats that are encoded using YCbCr.

上面的意思是,这些术语有时真的很难区分,因为定义也是模糊不清。

不过重要的是最后面那一句话:现在的YUV是通常用于计算机领域用来表示使用YCbCr编码的文件。所以可以粗浅地视YUV为YCbCr。

不过我在Camera预览中的每一帧中,除默认格式NV21外,还发现了其它的格式如YV12。去搜一些关于他们的资料时,发现都是yuv420系列的。具体有什么差异呢?

4 YUV分类与意义

planar和packed

对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。

对于packed的YUV格式,每个像素点的Y,U,V是连续交叉存储的。

YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像(是不是写错了),只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。

YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,关于其详细原理(请参考:“ffmpeg4.3.1--系列二--音视频基础理论”)。

这里我想强调的是如何根据其采样格式来从码流中还原每个像素点的YUV值,因为只有正确地还原了每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来。

5 存储方式

YUV数据格式

用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。

先记住下面这段话,以后提取每个像素的YUV分量会用到。

YUV 4:4:4采样,每一个Y对应一组UV分量

YUV 4:2:2采样,每两个Y共用一组UV分量 

YUV 4:2:0采样,每四个Y共用一组UV分量

下面我用图的形式给出常见的YUV码流的存储方式,并在存储方式后面附有取样每个像素点的YUV数据的方法,其中,Cb、Cr的含义等同于U、V。每个像素都是编码为四个连续字节的两个像素。

5.1 YUYV 格式 (属于YUV422)

YUYV为YUV422采样的存储格式中的一种,相邻的两个Y共用其相邻的两个Cb、Cr。

分析:对于像素点Y’00、Y’01 而言,其Cb、Cr的值均为 Cb00、Cr00,其他的像素点的YUV取值依次类推。

5.2 UYVY 格式 (属于YUV422)

UYVY格式也是YUV422采样的存储格式中的一种,只不过与YUYV不同的是UV的排列顺序不一样而已,还原其每个像素点的YUV值的方法与上面一样。

5.3 YUV422P(属于YUV422)

YUV422P也属于YUV422的一种,它是一种Plane模式,即平面模式,并不是将YUV数据交错存储,而是先存放所有的Y分量,然后存储所有的U(Cb)分量,最后存储所有的V(Cr)分量,如上图所示。

其每一个像素点的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即两个Y共用一个UV。比如,对于像素点Y’00、Y’01 而言,其Cb、Cr的值均为 Cb00、Cr00。

5.4 YV12,YU12格式(属于YUV420)

YU12和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。

其每一个像素点的YUV数据提取遵循YUV420格式的提取方式,即4个Y分量共用一组UV。注意,上图中,Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00,其他依次类推。

5.5 NV12、NV21(属于YUV420)

NV12和NV21属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。

其提取方式与上一种类似,即Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00

5.6 YU12 YV12 NV12 NV21区别(yuv420)

YU12(I420):

yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy   (w*h)

uuuuuuuu uuuuuuuu                                               (w*h/4)

vvvvvvvv vvvvvvvv                                                 (w*h/4)

YV12:

yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy (w*h)

vvvvvvvv vvvvvvvv (w*h/4)

uuuuuuuu uuuuuuuu (w*h/4)

 NV12: 先存y,然后uv交替,u在前,v在后

yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy   (w*h)

uvuvuvuv uvuvuvuv uvuvuvuv uvuvuvuv                                (w*h/2)

 NV21: 先存y,然后vu交替,v在前,u在后

yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy yyyyyyyy

vuvuvuvu vuvuvuvu vuvuvuvu vuvuvuvu

5.7 YUV文件大小计算

以720×480大小图象YUV420 planar为例,其存储格式是: 共大小为(720×480×3>>1)字节,分为三个部分:Y,U和V

Y分量: (720×480)个字节 

U(Cb)分量:(720×480>>2)个字节 

V(Cr)分量:(720×480>>2)个字节

三个部分内部均是行优先存储,三个部分之间是Y,U,V 顺序存储。 即

0--720×480字节是Y分量值, 

720×480--720×480×5/4字节是U分量 

720×480×5/4 --720×480×3/2字节是V分量。

5.8 YV12和I420的区别

一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Bytes,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Bytes。 

在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。

但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下:

YV12 : 亮度(行×列) + U(行×列/4) + V(行×列/4)

I420 : 亮度(行×列)  + V(行×列/4) + U(行×列/4)

可以看出,YV12和I420基本上是一样的,就是UV的顺序不同。

继续我们的话题,经过第一次数据压缩后RGB24->YUV(I420)。

这样,数据量将减少一半,为什么呢?呵呵,这个就太基础了,我就不多写了。

同样,如果是RGB24->YUV(YV12),也是减少一半。但是,虽然都是一半,如果是YV12的话效果就有很大损失。然后,经过X264编码后,数据量将大大减少。将编码后的数据打包,通过RTP实时传送。到达目的地后,将数据取出,进行解码。完成解码后,数据仍然是YUV格式的,所以,还需要一次转换,这样windows的驱动才可以处理,就是YUV2RGB24。

5.9 YUV420P和 YUV420SP的区别

YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。

I420格式和YV12格式的不同处在U平面和V平面的位置不同。

在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);

但YV12则是相反(即:YVU)。

YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 

NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。

I420: YYYYYYYY UU VV =>YUV420P 

YV12: YYYYYYYY VV UU =>YUV420P 

NV12: YYYYYYYY UVUV =>YUV420SP 

NV21: YYYYYYYY VUVU =>YUV420SP

6 色彩空间转换

6.1 4:2:2 和4:2:0 转换

最简单的方式:

YUV4:2:2 —> YUV4:2:0

 Y不变,将U和V信号值在行(垂直方向)再进行一次隔行抽样。

YUV4:2:0 —> YUV4:2:2 

Y不变,将U和V信号值的每一行分别拷贝一份形成连续两行数据。

在YUV420中,一个像素点对应一个Y,一个4X4的小方块对应一个U和V。

对于所有YUV420图像,它们的Y值排列是完全相同的,因为只有Y的图像就是灰度图像。

YUV420sp与YUV420p的数据格式它们的UV排列在原理上是完全不同的。

420p它是先把U存放完后,再存放V,也就是说UV它们是连续的。

而420sp它是UV、UV这样交替存放的。

有了上面的理论,我就可以准确的计算出一个YUV420在内存中存放的大小。 

width * hight =Y(总和), U = Y / 4, V = Y / 4。

所以YUV420 数据在内存中的长度是: width * hight * 3 / 2,

假设一个分辨率为8X4的YUV图像,它们的格式如下图:

YUV420sp数据格式如下图:

YUV420p数据格式如下图

6.2 YUYV与RGB互转

YUYV -> RGB的公式:

R = 1.164(Y-16) + 1.596(V-128)

G = 1.164(Y-16) - 0.392(U-128) - 0.813(V-128) 

B = 1.164(Y-16) + 2.017(U-128)

RGB -> YUYV公式:

Y = 0.257R  + 0.504G + 0.098B + 16

U = -0.148R - 0.291G + 0.439B + 128

V = 0.439R  - 0.368G - 0.071B + 128

7 详解YUV420

一、文字描述

YUV420格式的采样,对于每个2*2的像素块中,采样4次Y,采样1次U和1次V。

与YUV422相同,不同的存储方式同样也形成了不同的格式,详见存储示意图喽。

二、采样示意图

注: 示意图出于直观,将每4个像素采样的U,V分量都画在第一个像素点内,

而实际上每4个Y共用的一组U,V分量的值是根据四个像素的本来的U,V值进行插值而得到的。

三、存储示意图

(一)Three plane: 

Y, U, V分别存储,分别对应一个plane,统称为YUV420P格式

YV12:

YU12:

(二)Two plane: 

Y和UV分别存储,Y对应一个plane, UV对应一个plane,统称为YUV420SP格式

NV12:

NV21:

sws_scale颜色空间转换的案例代码

原理解析

FFmpeg里面的sws_scale库可以在一个函数里面同时实现:

1.图像色彩空间转换;

2.分辨率缩放;

3.前后图像滤波处理。

其核心函数主要有三个:

sws_getContext

sws_scale

sws_freeContext

// 初始化sws_scale

struct SwsContext *sws_getContext(

            int srcW, /* 输入图像的宽度 */

            int srcH, /* 输入图像的高度 */

            enum AVPixelFormat srcFormat, /* 输入图像的像素格式 */

            int dstW, /* 输出图像的宽度 */

            int dstH, /* 输出图像的高度 */

            enum AVPixelFormat dstFormat, /* 输出图像的像素格式 */

            int flags,/* 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR */

            SwsFilter *srcFilter, /* 输入图像的滤波器信息, 若不需要传NULL */

            SwsFilter *dstFilter, /* 输出图像的滤波器信息, 若不需要传NULL */

            const double *param /* 特定缩放算法需要的参数(?),默认为NULL */

            );

参数int srcW, int srcH, enum AVPixelFormat srcFormat定义输入图像信息(寬、高、颜色空间(像素格式))

参数int dstW, int dstH, enum AVPixelFormat dstFormat定义输出图像信息寬、高、颜色空间(像素格式))。

参数int flags选择缩放算法(只有当输入输出图像大小不同时有效)

参数SwsFilter *srcFilter, SwsFilter *dstFilter分别定义输入/输出图像滤波器信息,如果不做前后图像滤波,输入NULL

参数const double *param定义特定缩放算法需要的参数(?),默认为NULL

函数返回SwsContext结构体,定义了基本变换信息。

如果是对一个序列的所有帧做相同的处理,函数sws_getContext只需要调用一次就可以了。

sws_getContext(w, h, YV12, w, h, NV12, 0, NULL, NULL, NULL);      // YV12->NV12 色彩空间转换

sws_getContext(w, h, YV12, w/2, h/2, YV12, 0, NULL, NULL, NULL);  // YV12图像缩小到原图1/4

sws_getContext(w, h, YV12, 2w, 2h, YV12, 0, NULL, NULL, NULL);    // YV12图像放大到原图4倍,并转换为NV12结构

// 做转换

int sws_scale(struct SwsContext *c,

              const uint8_t *const srcSlice[], const int srcStride[],

              int srcSliceY, int srcSliceH,

              uint8_t *const dst[], const int dstStride[]);

参数struct SwsContext *c,为上面sws_getContext函数返回值;

参数const uint8_t *const srcSlice[], const int srcStride[]定义输入图像信息(当前处理区域的每个通道数据指针,每个通道行字节数)

stride定义下一行的起始位置。

stride和width不一定相同,这是因为:

1.由于数据帧存储的对齐,有可能会向每行后面增加一些填充字节这样 stride = width + N;

2.packet色彩空间下,每个像素几个通道数据混合在一起,例如RGB24,每个像素3字节连续存放,因此下一行的位置需要跳过3*width字节。

srcSlice和srcStride的维数相同,由srcFormat值来。

csp       维数        宽width      跨度stride      高

YUV420     3        w, w/2, w/2    s, s/2, s/2   h, h/2, h/2

YUYV       1        w, w/2, w/2   2s, 0, 0       h, h, h

NV12       2        w, w/2, w/2    s, s, 0       h, h/2

RGB24      1        w, w,   w     3s, 0, 0       h, 0, 0           

参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。

如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。

这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。

参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)

// 释放sws_scale

void sws_freeContext(struct SwsContext *swsContext);

代码解析
extern "C" 
    
    #include <libavutil/imgutils.h>
    #include <libavutil/samplefmt.h>
    #include <libavutil/timestamp.h>
    #include <libavformat/avformat.h>
    #include <libswscale/swscale.h>

/*
YUYV转YUV420P格式
*/
static void YUYV422_TO_YUV420P(uint8_t *yuyv422,uint8_t *yuv420p,int video_width,int video_height)

    AVFrame *Input_pFrame= nullptr;
    AVFrame *Output_pFrame = nullptr;
    struct SwsContext *img_convert_ctx=nullptr; //用于解码后的格式转换
    /*1. 申请空间*/
    Input_pFrame = av_frame_alloc();
    Output_pFrame = av_frame_alloc();
    /*2.设置转码参数*/
    img_convert_ctx=sws_getContext(video_width, video_height,AV_PIX_FMT_YUYV422, //输入
                                   video_width, video_height,AV_PIX_FMT_YUV420P,   //输出
                                   SWS_BICUBIC, nullptr, nullptr, nullptr);
    /*3. 申请转码需要空间*/
    /*4. 设置转码的源数据地址*/
    avpicture_fill((AVPicture *) Input_pFrame, yuyv422, AV_PIX_FMT_YUYV422,video_width, video_height);
    avpicture_fill((AVPicture *) Output_pFrame, yuv420p, AV_PIX_FMT_YUV420P,video_width, video_height);
    //转格式
    sws_scale(img_convert_ctx,
     (uint8_t const **) Input_pFrame->data,Input_pFrame->linesize,
      0, video_height, Output_pFrame->data,Output_pFrame->linesize);
    //释放空间
    if(Input_pFrame)av_free(Input_pFrame);
    if(Output_pFrame)av_free(Output_pFrame);
    if(img_convert_ctx)sws_freeContext(img_convert_ctx);

/*
YUYV422转RGB888格式
*/
static void YUYV422_TO_RGB888(uint8_t *yuyv422,uint8_t *rgb888,int image_width,int image_height)

    AVFrame *Input_pFrame= nullptr;
    AVFrame *Output_pFrame = nullptr;
    struct SwsContext *img_convert_ctx=nullptr;  //用于解码后的视频格式转换
    /*1. 申请空间*/
    Output_pFrame = av_frame_alloc();  //存放RGB数据的缓冲区
    Input_pFrame = av_frame_alloc();//存放YUV数据的缓冲区
    /*2.设置转码参数*/
img_convert_ctx=sws_getContext(
image_width, image_height,AV_PIX_FMT_YUYV422,
image_width, image_height,AV_PIX_FMT_RGB24,
        SWS_BICUBIC, nullptr, nullptr, nullptr);
    /*4. 设置转码的源数据地址*/
    avpicture_fill((AVPicture *) Output_pFrame, rgb888, AV_PIX_FMT_RGB24,image_width, image_height);
    avpicture_fill((AVPicture *) Input_pFrame, yuyv422, AV_PIX_FMT_YUYV422,image_width, image_height);
    //转格式
    sws_scale(img_convert_ctx,
     (uint8_t const **) Input_pFrame->data,
     Input_pFrame->linesize, 0, image_height, Output_pFrame->data,
     Output_pFrame->linesize);
    //释放空间
    if(Input_pFrame)av_free(Input_pFrame);
    if(Output_pFrame)av_free(Output_pFrame);
    if(img_convert_ctx)sws_freeContext(img_convert_ctx);

SDL2.0显示YUV案例实战

原理分析

详细内容请参考:“ffmpeg4.3.1--系列二--音视频基础理论”

实现的是播放yuv420P裸数据的代码,至于怎么得到yuv420P数据,用大家都喜欢的ffmpeg:

ffmpeg -i xxx.flv -pix_fmt yuv420p -s 352*288 -ss 5 -t 10 xxx.yuv

代码请参考:

SDL2_yuv.cpp

SDL2_yuv2.cpp

灰度处理

如何显示灰度图?

如何把彩色图转换成灰度图?

///灰度处理

        /*

        如果要把YUV格式像素变成灰度图像,只需要将U、V分别设置为128即可。

        因为U、V是图像经过偏置处理的色度分量,

        色度分量在偏置处理前的取值范围为-128至127,此时的无色对应的"0"值,

        经过偏置后色度分量取值变成了0至255,这时候无色对应的值就是128。

        */

        /// decolor

        memset(buf+width*height, 0x80, width*height / 4); //cb

        memset(buf+width*height / 4 * 5, 0x80, width*height / 4); //cr

完整代码:

#include <stdio.h>
#include <SDL.h>
#undef main
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
typedef unsigned char BYTE;
/* Prepare a dummy image.:填充随机颜色 */
static void FillYuvImage(BYTE* pYuv, int nWidth, int nHeight, int nIndex)

int x, y, i;
i = nIndex;
    // YV12
    BYTE* pY = pYuv;
    BYTE* pV = pYuv + nWidth * nHeight;
    BYTE* pU = pYuv + nWidth * nHeight * 5 / 4;
/* Y */
for (y = 0; y < nHeight; y++)

for (x = 0; x < nWidth; x++)

            pY[y * nWidth + x] = y%2!=0?0:255;//x + y + i * 2;//+ y


/* Cb and Cr */
for (y = 0; y < nHeight / 2; y++)

for (x = 0; x < nWidth / 2; x++)

            ///灰度处理
            /*
            如果要把YUV格式像素变成灰度图像,只需要将U、V分别设置为128即可。
            因为U、V是图像经过偏置处理的色度分量,
            色度分量在偏置处理前的取值范围为-128至127,此时的无色对应的"0"值,
            经过偏置后色度分量取值变成了0至255,这时候无色对应的值就是128。
            */
            pU[y * (nWidth / 2) + x] = 128;//64 + y + i * 2;//0x80;gray//64 + y + i * 2;
            pV[y * (nWidth / 2) + x] = 128;//64 + x + i * 5;//0x80;gray//64 + x + i * 5;



int main_testyuv001  (int argc, char* argv[])

if (SDL_Init(SDL_INIT_VIDEO))

printf("Could not initialize SDL - %s\\n", SDL_GetError());
return -1;

// 提升图像质量,否则默认缩放质量会有毛剌
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
    SDL_Window* window = SDL_CreateWindow("yx3sxmeng", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);
    SDL_Renderer* render = SDL_CreateRenderer(window, -1, 0);
const int W = 1920;
const int H = 1080;
    /// YUV420P: YV12, (YYYY,v,u)
    SDL_Texture*  texture = SDL_CreateTexture(
                render, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, W, H);
/// yuv 420P : planer
    static BYTE Yuv[W*H * 2];
    BYTE* pY = Yuv;        //Y ,      [0, W*H Bytes]
    BYTE* pV = Yuv + W*H;  //V,       [W*H, W*H/4]
    BYTE* pU = Yuv + W*H * 5 / 4;//U, [W*H + W*H/4, W*H/4]
int index = 0;
while (true)

        /// 刷新渲染器:三部曲: clear, copy, present
        SDL_RenderClear(render);
FillYuvImage(Yuv, W, H, index++);///填充随机颜色
        //memset(pU, 0x80, W*H/4);
        //memset(pV, 0x80, W*H/4);
int e = SDL_UpdateYUVTexture(texture, NULL,
pY, W,
pU, W / 2,
pV, W / 2);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);
        SDL_Delay(40);

SDL_DestroyTexture(texture);
SDL_DestroyRenderer(render);
//SDL_DestroyWindow(window);
SDL_Quit();
return 0;

SDL2.0实现YUV播放器

SDL2.0显示yuv文件?

大家好,我的第一本书正式出版了,可以在京东各大店铺抢购哦。

《FFmpeg入门详解--音视频原理及应用:梅会东:清华大学出版社》

京东自营链接:https://item.jd.com/13377793.html
京东其它链接:https://search.jd.com/Search?keyword=FFmpeg%E5%85%A5%E9%97%A8%E8%AF%A6%E8%A7%A3--%E9%9F%B3%E8%A7%86%E9%A2%91%E5%8E%9F%E7%90%86%E5%8F%8A%E5%BA%94%E7%94%A8&enc=utf-8&suggest=1.his.0.0&wq=&pvid=24e80535073b4e1f98e30a3e6963fe81
 

 

出书

出书过程非常艰辛,来回校正了好几遍,后续还有FFmpeg系列的其它图书。

第一本:FFmpeg入门详解--音视频原理及应用--梅会东--清华大学出版社

第二本:FFmpeg入门详解--流媒体直播原理及应用--梅会东--清华大学出版社

第三本:FFmpeg入门详解--命令行及音视频特效原理及应用--梅会东--清华大学出版社

第四本:FFmpeg入门详解--SDK二次开发及直播美颜原理及应用--梅会东--清华大学出版社

===================================
————————————————
版权声明:本文为CSDN博主「福优学苑@音视频+流媒体」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/teachermei/article/details/126700434

色彩(颜色)空间原理(中)

色彩(颜色)空间原理(中)

颜色的线性变换

现在我们知道如何定义RGB颜色空间,以及如何使用伽玛曲线在线性和伽玛校正值之间进行转换。剩下的最后一步是将线性RGB颜色转换为XYZ颜色。一旦进入XYZ空间,我们就可以转换回我们选择的任何RGB空间,但这实际上只是开始。因为XYZ空间是定义其他颜色的标准颜色空间,所以我们可以选择转换为许多非RGB颜色空间,例如在感知上更统一的Lab颜色空间或生物驱动的LMS颜色空间

线性RGB空间和XYZ空间之间转换的基本部分是认识到它们都是矢量空间。这基本上意味着数字以线性方式缩放。相反,经伽玛校正的sRGB空间以非线性方式缩放亮度,因此不是亮度的矢量空间。如果您有某种数学迷信并且想更深入地研究该主题,请查阅  格拉斯曼定律,该定律将色彩感知视为线性组合。

知道我们在向量空间内工作时,便可以使用各种线性代数工具。我们将使用的线性代数中的一种这样的工具是根据另一种颜色空间定义一种颜色空间的基础。这类似于在3d空间中定义对象的变换。

正如我们前面所讨论的,RGB颜色空间是通过将三种原色相加而建立的。第一个原色靠近光谱的红色部分。第二个接近绿色。第三个接近蓝色。为了获得黄色,我们将红色和绿色原色相加。此操作可以视为3维矢量加法。让向量一世一世 ?? 和 ?? 分别等于我们的原色红色,绿色和蓝色,这样

技术图片

看来我们已经使局势复杂化了,但它会有所收获。首先,让我们来看一些以这种方式定义颜色的示例。

技术图片

在上面的例子中,RGB原色i、j和k是根据它们自己的RGB颜色空间定义的,这使得这些值相当简单。当我们开始处理不同的颜色空间(如XYZ)时,事情变得更加有趣。             

目标是找到与线性RGB空间的原色相匹配的三种XYZ颜色。一旦在XYZ空间中有了i、j和k,就可以使用相同的r、g和b标量来找到RGB颜色的XYZ值。我会马上讨论如何推导这些新的初值,但首先我们假设我们已经知道它们的值,这样我们就可以用一个例子来说明这个过程。设l是XYZ空间中的红色主元素,m是XYZ空间中的绿色主元素,n是XYZ空间中的蓝色边缘元素。             

我们现在可以计算XYZ空间中的任何线性RGB颜色,将其作为XYZ空间中主色的线性组合。

技术图片

推导变换矩阵

如先前所示,从线性RGB空间到XYZ空间的转换矩阵具有从XYZ空间中的主要RGB颜色构建的列。为了找到这些值,我们需要将RGB空间的xy色度坐标用于红色,绿色,蓝色和白色。注意,我们只有x和y坐标。如前所述,我们可以根据x和y计算z坐标,但是我们需要X,Y或Z才能转换回XYZ坐标。就目前而言,我们没有足够的信息从xy色度空间到XYZ空间,但是按照我们转换的意图,我们可以再做一个假设。

让我们澄清一下此转换的目的。前面我提到过眼睛如何适应照明环境,以选择应被视为白色的颜色。为了我们的目的,我们希望将感知到的白色视为最大亮度。这意味着,如果我们从RGB空间A转换为XYZ空间然后再转换为RGB空间B,我们希望RGB空间A中的白色保持RGB空间B中的亮度。我们要做的就是确保当RGB空间转换为XYZ空间时,白色值始终以一致的Y(即一致的相对亮度)结束。

我提到过,只要我们保持一致,就可以为白点选择任何Y发光度,但是标准做法是使用Y值为1以获得全亮度。有时,您可能会看到100的Y表示全亮度,但是在这种情况下,标准值为1。有了白色的目标Y值,我们现在有足够的约束来求解矩阵。让我们列出我们开始的变量。

技术图片

目标是求解列主变换矩阵M,它将从线性RGB空间转换到XYZ空间。第一步是使用前面讨论的方程z=1−x−y将所有xy色度坐标转换为xyz色度坐标。

技术图片

技术图片

我们知道所有的xyz初值,因此知道上面等式中的左矩阵。我们的未知数现在降到构成右矩阵的三个标量值。请注意,虽然每个标量都被写成X、Y和Z分量的和,但我们真正关心的只是求和结果,而不是各个部分。这就是为什么我说只有三个未知数,而不是九个。为了解决这三个未知数,我们将使用已知的白点RGB和XYZ值。因为M从RGB变换到XYZ空间,所以我们可以声明如下等式:

技术图片

现在我们将把方程的每边乘以剩下的3x3矩阵的逆。这将把我们所有的已知值放在方程的左边,我们的未知值放在右边。如果您不熟悉3x3矩阵的逆运算,我将在本文的末尾提供代码,但出于理智起见,这里不会编写完整的推导。Google应该提供大量关于这个过程的结果,包括Wikipedia和Mathwords的这一个。

技术图片

我们现在可以重建我们的RGB到XYZ转换矩阵M。将wXYZ向量乘以上述3x3矩阵的倒数将得到将主XYZ坐标转换为XYZ坐标的标量值。这是我们从M中分解出来的标量矩阵的三个未知数。              

为了得到相反方向的矩阵变换(从XYZ空间到线性RGB空间),我们可以使用M的逆矩阵。我们也可以用与上面类似的方式来推导矩阵,但是在这么多类型化之后,仅仅使用逆似乎是一种更简单的方法。

以上是关于FFmpeg入门详解之121:颜色空间转换RGB和YUV的原理与实战的主要内容,如果未能解决你的问题,请参考以下文章

RGB 和 RYB 颜色空间之间的转换

rgb转换为cmyk颜色空间的举例

RGB颜色空间色调饱和度亮度HSV颜色空间详解

用MATLAB编写RGB到HSL和HSL到RGB颜色空间的转换程序:rgb2hsl.m和hsl2rgb.m

rgb颜色 红色 转换成yuv颜色空间是啥

物体检测学习笔记-RGB与YCbCr颜色空间的转换