JPEG_Encoder 算子

Operator Library: Compression

该算子对 8 位灰度图像执行 JPEG 压缩。它使用 JPEG 基线算法。算子的输出是一个霍夫曼流。可选地,输出中会包含 JPEG 标头(可配置)。如果包含标头,则输出格式为 1.2 版的 JFIF(JPEG 文件交换格式)。

压缩率(从而也是输出流大小)取决于两个因素:

  • 在运行时可更改的所选量化表。

  • 输入图像。

算子 JPEG_Encoder 能够处理链接参数化中指定的完整输入数据速率。您可以通过输入 Parallelism 来定义吞吐速率。请注意,更高的 Parallelism 会带来更高的 FPGA 资源利用率。

最大图像高度为 65,535 像素。如果图像高度不是 8 的倍数,算子会在内部添加虚拟行。这种行为会降低整体输入数据速率。

该算子允许您定义量化表。您有两种配置该表的选项:

  • 可以根据 Quality 值(以百分比为单位)自动计算量化表。计算时,使用标准亮度量化表(见下文)。使用参数 Quality(以百分比为单位)来配置自动计算。自动计算是算子的默认设置。

  • 或者,您可以单独设置每个量化表值。使用参数 LuminanceQuantization 输入您的值。在这种情况下,参数 Quality 会通过被设置为值 -1 而自动禁用。

标准亮度量化表(默认设置)如下所示:

在默认模式下(即根据参数 Quality 设置的标准亮度量化表进行自动计算),量化表通过以下公式计算得出:

如上所述,算子的输出是编码图像数据的霍夫曼流。对于霍夫曼编码,使用针对 DC 和 AC 系数的标准亮度表。这些表是固定的,取自文献,即 W. P. Pennbaker 和 J. L. Mitchell 所著的《JPEG Still Image Data Compression Standard》(Van Nostrand Rheinhold,1993 年)。如果参数 IncludeHeader 设置为 YES,则编码器生成的霍夫曼流包含 JPEG 标头。生成的数据流由多字节(8 位块)的并行输出组成,其中的 Parallelism 是根据吞吐量要求自动推导出来的。

算子限制

  • 该算子不支持空图像,即没有像素的图像。

  • 不允许输入行长度可变的图像。

  • 算子具有最小输入图像宽度,该宽度取决于输入 Parallelism。最小输入图像宽度至少是输入 Parallelism 的两倍。最小输入图像宽度的计算方法如下:

    1. 基于输入 Parallelism:确定等于或大于输入 Parallelism 的第一个 8 的倍数。

    2. 将此 8 的倍数加 1。

    3. 基于步骤 2 的结果:由于输入图像宽度必须是输入 Parallelism 的倍数,因此确定大于步骤 2 结果的 input parallelism 的第一个倍数。

    步骤 3 的结果即为算子的最小输入图像宽度。

    计算最小输入图像宽度的公式

    图 414. 计算最小输入图像宽度的公式


    例如,如果输入 Parallelism 为 4,则最小输入图像宽度为 12。

    如果输入 Parallelism 为 12,则最小输入图像宽度为 24。

[提示] 优化算子吞吐量

如果输入 Parallelism 大于 8,则数据吞吐量取决于输入图像的大小。可以通过以下图像大小 (OptimalSize) 实现最大数据吞吐量:

OptimalSize = Ceil(FrameSize/(PathCount * IntervalSize))*PathCount * IntervalSize

FrameSize = ceil(ImageWidth/8)*8 * ceil(ImageHeight/8)*8;

PathCount = ceil(Par/8)

IntervalSize: Next value greater than 7, which has no common factor with PathCount.

在内部,该算子始终使用是 8 的倍数的 Parallelism。根据输入 Parallelism 的不同,内部 Parallelism 转换可以补偿一部分带宽损失。带宽损失仅发生在帧结束时。

由于压缩图像数据的大小不可预测,压缩帧的最后一个输出数据字节可能不会与输出 Parallelism 对齐。因此,一帧的最后一个数据向量可能包含随机虚拟值。为了标记帧的实际结束,将使用 JPEG 标准中定义的 EOI (End of Image) 标记。EOI 标记由两个字节组成。每帧的倒数第二个字节是 0xFF,每帧的最后一个字节是 0xD9。

为了优化图像吞吐量(带宽),算子在激活标头生成后就会立即输出标头——甚至在图像数据到达算子输入端之前。这样,标头数据的传输就不会中断图像数据的传输,因为标头是提前传输的。这种做法的缺点是,算子的输出传输开始时间早于实际图像数据传输。在特定情况下,这可能会引起混淆:

  • 在 JPEG_Encoder 之后直接使用 SourceSelector 算子:Operator SourceSelector 一旦获取标头数据,就会注册部分处理的帧。因此,如果将 SourceSelector 切换为从 JPEG_Encoder 获取图像数据,则 SourceSelector 无法切换到任何其他源,因为它始终会检测到未完成的帧。此外,当启用标头生成且 SourceSelector 从其他源切换到 JPEG_Encoder 通道时,第一幅图像将丢失。

  • 要测量延迟,请使用 FrameEndToSignal 算子(而不是 FrameStartToSignal 和 SignalToDelay)。

使用 InfiniteSource 进行溢出管理

在 InfiniteSource 模式下,图像可能会丢失或损坏,因为 JPEG_Encoder 模块或后续模块无法处理该带宽。如果在算子已经接受部分图像的同时发生溢出,则所有后续传入的图像数据都将被丢弃,并且算子中的截断图像将被截断,从而产生部分输出图像。如果在帧开始到达时算子处于溢出状态,则整帧都会被丢弃且该帧丢失。对于每个截断或丢失的帧,都会生成一个 VA 事件(TruncatedEvent 和 LostEvent)。 JPEG_Encoder 事件由 3 个数据包组成,每个数据包 2 个字节。前两个 VA 事件数据包构成 frame-ID,它只是到达 JPEG_Encoder 输入的每个帧的 Counter。frame-ID 用于识别丢失或截断的确切帧。第三个数据包标记发生的错误类型。Bit 0 标记帧是丢失 (Bit0 = 1) 还是截断 (Bit0 = 0)。第三个数据包的其他 15 位仅在事件丢失的情况下使用(这种情况只有在事件发生速度过快超出了 CPU 处理能力时才会发生)。第三个数据包的 Bit1 标记任何丢失的 TruncatedEvent 的发生情况,同一数据包的 Bit2 标记任何丢失的 LostEvent 的发生情况。此外,对于丢失的 LostEvents ,Bit[15:3] 组成一个 Counter,其中包含丢失的 LostEvents 的数量。如果 Counter 值为零,则丢失-LostEvent-Counter 已溢出。由于截断帧会导致 JPEG_Encoder 输出数据,而丢失帧不会,因此 VA 事件仅对丢失的 LostEvents.

溢出事件数据

图 415. 溢出事件数据


I/O Property

Property 值
Operator Type M
Input Link I,数据输入
Output Link O,数据输出

Supported Link Format

Link Parameter Input Link I Output Link O
Bit Width 8 8
Arithmetic unsigned as I
Parallelism any 自动计算
Kernel Columns 1 as I
Kernel Rows 1 as I
Img Protocol VALT_IMAGE2D as I
Color Format VAF_GRAY as I
Color Flavor FL_NONE as I
Max. Img Width 2^16 -1 = 65,5351 any2
Max. Img Height 2^16 -1 = 65,535 1

1

该值不得低于最小图像宽度要求。

2

输出链路上的图像宽度是可配置的。如果输出图像大于为输出链路配置的最大图像宽度,则图像会被截断,并且丢弃其余的图像数据。每幅图像的最后两个字节始终包含图像结束标记 (EOI)。

参数

Quality
键入 静态/动态写入参数
默认 50.00
范围 [1.00 - 100.00 %], {-1}, 步长 = 0.01%

使用此参数可以更改 JPEG 压缩的 Quality。量化矩阵是根据上述公式从百分比值确定的。确定的量化值可以从参数 quantization_matrix 中读取。该参数是动态的,且只能在小程序空闲时进行更改。

可以定义介于 1 和 100 之间的 Quality 设置。写入此参数将覆盖通过参数 LuminanceQuantization 对量化矩阵所做的手动更改。如果从 Quality 中读取到 -1,则说明已对量化矩阵进行了手动更改。

Quality 参数是定义编码器压缩率的主要来源。亮度表将根据指定的 Quality 自动计算并可读回。不过,也可以直接修改亮度表。如果发生这种情况,Quality 参数将自动更改为 -1,以表明该参数不再有效且表使用的是手动覆盖模式。如果用户想要优化资源使用率,可以将 Quality 和量化表设置为静态模式。静态更改与动态更改只能针对 Quality 参数执行。量化表设置将自动遵循 Quality 类型,且无法手动覆盖。当 Quality 设置为 static 时,算子会根据 Quality 设置确定输出 Parallelism。在大多数情况下,与动态模式相比,输出 Parallelism 会降低,这可能意味着在牺牲运行时更改压缩率的灵活性的代价下,显著减少了 FPGA 资源。

LuminanceQuantization
键入 遵循 Quality 类型(动态或静态)写入参数
默认 没有任何
范围 [1 - 255]

针对范围在 [1.00 - 100.00] 内的 Quality 自动计算,手动设置时,Quality 将失效并显示为 -1。

IncludeHeader
键入 静态写参数
默认 YES
范围 [YES,NO]

JPEG 标头默认包含在压缩流中。但是,您可以禁用此功能。如果您将参数 IncludeHeader 设置为 NO,则 JPEG 参数 ImageHeight 和 ImageWidth 将变为未激活状态。如果您将参数 IncludeHeader 设置为 YES,则标头将被包含,并且您可以决定标头参数是应为静态还是必须为动态。如果设置为静态,则图像宽度和图像HEIGHT的值将静态嵌入标头中,并且无论输入图像大小如何都无法更改。如果设置为动态,您可以在运行时更改图像宽度和图像高度的值。请注意,这些标头参数不会自动更新为输入图像大小。如果您使用的算子,其图像大小在运行时动态变化,则您必须事后修补生成的标头。如果您将 ImageHeight 和 ImageWidth 都设置为“static”,则可以略微减少 FPGA 资源的使用量。

ImageWidth
键入 静态/动态写入参数
默认 1024
范围 1 - 2^16-1

此参数仅在参数 IncludeHeader 设置为 YES 时可用。

参数 ImageWidth 仅用于生成如参数 IncludeHeader 中所述的 JPEG 图像标头。

ImageHeight
键入 静态/动态写入参数
默认 1024
范围 1 - 2^16-1

此参数仅在参数 IncludeHeader 设置为 YES 时可用。

参数 ImageHeight 仅用于生成如参数 IncludeHeader 中所述的 JPEG 图像标头。

InfiniteSource
键入 静态写参数
默认 DISABLED
范围 {ENABLED, DISABLED}

该算子可以直接插入相机算子后面。在这种情况下,必须将 InfiniteSource 参数设置为 ENABLED。然后,算子将执行主动溢出管理,并通过 VA 事件系统向软件报告溢出状况。溢出仅在两种情况下发生:算子后面的接收器停止/暂停传输,或者输入图像高度不是 8 行的倍数。在后一种情况下,算子必须填充缺少的行,以完成 JPEG 标准要求的最后一行 JPEG 块。

有关更多信息,请参见 “Infinite Sources / Connecting Cameras”。

使用示例

以下示例展示了 JPEG_Encoder 算子的使用方法:

更多信息

使用以下霍夫曼 DC 和 AC 系数。

 typedef char DCHuffTableType[12][17]; // Huffman table for
      luminance DC coefficients DCHuffTableType Lum_DC_HuffmanTable= { "00", "010", "011", "100",
      "101", "110", "1110", "11110", "111110", "1111110", "11111110", "111111110" }; typedef char
      ACHuffTableType[16][11][17]; // Huffman table for luminance AC coefficients ACHuffTableType
      Lum_AC_HuffmanTable= { { //Run == 0 "1010",//EOB "00", "01", "100", "1011", "11010",
      "1111000", "11111000", "1111110110", "1111111110000010", "1111111110000011" }, { //Run == 1
      "1010",//EOB "1100", "11011", "1111001", "111110110", "11111110110", "1111111110000100",
      "1111111110000101", "1111111110000110", "1111111110000111", "1111111110001000" }, { //Run == 2
      "1010",//EOB "11100", "11111001", "1111110111", "111111110100", "1111111110001001",
      "1111111110001010", "1111111110001011", "1111111110001100", "1111111110001101",
      "1111111110001110" }, { //Run == 3 "1010",//EOB "111010", "111110111", "111111110101",
      "1111111110001111", "1111111110010000", "1111111110010001", "1111111110010010",
      "1111111110010011", "1111111110010100", "1111111110010101" }, { //Run == 4 "1010",//EOB
      "111011", "1111111000", "1111111110010110", "1111111110010111", "1111111110011000",
      "1111111110011001", "1111111110011010", "1111111110011011", "1111111110011100",
      "1111111110011101", }, { //Run == 5 "1010",//EOB "1111010", "11111110111", "1111111110011110",
      "1111111110011111", "1111111110100000", "1111111110100001", "1111111110100010",
      "1111111110100011", "1111111110100100", "1111111110100101" }, { //Run == 6 "1010",//EOB
      "1111011", "111111110110", "1111111110100110", "1111111110100111", "1111111110101000",
      "1111111110101001", "1111111110101010", "1111111110101011", "1111111110101100",
      "1111111110101101" }, { //Run == 7 "1010",//EOB "11111010", "111111110111",
      "1111111110101110", "1111111110101111", "1111111110110000", "1111111110110001",
      "1111111110110010", "1111111110110011", "1111111110110100", "1111111110110101", }, { //Run ==
      8 "1010",//EOB "111111000", "111111111000000", "1111111110110110", "1111111110110111",
      "1111111110111000", "1111111110111001", "1111111110111010", "1111111110111011",
      "1111111110111100", "1111111110111101" }, { //Run == 9 "1010",//EOB "111111001",
      "1111111110111110", "1111111110111111", "1111111111000000", "1111111111000001",
      "1111111111000010", "1111111111000011", "1111111111000100", "1111111111000101",
      "1111111111000110" }, { //Run == 0xA "1010",//EOB "111111010", "1111111111000111",
      "1111111111001000", "1111111111001001", "1111111111001010", "1111111111001011",
      "1111111111001100", "1111111111001101", "1111111111001110", "1111111111001111" }, { //Run ==
      0xB "1010",//EOB "1111111001", "1111111111010000", "1111111111010001", "1111111111010010",
      "1111111111010011", "1111111111010100", "1111111111010101", "1111111111010110",
      "1111111111010111", "1111111111011000" }, { //Run == 0xC "1010",//EOB "1111111010",
      "1111111111011001", "1111111111011010", "1111111111011011", "1111111111011100",
      "1111111111011101", "1111111111011110", "1111111111011111", "1111111111100000",
      "1111111111100001" }, { //Run == 0xD "1010",//EOB "11111111000", "1111111111100010",
      "1111111111100011", "1111111111100100", "1111111111100101", "1111111111100110",
      "1111111111100111", "1111111111101000", "1111111111101001", "1111111111101010" }, { //Run ==
      0xE "1010",//EOB "1111111111101011", "1111111111101100", "1111111111101101",
      "1111111111101110", "1111111111101111", "1111111111110000", "1111111111110001",
      "1111111111110010", "1111111111110011", "1111111111110100" }, { //Run == 0xF "11111111001",
      //ZRL "1111111111110101", "1111111111110110", "1111111111110111", "1111111111111000",
      "1111111111111001", "1111111111111010", "1111111111111011", "1111111111111100",
      "1111111111111101", "1111111111111110" } };