ESP-New-JPEG 组件
备注
有关 JPEG 的基础知识,可参考 JPEG
概述
ESP-New-JPEG 是乐鑫科技推出的轻量级 JPEG 编解码库。为提高效率,JPEG 编解码器经过深度优化,减少了内存消耗并提升了处理性能。对于支持 SIMD 指令的 ESP32-S3 芯片,利用这些指令进一步提升了处理速度。此外,还扩展了旋转、裁剪和缩放功能,能够在编解码过程中同时进行,从而简化用户操作。针对内存较小的芯片,引入了 block 模式,支持多次处理部分图像内容,有效减少内存压力。
ESP-New-JPEG 支持 Baseline Profile 的 JPEG 编解码,旋转、裁剪、缩放和 block 模式等功能仅在特定配置下能生效。
JPEG 编码器功能
编码器支持的 基本功能 如下:
支持任意宽度和高度解码
支持以下像素格式:RGB888、RGB565(大端)、RGB565(小端)、RGBA、YCbYCr、CbYCrY、YCbY2YCrY2、GRAY
当采用 YCbY2YCrY2 格式时,仅支持 YUV420 和 Gray 子采样
支持 YUV444、YUV422、YUV420、Gray 子采样
支持质量设置范围:1-100
扩展功能 如下:
支持 0°、90°、180°、270° 顺时针旋转
支持双任务(dual-task)编码
支持分块模式(block mode)编码
双任务编码可在双核芯片上使用,充分发挥双核并行编码的优势。其原理是一个核心处理主要编码任务,另一个核心负责熵编码部分的工作。在大多数情况下,启用双核编码能带来约 1.5 倍的性能提升。可以通过 menuconfig 配置选择是否启用双核解码,并调整熵编码任务的核心和优先级。
分块编码是指每次仅编码一个图像块的数据,经过多次处理后编码完整的图像。在 YUV420 子采样时,每个块的高度为 16 行,宽度为图像宽度;其他子采样格式下,每个块的高度为 8 行,宽度为图像宽度。由于分块编码每次处理的数据量较小,图像缓冲区可放入 DRAM,从而提高编码速度。分块编码的工作流程如下图所示:
扩展功能的配置要求如下所示:
JPEG 解码器功能
解码器支持的 基本功能 如下:
支持任意宽度和高度解码
支持单通道和三通道解码
支持以下像素格式输出:RGB888、RGB565(大端)、RGB565(小端)、CbYCrY
扩展功能 如下:
支持缩放(最大缩小倍率为 1/8)
支持裁剪(以左上角为原点进行裁剪)
支持 0°、90°、180°、270° 顺时针旋转
支持分块模式(block mode)解码
缩放、裁剪和旋转的处理流程是顺序进行的,示意图如下。解码后的 JPEG 数据流首先进行缩放,然后进行裁剪,最后进行旋转和输出。
使用缩放和裁剪功能时,需要配置 jpeg_resolution_t 结构体中的对应参数。组件支持仅对宽度或高度进行单独处理,例如,仅裁剪宽度而保持高度不变时,可以设置 clipper.height = 0,此时图像的高度将保持为原始 JPEG 图像的高度。处理流程可通过下述详细或简化配置完成。
// Detailed configuration
jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
config.output_type = JPEG_PIXEL_FORMAT_RGB565_LE;
config.scale.width = 320;
config.scale.height = 120;
config.clipper.width = 192;
config.clipper.height = 120;
config.rotate = JPEG_ROTATE_90D;
// Simplified configuration
jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
config.output_type = JPEG_PIXEL_FORMAT_RGB565_LE;
config.scale.width = 0; // keep width unchanged by setting to 0
config.scale.height = 120;
config.clipper.width = 192;
config.clipper.height = 0; // keep height unchanged by setting to 0
config.rotate = JPEG_ROTATE_90D;
分块解码是指每次仅解码一个图像块的数据,经过多次处理后完成整个图像的解码。在 YUV420 子采样时,每个块的高度为 16 行,宽度为图像宽度;对于其他子采样格式,每个块的高度为 8 行,宽度为图像宽度。由于分块解码每次处理的数据量较小,因此对于没有 PSRAM 的芯片更加友好,同时将输出图像缓冲区放入 DRAM 也能提高解码速度。分块解码的操作可以看成是分块编码的反过程。
分块解码的典型使用方法如下:
jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
config.block_enable = true;
jpeg_dec_open();
jpeg_dec_parse_header();
int output_len = 0;
int process_count = 0;
jpeg_dec_get_outbuf_len(hd, &output_len);
jpeg_dec_get_process_count(hd, &process_count);
for (int block_cnt = 0; block_cnt < process_count; block_cnt++) {
jpeg_dec_process();
}
jpeg_dec_close();
扩展功能的配置要求如下所示:
启用分块解码时,无法使用其他扩展功能
缩放、裁剪和旋转的配置参数中的宽度和高度均要求是 8 的整数倍
当同时启用缩放和裁剪时,要求裁剪的大小小于缩放后的大小
性能
ESP-New-JPEG 对 JPEG 编解码架构进行了深度优化:
优化数据处理流程,提高中间数据的复用效率,减少内存拷贝开销。
针对 Xtensa 架构芯片进行汇编级优化;在支持 SIMD 指令的 ESP32-S3 芯片上,显著提升计算性能。
将裁剪、旋转等多种图像操作整合进编解码器,提升整体系统效率
编解码性能测试数据请参考 Performance。
使用方法
ESP-New-JPEG 组件托管在 Github。可以通过在工程中输入以下命令将该组件添加到你的工程中。
idf.py add-dependency “espressif/esp_new_jpeg”
esp_new_jpeg 文件夹下的 test_app 文件夹中包含一个可运行的测试工程,里面展示了相关 API 调用流程。在使用 ESP-New-JPEG 组件前,建议先参考和调试该测试工程,以熟悉 API 的使用方法。
FAQ
Q: ESP-New-JPEG 支持解码渐进式 JPEG 吗?
A: 不支持,ESP-New-JPEG 仅支持解码基线 JPEG。可以使用以下代码检查图像是否为渐进式 JPEG。输出 1 表示渐进式 JPEG,输出 0 表示基线 JPEG。
python >>> from PIL import Image >>> Image.open("file_name.jpg").info.get('progressive', 0)
Q: 为什么输出图像看起来错位?
A:此问题通常发生在图像的左侧或右侧出现一些列,且这些列出现在图像的另一侧。如果您使用的是 ESP32-S3,可能的原因是解码器的输出缓冲区或编码器的输入缓冲区没有进行 16 字节对齐。请使用 jpeg_calloc_align() 函数来分配缓冲区。
Q: 如何预览图像原始数据,如查看 RGB888 数据?
A:可以使用 yuvplayer。支持查看灰度、RGB888、RGB565(小端)、UYVY、YUYV,YUV420P 等数据。
Q: 为什么 ESP_NEW_JPEG 在 ESP32-P4 上解码速度较慢?
A:ESP_NEW_JPEG 尚未针对 ESP32-P4 进行优化。但 ESP32-P4 配备了硬件 JPEG 编解码模块,硬件解码性能优于软件解码。建议在 ESP32-P4 上使用硬件 JPEG 模块以获得更优的解码性能。可以参考 JPEG 图像编解码器 - ESP32-P4 获取更多信息。
Q: ESP_NEW_JPEG 是否会与硬件编解码器整合到一个组件,类似 H264 的组件?
A:暂无计划。
Q: 如何估算解码速度?
A:特定分辨率图像的解码速度可以通过已测试的基准数据进行估算。如待测的图像分辨率为 480x512, 已知 640x480 的解码速度为 13.24 fps, 则可以估算出 480x512 的解码速度约为 13.24 * (480/640) * (512/480) = 10.59 fps。
已测试数据请参考 Performance。
Q: ESP_NEW_JPEG 的内存消耗如何?
A:目前仅统计了 decoder 的内存消耗情况。
当不开启 scale 时,内存消耗恒定,约 10 KB。
open()时分配大部分固定内存,close()时释放全部内存。当开启 scale 时,内存消耗随图像宽度增加而增加。
Q: 如何理解 ESP_NEW_JPEG 里的流式处理的概念?
A:ESP_NEW_JPEG 解码接口基础使用方式:open() > parse_header() > process() > close()
如果每张图像参数都一样,每次都 open 和 close 会浪费资源,因此设计了流式处理的示例:open 一次,循环 parse_header > process,结束后 close。
相关链接
组件注册表: esp_new_jpeg component