阅读 144

YUV转RGB有哪些重要的点

关于RGB

学过中学物理的都知道光的三原色:红、绿、蓝,就是俗称的Red、Green、Blue,这几乎能表现自然界中所有的颜色,所有的颜色都可以通过设置RGB的分量来呈现出来。RGB三种颜色混合得到是的白色,这就是我们通常看到的太阳光的颜色。

在计算机图形学中,RGB分别用8位来表示,R用8bit、G用8bit、B用8bit表示,每个颜色分量分量可以用256个数值来表示,实际上自然界的颜色是无法用256位来穷尽的,但是计算机语言中必须用标准化的东西表示,不然所有的东西都是一笔糊涂账。

RGB中也分为很多种,我们可以简单的了解一下,这些RGB分类已经具体的使用场景。

  • RGB24:这是标准的RGB模式,分别是8位R,8位G,8位B表示。

  • RGB555:RGB555是一种16位的RGB格式,RGB分量都用5位表示(剩下的1位不用),表示如下:X R R R R R G G G G G B B B B B (X表示不用,可以忽略)

  • RGB565:RGB565使用16位表示一个像素,这16位中的5位用于R,6位用于G,5位用于B。表示如下:R R R R R G G G G G G B B B B B

  • RGBA:这是RGB的基础上增加了A表示,A是透明度,总共有32位。

关于YUV

有了RGB颜色系统,应该是可以表示所有的图片和视频中的颜色的,但是我们程序生产中的颜色编码系统采用的却是YUV编码。主要是电视刚开始是以黑白为主,从黑白电视过渡到彩色电视,YUV就诞生了,其中Y分量表示灰度值,UV表示色彩度,如果只有Y分量,没有UV分量,那就是黑白电视。

从存储的角度来看,YUV可以分为紧缩格式和平面格式。

  • 紧缩格式:紧缩格式就是将YUV数据存放在一个数组中,就像RGB排列一样:R R R R R R R R G G G G G G G G B B B B B B B B

  • 平面格式:平面格式就是将YUV三个分量数据分开存储,先存储Y分量数据,然后U分量,再然后V分量,这样排列的好处是比较方便,平面格式是目前使用比较广泛的YUV排列方式。

YUV种类分为很多种,下面介绍一下比较通用的几种:

  • YUV444:4:4:4表示完全取样

  • YUV422:4:2:2表示2:1的水平取样,垂直完全采样。

  • YUV420:4:2:0表示2:1的水平取样,垂直2:1采样。

  • YUV411:4:1:1表示4:1的水平取样,垂直1:1采样。

YUV444就是Y、U、V分量的个数是一样的。

YUV422就是在水平方向上Y分量是UV分量的2倍,在垂直方向上Y分量和UV分量是一样的。

YUV420就是水平方向上Y分量是UV分量的2倍,在垂直方向上Y分量也是UV分量的2倍。

YUV411就是在水平方向上Y分量是UV分量的4倍,在垂直方向上Y分量是UV分量的2倍。

具体可以参考:dougkerr.net/Pumpkin/art…

image.png

上面左边的方框表示Y分量,黑色圆表示UV分量。

下面解答一下程序开发中一些YUV概念的区别。

  • YUVJ420P和YUV420P的区别?

  • NV12和NV21分别是什么?

YUVJ420P和YUV420P最大的不同是YUVJ420P是使用了JPEG的颜色范围,就是正常的YUV420P的颜色表示范围是16 ~ 235,16表示黑色,235表示白色。YUVJ420P使用的全颜色域的表示范围,0 ~ 255,0表示黑色,255表示白色。

NV12是YUV420的一种,不过与YUV420P的3-plane存储模式不同,NV12是2-plane存储的,3-plane就是Y/U/V分表存储在不同的地方,2-plane是Y/UV分表存储在不同的地方。NV12是Y-UV存储方式,NV21是Y-VU存储方式。

Android中Camera中的经常使用NV21方式。

如下图右边是原图,左边从下到下依次是Y分量、U分量、V分量。

hello-li.png

YUV转RGB

为什么需要YUV转RGB,从上面的分析中我们知道YUV编码系统(不管是YUV还是YCbCr)都是用在数字电视或者模拟电视上面的。但是我们要想将视频内容渲染出来,还是要转化为RGB的模式,所以YUV转RGB就是我们不得不考虑的事情了。

YUV的类型很多,RGB的类型也很多,在转换的过程中的需要我们考虑很多种情况。在转换之前,还给大家介绍一下YUV的BT.601/BT.709/BT.2020三种不同的兼容性标准。

  • BT.601

  • BT.709

  • BT.2020

正常而言,BT.609是针对标清视频,BT.709是针对HD高清视频,BT.2020是针对超高清的视频,目前BT.2020用的还比较少,主要是前两种标准。

YUV转RGB有三种常见的方式:

  • OpenGL shader方式

  • libyuv方式

  • FFmpeg swscale方式

YUV与RGB之间是可以转换的,例如YUV420P转换为RGBA,其中RGBA中的各个分量的范围是0 ~ 255,YUV420P中Y分量范围是16 ~ 235,UV分量是0 ~ 127,这就要求我们将YUV420P中各个分量映射到RGBA中,可以采用的方式也很多,主要的就是矩阵计算。在工程开发中,通常的做法有上面提供的四种方式。

具体的推导计算大家可以参考:en.wikipedia.org/wiki/YUV 因为我觉得这是比较常规的计算,就不在这儿赘述了。这儿推荐大家一个转换的站点:licheng.sakura.ne.jp/hatena6/rgb…

下面直接和大家分享一下具体的转换的公式:

  • RGB 转 BT.601 YUV

Y  =  0.257R + 0.504G + 0.098B + 16
Cb = -0.148R - 0.291G + 0.439B + 128
Cr =  0.439R - 0.368G - 0.071B + 128复制代码
  • BT.601 YUV转 RGB

R = 1.164(Y-16)                 + 1.596(Cr-128)
G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
B = 1.164(Y-16) + 2.018(Cb-128)复制代码

这儿的YUV是局部色域的,如果是全色域,转化公式如下:

R = Y                 + 1.402(Cr-128)
G = Y - 0.344(Cb-128) - 0.714(Cr-128)
B = Y + 1.772(Cb-128)复制代码
  • RGB 转 BT.709 YUV

Y  =  0.183R + 0.614G + 0.062B + 16
Cb = -0.101R - 0.339G + 0.439B + 128
Cr =  0.439R - 0.399G - 0.040B + 128复制代码
  • BT.709 YUV 转 RGB

R = 1.164(Y-16)                 + 1.793(Cr-128)
G = 1.164(Y-16) - 0.213(Cb-128) - 0.533(Cr-128)
B = 1.164(Y-16) + 2.112(Cb-128)复制代码

这儿的YUV是局部色域的,如果是全色域,转化公式如下:

R = Y                 + 1.280(Cr-128)
G = Y - 0.215(Cb-128) - 0.381(Cr-128)
B = Y + 2.128(Cb-128)复制代码

OpenGL shader方式

根据上面的矩阵计算,使用OpenGL shader的方式,可以得到如下的shader

# 部分色域 BT.601 YUV转 RGB
varying highp vec2 textureCoordinate;
uniform sampler2D texture_y;
uniform sampler2D texture_u;
uniform sampler2D texture_v;
void main() {
  highp float y = texture2D(texture_y, textureCoordinate).r - 0.0625;
  highp float u = texture2D(texture_u, textureCoordinate).r - 0.5;
  highp float v = texture2D(texture_v, textureCoordinate).r - 0.5;
  highp float r = 1.164 * y +             1.596 * v;
  highp float g = 1.164 * y - 0.391 * u - 0.813 * v;
  highp float b = 1.164 * y + 2.018 * u;
  gl_FragColor = vec4(r, g, b, 1.0);
}

# 全色域 BT.601 YUV转 RGB
varying highp vec2 textureCoordinate;
uniform sampler2D texture_y;
uniform sampler2D texture_u;
uniform sampler2D texture_v;
void main() {
  highp float y = texture2D(texture_y, textureCoordinate).r;
  highp float u = texture2D(texture_u, textureCoordinate).r - 0.5;
  highp float v = texture2D(texture_v, textureCoordinate).r - 0.5;
  highp float r = y +             1.402 * v;
  highp float g = y - 0.344 * u - 0.714 * v;
  highp float b = y + 1.772 * u;
  gl_FragColor = vec4(r, g, b, 1.0);
}

# 部分色域 BT.709 YUV转 RGB
varying highp vec2 textureCoordinate;
uniform sampler2D texture_y;
uniform sampler2D texture_u;
uniform sampler2D texture_v;
void main() {
  highp float y = texture2D(texture_y, textureCoordinate).r - 0.0625;
  highp float u = texture2D(texture_u, textureCoordinate).r - 0.5;
  highp float v = texture2D(texture_v, textureCoordinate).r - 0.5;
  highp float r = 1.164 * y +             1.793 * v;
  highp float g = 1.164 * y - 0.213 * u - 0.533 * v;
  highp float b = 1.164 * y + 2.112 * u;
  gl_FragColor = vec4(r, g, b, 1.0);
}

# 全色域 BT.709 YUV转 RGB
varying highp vec2 textureCoordinate;
uniform sampler2D texture_y;
uniform sampler2D texture_u;
uniform sampler2D texture_v;
void main() {
  highp float y = texture2D(texture_y, textureCoordinate).r;
  highp float u = texture2D(texture_u, textureCoordinate).r - 0.5;
  highp float v = texture2D(texture_v, textureCoordinate).r - 0.5;
  highp float r = y +             1.280 * v;
  highp float g = y - 0.215 * u - 0.381 * v;
  highp float b = y + 2.128 * u;
  gl_FragColor = vec4(r, g, b, 1.0);
}复制代码

libyuv方式

libyuv的方式就比较简单了,因为库里面都给你封装好了,可以看下libyuv的源码:github.com/lemenkov/li…头文件在include下面,源码在source下面。image.png

如果是编译Android端的库的话,编译的时候要按照下面的规则。WechatIMG148.png

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_CPP_EXTENSION := .cc

LOCAL_SRC_FILES := \
    source/compare.cc           \
    source/compare_common.cc    \
    source/convert.cc           \
    source/convert_argb.cc      \
    source/convert_from.cc      \
    source/convert_from_argb.cc \
    source/convert_to_argb.cc   \
    source/convert_to_i420.cc   \
    source/cpu_id.cc            \
    source/planar_functions.cc  \
    source/rotate.cc            \
    source/rotate_any.cc        \
    source/rotate_argb.cc       \
    source/rotate_common.cc     \
    source/row_any.cc           \
    source/row_common.cc        \
    source/scale.cc             \
    source/scale_any.cc         \
    source/scale_argb.cc        \
    source/scale_common.cc      \
    source/video_common.cc

ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
    LOCAL_CFLAGS += -DLIBYUV_NEON
    LOCAL_SRC_FILES += \
        source/compare_neon.cc.neon    \
        source/rotate_neon.cc.neon     \
        source/row_neon.cc.neon        \
        source/scale_neon.cc.neon
endif

ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
    LOCAL_CFLAGS += -DLIBYUV_NEON
    LOCAL_SRC_FILES += \
        source/compare_neon64.cc    \
        source/rotate_neon64.cc     \
        source/row_neon64.cc        \
        source/scale_neon64.cc 
endif

ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI), x86 x86_64))
    LOCAL_SRC_FILES += \
        source/compare_gcc.cc       \
        source/rotate_gcc.cc        \
        source/row_gcc.cc           \
        source/scale_gcc.cc
endif


LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include

LOCAL_MODULE := libyuv
LOCAL_MODULE_TAGS := optional

include $(BUILD_SHARED_LIBRARY)复制代码

记住一定要链接libjpeg库,不然像全色域的YUVJ420P的格式是无法识别的。 如果要实现YUV转换到RGB,可以看一下convert_argb的头文件:github.com/lemenkov/li…

// Conversion matrix for YUV to RGB
LIBYUV_API extern const struct YuvConstants kYuvI601Constants;   // BT.601
LIBYUV_API extern const struct YuvConstants kYuvJPEGConstants;   // BT.601 full
LIBYUV_API extern const struct YuvConstants kYuvH709Constants;   // BT.709
LIBYUV_API extern const struct YuvConstants kYuvF709Constants;   // BT.709 full
LIBYUV_API extern const struct YuvConstants kYuv2020Constants;   // BT.2020
LIBYUV_API extern const struct YuvConstants kYuvV2020Constants;  // BT.2020 full

// Conversion matrix for YVU to BGR
LIBYUV_API extern const struct YuvConstants kYvuI601Constants;   // BT.601
LIBYUV_API extern const struct YuvConstants kYvuJPEGConstants;   // BT.601 full
LIBYUV_API extern const struct YuvConstants kYvuH709Constants;   // BT.709
LIBYUV_API extern const struct YuvConstants kYvuF709Constants;   // BT.709 full
LIBYUV_API extern const struct YuvConstants kYvu2020Constants;   // BT.2020
LIBYUV_API extern const struct YuvConstants kYvuV2020Constants;  // BT.2020 full

int I420ToARGB(const uint8_t* src_y,
               int src_stride_y,
               const uint8_t* src_u,
               int src_stride_u,
               const uint8_t* src_v,
               int src_stride_v,
               uint8_t* dst_argb,
               int dst_stride_argb,
               int width,
               int height);复制代码
  • src_y、src_u、src_v表示Y/U/V三个分量数据源,src_stride_y、src_stride_u、src_stride_v表示Y/U/V三个分量的宽,高都是相同的。

  • dst_argb表示转换为ARGB的数据源,dst_stride_argb是4 * width,是原来4个分量数据的汇总。

FFmpeg swscale方式

FFmpeg中本来就有swscale的方式来实现YUV和RGB的转换,但是也是需要编译libjpeg库才能实现的。 实现的方式是:

struct SwsContext *img_convert_ctx = NULL;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
AVFrame *pFrameRGB = av_frame_alloc();
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);复制代码
  • 首先创建SWS上下文

  • 初始化SWS上下文,确立要转换的的目标PIX_FMT格式,以及转换的算法

  • 初始化目标数据,调用sws_scale转换

pCodecCtx->pix_fmt是源PIX_FMT,根据你输入的视频源的具体格式。 AV_PIX_FMT_RGB24是输出的PIX_FMT,SWS_FAST_BILINEAR是转换的算法。推荐使用SWS_FAST_BILINEAR,速度快,质量好。

/* values for the flags, the stuff on the command line is different */
#define SWS_FAST_BILINEAR     1
#define SWS_BILINEAR          2
#define SWS_BICUBIC           4
#define SWS_X                 8
#define SWS_POINT          0x10
#define SWS_AREA           0x20
#define SWS_BICUBLIN       0x40
#define SWS_GAUSS          0x80
#define SWS_SINC          0x100
#define SWS_LANCZOS       0x200
#define SWS_SPLINE        0x400复制代码

三种转换方式的优劣

转换优劣OpenGL shaderlibyuvFFmpeg swscale
优点速度快
不增加包体积
速度快,仅次OpenGL shader方式
兼容性好
功能全面
FFmpeg原生提供
缺点兼容性一般需要接入libjpeg速度慢
需要接入libjpeg


作者:码上就说
链接:https://juejin.cn/post/7033237038446936101


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐