VPI - 视觉编程接口

3.2 版本

转换图像格式

概述

“转换图像格式”用于将具有给定格式的图像转换为另一种格式。它处理颜色规格、格式和深度转换。该算法还支持输入范围转换,例如,当需要将 unsigned char \([0,255]\) 图像映射到 signed short \([-32768,32767]\) 范围时。

彩色输入灰度输出

C API 函数

有关实现该算法的限制、约束和后端的列表,请查阅以下函数的参考文档

函数描述
vpiSubmitConvertImageFormat 将图像内容转换为所需的格式,可选择缩放和偏移。

实现

该算法实现为逐像素转换函数,该函数读取输入像素,应用依赖于转换的一系列变换,并将结果写入输出图像中的相同位置。用户输入包括:

  • 使用请求的输入类型创建的输入图像
  • 使用请求的输出类型创建的输出图像
  • 标志指定类型转换的执行方式,请参阅 clamp/cast
  • 范围转换中使用的比例和偏移,请参阅 range

有几种类型的转换可用:

  • 灰度 \(\leftrightarrow\) 彩色
  • 灰度 \(\leftrightarrow\) 灰度(在深度和范围转换中很有用)
  • 彩色 \(\leftrightarrow\) 彩色(例如,YUV 到 RGB 和反之亦然)

可用的颜色格式有:

可用的单通道灰度格式有:

当存储的信息不表示为颜色时(例如温度、速度等),应使用非彩色格式。对于转换,它们被视为具有扩展范围的灰度。

下表显示了可用于转换的输入和输出图像类型组合。

输入
输出

转换公式

以下部分描述了输入值如何转换为输出值。通常,这些转换相当于颜色规格、深度、通道顺序(交换)、添加或删除 Alpha 通道、降采样或升采样变换。这些表示为由以下定义的基本处理块组成的转换管道。

通道深度转换

通道深度转换由名为“depth”的块表示,并由以下子管道定义:

depth\(=\) range\(\rightarrow\)round\(\rightarrow\)

clamp/cast

  • range:输入转换为浮点 (fp32),并应用以下公式:

    \[ f(x) = \text{scale} \times x + \text{offset} \]

    如果 scale==1offset==0,则会采取快捷方式,并且不执行任何操作(甚至不转换为浮点)。

  • round:四舍五入到最接近的整数,中间情况远离零舍入,例如 round(0.5) == 1.0round(-0.5) == -1.0
  • clamp/cast:由传递的标志控制的操作
    • VPI_CONVERSION_CAST:像常规 C 强制转换或 C++ 的 static_cast 一样,将输入强制转换为输出类型。下溢和溢出的行为将如 C 规范所述(包括未定义的行为)。当已知输入范围适合输出并且需要最大性能时,使用此选项。
    • VPI_CONVERSION_CLAMP:值被钳制,因此溢出和下溢将分别映射到输出类型的最大值和最小值。然后将结果强制转换为输出类型。当输出类型为浮点型时,钳制行为类似于强制转换。

当应用于多个通道(如 RGB)时,该操作在每个通道上独立执行。

通道顺序转换

这由以下块表示:

swizzle

它用于置换(或交换)输入类型的通道顺序。用于从/到可以多种方式表示的颜色规格进行转换,例如 RGB 和 BGR。颜色规格转换函数假定预定的通道顺序。为了使用它们,必须重新排序通道。

YUV 和 RGB 之间的转换

对于 RGB \(\leftrightarrow\) YUV 转换,VPI 使用 ITU-R BT.601 625 行规范。它与 JPEG 文件交换格式 (JFIF) 使用的 standard 相同。

为了精确地建立转换,让我们定义以下常量:

\begin{align} K_r &= 0.299 \\ K_g &= 0.587 \\ K_b &= 0.114 \\ K_{c_b} &= 1.772 \\ K_{c_r} &= 1.402 \\ \end{align}

为了表示方便,我们假设 \(U\) 和 \(V\) 分别对应于 \(C_b\) 和 \(C_r\)。此假设并非普遍成立。

转换块可以定义为:

rgb2yuv

\begin{align} Y(r,g,b) &= \text{round}(r K_r + g K_g + b K_b)\big|^{255}_{0} \\ C_b(r,g,b) &= \text{round}((-r K_r - g K_g + b (1 - K_b )) / K_ {c_b} + 128)\big|^{255}_{0} \\ C_r(r,g,b) &= \text{round}((r (1-K_r) - g K_g - b K_b) / K_{c_r} + 128)\big|^{255}_{0} \end{align}

这些函数期望 \(r,g,b \in [0,255]\)

yuv2rgb

\begin{align} R(y,c_b,c_r) &= \text{round}(y+K_{c_r}(c_r-128))\big|^{255}_{0} \\ G(y,c_b,c_r) &= \text{round}(y-[K_b K_{c_b} (c_b-128) + K_r K_{c_r} (c_r-128)] / K_g)\big|^{255}_{0} \\ B(y,c_b,c_r) &= \text{round}(y + K_{c_b} (c_b - 128))\big|^{255}_{0} \end{align}

这些函数期望 \(y,c_b,c_r \in [0,255]\)

符号 \(X\big|^{N} _ {M} \) 表示将 X 的下溢和溢出分别钳制为 MN

round 函数遵循 此处 的定义。

RGB 和灰度之间的转换

从 RGB 到灰度的转换遵循与从 RGB 到 YUV 的转换相同的规范,但仅返回亮度分量。因此,使用 此处 定义的相同常量。

rgb2gray

\[ Y(r,g,b) = \text{round}(K_r \times r + K_g \times g + K_b \times b)\big|^{255}_{0} \]

对于从灰度到 RGB 的转换,则非常简单:

gray2rgb

\[ f(x) = (x,x,x) \]

升采样/降采样

对于包含子采样平面的图像格式,如 VPI_IMAGE_FORMAT_NV12,需要以下块定义:

2x 降采样

\[ D[x,y] = S[2x,2y] \]

2x 升采样

\[ D[x,y] = S[\lfloor x/2 \rfloor, \lfloor y/2 \rfloor] \]

注意
VPI 有效地使用最近邻采样进行升采样。在未来的版本中,它将使用双线性升采样。

Alpha 通道处理

根据输入和输出像素类型(即,是否需要删除或添加 Alpha 通道),可以使用以下块:

alpha
  • 添加 Alpha:将不透明的 Alpha 通道附加到输入像素,例如,RGB 变为 RGBA。对于整数通道类型,新 Alpha 通道的值是其类型的最大可表示值,例如,8 位无符号整数为 255。目前,VPI 不支持其他通道类型上的 Alpha 通道。
  • 删除 Alpha:简单地丢弃 Alpha 通道,例如,RGBA 变为 RGB。
  • 不执行任何操作:当输入和输出都没有 Alpha 通道时。

转换管道

本节定义了输入像素如何转换为输出像素。它使用上一节中定义的基本转换块。

灰度到灰度

输入\(\rightarrow\)depth\(\rightarrow\)

输出

灰度到 NV12/NV24

输入\(\rightarrow\)depth\(\rightarrow\)Y 平面

\(\searrow\)

(128,128) \(\rightarrow\)UV 平面

\(\nearrow\)

输出
注意
由于 NV12/NV24 的像素深度为 8 位无符号,因此 \((u,v) = (128,128)\) 对应于零饱和度。

NV12/NV24 到灰度

输入

\(\rightarrow\)

Y 平面\(\rightarrow\)depth\(\rightarrow\)

输出

灰度到 RGB 空间

输入\(\rightarrow\)depth\(\rightarrow\)gray2rgb\(\rightarrow\)swizzle\(\rightarrow\)alpha\(\rightarrow\)

输出

RGB 空间到灰度

输入\(\rightarrow\)swizzle\(\rightarrow\)alpha\(\rightarrow\)rgb2gray\(\rightarrow\)depth\(\rightarrow\)

输出

RGB 空间到 NV12

输入\(\rightarrow\)swizzle\(\rightarrow\)alpha\(\rightarrow\)depth\(\rightarrow\)

rgb2yuv

\(\nearrow\)Y 平面

\(\searrow\)

\(\searrow\)UV 平面\(\rightarrow\)2x 降采样

\(\nearrow\)

输出

RGB 空间到 NV24

输入\(\rightarrow\)swizzle\(\rightarrow\)alpha\(\rightarrow\)depth\(\rightarrow\)

rgb2yuv

\(\nearrow\)Y 平面

\(\searrow\)

\(\searrow\)UV 平面

\(\nearrow\)

输出

NV12 到 RGB 空间

输入
\(\nearrow\)Y 平面\(\searrow\)
\(\searrow\)UV 平面\(\rightarrow\)2x 升采样

\(\nearrow\)

yuv2rgb\(\rightarrow\)depth\(\rightarrow\)swizzle\(\rightarrow\)alpha\(\rightarrow\)

输出

NV24 到 RGB 空间

输入
\(\nearrow\)Y 平面\(\searrow\)
\(\searrow\)UV 平面

\(\nearrow\)

yuv2rgb\(\rightarrow\)depth\(\rightarrow\)swizzle\(\rightarrow\)alpha\(\rightarrow\)

输出

用法

语言
  1. 导入 VPI 模块
    import vpi
  2. 将 VPI 图像转换为 S16 格式(有符号 16 位),将范围从 \([0,255]\) 映射到 \([-32768,32767]\)。
    with vpi.Backend.CPU
    output = input.convert(vpi.Format.S16, scale=257, offset=-32768)
  1. 初始化阶段
    1. 包含定义图像格式转换器函数的头文件。
      声明处理图像格式转换的函数。
    2. 创建将在其中提交算法以执行的流。
      VPIStream stream;
      vpiStreamCreate(0, &stream);
      struct VPIStreamImpl * VPIStream
      流的句柄。
      Definition: Types.h:250
      VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
      创建流实例。
    3. 定义输入图像。此处以示例为例,我们创建一个尺寸为 \(w \times h\) 且 NV12 图像类型的彩色图像。
      VPIImage input;
      #define VPI_IMAGE_FORMAT_NV12_ER
      YUV420sp 8 位 pitch-linear 格式,具有完整范围。
      Definition: ImageFormat.h:222
      struct VPIImageImpl * VPIImage
      图像的句柄。
      Definition: Types.h:256
      VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
      使用指定的标志创建空的图像实例。
    4. 使用目标图像类型创建输出图像。在本例中,我们希望将输入转换为 16 位有符号整数灰度。
      int w, h;
      vpiImageGetSize(input, &w, &h);
      VPIImage output;
      #define VPI_IMAGE_FORMAT_S16
      具有一个 16 位有符号整数通道的单平面。
      Definition: ImageFormat.h:120
      VPIStatus vpiImageGetSize(VPIImage img, int32_t *width, int32_t *height)
      获取图像尺寸(以像素为单位)。
  2. 处理阶段
    1. 定义转换参数,以将范围从 \([0,255]\) 映射到 \([-32768,32767]\)。
      cvtParams.scale = 257;
      cvtParams.offset = -32768;
      float offset
      偏移因子。
      VPIConversionPolicy policy
      要使用的转换策略。
      float scale
      缩放因子。
      VPIStatus vpiInitConvertImageFormatParams(VPIConvertImageFormatParams *params)
      使用默认值初始化 VPIConvertImageFormatParams。
      @ VPI_CONVERSION_CLAMP
      将输入钳制到输出的类型范围。
      Definition: Types.h:297
      用于自定义图像格式转换的参数。
    2. 将算法提交到流、输入、输出图像和其他参数。
      vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CPU, input, output, &cvtParams);
      VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
      将图像内容转换为所需的格式,可选择缩放和偏移。
      @ VPI_BACKEND_CPU
      CPU 后端。
      Definition: Types.h:92
    3. 可选地,等待直到处理完成。
      vpiStreamSync(stream);
      VPIStatus vpiStreamSync(VPIStream stream)
      阻塞调用线程,直到此流队列中的所有提交命令都完成(队列为空)。
  3. 清理阶段
    1. 释放流以及输入和输出图像所持有的资源。
      vpiImageDestroy(output);
      void vpiImageDestroy(VPIImage img)
      销毁图像实例。
      void vpiStreamDestroy(VPIStream stream)
      销毁流实例并释放所有硬件资源。

有关更多信息,请参阅 VPI - 视觉编程接口 的 “C API 参考” 部分中的 转换图像格式

性能

有关如何使用下表中的性能信息,请参阅 算法性能表
在比较测量结果之前,请查阅 比较算法运行时间
有关性能基准测试方式的更多信息,请参阅 性能基准测试

注意
尽管 VIC 仅支持 clamp 转换策略,但下表显示了即使对于 cast 策略的性能数据。内部它仍然使用 clamp。在 VIC 上,即使它支持 cast 也不会有任何区别。
 -