乐趣区

关于音视频:音视频YUV

什麼是 yuv

YUV 色彩编码采纳的是 亮堂度 色度 来指定像素的色彩。

其中,Y 示意亮堂度(Luminance、Luma),而 U 和 V 示意色度、浓度(Chrominance、Chroma)。

和 RGB 示意图像相似,每个像素点都蕴含 Y、U、V 重量。然而它的 Y 和 UV 重量是能够拆散的,如果没有 UV 重量一样能够显示残缺的图像,只不过是黑白的,所以 yuv 图像能够兼容於黑白影像和黑白影像。

为什么 yuv 更省空间

RGB 像素表示法很简略,如果你没做过数字图像和视频的开发,可能很少据说过 YUV。但在数字图像和视频编码里畛域,YUV 像素表示法十分风行,有几个起因造成。首先,人眼对亮度更敏感,对色彩的敏感度稍弱,所以应用 YUV 来示意图像能够节俭存储资源。其次因为数字摄像机传感器不能间接采样三原色,所以 RGB 也不适宜硬件解决。因而 YUV 才如此利用宽泛。

用 RGB 示意像素须要用 3 个字节。但 YUV 示意一个像素,可能是 3 个字节,也可能是 2 个字节(丢掉 U 或者丢掉 V),还可能只有 1 个字节(丢掉 U 和 V)。占用字节大小的不同因为采纳不同的采样形式。

YUV 采样格局

YUV 图像的支流采样形式有如下三种:

  • YUV 4:4:4 采样
  • YUV 4:2:2 采样
  • YUV 4:2:0 采样

YUV 4:4:4 采样

YUV 4:4:4 采样,意味着 Y、U、V 三个重量的采样比例雷同,一个像素点,都是(Y、U、V)3 个字节组成

举个例子:如果图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]

那么采样的码流为:Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3 

最初映射出的像素点仍旧为 [Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3] 

YUV 4:2:2 采样

YUV 4:2:2 采样,意味着 UV 重量是 Y 重量采样的一半,Y 重量和 UV 重量依照 2 : 1 的比例采样。如果程度方向有 10 个像素点,那么采样了 10 个 Y 重量,而只采样了 5 个 UV 重量。

 举个例子:如果图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]

 那么采样的码流为:Y0 U0 Y1 V1 Y2 U2 Y3 V3 

 其中,每采样过一个像素点,都会采样其 Y 重量,而 U、V 重量就会距离一个采集一个。最初映射出的像素点为 [Y0 U0 V1]、[Y1 U0 V1]、[Y2 U2 V3]、[Y3 U2 V3]

YUV 4:2:0 采样

举个例子:假如图像像素为:[Y0 U0 V0]、[Y1 U1 V1]、[Y2 U2 V2]、[Y3 U3 V3]
[Y5 U5 V5]、[Y6 U6 V6]、[Y7 U7 V7]、[Y8 U8 V8]

那么采样的码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8

其中,每采样过一个像素点,都会采样其 Y 重量,而 U、V 重量就会距离一行依照 2 : 1 进行采样。最初映射出的像素点为:[Y0 U0 V5]、[Y1 U0 V5]、[Y2 U2 V7]、[Y3 U2 V7]
[Y5 U0 V5]、[Y6 U0 V5]、[Y7 U2 V7]、[Y8 U2 V7]

YUV 存储格局

采样之后,就是 YUV 的存储,往往咱们操作的都是存储格局的二进制,所以要了解 YUV 存储的排列形式。能力失常解析 YUV 图像。

YUV 的存储格局,有两种:

  • planar 立体格局
    • 指先间断存储所有像素点的 Y 重量,而后存储 U 重量,最初是 V 重量。
  • packed 打包模式
    • 指每个像素点的 Y、U、V 重量是间断交替存储的。

这里篇幅关系,只介绍 YUV420 的存储格局(比拟罕用)

YUV 420P 和 YUV 420SP 都是基于 Planar 立体模式 进行存储的,先存储所有的 Y 重量后,YUV420P 类型就会先存储所有的 U 重量或者 V 重量,而 YUV420SP 则是依照 UV 或者 VU 的交替程序进行存储了,具体查看看下图:

YUV420SP 的格局:

YUV420P 的格局:

為什麼 yuv 要轉換成 rgb?

浏览器不能间接辨认输入 yuv 图像,须要转成 rgb 格局。

YUV 与 RBG 的转换

为了实现格局转换,咱们首先要明确待转换格局和指标格局的特点和互相转换关系,这是编程实现转换的外围。

YUV 转 RGB 的公式,我查阅到的不少于 3 个,但我不明确原理,须要查阅下:

Y = 0.298R + 0.612G + 0.117B;
 
U = -0.168R - 0.330G + 0.498B + 128;
 
V = 0.449R - 0.435G - 0.083B + 128;
 
R = Y + 1.4075(V - 128);
 
G = Y - 0.3455(U - 128) - 0.7169(V - 128);
 
B = Y + 1.779(U - 128);

比方 YUV 图像转 RGB 图像,须要一一遍历像素所需的 YUV 字节,通过公式转换为每个像素对应的 RGB 字节即可

YUV 中的 stride

stride 能够翻译为:跨距、步长
stride 指在内存中每行像素所占的空间。如下图所示,为了实现内存对齐,每行像素在内存中所占的空间并不是图像的宽度。

个别 stride 都是等于图像的 width,除非有内存对齐的须要,不过我还没遇到这种状况。

JS 实现 YUV 转 RGB 渲染图像

在线预览地址

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style></style>
  </head>
  <body>
    <img id="img" src="./cat-color.jpg" alt="" />
    <input type="file" />
    <canvas id="canvas"></canvas>
    <script>
      var clamp = function(val) {if(val < 0) return 0
        if(val > 255) return 255
        return val
      }
      var image = document.getElementById("img");
      image.onload = function () {var canvas = document.createElement("canvas");
        (canvas.width = image.naturalWidth),
          (canvas.height = image.naturalHeight);
        var context = canvas.getContext("2d");
        context.drawImage(image, 0, 0);
        var data = context.getImageData(0, 0, canvas.width, canvas.height);
        console.log(data);
      };
      const input = document.querySelector("input");
      input.addEventListener("change", function (e) {var file = e.target.files[0];
        var fr = new FileReader();
        fr.readAsArrayBuffer(file);
        fr.addEventListener(
          "loadend",
          (e) => {
            var buf = e.target.result;
            var data = new Uint8ClampedArray(buf);
            // 这里是没问题的
            const width = 640;
            const height = 480;
            const bytesY = data.slice(0, width * height);
            const bytesCb = data.slice(width * height, width * height * 1.25);
            const bytesCr = data.slice(
              width * height * 1.25,
              width * height * 1.5
            );

            const strideY = 640;
            const strideCb = strideY / 2;
            const strideCr = strideY / 2;
            const hdec = 1;
            const vdec = 1;
            let output = new Uint8ClampedArray(width * height * 4);
            let xdec = 0;
            let outPtr = 0;
            let pos = 0;
            for (y = 0; y < height; y++) {
              xdec = 0;
              ydec = y >> vdec;

              YPtr = (ydec * strideY) | 0;
              CbPtr = (ydec * strideCb) | 0;
              CrPtr = (ydec * strideCr) | 0;
              // 遍历
              for (x = 0; x < width; x++) {
                xdec = x >> hdec;
                colorCb = bytesCb[CbPtr + xdec] | 0;
                colorCr = bytesCr[CrPtr + xdec] | 0;
                if(pos == 1280) {console.log(CbPtr + xdec)
                  console.log(CrPtr + xdec)
                }

                let Y = bytesY[pos];
                let U = colorCb;
                let V = colorCr;
                
                let C = Y - 16;
                let D = U - 128;
                let E = V - 128;

                output[outPtr] = clamp((298*C + 409 * E +128)>>8)

                output[outPtr + 1] = clamp((298*C - 100* D - 208* E+ 128)>>8)
                output[outPtr + 2] = clamp((298*C + 516* D- 128)>>8)

                output[outPtr + 3] = 255;
                outPtr += 4;
                pos++;
              }
            }
            console.log(output);
            var canvas = document.getElementById("canvas");
            canvas.width = width;
            canvas.height = height;
            var ctx = canvas.getContext("2d");
            var imgData = ctx.createImageData(width, height);
            for (var i = 0; i < output.length; i++) {imgData.data[i] = output[i];
            }
            console.log(imgData);
            ctx.putImageData(imgData, 0, 0, 0, 0, width, height);
          },
          false
        );
      });
    </script>
  </body>
</html>

参考文章

  • yuv 图像里的 stride 和 plane 的解释
  • 一文读懂 YUV 的采样与格局
  • 音视频平庸之路之 YUV 像素介绍
退出移动版