TinyEXR 单头文件 OpenEXR 读写库完全指南:格式支持、API 实战与 Instant-NGP 集成
发布时间:2026/9/21 15:24:45 锦皓数字建站

人工智能深度学习计算机视觉3D渲染图形学科研【免费下载链接】instant-ngpInstant neural graphics primitives: lightning fast NeRF and more项目地址https://gitcode.com/gh_mirrors/in/instant-ngp点击查看免费下载TinyEXR 是一个零外部依赖、以单个头文件分发的 OpenEXR.exr图像读写库采用可移植 C 编写除 STL 外不依赖任何第三方库。本文以dependencies/tinyexr/README.md为主体骨架完整讲解其格式支持矩阵、编译开关、从快速读取到深图像读写的全套 API 用法并结合本仓库instant-ngp中 tinyexr_wrapper.cu 等源码剖析它在 HDR 环境贴图、训练图像加载等真实场景中的落地方式。读完本文你将能够独立将 TinyEXR 嵌入自己的项目并理解 instant-ngp 内部 EXR 数据的完整流转链路。一、TinyEXR 是什么tinyexr是一个用于加载与保存 OpenEXR.exr图像的小型单头文件库其核心头文件位于 dependencies/tinyexr/tinyexr.h。它的设计目标非常明确可移植、易嵌入。整个库以可移植 C 写成除了 STL 之外没有库依赖使用时只需把tinyexr.h复制到你的项目里即可README 明确说明 To usetinyexr, simply copytinyexr.hinto your project.。与庞大的 OpenEXR 官方实现IlmImf相比TinyEXR 的价值在于单头文件分发无需复杂的依赖链和构建系统一个头文件即完整实现无外部依赖默认内置 miniz 处理 ZIP/ZIPS 压缩不需要单独链接 zlibC 接口友好README 指出其提供 C 接口便于为其他语言如 golang编写绑定性能优化支持 C11 线程加载与 OpenMP 多线程读写。需要说明的是README 特别提示API 仍可能变动API is still subject to change细节请以源码为准。二、格式与功能支持矩阵README 用清单方式完整列出了当前实现状态这是评估 TinyEXR 能力边界的第一手资料整理如下功能类别已支持未支持/待实现OpenEXR v1 图像扫描线Scanline格式、自定义属性Custom attributes、无 LoD 的 Tiled 格式加载Tiled 带 LoD 加载、Tiled 保存含/不含 LoDOpenEXR v2 图像加载多部件Multipart图像保存多部件图像、多部件深图像读写Deep 深图像加载扫描线 ZIPS HALF 或 FLOAT 像素类型深图像保存、UINT/FLOAT 深图像保存压缩方式NONE、RLE、ZIP、ZIPS、PIZ以及 TinyEXR 扩展的 ZFPB44、B44A、PIX24行序Line order递增、递减加载随机行序、递增/递减保存像素格式UINT、FLOAT加载/保存UINT、FLOAT深图像加载深图像保存大端机器扫描线图像加载与保存多部件通道 EXR、深图像的加载与保存优化C11 线程加载、OpenMP 多线程 EXR 加载/保存C11 线程保存、ISPC、深图像多线程从tinyexr.h源码可以印证上述像素类型的定义tinyexr.h// pixel type: possible values are: UINT 0 HALF 1 FLOAT 2 #define TINYEXR_PIXELTYPE_UINT (0) #define TINYEXR_PIXELTYPE_HALF (1) #define TINYEXR_PIXELTYPE_FLOAT (2)理解这张表的意义在于如果你的业务需要读写 Tiled 带 mipmap 的 EXR、多部件保存或深图像保存TinyEXR 目前无法胜任应改用官方 OpenEXR 库反之常见的单部件扫描线 RGBA 图像、HDR 帧序列、环境贴图TinyEXR 是轻量高效的选择。三、快速集成与编译开关3.1 引入方式在一个.cc/.cu 文件中定义TINYEXR_IMPLEMENTATION宏后再包含头文件其余文件直接包含头文件即可// 若你关闭 TINYEXR_USE_MINIZ则需在包含 tinyexr.h 之前 // 自行包含 zlib 兼容的 API 头文件 // #define TINYEXR_USE_MINIZ 0 // #include zlib.h #define TINYEXR_IMPLEMENTATION #include tinyexr.h本仓库正是这样做的在 src/tinyexr_wrapper.cu 中TINYEXR_IMPLEMENTATION被定义后引入tinyexr/tinyexr.h从而在整个 instant-ngp 中只实例化一次 TinyEXR 实现其余模块通过 include/neural-graphics-primitives/tinyexr_wrapper.h 声明的接口调用。3.2 编译宏README 列出的五个编译宏及其默认值如下宏默认值作用TINYEXR_USE_MINIZ1使用内置 miniz 处理压缩。置 0 时需在tinyexr.h之前自行包含zlib.hTINYEXR_USE_PIZ1启用 PIZ 压缩支持TINYEXR_USE_ZFP0启用 ZFP 压缩TinyEXR 扩展默认关闭TINYEXR_USE_THREAD0启用基于 C11 线程的并行加载要求 C11 编译器TINYEXR_USE_OPENMP1若定义了_OPENMP启用 OpenMP 多线程可用TINYEXR_USE_OPENMP0强制关闭这些默认值在 tinyexr.h 中有对应实现例如TINYEXR_USE_OPENMP是依据编译器是否定义_OPENMP自动决定的。在 CUDA 项目中编译器不一定定义_OPENMP因此 OpenMP 路径通常不会生效这是正常的。四、API 实战读取 EXR4.1 最快路径LoadEXR一键读取 RGBA如果你只需要把一张 EXR 读成width * height * RGBA的 float 数组LoadEXR是最简单的方式const char* input asakusa.exr; float* out; // width * height * RGBA int width; int height; const char* err NULL; // C11 中可用 nullptr int ret LoadEXR(out, width, height, input, err); if (ret ! TINYEXR_SUCCESS) { if (err) { fprintf(stderr, ERR : %s\n, err); FreeEXRErrorMessage(err); // 释放错误信息内存 } } else { // ... 使用 out free(out); // 释放图像数据内存 }注意三个内存管理约定错误信息用FreeEXRErrorMessage释放、图像数据用free释放、出错时err可能非空但out无有效数据。4.2 分层 EXRLoadEXRWithLayer当 EXR 的通道名包含.分隔符如diffuse.R、diffuse.G时需要用分层读取接口。层名需要提前获知可通过EXRLayersAPI 枚举const char* input ...; const char* layer_name diffuse; // 或先用 EXRLayers 获取 EXR 中的层名列表 float* out; // width * height * RGBA int width; int height; const char* err NULL; // 将读取 diffuse.R、diffuse.G、diffuse.B、如有则 diffuse.A通道 int ret LoadEXRWithLayer(out, width, height, input, layer_name, err); // 错误处理与释放方式同 LoadEXR4.3 标准流程单部件 EXR 的完整加载当需要精细控制例如检查版本、读取 HALF 为 FLOAT、区分 scanline 与 tiled时使用分段 API。该流程分为“解析版本 → 解析头 → 加载图像 → 释放”四步// 1. 读取 EXR 版本 EXRVersion exr_version; int ret ParseEXRVersionFromFile(exr_version, argv[1]); if (ret ! 0) { fprintf(stderr, Invalid EXR file: %s\n, argv[1]); return -1; } if (exr_version.multipart) { // 单部件流程要求 multipart 标志为 false return -1; } // 2. 读取 EXR 头 EXRHeader exr_header; InitEXRHeader(exr_header); const char* err NULL; ret ParseEXRHeaderFromFile(exr_header, exr_version, argv[1], err); if (ret ! 0) { fprintf(stderr, Parse EXR err: %s\n, err); FreeEXRErrorMessage(err); return ret; } // // 将 HALF 通道按 FLOAT 读取按需启用 // for (int i 0; i exr_header.num_channels; i) { // if (exr_header.pixel_types[i] TINYEXR_PIXELTYPE_HALF) { // exr_header.requested_pixel_types[i] TINYEXR_PIXELTYPE_FLOAT; // } // } EXRImage exr_image; InitEXRImage(exr_image); ret LoadEXRImageFromFile(exr_image, exr_header, argv[1], err); if (ret ! 0) { fprintf(stderr, Load EXR err: %s\n, err); FreeEXRHeader(exr_header); FreeEXRErrorMessage(err); return ret; } // 3. 访问图像数据 // exr_image.images 在 EXR 为 scanline 格式时填充 // exr_image.tiled 在 EXR 为 tiled 格式时填充 // 4. 释放 FreeEXRImage(exr_image); FreeEXRHeader(exr_header);这里体现了一个重要机制EXRHeader.pixel_types表示文件中的原始像素类型而requested_pixel_types表示你希望以什么类型读出加载时通道数据会按requested_pixel_types进行转换tinyexr.h 中对此有明确注释。4.4 多部件MultipartEXR 加载EXR 2.0 的 multipart 文件包含多个独立 part。加载流程是先解析版本并确认multipart为 true再用ParseEXRMultipartHeaderFromFile一次性解析出所有 part 的头EXRVersion exr_version; int ret ParseEXRVersionFromFile(exr_version, argv[1]); if (ret ! 0) { return -1; } if (!exr_version.multipart) { return -1; } // 必须为 multipart EXRHeader **exr_headers; // EXRHeader 指针数组 int num_exr_headers; const char *err NULL; // EXRHeader 的内存由 ParseEXRMultipartHeaderFromFile 内部分配 ret ParseEXRMultipartHeaderFromFile(exr_headers, num_exr_headers, exr_version, argv[1], err); if (ret ! 0) { fprintf(stderr, Parse EXR err: %s\n, err); FreeEXRErrorMessage(err); return ret; } printf(num parts %d\n, num_exr_headers); // 3. 加载图像为每个 part 准备 EXRImage std::vectorEXRImage images(num_exr_headers); for (int i 0; i num_exr_headers; i) { InitEXRImage(images[i]); } ret LoadEXRMultipartImageFromFile(images.at(0), const_castconst EXRHeader**(exr_headers), num_exr_headers, argv[1], err); if (ret ! 0) { fprintf(stderr, Parse EXR err: %s\n, err); FreeEXRErrorMessage(err); return ret; } printf(Loaded %d part images\n, num_exr_headers); // 5. 依次释放图像与头 for (int i 0; i num_exr_headers; i) { FreeEXRImage(images.at(i)); } for (int i 0; i num_exr_headers; i) { FreeEXRHeader(exr_headers[i]); free(exr_headers[i]); } free(exr_headers);需要特别留意的是所有权边界ParseEXRMultipartHeaderFromFile分配的EXRHeader数组最终要逐个FreeEXRHeader后再free指针本身最后释放数组指针避免泄漏。五、API 实战保存扫描线 EXR保存流程与加载对称初始化EXRHeader与EXRImage把交错的 RGB 拆成独立的 R/G/B 通道平面再调用SaveEXRImageToFile。README 给出的完整示例核心逻辑如下bool SaveEXR(const float* rgb, int width, int height, const char* outfilename) { EXRHeader header; InitEXRHeader(header); EXRImage image; InitEXRImage(image); image.num_channels 3; std::vectorfloat images[3]; images[0].resize(width * height); images[1].resize(width * height); images[2].resize(width * height); // 把 RGBRGBRGB... 交错数据拆成 R、G、B 三个平面 for (int i 0; i width * height; i) { images[0][i] rgb[3*i0]; images[1][i] rgb[3*i1]; images[2][i] rgb[3*i2]; } float* image_ptr[3]; image_ptr[0] (images[2].at(0)); // B image_ptr[1] (images[1].at(0)); // G image_ptr[2] (images[0].at(0)); // R image.images (unsigned char**)image_ptr; image.width width; image.height height; header.num_channels 3; header.channels (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels); // 必须是 (A)BGR 顺序因为大多数 EXR 查看器期望这个通道顺序 strncpy(header.channels[0].name, B, 255); header.channels[0].name[strlen(B)] \0; strncpy(header.channels[1].name, G, 255); header.channels[1].name[strlen(G)] \0; strncpy(header.channels[2].name, R, 255); header.channels[2].name[strlen(R)] \0; header.pixel_types (int *)malloc(sizeof(int) * header.num_channels); header.requested_pixel_types (int *)malloc(sizeof(int) * header.num_channels); for (int i 0; i header.num_channels; i) { header.pixel_types[i] TINYEXR_PIXELTYPE_FLOAT; // 输入图像像素类型 header.requested_pixel_types[i] TINYEXR_PIXELTYPE_HALF; // 写入 .EXR 文件的输出像素类型 } const char* err NULL; int ret SaveEXRImageToFile(image, header, outfilename, err); if (ret ! TINYEXR_SUCCESS) { fprintf(stderr, Save EXR err: %s\n, err); FreeEXRErrorMessage(err); return ret; } printf(Saved exr file. [ %s ] \n, outfilename); free(rgb); free(header.channels); free(header.pixel_types); free(header.requested_pixel_types); }两个关键实践要点通道命名与顺序EXR 查看器普遍期望 (A)BGR 顺序因此header.channels[0]写 B、channels[1]写 G、channels[2]写 R同时image_ptr也按 B、G、R 排列指向对应平面HALF 输出把requested_pixel_types设为TINYEXR_PIXELTYPE_HALF即可在保存时把 FLOAT 数据降为 HALF16 位浮点这是 HDR 图像文件的标准做法文件体积减半而视觉精度几乎无损。六、Deep 深图像读取深图像deep image每个像素包含可变数量的采样点。加载后通过offset_table定位每个像素的采样区间再按通道读取样本值。README 给出的访问模式如下const char* input deepimage.exr; const char* err NULL; DeepImage deepImage; int ret LoadDeepEXR(deepImage, input, err); // 访问深像素中的每个采样点 for (int y 0; y deepImage.height; y) { int sampleNum deepImage.offset_table[y][deepImage.width-1]; for (int x 0; x deepImage.width-1; x) { int s_start deepImage.offset_table[y][x]; int s_end deepImage.offset_table[y][x1]; if (s_start sampleNum) { continue; } s_end (s_end sampleNum) ? s_end : sampleNum; for (int s s_start; s s_end; s) { float val deepImage.image[depthChan][y][s]; // ... } } }这里offset_table[y][x]到offset_table[y][x1]之间的整数区间就是像素(x, y)的采样点索引范围用sampleNum本行最后一个像素的累计采样数做边界裁剪即可安全遍历。README 提到examples/deepview是配套的 OpenGL 深图像查看器示例该示例位于 TinyEXR 上游仓库的 examples 目录当前仓库快照未包含该目录。七、TinyEXR 扩展ZFP 有损压缩ZFP 是 TinyEXR 对 OpenEXR 规范之外的自定义扩展用于对 FLOAT 像素做块式有损压缩4×4 像素块仅支持 Linux 与 macOS。其约束与构建步骤在 README 中有明确说明。约束仅支持 FLOAT 像素格式图像宽高必须是 4 的倍数因为 ZFP 以 4×4 像素块为单位压缩。构建当前仓库已内置 ZFP 源码于 dependencies/tinyexr/deps/ZFP$ git submodule update --init # 若 ZFP 以子模块方式检出 $ cd deps/ZFP $ mkdir -p lib # 若 lib 目录不存在则创建 $ make然后将 tinyexr.h 中的TINYEXR_USE_ZFP设为 1并在构建应用时链接deps/ZFP/lib/libzfp.a。ZFP 属性约定ZFP 压缩的 EXR 必须包含如下自定义属性属性名类型含义zfpCompressionTypeuchar0 固定速率压缩1 基于精度的可变速率压缩2 基于误差容忍的可变速率压缩zfpCompressionRatedouble固定速率压缩的压缩率类型为 0 时存在zfpCompressionPrecisionint32基于精度的可变速率压缩的位数类型为 1 时存在zfpCompressionTolerancedouble基于误差容忍压缩的容忍值类型为 2 时存在其中zfpCompressionType必选其余三个属性按类型三选一。README 还指出 ZFP 压缩器本身在大端机器上工作正常而 TinyEXR 的 ZFP 扩展整体仍是实验性支持。八、在 instant-ngp 中的真实集成从 EXR 到 GPU 训练数据TinyEXR 在本仓库instant-ngp中扮演 HDR 图像基础库的角色其价值从源码调用链中可以清晰看到。8.1 封装层NVIDIA 为 instant-ngp 封装了三个高层接口声明在 include/neural-graphics-primitives/tinyexr_wrapper.h实现在 src/tinyexr_wrapper.cusave_exr(...)把 FLOAT 数据按 (A)BGR 通道序保存为 HALF 型 EXR与 README 示例完全一致见tinyexr_wrapper.cu中channel_names的倒序命名与requested_pixel_types TINYEXR_PIXELTYPE_HALFload_exr(...)读文件到内存并用LoadEXRFromMemory解析出 RGBA float 数据load_exr_to_gpu(...)内存版全流程——ParseEXRVersionFromMemory→ParseEXRHeaderFromMemory→LoadEXRImageFromMemory逐通道cudaMemcpy到 GPU再由 CUDA kernelinterleave_and_cast_kernel把平面数据交错为 RGBA 并转为__half同时支持fix_premult预乘修正。封装层还体现了几条实用的工程约束拒绝 multipart 文件exr_version.multipart为真即抛异常、拒绝混合通道类型所有通道必须同为 FLOAT 或同为 HALF否则报 Cant handle EXR images with mixed channel types。这些约束与 README 中“单部件加载”的流程一脉相承。8.2 在 NeRF / 图像测试台中的使用训练图像加载src/nerf_loader.cu 中当 NeRF 数据集帧图像的扩展名是exr时调用load_exr_to_gpu将 HDR 图像直接载入 GPU 并标记为EImageDataType::Half、is_hdr true环境贴图加载src/nerf_loader.cu 中transforms.json 里envmap字段指向.exr时通过load_exr_gpu见 src/common_host.cu加载 HDR 环境光图像模式src/testbed_image.cu 的load_image根据扩展名分发.exr走load_exr_image路径加载结果作为待拟合的 HDR 图像仓库自带的示例数据 data/image/albert.exr 正是这一模式的标准输入模型权重导出src/testbed.cu 使用save_exr把神经网络各层参数与 non-layer 参数分别导出为*-layer-*.exr与*-non-layer.exr利用 HALF 精度压缩权重文件体积。因此configs/image/base.json 这类图像拟合配置即可直接消费 EXR 输入——它定义的是 HashGrid 编码 两层 64 神经元 MLP 的拟合网络EXR 的 HDR 动态范围正是这类图像学习任务需要保留的关键信息。九、测试、实验与周边单元测试README 指出单元测试位于上游的test/unit目录当前仓库快照中与之对应的可直接构建的测试入口是 dependencies/tinyexr/test_tinyexr.cc配套 Makefile 可用于本地构建验证JS 移植上游 README 提到基于 Emscripten 的 JavaScript 移植位于上游experimental/js当前仓库保留了对应的 experimental/js 目录包含binding.cc、compile_to_js.sh与index.html等Android JNIjni 目录提供 Android.mk/Application.mk 构建脚本说明 TinyEXR 可在移动端嵌入许可TinyEXR 采用 3-clause BSD 许可见 LICENSE.txt内部使用 Rich Geldreich 开发的公有领域 miniz工具部分使用公有领域的 stb并含少量来自 OpenEXR3-clause BSD的代码——这也是它适合商业嵌入的许可基础。十、总结与选型建议TinyEXR 以“单头文件 零依赖”的极简形态覆盖了 OpenEXR 日常读写的大部分需求扫描线与无 LoD 平铺格式加载、NONE/RLE/ZIP/ZIPS/PIZ 压缩、HALF/FLOAT/UINT 像素、多部件加载、深图像加载以及 ZFP 有损压缩扩展。在 instant-ngp 中它承担了 HDR 图像从磁盘到 GPU 显存的全链路职责是 NeRF 训练与环境贴图渲染的重要基础组件。选型时请对照第二节的支持矩阵需要保存多部件/深图像、Tiled 带 LoD 或 B44/B44A 压缩时TinyEXR 尚不支持应评估官方 OpenEXR而追求嵌入轻量、快速读取 HDR 帧序列与扫描线 EXR 时TinyEXR 是经过 instant-ngp 生产验证的可靠选择。若需深入了解 API 细节请直接阅读 tinyexr.h 中的函数注释与常量定义。赞分享人工智能深度学习计算机视觉3D渲染图形学科研【免费下载链接】instant-ngpInstant neural graphics primitives: lightning fast NeRF and more项目地址https://gitcode.com/gh_mirrors/in/instant-ngp点击查看免费下载相关推荐Apache Arrow文件格式支持Parquet、CSV等格式读写指南Apache Arrow文件格式支持Parquet、CSV等格式读写指南 概述 Apache Arrow是一个跨语言的内存格式主要用于高效地传输和存储数据。大数据数据分析数据工程序列化instant-ngp 中的 NaturalSortC 单头文件自然排序库的 API、算法原理与 NeRF 数据加载实践instant ngp 中的 NaturalSortC 单头文件自然排序库的 API、算法原理与 NeRF 数据加载实践 导读 NaturalSort 是人工智能深度学习计算机视觉3D渲染图形学科研Apache Arrow C IPC 读写 API 实战流式格式与文件格式的完整解析Apache Arrow C IPC 读写 API 实战流式格式与文件格式的完整解析 Apache Arrow 的 IPCInter Process C大数据数据分析数据工程序列化上一篇Flexile服务发现动态服务注册与发现下一篇Flexile网络防护网络攻击防护与WAF配置创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。