百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

图像显示有色差?YUV 与RGB 用对了吗?

liebian365 2024-11-21 17:36 22 浏览 0 评论

一、背景

问题来源于游戏中心进行Switch投屏时有用户反馈车内投屏效果没有在他家的电视投屏效果好,车机投屏效果偏白,很刺眼。使用OBS 抓取一张图像比对确实存在问题(注意看两张图片耳朵处,左边为问题图片,右边为OBS投屏的效果)。

从上面两张图可以看出呈现的问题是:整体效果比原始图像偏白且存在色差。遇到问题莫慌,莫怕,更不怕横向对比。我们的服务宗旨是开发业内领先的软件产品,给用户提供一流的体验。直面冷对并修复它才是一个合格的程序员自我修养。牛已经吹了,那就干吧。

二、解决思路

2.1 采集卡投屏的数据链路

通过采集卡将Switch画面数据投屏到车机上,完整的数据链路如下:

2.2 从源头开始

  1. 采集卡投屏最原始的数据是通过UVC协议传送的MJPEG编码数据,UVC 这块可以断定没问题否则数据在最开始的时候就会解码失败,而不是出现色差。为了验证这个诊断的正确性,这里可以做如下验证:
  2. 保留一段MJPEG 数据到本地,然后找一个可以播放MJPEG 编码的播放器进行播放,看看效果。
  3. 也可以将MJPEG->ARGB 然后按转换成bitmap 保存到本地查看位图。
  4. 我这里采用的第二种方式来验证,保存的位图效果如下:
int UVCPreview::bmp_write(unsigned char *image, int imageWidth, int imageHeight, const char *filename) {

    /*
     *  bmp文件头(14 bytes) + 位图信息头(40 bytes) + 调色板(由颜色索引数决定) + 位图数据(由图像尺寸决定)
     *  0x42, 0x4d  fileType -- 相对String "BM"
     *  0, 0, 0, 0  -- The size of the BMP file in bytes
     *  0, 0, 0, 0  -- Reserved
     *  54, 0, 0, 0 -- The offset, i.e. starting address, of the byte where the bitmap image data (pixel array) can be found,
     *  40, 0, 0, 0 -- The size of this header (12 bytes)
     *  0, 0, 0, 0  -- The bitmap width in pixels
     *  0, 0, 0, 0  -- The bitmap height in pixels
     *  1, 0 -- The number of color planes, must be 1
     *  32, 0 -- The number of bits per pixel 即 RGBA 8888
     * */
    unsigned char header[54] = {
            0x42, 0x4d, 0, 0, 0, 0, 0, 0, 0, 0,
            54, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 32, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0
    };

    long file_size = (long)imageWidth * (long)imageHeight * 4 + 54;
    header[2] = (unsigned char)(file_size &0x000000ff);
    header[3] = (file_size >> 8) & 0x000000ff;
    header[4] = (file_size >> 16) & 0x000000ff;
    header[5] = (file_size >> 24) & 0x000000ff;
    long width = imageWidth;
    header[18] = width & 0x000000ff;
    header[19] = (width >> 8) &0x000000ff;
    header[20] = (width >> 16) &0x000000ff;
    header[21] = (width >> 24) &0x000000ff;

    long height = imageHeight;
    header[22] = height &0x000000ff;
    header[23] = (height >> 8) &0x000000ff;
    header[24] = (height >> 16) &0x000000ff;
    header[25] = (height >> 24) &0x000000ff;

    char fname_bmp[128];
    sprintf(fname_bmp, "%s", filename);

    FILE *fp;
    if (!(fp = fopen(fname_bmp, "wb")))
        return -1;

    fwrite(header, sizeof(unsigned char), 54, fp);
    fwrite(image, sizeof(unsigned char), (size_t)(long)imageWidth * imageHeight * 4, fp);

    fclose(fp);
    return 0;
}

从位图可以看出效果正常,也验证了我们的推断。

2.2 验证MJPEG->YUYV 解码阶段

  1. 原始的MJPEG 数据没问题,接下来需要验证的环节为MJPEG->YUYV 解码过程是否存在问题。为了验证这个环节是否有问题,需要先了解MJPEG与YUYV 是个啥。

MJPEG:Motion Joint Photographic Experts Group 是一种影像压缩格式,其中每一帧图像都分别使用JPEG编码。M-JPEG常用在数字相机和摄像头之类的图像采集设备上。MJPEG即动态JPEG,按照至少达到25帧/秒速度使用JPEG压缩算法压缩视频信号,完成动态视频的压缩。MJPEG压缩标准是由JPEG专家组制定的,其图像格式是对每一帧JPEG图像进行压缩。MJPEG是一种基于静态图像压缩技术JPEG发展起来的动态图像压缩技术,可以生成序列化的运动图像。实际上MJPEG图像数据流就是一帧一帧的JPEG格式图片 MJPEG只是有帧内压缩(区别于算法更复杂的帧间压缩),只单独的对某一帧进行压缩,而不考虑影像画面中不同帧之间的变化。因此压缩效率比较低,而使用了帧间压缩的现代影像压缩格式(如MPEG1、MPEG2和H.264/MPEG-4 AVC)一般压缩率比较高。这里与H264 编码格式有一些差异,H264 不但有帧内压缩还是帧参考。帧内压缩算法的帧通常只有I帧,帧间参考的则是P/B帧。

我们不需要完全去扣MJPEG 的编码格式各个字节的含义,这个对问题的解决帮助不大(原因第一步中就验证了原始MJPEG 数据没问题),只需要了解这玩意是个啥。

将YUYV 数据保存到本地,然后使用如下命令验证解码环节是否有问题:

ffplay -f rawvideo -video_size 640x360 /Users/rambo.liu/Desktop/test2.yuv 

很好,这一步也没问题,那么问题就很明显了:YUYV->ARGB 环节出错了导致显示在车机上的图像出现了色差。先来了解本文的第一个主角YUYV。

三、YUYV格式介绍

YUV(YCbCr)是一种像素格式,常见于视频编码与静态图像。与 RGB格式(红-绿-蓝)相反,YUV分别由一个称为 Y(相当于灰度)的“亮度”分量(Luminance or Luma)和两个称为 U(蓝色投影 Cb)和 V(红色投影 Cr)的“色度”分量(Chrominance or Chroma)表示,由此得名。仅有 Y 分量而没有 UV 分量信息,一样可以显示完整的黑白(灰度)图像,解决了模拟信号电视黑白与彩色的兼容问题。

3.1 采样

色度通道(UV)的采样率可以低于亮度通道(Y),而不会显着降低感知质量。一种称为 “A:B:C” 的表示法用于描述相对于 Y 采样, U 和 V 的频率:

  • 4:4:4 表示不降低色度(UV)通道的采样率。每个 Y 分量对应一组 UV 分量。
  • 4:2:2 表示 2:1 水平下采样,没有垂直下采样。每两个 Y 分量共享一组 UV 分量。
  • 4:2:0 表示 2:1 水平下采样,同时 2:1 垂直下采样。每四个 Y 分量共享一组 UV 分量。
  • 4:1:1 表示 4:1 水平下采样,没有垂直下采样。每四个 Y 分量共享一组 UV 分量。4:1:1 采样比其他格式少见,本文不再详细讨论。

下图显示了如何针对每个下采样率采样色度。亮度样本用十字表示,色度样本用圆圈表示。

3.2 存储格式

YUV 在存储上通常分为平面格式(Planar),半平面格式(Semi-Planar)以及打包格式(Packed)。各格式如下图所示:

3.2.1 Planar 平面格式

平面格式有时也称为三面格式(Triplanar),即 Y, U, V 三个分量各自使用单独的数组保存,这种三平面分离的格式比较方便视频编码Planar 平面格式,指先连续存储所有像素点的 Y 分量,再存储 U 分量,最后才是 V 分量。典型的例子有 I420(视频中最常用),基于 YUV 4:2:0 采样格式。以 4 * 4 像素为例,排列方式如下:

使用平面格式的有以下几种:

  1. YUV420 平面格式有以下两种
  2. YU12 (I420)
    1. 4:2:0 Formats, 12 Bits per Pixel, 3 Planars
    2. 其中的4:2:0比例是指 Y、U、V 表示的像素,三者分别占的比值。是指每 4 个 Y 采样,对应 2 个 U 采样或者 2 个 V 采样,注意其中并不是表示 2 个 U 和 0 个 V,而是指无论水平下采样还是垂直下采样,色度采样都只有亮度的一半。该存储格式下,平均每个像素占用空间为 8+2+2=12 位(每种原色占用一个字节(8bit))。假设分辨率信息为width*height那么YUV 文件大小为:width*height*(8bit + 2 bit + 2 bit) = width * height *1.5byte。
    3. YU12 即 I420,也叫 IYUV,属于 YUV420P 格式。三个平面,分别存储 Y U V 分量。每四个 Y 分量共享一组 UV 分量。U、V 平面的 strides, width 和 height 都是 Y 平面的一半,因此一个像素 12 bits,内存排列如下图所示:

从图中可看出,U、V 平面的每行字节数(strides)、高(height)都是 Y 平面的一半。I420 是音视频开发中常用的一种格式。这些资料来自Recommended 8-Bit YUV Formats for Video Rendering - Win32 apps | Microsoft Learn了解了这些格式之后就需要从数据中取出Y、U、V分量,不然都是白搭。

先假设YUV 数据都存储在uint8_t* in 这个指针中,那么Y、U、V分量的在上述存储格式下取法如下:

  1. y在前面,共width *heigh字节。
  2. 接着u,共width *heigh / 4字节,宽高都是y的一半。
  3. 接着v,与u一样。

size_t size = in->width * in->height;  //
uint8_t *y = (uint8_t *) out->data + size;   //取y分量
uint8_t *u = y + size / 4;          //  取u 分量
uint8_t *v = u + size / 4;          // 取v 分量

实践是检验真理的唯一标准,下面使用一个demo 将各分量取出来,验证上述推断是否正确。

原始图片:

序号

操作项

结果

1

先使用如下命令将一张图片转换为yuv420 格式

ffmpeg -i /Users/rambo.liu/Desktop/af4d02c6e5894504bb7068fbd4563c72.jpeg -s 510x510 -pix_fmt yuv420p input.yuv


2

检验输出的yuv 数据是否正确

ffplay -f rawvideo -video_size 510x510 ./input.yuv



编码抽Y、U、V分量再验证。

FILE *fp_yuv = fopen(inputPath, "rb+");

FILE *fp_y = fopen("output_420_y.y", "wb+");
FILE *fp_u = fopen("output_420_u.u", "wb+");
FILE *fp_v = fopen("output_420_v.v", "wb+");

unsigned char *data = (unsigned char *) malloc(width * height * 3 / 2);

fread(data, 1, width * height * 3 / 2, fp_yuv); //一帧YUV420P像素数据一共占用w*h*3/2 Byte的数据
//Y
fwrite(data, 1, width * height, fp_y); // w*h Byte存储Y
//U
fwrite(data + width * height, 1, width * height / 4, fp_u); //w*h*1/4 Byte存储U
//V
fwrite(data + width * height * 5 / 4, 1, width * height / 4, fp_v); //w*h*1/4 Byte存储V

生成的3个文件并使用YUV 播放器播放各分量效果:

序号

分量文件

预览效果

图片分辨率信息

大小信息

1

output_420_y.y

510x510

图片是截图信息 分辨率信息有误差


从分量图大小也可以得知

  1. Y 大小254 = (63.5 * 4)u *4 = (63.5 * 4) v *4
  2. Y 分量分辨率信息与原图一致
  3. U/V 分量分辨率信息 width= 原图 width / 2,height = 原图height / 2
  4. 上述的结论与我们的推断完全一样


2

output_420_v.y

255x255


3

output_420_u.y

255x255

其他格式取分量值也类似,不再赘述了。

  1. YV12
  2. 4:2:0 Formats, 12 Bits per Pixel, 3 Planars
  3. YV12I420 几乎一样,仅改变了 U, V 平面的顺序。内存排列如下图所示:


  1. YUV422平面格式
  2. I422
    1. 4:2:2 Formats, 16 Bits per Pixel, 3 Planars
    2. I422 属于 YUV422P 格式。三个平面,分别存储 Y U V 分量。每两个 Y 分量共享一组 UV 分量。U、V 平面的 strides, width 是 Y 平面的一半,但 height 与 Y 平面一致,因此一个像素 16 bits(每2个 Y 分量共用一对 UV 分量,每像素占用 (Y + 0.5U + 0.5V = 8 + 4 + 4 = 16bits)),内存排列如下图所示:
    1. 从图中可看出,U、V 平面的每行字节数(strides)是 Y 平面的一半,高(height)与 Y 平面一致。
  1. J422
  • 4:2:2 Formats, 16 Bits per Pixel, 3 Planars
  • J422 与 I422 完全相同,但具有完整范围(0-255,full range)的亮度(Y)分量,而不是有限范围(16-240,limited range,在 iOS 上也叫做 video range)。色度(UV)分量与 I420 中的完全相同。
  1. YUV444 平面格式
  • YUV444又称全采样,意思是每个Y分量使用一个UV分量,得到的图像和原始RGB图像的大小是一样的
  • I444
  • 4:4:4 Formats, 24 Bits per Pixel, 3 Planars
  • 每1个 Y 分量对于一对 UV 分量,每像素占用 (Y + U + V = 8 + 8 + 8 = 24bits)3 字节
  1. 其他的半平面模式与打包格式就不一一展开了,感兴趣的可自行查阅资料

了解YUV的存储格式有利于转换时如何填参数,以及问题如何解决,比如libyuv 如下函数的使用:

int I420ToABGR(const uint8_t* src_y,    //Y 分量的原始数据
               int src_stride_y,        //Y 分量的步长,可理解转换为目的图像的宽
               const uint8_t* src_u,    //U分量的原始数据
               int src_stride_u,        //U分量的步长,I420 格式下,U分量 的步长为Y 分量的一半,即宽的一半
               const uint8_t* src_v,    //V分量的原始数据
               int src_stride_v,        //V分量的步长,I420 格式下,V分量 的步长为Y 分量的一半,即宽的一半
               uint8_t* dst_abgr,       //转换目的agbr的数据指针
               int dst_stride_abgr,     //转换agbr的步长 通常为宽度*4(转换之后每像素占用r、g、b、a 4个通道)
               int width,               // 图像宽度
               int height)              //图像高度

3.3 参数使用错误时的转换效果

  1. 步长使用错误时,比如:YUV420 格式下转换时uv分量步长与y分量一样,得到的效果如下:

Uv 分量步长= y 分量步长

转换后abgr 步长==y分量 步长

转换后abgr 步长==y分量 步长/2



  1. 分量使用错误时转换效果

都使用y分量转换时,且uv 步长= y 步长

Uv 分量反转,即 转换时 :yvu (红蓝反转)

转换后abgr 步长==width/2

  1. 从上述的例子可以看出,分量使用错误时影响转换后的颜色效果,步长使用错误时影响转换后的大小

经过上述一通尝试之后似乎还是没解决我们的问题(注意看两张图片耳朵处,左边为问题图片,右边为OBS投屏的效果

但至少可以得出一个结论:原始MJPEG 与MJPEG->YUV 数据都没问题,那么问题就很清晰了,就是出现在:YUV->RGB 这个环节。libyuv 提供的转换函数有:

int I420ToBGRA(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_bgra,
               int dst_stride_bgra,
               int width,
               int height);

LIBYUV_API
int I420ToABGR(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_abgr,
               int dst_stride_abgr,
               int width,
               int height);

LIBYUV_API
int I420ToRGBA(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_rgba,
               int dst_stride_rgba,
               int width,
               int height);
               int I420ToRGB24(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_rgb24,
                int dst_stride_rgb24,
                int width,
                int height);

LIBYUV_API
int I420ToRAW(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_raw,
              int dst_stride_raw,
              int width,
              int height);
              int Android420ToABGR(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,
                     int src_pixel_stride_uv,
                     uint8_t* dst_abgr,
                     int dst_stride_abgr,
                     int width,
                     int height);

// Convert NV12 to RGB565.
LIBYUV_API
int NV12ToRGB565(const uint8_t* src_y,
                 int src_stride_y,
                 const uint8_t* src_uv,
                 int src_stride_uv,
                 uint8_t* dst_rgb565,
                 int dst_stride_rgb565,
                 int width,
                 int height);
                 //下面还有很多  就不一一列举了

转换的函数有点多,逐一尝试貌似可以得到我们想要的结果。但本在打破沙锅问到底的原则不能这么轻易的停下追寻真理的脚步,否则下次遇到坑时估计还会摔跤。

揭开真相的倒数第二步:了解RGB。

四、RGB 格式介绍

RGB是构成多种颜色的三基色(红绿蓝),也称为加成色。主要是图像的采集和显示。RGB 格式非常好理解,R、G、B 分别表示红绿蓝三个通道的值。RGB 格式根据存储的位数可以分为 16 位格式 、 24 位格式 和 32 位格式。在 FFmpeg 的源码中也可以看到 16bpp、24bpp 和 32bpp 的注释说明。(因为内存的字节顺序有大端序和小端序区别,RGB 可能被表达为 BGR 顺序,本质上是一样的。注意通道顺序决定颜色的显色顺序如果转换时通道顺序乱了有可能出错,这次遇到的问题就是通道顺序乱了

enum AVPixelFormat {
  // ... 省略部分不怎么重要的类型
  ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)

  ///< packed RGB 8:8:8, 24bpp, RGBRGB...
  AV_PIX_FMT_RGB24,
  ///< packed RGB 8:8:8, 24bpp, BGRBGR...
  AV_PIX_FMT_BGR24,
  
  ///< packed ARGB 8:8:8:8, 32bpp, ARGBARGB...
  AV_PIX_FMT_ARGB,
  ///< packed RGBA 8:8:8:8, 32bpp, RGBARGBA...
  AV_PIX_FMT_RGBA,
  ///< packed ABGR 8:8:8:8, 32bpp, ABGRABGR...
  AV_PIX_FMT_ABGR,
  ///< packed BGRA 8:8:8:8, 32bpp, BGRABGRA...
  AV_PIX_FMT_BGRA,

  ///< packed RGB 5:6:5, 16bpp, (msb)   5R 6G 5B(lsb), big-endian
  AV_PIX_FMT_RGB565BE,
  ///< packed RGB 5:6:5, 16bpp, (msb)   5R 6G 5B(lsb), little-endian
  AV_PIX_FMT_RGB565LE,
  ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), big-endian   , X=unused/undefined
  AV_PIX_FMT_RGB555BE,
  ///< packed RGB 5:5:5, 16bpp, (msb)1X 5R 5G 5B(lsb), little-endian, X=unused/undefined
  AV_PIX_FMT_RGB555LE,

  ///< packed BGR 5:6:5, 16bpp, (msb)   5B 6G 5R(lsb), big-endian
  AV_PIX_FMT_BGR565BE,
  ///< packed BGR 5:6:5, 16bpp, (msb)   5B 6G 5R(lsb), little-endian
  AV_PIX_FMT_BGR565LE,
  ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), big-endian   , X=unused/undefined
  AV_PIX_FMT_BGR555BE,
  ///< packed BGR 5:5:5, 16bpp, (msb)1X 5B 5G 5R(lsb), little-endian, X=unused/undefined
  AV_PIX_FMT_BGR555LE,
    
}

16 位格式主要是 RGB555 和 RGB565 两种表达方式。RGB555 是每个通道分量占 5 位,空出一位不用。RGB565 则顾名思义,R 和 B 通道占 5 位,G 通道占 6 位。

# RGB555
XRRR RRGG GGGB BBBB

# RGB565
RRRR RGGG GGGB BBBB

24位格式和32位格式我们最常用到,RGB24 表示每个颜色通道分量占 8 位,共 24 位。RGB32 表示除了每个颜色通道分量占8位外,还有8位用于表示透明通道,又称RGBA或ARGB等。

# RGB24
RRRR RRRR GGGG GGGG BBBB BBBB

# RGB32
RRRR RRRR GGGG GGGG BBBB BBBB AAAA AAAA

其中ARGB 是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构。

RGBA 是代表Red(红色) Green(绿色) Blue(蓝色)和 Alpha的色彩空间。虽然它有的时候被描述为一个颜色空间,但是它其实仅仅是RGB模型的附加了额外的信息。采用的颜色是RGB,可以属于任何一种RGB颜色空间,但是Catmull和Smith在1971至1972年间提出了这个不可或缺的alpha数值,使得alpha渲染和alpha合成变得可能。提出者以alpha来命名是源于经典的线性插值方程αA + (1-α)B所用的就是这个希腊字母。 PNG是一种使用RGBA的图像格式。

android 定义颜色color时6位或8位值的区别: 6位(#000000)就是RGB值 8位(#1e000000)ARGB 头两位是透明度,00是完全透明,ff是完全不透明,后6位是RGB值,比较适中的透明度值。

揭开真相的最后一步:将转换后有问题的rgb 数据保存为位图然后使用sublime 查看它的rgba 通道顺序

  1. test4.txt 保存的bitmap 图片数据,并修改后缀名
  2. 使用sublime 打开得到如下数据

红框内容:文件头 14Byte

黄框内容:信息头 40Byte

绿框表示颜色值:颜色数据

附BMP 格式分析:

BMP文件格式,又称为Bitmap(位图)或是DIB(Device-Independent Device,设备无关位图),是Windows系统中广泛使用的图像文件格式。由于它可以不作任何变换地保存图像像素域的数据,因此成为我们取得RAW数据的重要来源。Windows的图形用户界面(graphical user interfaces)也在它的内建图像子系统GDI中对BMP格式提供了支持。

下面以Sublime为分析工具,结合Windows的位图数据结构对BMP文件格式进行一个深度的剖析。

BMP文件的数据按照从文件头开始的先后顺序分为四个部分:

1. bmp文件头(bmp file header):提供文件的格式、大小等信息

2. 位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息

3. 调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表

4. 位图数据(bitmap data):就是图像数据

下面结合Windows结构体的定义,通过一个表来分析这四个部分。

最后三个0字节是windows的字节对齐产生的,由于是红色图片,所以颜色值为0000FF(BGR)但Android 平台8位RGB通道顺序为ARGB 头两位是透明度,00是完全透明,ff是完全不透明,后6位是RGB值。比如:白色(#FFFFFFFF)

所以需要需要将yuv 数据转换ARGB 的通道顺序。转换之后就得到了正确的结果:

到此问题得到了解决,顺便介绍了YUV与RGB 及其关系。后续流媒体领域遇到类似的问题也可以参照此方法分析。

五、YUV 与RGB 格式关系

YUV 是视频数据的原始格式,音频的原始数据格式为PCM。Android 平台将最终图像数据显示的格式为RGB,所以在显示时需要将YUV 转换RGB。至于转换的关系见下公式:

uint32_t UVCPreview::YUV2RGB(int nY, int nU, int nV) {
   nY -= 16;
   nU -= 128;
   nV -= 128;
   if (nY < 0) nY = 0;

   // This is the floating point equivalent. We do the conversion in integer
   // because some Android devices do not have floating point in hardware.
//  int nR = (int)(1.164 * nY + 2.018 * nU);
//  int nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU);
//  int nB = (int)(1.164 * nY + 1.596 * nV);

   int nR = 1192 * nY + 1634 * nV;
   int nG = 1192 * nY - 833 * nV - 255 * nU;
   int nB = 1192 * nY + 2066 * nU;

   nR = MIN(kMaxChannelValue, MAX(0, nR));
   nG = MIN(kMaxChannelValue, MAX(0, nG));
   nB = MIN(kMaxChannelValue, MAX(0, nB));

   nR = (nR >> 10) & 0xff;
   nG = (nG >> 10) & 0xff;
   nB = (nB >> 10) & 0xff;

   return 0xff000000 | (nR << 16) | (nG << 8) | nB;
}

这个公式并不唯一,网上有很多视觉算法大神的转换大法,这里贴出一个仅供参考。对于这两者的关系只能就此打住了(主要是才疏学浅了解有限,再深挖下去就得整视觉那套了,远远超出我的业务范围了)

六、参考文档

  1. Microsoft: Recommended 8-Bit YUV Formats for Video Rendering

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: