S100 OpenExplorer

# #常见图像格式

## #简介

随着人工智能的发展，深度神经网络在视觉领域“百花齐放”，为了满足不同场景的需求，我们会接触到多种图像数据格式，本小节为大家详细介绍深度学习场景中常用的几种图像数据格式：RGB、BGR、YUV、NV12 和 Gray。

## #RGB

RGB，一种常见的彩色图像格式。图像的每一个像素点都会存储红（Red）、绿（Green）、蓝（Blue）三个颜色通道的亮度值（0 ~ 255，UINT8）。

基于此，如果按（R，G，B）的方式记录，那么（255,0,0）、（0,255,0）、（0,0,255）可以分别表示最纯粹的 红、绿、蓝。而若 RGB 三个通道的数值均为 0，综合得到黑色；若三个通道的数值均取最大值 255，综合得到白色。

RGB可表示的色彩数量可达256x256x256≈1677 万，远超人眼的感知范围（约1000万种），因此，RGB 被广泛应用于各种显示领域，与日常生活息息相关。

然而，RGB在表示颜色时有一个特点，每个像素点必须同时存储 R、G、B 三个通道数值，即每个像素点需要3个字节的存储空间，而这个特点针对视频场景的存储与传输是非常不友好的，会占用大量的空间和带宽。

## #BGR

BGR图像格式与RGB类似，只是红、绿、蓝三个通道的排列顺序不同。RGB格式中，像素点的通道顺序是红、绿、蓝，而在BGR格式中，像素点的通道顺序是蓝、绿、红。

BGR格式常用于OpenCV等计算机视觉库中，是一些软件和硬件的默认图像格式，与这些软件和硬件的兼容性更好。

BGR与RGB一样，数据量较大，不适合视频场景的存储与传输。因此，我们还需要其他的图像格式来替代 RGB/BGR用于视频领域。

## #YUV

### #简介

YUV，一种彩色图像格式，其中Y表示亮度（Luminance），用于指定一个像素的亮度（可以理解为是黑白程度），U和V表示色度（Chrominance或Chroma），用于指定像素的颜色，每个数值都采用UINT8表示。

YUV格式采用亮度-色度分离的方式，也就是说只有U、V参与颜色的表示，这一点与RGB是不同的。

即使没有U、V分量，仅凭Y分量我们也能 “识别” 出一幅图像的基本内容，只不过此时呈现的是一张黑白图像。而U、V分量为这些基本内容赋予了色彩，黑白图像演变为了彩色图像。 这意味着，我们可以在保留Y分量信息的情况下，尽可能地减少U、V两个分量的采样，以实现最大限度地减少数据量，这对于视频数据的存储和传输是有极大裨益的，这也是YUV相比于RGB更适合视频处理领域的原因。

### #YUV常见格式

据研究表明，人眼对亮度信息比色彩信息更加敏感。YUV下采样就是根据人眼的特点，将人眼相对不敏感的色彩信息进行压缩采样，得到相对小的文件进行播放和传输。根据Y和UV的占比，常用的YUV格式有：YUV444，YUV422，YUV420三种。

用三个图来直观地表示不同采集方式下Y和UV的占比。

- YUV444：每一个Y分量对应一对UV分量，每像素占用3字节（Y + U + V = 8 + 8 + 8 = 24bits）。
- YUV422：每两个Y分量共用一对UV分量，每像素占用2字节（Y + 0.5U + 0.5V = 8 + 4 + 4 = 16bits）。
- YUV420：每四个Y分量共用一对UV分量，每像素占用1.5字节（Y + 0.25U + 0.25V = 8 + 2 + 2 = 12bits）。

此时来理解YUV4xx中的4，这个4，实际上表达了最大的共享单位！也就是最多4个Y共享一对UV。

### #YUV420详解

在YUV420中，一个像素点对应一个Y，一个4X4的小方块对应一个U和V，每个像素占用1.5个字节。依据不同的UV分量排列方式，还可以将YUV420分为YUV420P和YUV420SP两种格式。

YUV420P是先把U存放完，再存放V，排列方式如下图：

YUV420SP是UV、UV交替存放的，排列方式如下图：

此时，相信大家就可以理解为什么YUV420 数据在内存中的长度是 `width * height * 3 / 2` 了。

## #NV12

NV12是一种广泛应用的图像格式，特别在视频编解码领域及自动驾驶领域，NV12能够在保持图像亮度信息的同时，数据量是RGB/BGR等格式的一半，可以减少模型加载输入数据的时间，因此，嵌入式端通常选用NV12图像作为部署时的图像数据输入。

NV12图像格式属于YUV颜色空间中的YUV420SP格式，采用YUV 4:2:0的采样方式，每四个Y分量共用一组U分量和V分量，Y连续存放，U与V交叉存放。

下面为您提供将RGB图像转为NV12形式的Python示例代码介绍和NV12输入数据准备的C++参考实现，以及两种常见图像处理库将图像转为NV12形式的Python示例介绍作为补充介绍。

### #RGB转为NV12（Python）

`import os import numpy as np def rgb_to_nv12(image_data: np.array): r = image_data[:, :, 0] g = image_data[:, :, 1] b = image_data[:, :, 2] y = (0.299 * r + 0.587 * g + 0.114 * b) u = (-0.169 * r - 0.331 * g + 0.5 * b + 128)[::2, ::2] v = (0.5 * r - 0.419 * g - 0.081 * b + 128)[::2, ::2] uv = np.zeros(shape=(u.shape[0], u.shape[1] * 2)) for i in range(0, u.shape[0]): for j in range(0, u.shape[1]): uv[i, 2 * j] = u[i, j] uv[i, 2 * j + 1] = v[i, j] y = y.astype(np.uint8) uv = uv.astype(np.uint8) # Return separately return y, uv if __name__ == '__main__': image_data = np.array(pil_image_data).astype(np.uint8) y, uv = rgb_to_nv12(image_data)`

### #NV12输入的数据准备（C++）

`#include <fstream> #include <iostream> #include <vector> #include <cstring> #include "hobot/dnn/hb_dnn.h" #include "hobot/hb_ucp.h" #include "hobot/hb_ucp_sys.h" int32_t read_image_2_tensor_as_nv12(std::string &image_file, hbDNNTensor *input_tensor) { // the struct of input shape is NHWC int input_h = input_tensor[0].properties.validShape.dimensionSize[1]; int input_w = input_tensor[0].properties.validShape.dimensionSize[2]; cv::Mat bgr_mat = cv::imread(image_file, cv::IMREAD_COLOR); if (bgr_mat.empty()) { std::cout << "image file not exist!" << std::endl; return -1; } // resize cv::Mat mat; mat.create(input_h, input_w, bgr_mat.type()); cv::resize(bgr_mat, mat, mat.size(), 0, 0); // convert to YUV420 if (input_h % 2 || input_w % 2) { std::cout << "input img height and width must aligned by 2!" << std::endl; return -1; } cv::Mat yuv_mat; cv::cvtColor(mat, yuv_mat, cv::COLOR_BGR2YUV_I420); uint8_t *yuv_data = yuv_mat.ptr<uint8_t>(); uint8_t *y_data_src = yuv_data; // copy y data uint8_t *y_data_dst = reinterpret_cast<uint8_t *>(input_tensor[0].sysMem.virAddr); for (int32_t h = 0; h < input_h; ++h) { memcpy(y_data_dst, y_data_src, input_w); y_data_src += input_w; // add padding y_data_dst += input_tensor[0].properties.stride[1]; } // copy uv data int32_t uv_height = input_tensor[1].properties.validShape.dimensionSize[1]; int32_t uv_width = input_tensor[1].properties.validShape.dimensionSize[2]; uint8_t *uv_data_dst = reinterpret_cast<uint8_t *>(input_tensor[1].sysMem.virAddr); uint8_t *u_data_src = yuv_data + input_h * input_w; uint8_t *v_data_src = u_data_src + uv_height * uv_width; for (int32_t h = 0; h < uv_height; ++h) { auto *cur_data = uv_data_dst; for (int32_t w = 0; w < uv_width; ++w) { *cur_data++ = *u_data_src++; *cur_data++ = *v_data_src++; } // add padding uv_data_dst += input_tensor[1].properties.stride[1]; } // make sure memory data is flushed to DDR before inference hbUCPMemFlush(&input_tensor[0].sysMem, HB_SYS_MEM_CACHE_CLEAN); hbUCPMemFlush(&input_tensor[1].sysMem, HB_SYS_MEM_CACHE_CLEAN); return 0; }`

### #补充介绍

#### #PIL将图像转为NV12

`import sys import numpy as np from PIL import Image def generate_nv12(input_path, output_path='./'): img = Image.open(input_path) w,h = img.size # Convert images to YUV format yuv_img = img.convert('YCbCr') y_data, u_data, v_data = yuv_img.split() # Convert Y, U, and V channel data to byte streams y_data_bytes = y_data.tobytes() u_data_bytes = u_data.resize((u_data.width // 2, u_data.height // 2)).tobytes() v_data_bytes = v_data.resize((v_data.width // 2, v_data.height // 2)).tobytes() # Arrange the UV data in the form of UVUVUVUV... uvuvuv_data = bytearray() for u_byte, v_byte in zip(u_data_bytes, v_data_bytes): uvuvuv_data.extend([u_byte, v_byte]) # y data y_path = output_path + "_y.bin" with open(y_path, 'wb') as f: f.write(y_data_bytes) # uv data uv_path = output_path + "_uv.bin" with open(uv_path, 'wb') as f: f.write(uvuvuv_data) nv12_data = y_data_bytes + uvuvuv_data # Save as NV12 format file nv12_path = output_path + "_nv12.bin" with open(nv12_path, 'wb') as f: f.write(nv12_data) # Input for the hbir model y = np.frombuffer(y_data_bytes, dtype=np.uint8).reshape(1, h, w, 1).astype(np.uint8) uv = np.frombuffer(uvuvuv_data, dtype=np.uint8).reshape(1, h//2, w//2, 2).astype(np.uint8) return y, uv if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python resize_image.py <input_path> <output_path>") sys.exit(1) input_path = sys.argv[1] output_path = sys.argv[2] y, uv = generate_nv12(input_path, output_path)`

#### #OpenCV将图像转为NV12

`import cv2 import numpy as np def image2nv12(image): image = image.astype(np.uint8) height, width = image.shape[0], image.shape[1] yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((height * width * 3 // 2, )) y = yuv420p[:height * width] uv_planar = yuv420p[height * width:].reshape((2, height * width // 4)) uv = uv_planar.transpose((1, 0)).reshape((height * width // 2, )) nv12 = np.zeros_like(yuv420p) # y component nv12[:height * width] = y # uv component, UVUV alternate store nv12[height * width:] = uv # Return separately return y, uv image = cv2.imread("./image.jpg") nv12 = image2nv12(image)`

## #Gray

Gray图像格式，也称灰度图像格式，是一种单通道图像格式。在Gray图像中，每个像素只包含一个亮度值，每个数值都采用UINT8类型表示，即0~255之间的整数。这个亮度值表示图像中每个像素的明暗程度，取值越大表示像素越亮，取值越小表示像素越暗。

Gray图像格式也是其他彩色图像格式（如RGB、YUV等）转换为单通道图像时的一种常见格式，只包含图像的亮度信息，图像数据相对较小，因此针对一些对图像色彩信息不太敏感的场景，仍然具有重要的应用价值。

## #图像格式之间的转换

在图像采集、显示方面，主要使用 RGB，但是在图像存储、处理、传输方面，又要选择 YUV，在一个完整的应用场景中，可能会需要用到不同的图像格式，这时就需要进行图像格式的转换。

那如何实现图像格式之间的转换呢？可以简单地理解为，有一个“标准”，基于这个标准，通过一定的数学运算即可完成不同图像格式之间的转换。下面以计算机视觉库opencv封装好的函数为例，看一下如何实现图片格式转换：

`import cv2`
`# 读取图片，opencv读取图片默认为BGR格式 bgr_img = cv2.imread('example.jpg') cv2.imwrite('bgr_image.jpg', bgr_img) # 将BGR格式转换为RGB格式 rgb_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) cv2.imwrite('rgb_image.jpg', rgb_img) # 将BGR格式转换为YUV444格式 yuv_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2YUV) cv2.imwrite('yuv_image.jpg', yuv_img) # 将BGR格式转换为GRAY格式 gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY) cv2.imwrite('gray_image.jpg', gray_img)`

我们在OE包内中提供了常见图像格式之间的转换源码（例如：RGB2NV12、BGR2RGB等），图片处理常用transformer说明文档请参考用户手册 图片处理transformer说明 章节，对应的源码位于OE开发包的`samples/ai_toolchain/horizon_model_convert_sample/01_common/python/data`路径下。

不同的图像格式具有不同的性能和优缺点，实际使用时，可以根据自己的需求，个性化选择图像格式。

## #参考链接

- https://blog.csdn.net/onion2007/article/details/46805335

- https://zhuanlan.zhihu.com/p/538058910

- https://zhuanlan.zhihu.com/p/248116694

- https://blog.csdn.net/zego_0616/article/details/126658494

- https://blog.csdn.net/luoyingxing/article/details/108516163

页面目录