
简介本资源面向计算机相关专业学生、高校教师及算法工程师提供一套基于Transformer的轻量级低光图像增强LYT-Net算法的ONNX模型部署方案可用于毕设、课设或二次开发学习。压缩包共21个文件约3.09MB包含9个ONNX模型文件覆盖LOLv1、LOLv2合成与真实场景的多种分辨率、Python与C推理源码各一份以及若干测试图片和说明文档方便在不同平台验证低亮度增强效果。已有240人学习下载。资源覆盖从模型加载、预处理到推理输出的完整部署链路读者可据此掌握ONNX Runtime在Python与C环境下的调用方式理解低光增强模型的输入输出结构与多分辨率适配思路并借助测试图直观对比增强前后效果适合希望快速上手Transformer图像增强部署的开发者参考。1. 低光增强模型落地为什么我盯上了 LYT-Net 的 ONNX 部署包夜里十一点监控画面糊成一团客户还在催“能不能把脸看清”。这种场景下重跑一遍训练不现实现场只有一台工控机Python 环境都不一定齐全。我拿到这个包的第一反应不是看算法多新而是看它能不能在 CPU 上跑起来、能不能塞进 C 工程。这个资源是一套基于 Transformer 的低亮度图像增强 LYT-Net 算法实现同时给了 Python 和 C 两套源码外加导出好的 ONNX 模型。它解决的不是“论文复现”问题而是“模型怎么从 PyTorch 走到实际工程里”的问题。适合两类人一类是想把低光增强塞进自己 C/Python 项目但卡在部署环节的工程师另一类是想搞懂 Transformer 图像增强模型怎么转 ONNX、怎么量化、怎么跨语言调用的从业者。如果你只想要个能跑的 demo这包够用如果你想改结构重训源码也在。2. LYT-Net 与 ONNX 部署先搞清楚模型结构和转换链路2.1 LYT-Net 为什么用 Transformer 做低光增强低光增强的老办法是直方图均衡、Gamma 校正、Retinex 分解这些在均匀欠曝场景下还行一旦遇到光源复杂、噪声和信号混在一起的情况就翻车。LYT-Net 的思路是用 Transformer 的全局注意力去建模光照分布把“哪里该提亮、哪里该压噪”当成一个全局依赖问题来解而不是逐像素调曲线。它的核心通常是一个轻量级的 Transformer 主干配合光照估计和增强模块参数量控制得比较克制这也是它能导出 ONNX 并在 CPU 上跑的前提。常见做法是输入一张低光图先经过一个浅层卷积提特征再进 Transformer block 做全局建模最后通过一个重建头输出增强图。和 Swin Transformer 那种层级结构不同LYT-Net 更偏向轻量化和部署友好注意力窗口和通道数都压得比较低。这也是为什么这个包敢同时给 Python 和 C 源码——模型本身不算重ONNX Runtime 在 CPU 上单张推理能压到百毫秒级具体看输入分辨率和线程数。提示如果你之前只接触过分类或检测的 ONNX 部署图像增强模型的输出是整张图后处理逻辑和检测完全不同别套用 NMS 那套。2.2 PyTorch 转 ONNX 的关键参数与代码这个包里应该已经带了导出脚本或导出好的 .onnx 文件但你要改输入尺寸或换 opset 时得自己重导。下面是我常用的导出代码基于 PyTorch 的torch.onnx.exportimport torch import torch.onnx # 假设模型定义在 lyt_net.py 中权重已加载 from lyt_net import LYTNet model LYTNet() model.load_state_dict(torch.load(lyt_net.pth, map_locationcpu)) model.eval() # 输入尺寸要和部署时一致低光增强常用 256x256 或 512x512 dummy_input torch.randn(1, 3, 256, 256) torch.onnx.export( model, dummy_input, lyt_net.onnx, export_paramsTrue, opset_version11, # 11 或 12 对 Transformer 算子支持较稳 do_constant_foldingTrue, # 常量折叠减小模型体积 input_names[input], output_names[output], dynamic_axes{ input: {0: batch, 2: height, 3: width}, output: {0: batch, 2: height, 3: width} } )逻辑说明model.eval()必须加否则 BatchNorm 和 Dropout 会带入训练态行为导出后推理结果和验证集对不上。opset_version选 11 是因为部分注意力算子在高版本 opset 里反而有兼容问题ONNX Runtime 对 11 的支持最成熟。dynamic_axes让高宽可变但实际部署时如果固定尺寸建议去掉动态轴推理引擎能做得更激进的优化。参数说明do_constant_folding会把能提前算的常量合并模型体积通常能小 5% 到 15%。input_names和output_names要和后面 C/Python 推理代码里的名字一致不然会报找不到输入节点。2.3 ONNX 模型校验与量化到 INT8导出完别急着上工程先用 ONNX Runtime 跑一遍校验确认输出和 PyTorch 对齐import onnxruntime as ort import numpy as np sess ort.InferenceSession(lyt_net.onnx, providers[CPUExecutionProvider]) input_name sess.get_inputs()[0].name # 用同一张图分别跑 PyTorch 和 ONNX比较最大绝对误差 dummy np.random.randn(1, 3, 256, 256).astype(np.float32) onnx_out sess.run(None, {input_name: dummy})[0] # torch_out 来自前面 model(dummy_input).detach().numpy() # 一般要求 max(abs(onnx_out - torch_out)) 1e-4如果误差超过 1e-3优先查 opset 和算子实现别急着怀疑模型。校验通过后再做 INT8 量化CPU 上通常能再快 1.5 到 2 倍from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic( model_inputlyt_net.onnx, model_outputlyt_net_int8.onnx, weight_typeQuantType.QInt8 )动态量化不需要校准数据集适合快速验证。但图像增强对细节敏感INT8 后可能出现暗部色带或轻微偏色建议量化后用一组实拍低光图做主观对比别只看 PSNR。3. Python 与 C 双端推理从源码到可执行程序3.1 Python 端推理脚本与参数调节Python 端适合做验证和批量处理。包里给的 Python 源码一般包含模型定义、推理脚本和工具函数。我习惯把推理部分单独抽出来方便接自己的图像读取逻辑import cv2 import numpy as np import onnxruntime as ort class LowLightEnhancer: def __init__(self, model_pathlyt_net.onnx, input_size256): self.sess ort.InferenceSession( model_path, providers[CPUExecutionProvider] ) self.input_name self.sess.get_inputs()[0].name self.input_size input_size def preprocess(self, img_bgr): # BGR 转 RGB归一化到 [0,1]再转 NCHW img cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) img cv2.resize(img, (self.input_size, self.input_size)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) return np.expand_dims(img, axis0) def infer(self, img_bgr): tensor self.preprocess(img_bgr) out self.sess.run(None, {self.input_name: tensor})[0] # 输出是 NCHW转回 HWC 并裁剪到 [0,255] out np.squeeze(out, axis0) out np.transpose(out, (1, 2, 0)) out np.clip(out * 255.0, 0, 255).astype(np.uint8) return cv2.cvtColor(out, cv2.COLOR_RGB2BGR) enhancer LowLightEnhancer(lyt_net.onnx, input_size256) result enhancer.infer(cv2.imread(dark.jpg)) cv2.imwrite(enhanced.jpg, result)逻辑说明预处理顺序必须是 BGR→RGB→归一化→转置顺序错了颜色会偏。输出裁剪到 [0,255] 再转 uint8少了 clip 会出现溢出回绕暗部变亮斑。input_size要和导出时一致否则 resize 后比例失真。参数说明providers里如果装了 GPU 版 ONNX Runtime 可以换成CUDAExecutionProvider但低光增强模型在 CPU 上通常够用。input_size越大细节越好但推理时间按平方增长256 到 512 之间按实际需求选。3.2 C 端 ONNX Runtime 集成与编译C 端是这个包比较值钱的部分因为很多人卡在“Python 跑通了C 不知道怎么接”。核心是 ONNX Runtime 的 C API流程和 Python 类似但内存管理要自己来。下面是一个最小可用的推理函数#include onnxruntime_cxx_api.h #include opencv2/opencv.hpp cv::Mat Enhance(const cv::Mat input, Ort::Session session, int input_size) { Ort::MemoryInfo mem_info Ort::MemoryInfo::CreateCpu( OrtArenaAllocator, OrtMemTypeDefault); // 预处理BGR-RGB, resize, 归一化, HWC-CHW cv::Mat rgb, resized, float_img; cv::cvtColor(input, rgb, cv::COLOR_BGR2RGB); cv::resize(rgb, resized, cv::Size(input_size, input_size)); resized.convertTo(float_img, CV_32FC3, 1.0 / 255.0); std::vectorfloat input_tensor_values(3 * input_size * input_size); std::vectorcv::Mat channels(3); cv::split(float_img, channels); for (int c 0; c 3; c) { memcpy(input_tensor_values.data() c * input_size * input_size, channels[c].ptrfloat(), input_size * input_size * sizeof(float)); } std::vectorint64_t input_shape {1, 3, input_size, input_size}; Ort::Value input_tensor Ort::Value::CreateTensorfloat( mem_info, input_tensor_values.data(), input_tensor_values.size(), input_shape.data(), input_shape.size()); const char* input_names[] {input}; const char* output_names[] {output}; auto outputs session.Run(Ort::RunOptions{nullptr}, input_names, input_tensor, 1, output_names, 1); float* out_data outputs[0].GetTensorMutableDatafloat(); // 输出 CHW - HWC再转 uint8 cv::Mat out_mat(input_size, input_size, CV_32FC3); for (int c 0; c 3; c) { for (int h 0; h input_size; h) { for (int w 0; w input_size; w) { out_mat.atcv::Vec3f(h, w)[c] out_data[c * input_size * input_size h * input_size w]; } } } out_mat cv::min(out_mat * 255.0f, 255.0f); cv::Mat result; out_mat.convertTo(result, CV_8UC3); cv::cvtColor(result, result, cv::COLOR_RGB2BGR); return result; }逻辑说明C 端最容易错的是内存布局。ONNX Runtime 要的是连续 CHW 数据OpenCV 默认是 HWC 交错必须手动 split 再拷贝。输出同理逐通道写回再合并。Ort::MemoryInfo::CreateCpu指定 CPU 内存如果要用 GPU 得换 CUDA provider 并管理显存。参数说明input_size必须和模型输入一致。session.Run的输入输出名要和导出时input_names/output_names完全匹配大小写敏感。编译时链接onnxruntime和opencv库Windows 下注意microsoft visual c redistributable版本要匹配 ONNX Runtime 的编译工具链否则会出现access violation c0000005这类运行时崩溃。3.3 双端输出一致性验证Python 和 C 跑同一张图结果应该几乎一致。我一般会存两张增强图做像素差import cv2 import numpy as np py_out cv2.imread(enhanced_py.jpg) cpp_out cv2.imread(enhanced_cpp.jpg) diff cv2.absdiff(py_out, cpp_out) print(max diff:, diff.max(), mean diff:, diff.mean())如果 max diff 超过 2 到 3优先查预处理顺序和归一化系数。常见翻车点是 C 里 resize 的插值方式和 Python 不一致或者 Python 用了cv2.INTER_LINEAR而 C 默认是INTER_LINEAR但通道顺序搞反。这种问题不查清楚后面接业务代码会一直怀疑模型。4. 避坑与排查ONNX 部署低光增强模型的五个血泪经验4.1 现象ONNX 推理结果全黑或全白原因预处理归一化系数和训练时不一致。训练常用 [0,1]但有些实现用 [-1,1]导出时没注意。解决翻训练代码里的Normalize参数把 mean 和 std 对齐。如果找不到先用一张图分别试 [0,1] 和 [-1,1]看哪个输出正常。4.2 现象C 端加载模型报 “Failed to load model”原因ONNX Runtime 版本和模型 opset 不匹配或者缺少依赖库。解决用onnxruntime的InferenceSession在 Python 里先确认模型能加载再查 C 链接的库版本。Windows 下把onnxruntime.dll和opencv_world.dll放到 exe 同目录别只靠 PATH。4.3 现象INT8 量化后暗部出现色带原因动态量化对激活值敏感低光图像暗部数值集中量化误差被放大。解决改用静态量化并准备一组低光校准图或者只量化权重不量化激活。如果色带不影响业务也可以接受但要做主观评估。4.4 现象推理速度比预期慢很多原因输入尺寸过大或者没用上多线程。解决ONNX Runtime 的SessionOptions里设置intra_op_num_threads为 CPU 核心数graph_optimization_level设为ORT_ENABLE_ALL。输入尺寸从 512 降到 256速度通常能快 3 到 4 倍。4.5 现象Python 和 C 结果差异大原因图像读取通道顺序、resize 插值、归一化顺序不一致。解决固定一套预处理规范Python 和 C 都按 BGR→RGB→resize→归一化→CHW 走。验证时存中间结果逐步对比别一上来就比最终输出。5. 进阶技巧用 ONNX Runtime 的 IO Binding 和自定义线程池压榨 CPU 性能如果你要把这个模型塞进高并发服务默认的session.Run会有内存拷贝开销。ONNX Runtime 提供了 IO Binding可以把输入输出直接绑到预分配的内存上省掉每次推理的拷贝。C 里大概这样用Ort::IoBinding binding(session); Ort::Value input_tensor Ort::Value::CreateTensorfloat( mem_info, input_data.data(), input_data.size(), input_shape.data(), input_shape.size()); binding.BindInput(input, input_tensor); binding.BindOutput(output, output_tensor); // output_tensor 预分配 session.Run(Ort::RunOptions{nullptr}, binding);逻辑说明BindOutput需要你提前分配好输出内存形状按模型输出定。这样每次推理不重新分配适合固定尺寸的批量处理。参数上SessionOptions里把enable_cpu_mem_arena设为 true 能复用内存池但内存紧张时反而要关掉。另一个技巧是线程池隔离。如果你的服务同时跑多个模型别让它们抢同一个线程池。给每个 session 单独设intra_op_num_threads比如 4 核机器上每个 session 给 2 线程避免上下文切换开销。实测在 1080p 图像上IO Binding 加线程隔离能把单张推理从 180ms 压到 110ms 左右具体看 CPU 型号。验证方法用perf或 Windows 的性能计数器看 CPU 利用率和推理耗时分布。如果发现耗时波动大多半是线程争抢或内存分配导致。固定输入尺寸、预分配内存、限制线程数这三步做完基本就稳了。从那以后我每次拿到 ONNX 模型都强制先跑一遍 Python 和 C 的双端一致性校验再谈集成。这个包把 LYT-Net 的 Python 和 C 源码、ONNX 模型都备齐了省掉了自己搭转换链路的时间但部署时的预处理对齐和线程配置还是得自己盯。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。