canvas的drawImage函数如何将YUV转换为sRGB

问题描述 投票:0回答:1

我使用网络编解码器,发现当我手动将 NV12 格式转换为 RGB 格式时,我得到的值与简单地将 VideoFrame 写入画布并读取 imageData 时得到的值略有不同。

画布中的图像数据 [87, 87, 39, 255, 86, 86, 39, 255, 85, 85, 39, 255, 83, 84, 39, 255, 81, 83, 39, 255, 79, 82, 38, 255, 77 , 81, 37, 255, 76, 80, 36, 255, 79, 83, ... ]

手动将 NV12 格式转换为 RGB 格式的图像数据: [94, 101, 62, 255, 94, 101, 62, 255, 95, 102, 63, 255, 97, 103, 64, 255, 97, 103, 64, 255, 98, 104, 66, 255, 100 , 106, 68, 255, 101, 108, 69, 25, ...]

谁能告诉我这些差异是如何产生的,或者 Canvas 如何将 YUV 格式转换为 RGB 格式?

这是两个不同选项的代码:

  1. 在画布上绘制图像并从画布中获取ImageData
    this.context.drawImage(frame, 0,0, this.canvas.width, this.canvas.height);
    let imageData = this.context!.getImageData(0,0,this.canvas.width,this.canvas.height);
  1. 将 VideoFrame 的内容写入数组并将数组转换为 RGB 格式。
  ...
  let imageData = convertToImageData(videoFrame);
  ...

  public async convertToImageData(frame) {
    let buffer : Uint8ClampedArray = new Uint8ClampedArray(frame.allocationSize());
    await frame.copyTo(buffer) 
    let imageData = await this.convertNV12ToRGB(buffer)
    return imageData;
  }

  private convertNV12ToRGB(buffer : Uint8ClampedArray) {
    // Y should be from 0-921599 (1280x720)
    // Cr & Cb should be from 921600 - 1382399 interleaved
    // NOTE: Solution is slow: 1280 * 720; 1 Minute -> 44 Seconds full convert.
    
    let imageData : ImageData = this.context!.getImageData(0, 0, 1280, 720);
        
    let pixels : Uint8ClampedArray = imageData.data;

    let row : number = 0;
    let col : number = 0;
    const plane2Start = 1280 * 720; //Hier starten die Chroma-Werte
    for (row = 0; row < 720; row++) {
      const p2row = (row % 2 === 0 ? row / 2 : row / 2 - 0.5) * 1280; //Hälfte der Row 
      let cr = 0;
      let cb = 0;
      const rowOffset : number = row * 1280;
      for (col = 0; col < 1280; col++) {
        const indexY = rowOffset + col;
        let y = buffer[indexY];
        if (col % 2 === 0) {
          const indexCr = plane2Start + p2row + (col);
          const indexCb = indexCr + 1;
          cr = buffer[indexCr];
          cb = buffer[indexCb];
        }
        this.yuvToRgb(y, cr, cb, row, col, pixels);
      }
    }
    return imageData;
  }

  private yuvToRgb(y : number, u : number, v : number, row : number, col : number, pixels : Uint8ClampedArray) {
    y -= 16;
    u -= 128;
    v -= 128;
    let r = 1.164 * y             + 1.596 * v;
    let g = 1.164 * y - 0.392 * u - 0.813 * v;
    let b = 1.164 * y + 2.017 * u;

    const index = (row * 1280 + col) * 4;
    pixels[index] = r;
    pixels[index + 1] = g;
    pixels[index + 2] = b;
    pixels[index + 3] = 255;
  } ```


javascript html5-canvas yuv webcodecs nv12-nv21
1个回答
1
投票

Canvas 使用的转换公式应用 BT.709“有限范围”转换系数。

将YUV转换为RGB矩阵系数(减去[16, 128, 128]后):

1.1644   -0.0000    1.7927
1.1644   -0.2132   -0.5329
1.1644    2.1124    0.0000

Canvas 转换似乎使用了某种近似值,因为我无法得到确切的结果。

JavaScript像素转换代码:

function yuvToRgb(y, u, v, row, col, pixels) {
  y -= 16;
  u -= 128;
  v -= 128;

  //let r = 1.164 * y             + 1.596 * v;
  //let g = 1.164 * y - 0.392 * u - 0.813 * v;
  //let b = 1.164 * y + 2.017 * u;

  //Use BT.709 "limited range" conversion formula:
  //1.1644   -0.0000    1.7927
  //1.1644   -0.2132   -0.5329
  //1.1644    2.1124    0.0000
  let r = 1.1644 * y              + 1.7927 * v;
  let g = 1.1644 * y - 0.2132 * u - 0.5329 * v;
  let b = 1.1644 * y + 2.1124 * u;

  const index = (row * 4 + col) * 4;
  pixels[index] = Math.max(Math.min(Math.round(r), 255), 0); //Round and clip range to [0, 255]
  pixels[index + 1] = Math.max(Math.min(Math.round(g), 255), 0);
  pixels[index + 2] = Math.max(Math.min(Math.round(b), 255), 0);
  pixels[index + 3] = 255;
}

备注:

  • 每个元素都进行舍入并剪裁到范围 [0, 255]。
  • 您的代码存在命名约定问题。
    命名约定为:
    u
    适用于
    cb
    v
    适用于
    cr

测试代码:

function yuvToRgb(y, u, v, row, col, pixels) {
  y -= 16;
  u -= 128;
  v -= 128;

  //let r = 1.164 * y             + 1.596 * v;
  //let g = 1.164 * y - 0.392 * u - 0.813 * v;
  //let b = 1.164 * y + 2.017 * u;

  //Use BT.709 "limited range" conversion formula:
  //1.1644   -0.0000    1.7927
  //1.1644   -0.2132   -0.5329
  //1.1644    2.1124    0.0000
  let r = 1.1644 * y              + 1.7927 * v;
  let g = 1.1644 * y - 0.2132 * u - 0.5329 * v;
  let b = 1.1644 * y + 2.1124 * u;

  const index = (row * 4 + col) * 4;
  pixels[index] = Math.max(Math.min(Math.round(r), 255), 0); //Round and clip range to [0, 255]
  pixels[index + 1] = Math.max(Math.min(Math.round(g), 255), 0);
  pixels[index + 2] = Math.max(Math.min(Math.round(b), 255), 0);
  pixels[index + 3] = 255;
}

//read image
function convertNV12ToRGBExample() {
  let NV12 = new Array(16, 81, 145, 210, 16, 170, 106, 41, 16, 170, 106, 41, 81, 177, 170, 74, 128, 128, 128, 128, 105, 118, 207, 128);
  let RGBA = new Array(64); //Output

  let row = 0;
  let col = 0;
  const plane2Start = 4 * 4;
  for (row = 0; row < 4; row++) {
    const p2row = (row % 2 === 0 ? row / 2 : (row-1) / 2) * 4;
    let cr = 0;
    let cb = 0;
    const rowOffset = row * 4;
    for (col = 0; col < 4; col++) {
      const indexY = rowOffset + col;
      let y = NV12[indexY];
      if (col % 2 === 0) {
        //const indexCr = plane2Start + p2row + col;
        //const indexCb = indexCr + 1;
        const indexCb = plane2Start + p2row + col;
        const indexCr = indexCb + 1;
        cb = NV12[indexCb];
        cr = NV12[indexCr];
      }

      yuvToRgb(y, cb, cr, row, col, RGBA);
    }
  }

  console.log(RGBA);
}

MATLAB测试代码(代码不完整):

NV12 = uint8([16,81,145,210,16,170,106,41,16,170,106,41,81,177,170,74,128,128,128,128,105,118,207,128]);
convertNV12ToRGB = uint8([0, 0, 0, 255, 75, 75, 75, 255, 150, 150, 150, 255, 225, 225, 225, 255, 241, 17, 210, 255, 163, 196, 132, 255, 104, 73, 8, 255, 29, 255, 188, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255]);
Canvas2D         = uint8([0, 0, 0, 255, 76, 76, 76, 255, 150, 150, 150, 255, 226, 226, 226, 255, 0, 0, 0, 255, 179, 179, 179, 255, 105, 105, 105, 255, 29, 29, 29, 255, 0, 10, 0, 255, 161, 190, 133, 255, 105, 87, 255, 255, 29, 12, 187, 255, 58, 86, 30, 255, 169, 198, 141, 255, 179, 162, 255, 255, 68, 50, 226, 255]);

Y = reshape(NV12(1:16), [4, 4])';
U = reshape(NV12(17:2:end), [2, 2])';
V = reshape(NV12(18:2:end), [2, 2])';
U = imresize(U, [4, 4], 'nearest');
V = imresize(V, [4, 4], 'nearest');
YUV = cat(3, Y, U, V);

T = [1.1644   -0.0000    1.7927
     1.1644   -0.2132   -0.5329
     1.1644    2.1124    0.0000];

yuv = double(YUV);
yuv(:,:,1) = yuv(:,:,1) - 16;
yuv(:,:,2) = yuv(:,:,2) - 128;
yuv(:,:,3) = yuv(:,:,3) - 128;

RGB = zeros(size(Y,1), size(Y,2), 3);
RGB(:,:,1) = T(1,1) * yuv(:,:,1) + T(1,2) * yuv(:,:,2) + T(1,3) * yuv(:,:,3);
RGB(:,:,2) = T(2,1) * yuv(:,:,1) + T(2,2) * yuv(:,:,2) + T(2,3) * yuv(:,:,3);
RGB(:,:,3) = T(3,1) * yuv(:,:,1) + T(3,2) * yuv(:,:,2) + T(3,3) * yuv(:,:,3);
RGB = uint8(round(RGB));

RGBA = cat(3, RGB(:,:,1), RGB(:,:,2), RGB(:,:,3), uint8(ones(4)*255)); % Convert to RGBA
rgba_data = reshape(permute(RGBA, [3, 2, 1]), [1, 64]);  % Convert to vector

C = permute(reshape(Canvas2D, [4, 4, 4]), [3, 2, 1]);
C = C(:, :, 1:3);
© www.soinside.com 2019 - 2024. All rights reserved.