# 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=20260929T032805Z&HW-CC-Expire=31536000000&HW-CC-Sign=9E1D6DA3EDCB547D8689AA036B44296B1798DE753D7ED65C556A71841E8C4CA6)|
|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=20260929T032805Z&HW-CC-Expire=31536000000&HW-CC-Sign=612F088416B18149EC4D4670E4AEEFB434C2566843DA99641D56CE4FDA71CC85)|

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

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

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

```ts
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数据，示例代码如下：

   ```ts
   // 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. 完整示例代码如下：

   ```ts
   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%')
     }
   }
   ```

