YUV格式图片如何转换为RGB格式并展示
====================

#### 问题现象

如何将YUV格式图片转换为RGB格式并展示？比如YUV422采样格式，NV16或者YUYV存储格式的YUV图片。

#### 背景知识

**一、YUV简介。**

YUV是一种颜色编码方法，常使用在各个视频处理组件中。大多数视频采集芯片输出的原始图像数据通常为YUV格式（如YUV422、YUV420等），后续视频处理流程会基于该YUV数据进行预处理（如降噪/缩放）、视频编码（如H.264/H.265压缩），最终生成编码后的视频码流。

YUV编码采用了明亮度和色度表示每个像素的颜色。其中Y表示明亮度（Luminance、Luma），也就是灰阶值。U、V表示色度（Chrominance或Chroma），描述的是色调和饱和度。YCbCr其实是YUV经过缩放和偏移的翻版。其中Y与YUV中的Y含义一致，Cb，Cr同样都指色彩，只是在表示方法上不同而已。YCbCr其中Y是指亮度分量，Cb指蓝色色度分量，而Cr指红色色度分量。

**二、YUV采样格式和存储格式。**

以NV16和YUYV为例，存储顺序简介以及图例参考如下表格：

| 
存储格式

 | 

存储顺序简介

 | 

存储顺序图例

 |
| :-- | :-- | :-- |
| 

NV16

 | 

NV16是YUV422Semi-Planar的一种，Y分量单独存放，UV分量交错存放。UV在排列的时候，从U开始，总长度为w＊h＊2。

 | 

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/58/v3/rHELV0ytQKid6EzLlsrjfw/zh-cn_image_0000002628392614.png?HW-CC-KV=V1&HW-CC-Date=20260709T134241Z&HW-CC-Expire=86400&HW-CC-Sign=A766B000AD620A0E23E3CB480026890B26D067C60E0453D2E19DD81938DF49BA)

 |
| 

YUYV

 | 

YUYV属于YUV422Interleaved的一种。事实上，Interleaved是属于Packed的，但是在422中，用Interleaved更加形象一些。在Packed内部，YUV的排列顺序是YUVY，两个Y共用一组UV。

 | 

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/f6/v3/oli70LmHRHSZKHGtaTak7g/zh-cn_image_0000002658791879.png?HW-CC-KV=V1&HW-CC-Date=20260709T134241Z&HW-CC-Expire=86400&HW-CC-Sign=B2F97F0CDE96A31F8C755459EE91A73E56924B27181D61EA17A400561A803DB6)

 |

**三、YUV与RGB之间的转换。**

在图像处理过程中，经常会遇到YUV与RGB之间的转换，以BT601 full range标准为基础，YUV和RGB转换的公式如下：

BT601 full range RGB=\[0, 255\], Y=\[16, 235\], UV=\[16, 240\]。

R = 1.164 \* (Y - 16) + 1.596 \* (V - 128)
G = 1.164 \* (Y - 16) - 0.392 \* (U - 128) - 0.812 \* (V - 128)
B = 1.164 \* (Y - 16) + 2.016 \* (U - 128)

#### 解决方案

展示YUV格式的图片，首先要确认YUV的采样格式，存储格式，以及YUV与RGB的转换公式。将YUV数据转为RGB数据，再将RGB数据编码为pixelMap。

比如YUV图片是YUV422采样格式，NV16或者YUYV的存储格式，BT601 full range的转换公式。

1.  将YUV数据转为RGB数据，示例代码如下：
    
    _// nv16数据转为rgba数据_
    convertNv16ToRgba(nv16Data: Uint8Array, width: number, height: number): Uint8Array {
      const rgbaData = new Uint8Array(width \* height \* 4);
      const yPlaneSize = width \* height;
    
      for (let i = 0; i < width \* height; i++) {
        const row = Math.floor(i / width);
        const col = i % width;
        _// Y分量_
        const y = nv16Data\[i\];
       _ // UV分量 (NV16水平2:1下采样)_
        const uvRow = row;
        const uvCol = Math.floor(col / 2) \* 2;
        const uvIndex = yPlaneSize + (uvRow \* width) + uvCol;
    
        let u = 0, v = 0;
        if (col % 2 === 0) {
          u = nv16Data\[uvIndex\];
          v = nv16Data\[uvIndex + 1\];
        } else {
          u = nv16Data\[uvIndex - 2\];
          v = nv16Data\[uvIndex - 1\];
        }
       _ // 使用查找表快速转换_
        const rgb = this.fastYuvToRgb(y, u, v);
       _ // 填充RGBA_
        const rgbaIndex = i \* 4;
        rgbaData\[rgbaIndex\] = rgb.b;
        rgbaData\[rgbaIndex + 1\] = rgb.g;
        rgbaData\[rgbaIndex + 2\] = rgb.r;
        rgbaData\[rgbaIndex + 3\] = 255;
      }
      return rgbaData;
    }
    
    _// yuyv数据转为rgba数据_
    convertYuyvToRgba(yuyvArray: Uint8Array, width: number, height: number): Uint8Array {
      const evenWidth = width % 2 === 0 ? width : width + 1;
      _// 创建RGBA缓冲区_
      const rgbaBuffer = new ArrayBuffer(evenWidth \* height \* 4);
      const rgbaArray = new Uint8Array(rgbaBuffer);
    
      for (let y = 0; y < height; y++) {
        for (let x = 0; x < evenWidth; x += 2) {
          const yuyvIdx = (y \* evenWidth + x) \* 2;
         _ // 获取YUYV分量_
          const y1 = yuyvArray\[yuyvIdx\]; // Y1
          const u = yuyvArray\[yuyvIdx + 1\]; // U
          const y2 = yuyvArray\[yuyvIdx + 2\]; // Y2
          const v = yuyvArray\[yuyvIdx + 3\]; // V
         _ // 转换为RGB_
          const rgb1 = this.fastYuvToRgb(y1, u, v);
          const rgb2 = this.fastYuvToRgb(y2, u, v);
         _ // 填入RGBA数组_
          const idx1 = (y \* evenWidth + x) \* 4;
          const idx2 = (y \* evenWidth + x + 1) \* 4;
          if (x < width) {
            rgbaArray\[idx1\] = rgb1.b; _// B_
            rgbaArray\[idx1 + 1\] = rgb1.g; _// G_
            rgbaArray\[idx1 + 2\] = rgb1.r; _// R_
            rgbaArray\[idx1 + 3\] = 255; _// A（完全不透明）_
          }
          if ((x + 1) < width) {
            rgbaArray\[idx2\] = rgb2.b;
            rgbaArray\[idx2 + 1\] = rgb2.g;
            rgbaArray\[idx2 + 2\] = rgb2.r;
            rgbaArray\[idx2 + 3\] = 255;
          }
        }
      }
      return rgbaArray;
    }
    
    _/\*\*_
     _\* 快速YUV转RGB (整数运算)_
     _\*/_
    fastYuvToRgb(y: number, u: number, v: number): rgbInfo {
     _ // 使用整数运算的近似公式_
      const c = y - 16;
      const d = u - 128;
      const e = v - 128;
    
     _ // 使用整数运算 (乘以1000避免浮点数运算)_
      let r = (1164 \* c + 1596 \* e) / 1000;
      let g = (1164 \* c - 392 \* d - 812 \* e) / 1000;
      let b = (1164 \* c + 2016 \* d) / 1000;
    
     _ // 限制范围_
      r = Math.min(255, Math.max(0, Math.round(r)));
      g = Math.min(255, Math.max(0, Math.round(g)));
      b = Math.min(255, Math.max(0, Math.round(b)));
    
      return { r, g, b };
    }
    
2.  完整示例代码如下：
    
    import { resourceManager } from '@kit.LocalizationKit';
    import { image } from '@kit.ImageKit';
    
    export class rgbInfo {
      r: number = 0;
      g: number = 0;
      b: number = 0;
    }
    
    @Entry
    @Component
    struct YuvTogrba {
      @State pixelMapNv16: PixelMap | undefined = undefined;
      @State pixelMapYuyv: PixelMap | undefined = undefined;
      private context: Context = this.getUIContext().getHostContext() as Context;
    
     _ // nv16数据转为rgba数据_
      convertNv16ToRgba(nv16Data: Uint8Array, width: number, height: number): Uint8Array {
        const rgbaData = new Uint8Array(width \* height \* 4);
        const yPlaneSize = width \* height;
    
        for (let i = 0; i < width \* height; i++) {
          const row = Math.floor(i / width);
          const col = i % width;
         _ // Y分量_
          const y = nv16Data\[i\];
        _  // UV分量 (NV16水平2:1下采样)_
          const uvRow = row;
          const uvCol = Math.floor(col / 2) \* 2;
          const uvIndex = yPlaneSize + (uvRow \* width) + uvCol;
    
          let u = 0, v = 0;
          if (col % 2 === 0) {
            u = nv16Data\[uvIndex\];
            v = nv16Data\[uvIndex + 1\];
          } else {
            u = nv16Data\[uvIndex - 2\];
            v = nv16Data\[uvIndex - 1\];
          }
         _ // 使用查找表快速转换_
          const rgb = this.fastYuvToRgb(y, u, v);
         _ // 填充RGBA_
          const rgbaIndex = i \* 4;
          rgbaData\[rgbaIndex\] = rgb.b;
          rgbaData\[rgbaIndex + 1\] = rgb.g;
          rgbaData\[rgbaIndex + 2\] = rgb.r;
          rgbaData\[rgbaIndex + 3\] = 255;
        }
        return rgbaData;
      }
    
     _ // yuyv数据转为rgba数据_
      convertYuyvToRgba(yuyvArray: Uint8Array, width: number, height: number): Uint8Array {
        const evenWidth = width % 2 === 0 ? width : width + 1;
       _ // 创建RGBA缓冲区_
        const rgbaBuffer = new ArrayBuffer(evenWidth \* height \* 4);
        const rgbaArray = new Uint8Array(rgbaBuffer);
    
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < evenWidth; x += 2) {
            const yuyvIdx = (y \* evenWidth + x) \* 2;
         _   // 获取YUYV分量_
            const y1 = yuyvArray\[yuyvIdx\]; // Y1
            const u = yuyvArray\[yuyvIdx + 1\]; // U
            const y2 = yuyvArray\[yuyvIdx + 2\]; // Y2
            const v = yuyvArray\[yuyvIdx + 3\]; // V
          _  // 转换为RGB_
            const rgb1 = this.fastYuvToRgb(y1, u, v);
            const rgb2 = this.fastYuvToRgb(y2, u, v);
      _      // 填入RGBA数组_
            const idx1 = (y \* evenWidth + x) \* 4;
            const idx2 = (y \* evenWidth + x + 1) \* 4;
            if (x < width) {
              rgbaArray\[idx1\] = rgb1.b; _// B_
              rgbaArray\[idx1 + 1\] = rgb1.g; _// G_
              rgbaArray\[idx1 + 2\] = rgb1.r; _// R_
              rgbaArray\[idx1 + 3\] = 255; _// A（完全不透明）_
            }
            if ((x + 1) < width) {
              rgbaArray\[idx2\] = rgb2.b;
              rgbaArray\[idx2 + 1\] = rgb2.g;
              rgbaArray\[idx2 + 2\] = rgb2.r;
              rgbaArray\[idx2 + 3\] = 255;
            }
          }
        }
        return rgbaArray;
      }
    
      _/\*\*_
       _\* 快速YUV转RGB (整数运算)_
       _\*/_
      fastYuvToRgb(y: number, u: number, v: number): rgbInfo {
      _  // 使用整数运算的近似公式_
        const c = y - 16;
        const d = u - 128;
        const e = v - 128;
    
       _ // 使用整数运算 (乘以1000避免浮点数运算)_
        let r = (1164 \* c + 1596 \* e) / 1000;
        let g = (1164 \* c - 392 \* d - 812 \* e) / 1000;
        let b = (1164 \* c + 2016 \* d) / 1000;
    
       _ // 限制范围_
        r = Math.min(255, Math.max(0, Math.round(r)));
        g = Math.min(255, Math.max(0, Math.round(g)));
        b = Math.min(255, Math.max(0, Math.round(b)));
    
        return { r, g, b };
      }
    
      build() {
        Row() {
          Column() {
            Row() {
              Column() {
                Image(this.pixelMapNv16)
                  .margin({ bottom: 10 })
                  .width('45%')
                  .height(140)
                Button('nv16转rgba')
                  .onClick(() => {
                    const resourceMgr: resourceManager.ResourceManager = this.context.resourceManager;
                    let rawBuf = resourceMgr.getRawFileContentSync('test\_nv16.yuv'); _// 需要替换为rawfile目录下实际nv16存储格式的yuv数据_
                    let readBuffer = this.convertNv16ToRgba(rawBuf, 550, 400); _// 像素宽高要根据实际的yuv数据填写_
                    let opts: image.InitializationOptions =
                      { editable: true, pixelFormat: 3, size: { height: 400, width: 550 } }; _// 像素宽高要根据实际的yuv数据填写_
                    this.pixelMapNv16 = image.createPixelMapSync(readBuffer.buffer, opts);
                  })
              }.margin({ right: 5 })
    
              Column() {
                Image(this.pixelMapYuyv)
                  .margin({ bottom: 10 })
                  .width('45%')
                  .height(140)
                Button('yuyv转rgba')
                  .onClick(() => {
                    const resourceMgr: resourceManager.ResourceManager = this.context.resourceManager;
                    let rawBuf = resourceMgr.getRawFileContentSync('test\_yuyv.yuv'); _// 需要替换为rawfile目录下实际yuyv存储格式的yuv数据_
                    let readBuffer = this.convertYuyvToRgba(rawBuf, 550, 400); _// 像素宽高要根据实际的yuv数据填写_
                    let opts: image.InitializationOptions =
                      { editable: true, pixelFormat: 3, size: { height: 400, width: 550 } }; _// 像素宽高要根据实际的yuv数据填写_
                    this.pixelMapYuyv = image.createPixelMapSync(readBuffer.buffer, opts);
                  })
              }.margin({ left: 5 })
            }
          }.width('100%')
        }.height('100%')
      }
    }