NTIRE 系列比赛一直是图像修复与增强方向的“风向标”,每年 CVPR 期间都会集中放出大量高质量赛道。这次 NTIRE 2026 的低光增强赛道名字很有意思:Twilight Cowboy Challenge。从命名来看,重点大概率落在黄昏、夜晚这类真实弱光场景的恢复与增强上,而不是简单地把暗图拉亮。低光增强一直是竞赛和工程落地的双热点:手机夜景模式、监控视频增强、老照片修复、医学内窥成像,全都依赖这个方向。这篇文章就来拆一下:NTIRE 2026 低光增强挑战赛可能考什么、需要准备哪些技术栈、本地怎么复现和验证,以及直接把模型接到批量任务和 API 服务时要注意什么。
先说结论:如果你已经有图像复原模型的基础,这次挑战赛的技术门槛不算特别高,真正的难点在于数据分布、指标博弈和极端暗光下的稳定性。本文会围绕低光增强的常用技术路线、训练验证流程、显存优化、批量推理和接口部署给出一套完整的实操思路。没有官方赛题细节的部分,我会明确标出“需以官方发布为准”,不做过度推测。
1. NTIRE 2026 低光增强挑战赛概览
NTIRE,全称 New Trends in Image Restoration and Enhancement,是计算机视觉顶级会议 CVPR 上的常驻研讨会和挑战赛品牌。每年的 NTIRE 挑战赛都会设置多个图像复原赛道,低光增强 Low-light Enhancement 是其中最稳定的赛道之一。2026 年这届赛题叫 Twilight Cowboy Challenge,从名称上判断,场景设计会更贴近“黄昏到夜晚”的过渡光环境,可能包含城市夜景、室内弱光、逆光人像等复杂子任务。下面用一张表把赛前需要关注的信息列清楚。
| 项目 | 说明 |
|---|---|
| 赛事背景 | NTIRE 系列挑战赛,通常在 CVPR 期间发布结果 |
| 赛题名称 | NTIRE 2026 Low-light Enhancement: Twilight Cowboy Challenge |
| 任务类型 | 低光图像增强、暗光恢复、色彩还原、去噪 |
| 核心难点 | 极端暗光、光照不均匀、噪声放大、颜色偏移、细节保持 |
| 主流评估指标 | PSNR、SSIM、LPIPS、NIQE,以及主观视觉质量 |
| 参赛常用模型 | Retinex 类、CNN 类、Transformer 类、扩散模型类 |
| 训练数据形式 | 成对低光/正常光图像,或真实场景无参考对 |
| 官方 baseline | 以挑战赛官网发布为准,本文不臆测具体数值 |
从以往 NTIRE 低光赛道来看,比赛通常会提供一批成对训练数据,让参赛者通过监督学习让模型学习“低光图到正常光图”的映射。对于这类任务,模型的亮度提升能力只是底线,真正拉开差距的是噪声抑制、颜色恢复、纹理保留和极端暗部的可用性。“Twilight”这个关键词提示我们需要特别关注低照度下的色彩层次和边缘细节,而不是单纯追求高 PSNR。
对参赛者来说,需要准备的工程能力包括:数据处理、模型训练、指标评估、推理加速、结果可视化。任何一个环节做得不够扎实,最后的测试集成绩都可能出现明显波动。这篇文章后面会按这条链路逐步展开。
2. 低光增强的主流技术路线
低光增强不是单一模型问题,而是一个“感知+重建”的综合问题。目前主流方案可以分为四类:基于物理模型的传统方法、基于 CNN 的监督方法、基于 Transformer 的全局建模方法、以及基于扩散模型的生成式方法。
2.1 基于物理模型的方法
这类方法以 Retinex 理论为核心,认为图像可以分解成反射分量和光照分量。早期方法通过估计光照图、增强光照、再合成增强图像来完成低光增强。优点是无需大量训练数据,计算速度快;缺点是在复杂噪声场景下容易产生局部过曝、颜色失真和伪影。很多竞赛方案会把 Retinex 作为预处理或后处理模块,用来做曝光校正和颜色校正,而不是直接作为最终模型。
2.2 基于 CNN 的监督方法
这是竞赛中最常见的做法。直接让网络学习“低光图像 -> 正常图像”的端到端映射,常用 U-Net 结构、残差连接和注意力机制。CNN 类方法训练稳定、显存可控、推理速度快,适合作为 Baseline 和后期微调基础。问题是,纯 CNN 感受野有限,对大面积暗部和全局光线变化建模能力偏弱。要改善这一点,通常会在网络中间层加入通道注意力或空间注意力,让模型更关注暗部区域的信息传播。
2.3 基于 Transformer 的方法
Transformer 类模型通过自注意力机制捕获全局依赖关系,在低光增强中往往能更好处理光照不均匀和场景结构恢复。Restormer、RetinexFormer 这类模型在图像复原任务中效果明显,但它们对显存和计算资源要求更高,训练时通常需要使用 Patch 训练策略。如果你的显卡显存是 8GB 或 12GB,可以考虑用小 Patch 加梯度累积的方式训练;如果是 24GB 以上,就可以尝试较大分辨率训练。
2.4 基于扩散模型的生成式方法
扩散模型在低光增强里是最近几年的热门方向,优势是生成图像细节丰富、整体自然度更高,缺点是推理速度慢、显存占用大、训练难度高。在竞赛中,扩散模型常用于“精修阶段”:先用轻量模型做粗增强,再用扩散模型恢复纹理和抑制伪影。这种两阶段方案在主观评分和 LPIPS 这类感知指标上往往有优势,但需要赛方提供的数据量和官方评测标准来权衡。若赛题强调推理效率或可部署性,扩散模型可能不是最优选择。
2.5 你的 baseline 应该怎么选?
最稳妥的策略是准备两套方案:一套 CNN 或 Transformer 模型作为快速迭代基线,保证 PSNR 和 SSIM 稳步提升;另一套用扩散模型或两阶段精修方案作为主观质量和感知指标冲刺方案。竞赛中,客观指标和主观评分往往都要看,过度追求 PSNR 可能导致图像过于平滑,过度追求感知质量又可能产生不真实纹理。两者之间的平衡,需要大量实验。
3. 评估指标:别让分数骗了你
低光增强的评估是竞赛最容易被忽略的部分。常见的客观指标有 PSNR、SSIM、LPIPS,无参考指标有 NIQE、BRISQUE、LOE 等。这些指标各有偏向,理解它们的特性才能避免调错方向。
| 指标 | 评估内容 | 特点 | 使用建议 |
|---|---|---|---|
| PSNR | 像素级误差 | 对像素差异敏感,偏好平滑结果 | 用于训练收敛参考 |
| SSIM | 结构相似度 | 更关注亮度、对比度、结构 | 配合 PSNR 一起看 |
| LPIPS | 感知相似度 | 基于深度特征比较 | 主观质量强相关,但计算较慢 |
| NIQE | 无参考自然度 | 不需要 GT,评估图像自然度 | 验证真实测试时有用 |
| LOE | 光照顺序误差 | 衡量增强后自然光照保持 | 低光增强特有指标 |
在实际操作中,建议每训练几个 epoch 就保存一次验证集结果,把 PSNR、SSIM、LPIPS 全部计算出来。很多选手只盯 PSNR,结果模型在验证集分数很高,一到真实夜景就暴露问题:颜色发灰、暗部噪声依然明显、高光区域过曝。所以无论官方采用什么指标,自己都要建立一个“主观+客观”双轨评估流程。
如果要评估极暗光下的表现,可以专门构造一组“低光+强噪声”测试图,观察模型在高 ISO 场景下的去噪和颜色恢复能力。这个测试集不参与训练,只用于局部对比。对于 Twilight Cowboy Challenge 这类强调黄昏夜景场景的赛题,暗部细节和颜色层次的主观验证尤其重要。
4. 本地复现环境与数据准备
不管参赛还是学习复现,第一步都是搭建可重复运行的本地环境。这里给出一套通用流程,具体版本号需要按模型和赛方数据要求调整。
4.1 硬件预期
低光增强模型训练,显卡建议至少 8GB 显存起步,12GB 会更宽裕。如果只能用 CPU 训练,建议只跑极小规模消融实验,训练主模型还是要用 GPU。推理阶段的显存占用一般比训练低,8GB 显卡通常可以跑 1080p 分辨率的小型网络。如果使用扩散模型或 Transformer 大模型,高分辨率推理需要分块处理,或者依赖更大显存。
这里要强调一句:显存占用取决于模型参数量、输入分辨率、Batch Size 和是否使用梯度检查点,不能只凭模型类型判断。新手第一次跑模型前,先用nvidia-smi盯一下显存曲线,确认占用不会把显存打满。
4.2 环境搭建
建议使用 Conda 管理独立环境,避免依赖冲突。以下是通用模板:
conda create -n lowlight python=3.10 -y conda activate lowlight pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow numpy tqdm wandb pip install scikit-image lpips如果你需要训练 Transformer 类模型,再安装对应的模型库:
pip install einops timmCUDA 版本和 PyTorch 版本必须与显卡驱动匹配。安装前可以先执行nvidia-smi查看驱动最高支持的 CUDA 版本,再选择对应的 PyTorch 安装源。如果驱动版本较低,直接安装最新版 PyTorch 可能会导致CUDA unavailable。
4.3 数据目录组织
参赛过程会产生大量实验数据,建议从一开始就按目录管理:
lowlight_project/ ├── configs/ ├── data/ │ ├── train/ │ └── val/ ├── models/ ├── scripts/ ├── experiments/ │ ├── exp001/ │ └── exp002/ └── outputs/训练前先用脚本统计一下数据集的亮度分布、图像分辨率和文件数量,判断数据是否平衡。如果训练集中低光图的暗部区域占比差异很大,可以考虑对数据做采样加权,避免模型偏向过暗或过亮样本。
5. 训练与验证流程
数据准备好后,可以开始训练。下面给出一套图像到图像的训练循环模板,需要根据具体模型替换网络结构和损失函数。
5.1 数据加载
低光增强的成对数据一般是一张低光图和一张对应的正常光图。读取图像后,先用cv2.cvtColor把它从 BGR 转成 RGB,再归一化到[0,1]。训练时通常使用随机裁剪增强数据多样性,例如裁剪成256x256或512x512的 Patch;验证时保持原图或按固定分辨率缩放。
import cv2 import torch from torch.utils.data import Dataset class LowLightDataset(Dataset): def __init__(self, file_list, patch_size=256, is_train=True): self.file_list = file_list self.patch_size = patch_size self.is_train = is_train def __len__(self): return len(self.file_list) def __getitem__(self, idx): input_path, gt_path = self.file_list[idx] low = cv2.imread(input_path) high = cv2.imread(gt_path) low = cv2.cvtColor(low, cv2.COLOR_BGR2RGB) high = cv2.cvtColor(high, cv2.COLOR_BGR2RGB) if self.is_train: # 随机裁剪相同位置 h, w, _ = low.shape x = torch.randint(0, w - self.patch_size + 1, (1,)).item() y = torch.randint(0, h - self.patch_size + 1, (1,)).item() low = low[y:y+self.patch_size, x:x+self.patch_size] high = high[y:y+self.patch_size, x:x+self.patch_size] low = torch.from_numpy(low).permute(2, 0, 1).float() / 255.0 high = torch.from_numpy(high).permute(2, 0, 1).float() / 255.0 return low, high5.2 训练循环
训练时建议先用小分辨率和小 Batch Size 做一个 20 到 30 轮的冒烟测试,确认 Loss 能下降、验证集指标有变化,再跑正式训练。Loss 部分可以使用 L1 Loss 加感知 Loss 的组合。L1 Loss 保证像素收敛,感知 Loss 提升视觉质感。不要一上来就堆大量 Loss 项,否则很难定位问题。
import torch import torch.nn as nn def l1_loss(pred, target): return nn.L1Loss()(pred, target) def train_one_epoch(model, dataloader, optimizer, device): model.train() total_loss = 0.0 for low, high in dataloader: low = low.to(device) high = high.to(device) pred = model(low) loss = l1_loss(pred, high) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(dataloader)训练时要注意学习率策略。低光增强任务常用的做法是先用 1e-4 到 5e-4 的学习率训练,验证集指标平稳后再用余弦退火或 ReduceLROnPlateau 降低学习率。如果训练过程出现 Loss 震荡,优先降低学习率而不是调整模型结构。
5.3 验证与指标计算
每个 Epoch 结束后,在验证集上计算 PSNR 和 SSIM。为了节省时间,可以每 5 个 Epoch 完整计算一次 LPIPS。保存模型时不要只保存最后一个 Epoch,最好保留验证集指标最好的权重。判断实验是否有效的标准是:验证集 PSNR 和 SSIM 上升、输出图像没有明显偏色和伪影、训练 Loss 没有突然发散。
import torch import numpy as np from skimage.metrics import structural_similarity as ssim import cv2 def calculate_psnr_ssim(pred, gt): pred = pred.clamp(0, 1).cpu().numpy().transpose(1, 2, 0) gt = gt.cpu().numpy().transpose(1, 2, 0) pred = (pred * 255.0).astype(np.uint8) gt = (gt * 255.0).astype(np.uint8) pred_y = cv2.cvtColor(pred, cv2.COLOR_RGB2GRAY) gt_y = cv2.cvtColor(gt, cv2.COLOR_RGB2GRAY) psnr = cv2.PSNR(pred, gt) ssim_value = ssim(pred_y, gt_y) return psnr, ssim_value这里有一个容易踩的坑:PSNR 和 SSIM 对数据范围非常敏感。如果预测值没有收敛到[0,1],或者 GT 的归一化方式不一致,指标会直接失真。建议统一在计算指标前把预测值clamp(0,1)。
6. 推理、批量处理与接口服务
竞赛之外,低光增强模型经常要落地到批量任务或服务接口。训练完成后,可以把模型导出为 TorchScript 或 ONNX,也可以直接用 PyTorch 做推理服务。这里给出一套批量推理和 FastAPI 接口的通用实现。
6.1 批量推理脚本
批量推理的核心是:读图、推理、写图。为了提高效率,建议使用torch.no_grad(),并对图片做保持长宽比的缩放。批量处理时要注意显存波动,最好设置一个进程内的最大 Batch Size,避免一次加载过多图片导致显存溢出。
import torch import cv2 import glob import os def inference_folder(model, input_dir, output_dir, device, size=1024): model.eval() os.makedirs(output_dir, exist_ok=True) image_paths = glob.glob(os.path.join(input_dir, "*.jpg")) + \ glob.glob(os.path.join(input_dir, "*.png")) with torch.no_grad(): for idx, path in enumerate(image_paths): img = cv2.imread(path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, _ = img.shape scale = min(size / w, size / h) if scale < 1.0: new_w, new_h = int(w * scale), int(h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA) tensor = torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 tensor = tensor.unsqueeze(0).to(device) pred = model(tensor).squeeze(0) pred = pred.clamp(0, 1).cpu().numpy().transpose(1, 2, 0) pred = (pred * 255.0).astype("uint8") pred = cv2.cvtColor(pred, cv2.COLOR_RGB2BGR) if scale < 1.0: pred = cv2.resize(pred, (w, h), interpolation=cv2.INTER_LINEAR) name = os.path.basename(path) cv2.imwrite(os.path.join(output_dir, name), pred) print(f"[{idx+1}/{len(image_paths)}] {name} done")批量任务失败时,不要整个流程重跑。更好的做法是记录失败文件清单,后续单独重试。批量推理脚本里建议加入try-except,遇到单张图片损坏或格式异常时跳过并记录日志。
6.2 FastAPI 接口服务
如果要给其他系统提供低光增强能力,直接用 FastAPI 起一个服务。接口设计成 POST,请求体带image_base64或图片 URL,返回处理后的 base64 结果。这里用uvicorn启动,端口可以按需调整。
pip install fastapi uvicorn python-multipartimport base64 import io import torch import cv2 import numpy as np from fastapi import FastAPI, UploadFile, File from fastapi.responses import JSONResponse app = FastAPI() device = "cuda" if torch.cuda.is_available() else "cpu" # 初始化模型,实际使用时替换为你的网络结构 # model = YourModel().to(device) # model.eval() @app.post("/enhance") async def enhance(file: UploadFile = File(...)): raw = await file.read() img = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) tensor = torch.from_numpy(img).permute(2, 0, 1).float() / 255.0 tensor = tensor.unsqueeze(0).to(device) with torch.no_grad(): pred = model(tensor).squeeze(0) pred = pred.clamp(0, 1).cpu().numpy().transpose(1, 2, 0) pred = (pred * 255.0).astype("uint8") pred = cv2.cvtColor(pred, cv2.COLOR_RGB2BGR) _, encoded = cv2.imencode(".jpg", pred) result_base64 = base64.b64encode(encoded.tobytes()).decode("utf-8") return JSONResponse(content={"success": True, "image_base64": result_base64})启动服务的命令:
uvicorn server:app --host 127.0.0.1 --port 8000接口测试时,用 curl 上传一张图:
curl -X POST "http://127.0.0.1:8000/enhance" \ -H "Content-Type: multipart/form-data" \ -F "file=@./test_lowlight.jpg" \ -o response.json接口服务部署后,一定要加访问控制。这个服务如果暴露到公网,很容易被滥用。最简单的做法是绑127.0.0.1,或者加一层 Token 校验。涉及真实人脸或隐私照片,更要控制访问范围并做好日志脱敏。
7. 性能观察与显存优化
低光增强模型在训练和推理时的资源占用,主要有三个变量:输入分辨率、Batch Size、模型参数量。分辨率越高、Batch Size 越大,显存占用越高。下面是一套通用的观察和优化方法。
7.1 如何观察显存占用
训练时,另开一个终端执行:
watch -n 1 nvidia-smi重点看Memory-Usage和Power Usage。如果显存占用接近上限,说明 Batch Size 或分辨率需要下调;如果 GPU 利用率极低但显存很高,说明模型可能等待数据加载,需要检查 DataLoader 的num_workers和pin_memory设置。
7.2 降低显存占用的策略
显存不足时,按优先级尝试以下几种方式:
- 减小输入 Patch 尺寸,例如从
512x512改成256x256。 - 降低 Batch Size,配合梯度累积保证训练稳定。
- 开启梯度检查点
torch.utils.checkpoint,用计算换显存。 - 使用混合精度训练
torch.cuda.amp。 - 推理时使用分块预测,对高分辨率图做 Overlap 切块处理,拼回时用加权融合避免接缝。
如果使用 Transformer 类模型,显存占用通常比 CNN 高很多。建议先在小分辨率 Patch 上验证模型能否正常收敛,再逐步增大输入尺寸。不要一上来就用完整分辨率训练,很容易把显卡显存打满并导致训练中断。
7.3 CPU 推理与 GPU 推理的差异
CPU 推理适合做单张图片的简单测试和调试,速度慢但不会报显存错误。GPU 推理速度快,但需要 CUDA 环境配置正确。批量任务场景下,如果每张图只有几百 KB,可以用 GPU Batch 推理提升吞吐;如果是超高清图,瓶颈可能不在模型而在图像编解码,建议先用 OpenCV 做压缩和缩放,再交给模型处理。
8. 常见问题与排查方法
低光增强的复现和调参过程中,问题通常集中在环境、数据、显存和效果四个层面。下面整理了一张排查表格。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 训练时出现 CUDA out of memory | Batch Size 过大或分辨率过高 | nvidia-smi查看显存占用 | 减小 Batch Size、削减 Patch、开启梯度检查点 |
| PyTorch 检测不到 GPU | CUDA 版本与驱动不匹配 | 执行torch.cuda.is_available() | 重装对应版本的 PyTorch 或升级驱动 |
| 训练 Loss 不下降 | 学习率设置不合理或数据归一化错误 | 打印输入输出范围 | 调低学习率,确认低光图和 GT 归一化一致 |
| 输出图像偏色 | 模型没有学到颜色映射 | 检查训练数据颜色空间 | 统一使用 RGB 或 BGR,避免混用 cv2 默认格式 |
| 暗部噪声明显 | 数据中噪声样本不足 | 增强训练集的暗光噪声 | 加入真实噪声数据,或使用噪声增强策略 |
| 高光区域过曝 | 模型过度提升亮度 | 观察低光图的高光分布 | 在 Loss 中加入感知 Loss,降低像素级亮度权重 |
| 批量推理时单张图失败导致中断 | 图片格式异常 | 添加 try-except 和日志 | 单张失败跳过,记录失败路径并重试 |
| 接口返回超时 | 推理时间过长或并发过高 | 检查模型推理耗时 | 缩小输入尺寸、使用异步推理、限制并发数 |
这类问题在 NTIRE 低光增强挑战赛中非常常见。尤其是“输出图像偏色”和“暗部噪声明显”,几乎每个低光增强项目都会遇到。解决偏色问题,第一步是检查数据加载时是不是 RGB 和 BGR 混用了;第二步是检查 Loss 是否对颜色通道一视同仁。很多情况下,问题根源不是模型结构,而是数据管线的细节。
9. 参赛与部署的最佳实践
竞赛和工程部署的目标不完全一样。如果目标是拿一个好名次,建议把精力集中在指标优化和结果对比上;如果目标是落地,建议把模型轻量化、接口化和工程化。无论哪种场景,以下几条最佳实践都可以直接套用。
9.1 建立最小可运行基线
不要一开始就追求大模型。先用一个小网络跑通训练、验证、推理、指标计算全流程,再逐步替换为复杂模型。这样可以快速判断问题出在代码还是模型,避免浪费时间在无效实验上。
9.2 实验版本管理
低光增强模型实验变量非常多:数据增强方式、Loss 组合、分辨率、Batch Size、学习率、模型结构。建议命名规则明确,例如exp001_res512_patch256_l1_lpips。同时每轮实验都保存最优权重和对应指标,方便复盘和回滚。
9.3 指标与可视化同步记录
只记录指标还不够,最好每个实验都保存一组固定验证集的可视化结果。很多时候指标提升并不等于视觉效果提升,反过来也一样。可视化结果可以快速判断模型是否出现偏色、伪影、过曝等问题。
9.4 隐私与合规边界
如果你的训练数据或测试数据包含人脸、车牌、医疗图像或者未授权的摄影作品,必须严格遵守数据来源协议和版权规定。低光增强模型往往会大幅度改变图像亮度,也可能让原本难以辨认的细节变得清晰,这带来隐私风险。在分享 demo、参赛提交或部署服务时,应确认素材来源合法、用途可用,避免用真实人物照片做无授权演示。
如果模型最终要商用,还要考虑输出内容的可解释性和质检流程。低光增强不是“所有图片都能顺利增强”,有些极端暗光图可能产生色块或伪影,上线前需要人工抽检。
10. 总结与下一步建议
NTIRE 2026 Low-light Enhancement: Twilight Cowboy Challenge 是一次很好的技术验证机会:既要处理极端低照度下的噪声和颜色恢复,又要保证模型的泛化能力和可部署性。从准备工作来看,第一阶段建议先复现一个简单的端到端基线模型,准备好训练脚本、验证脚本和指标计算脚本;第二阶段再根据验证集效果决定是换更强的模型结构,还是针对特定场景做数据增广;第三阶段,如果有余力,可以做模型轻量化或用蒸馏方式提升推理速度。
这篇文章里提到的代码都是通用模板,具体到赛题时,需要根据官方发布的数据格式、评估方式和模型结构做替换。最容易踩的坑有三个:数据加载时颜色空间混乱、训练和推理的归一化不一致、以及只看 PSNR 忽略 LPIPS 和主观效果。
低光增强是一个下限低、上限也很高的方向。简单方法能快速看到效果,但要达到 NTIRE 级水准,需要在数据、模型结构、损失函数和推理细节上做大量实验。建议把这篇的流程保存起来,作为后续复现和调参的检查清单。拿到赛题数据集后,先跑通一轮完整流程,再决定投入方向。