
简介一套面向图像处理初学者和开发者的多曝光融合实现方案包含MATLAB与Python两套独立程序用于合并不同曝光下的同一场景图像在保留亮部细节的同时兼顾暗部信息适用于HDR图像合成和影像增强等场景。压缩包共10个文件内含4个MATLAB脚本、2个Python脚本并附有3张TIF测试图像和1张PNG样例图整体大小仅48KB轻量便于直接阅读与运行调试。该资源目前已有433人学习代码结构清晰两套程序均覆盖图像读取、预处理、对齐、权重分配和融合输出等关键步骤适合初学者对照实践。通过运行自带测试图像可以直观比较MATLAB内置函数与PythonOpenCV两种实现方式的差异并根据需要调整参数以适配不同拍摄场景。整体上非常适合作为课程设计、动手实验或理解多曝光融合原理的入门资料。 拍夜景、逆光人像或者室内窗边场景时最头疼的就是单张照片永远顾此失彼——天空一旦过曝地面就死黑地面拉亮了天空又白茫茫一片。多曝光融合就是专门解决这类“大光比”问题的把同一场景、不同曝光参数下拍到的几张照片把各自表现最好的区域挑出来合成一张细节完整的图。这篇文章里我用MATLAB和Python分别实现了一套多曝光融合程序同一套算法逻辑、两套语言版本从原理到可运行的代码都有。无论你是做图像处理课程设计、入门HDR方向研究还是想给摄影后期流程加一个自研工具都可以直接参考这套实现。1. 多曝光融合的核心原理与方案选型1.1 为什么不能简单“把暗部拉亮”——先搞清楚问题本质很多人第一次接触这类需求时第一反应是“我把暗部提亮不就行了”但实际试过就会发现单张RAW暗部提亮3档以上噪点会像撒了一把盐而且彩色噪声非常难处理。即使你用后期软件里的高光/阴影恢复能找回的动态范围也很有限因为单帧传感器捕获的信息总量是固定的——高光区域一旦溢出信号就是纯白后期没有任何手段恢复细节。多曝光融合的本质是把多帧信息在时间轴上“拼”回来相当于用拍摄时多付出的时间成本换取单帧硬件达不到的动态范围。另外还有一个常见的误区是“先做HDR再做色调映射”。传统HDR流程确实要先从多帧里估出辐射图再做色调映射压缩到8位可显示范围但这条路线需要标定相机响应函数流程长、参数多。而Mertens等人提出的曝光融合算法Exposure Fusion跳过了辐射图重建直接在图像域用质量权重把多帧叠起来既简单又能出偏自然主义的结果。现在很多手机上的HDR模式本质上跑的就是这条路线。1.2 融合权重对比度、饱和度、曝光度三件套Mertens算法的核心是给每张图里的每个像素计算一个“质量分数”由三个测度相乘得到。第一个是对比度用拉普拉斯滤波或者梯度幅值来衡量作用是让边缘纹理清晰的区域拿到高权重第二个是饱和度用RGB三通道的标准差表示优先把色彩丰富的区域选出来避免合成结果发灰第三个是良好曝光度用像素值到0.5归一化后的高斯距离来衡量像素越接近中灰越认为这个区域曝光正常过曝的255和欠曝的0都会拿到很低的权重。这三个测度相乘得到权重图之后还不能直接逐像素加权求和。因为相邻两帧的权重可能跳变剧烈直接加权会在物体边缘出现锯齿和光晕。解决方案是把每幅图像做拉普拉斯金字塔分解把权重图做高斯金字塔分解在同一尺度下逐层融合最后逆金字塔重建。高斯金字塔负责平滑权重拉普拉斯金字塔保留高频边缘细节这个组合是整套算法能稳定出片的关键。后面两套程序都是围绕这个思想展开的。1.3 环境准备两套技术栈的选型逻辑我做两个版本不是因为闲而是应用场景不同。MATLAB版本胜在代码可读性好、绘图调试方便适合课程设计和算法验证花一下午把金字塔逻辑写清楚对理解多分辨率融合非常有帮助Python版本胜在生态和工程化OpenCV一行createMergeMertens就能出结果numpy手动实现也不难适合接到自动化处理流程里。环境要求也比较简单。MATLAB这边需要R2020a以上版本加上Image Processing Toolbox主要用到im2double、imfilter、fspecial、imresize这几个函数Python这边推荐3.8以上版本安装opencv-python和numpy两个包就够了不需要额外装深度学习框架。素材方面建议用相机的AEB包围曝光模式拍3到5张间隔1EV左右场景一定要固定最好用三脚架手机用户至少也要找固定支撑连拍否则后续还得做对齐。2. MATLAB实现手写拉普拉斯金字塔融合2.1 程序整体流程设计MATLAB版本我没有用工具箱里现成的HDR函数而是从零手写了一套核心原因是每个中间变量都能可视化方便理解算法到底在做什么。整个流程分四步第一步读入多幅曝光图像并统一转为double类型第二步计算每幅图像的对比度、饱和度、良好曝光度三张测度图相乘得到权重图第三步将权重图归一化使同一位置的所有权重和为1第四步对每幅图像做拉普拉斯金字塔分解对每幅权重图做高斯金字塔分解然后在每一层做加权融合最后从顶层逐级上采样重建融合图像。这个流程里有一个非常关键的细节权重图的归一化一定要在所有测度计算完之后全局做不能一张一张独立归一化否则会破坏不同曝光帧之间的权重比例关系导致合成结果整体偏向其中某张图。我第一次实现时就踩了这个坑当时融合出来的画面亮度忽高忽低查了半天才发现是归一化写错了位置。2.2 核心代码权重计算与金字塔融合下面是MATLAB程序的核心代码片段我把权重计算和金字塔融合拆成两个函数。权重计算部分饱和度用通道间标准差对比度用拉普拉斯滤波良好曝光度用高斯函数具体参数都可以根据素材微调function w qualityWeight(im) % im: HxWx3 double 图像 % 饱和度通道间标准差 sat std(im, 0, 3); % 对比度拉普拉斯滤波幅值 lap fspecial(laplacian, 0.5); con abs(imfilter(rgb2gray(im), lap, replicate)); % 良好曝光度接近中灰0.5的权重 sigma 0.2; well exp(-(rgb2gray(im) - 0.5).^2 / (2 * sigma^2)); w con .* sat .* well 1e-12; end接着是金字塔融合的主函数。我用imresize配合高斯核实现金字塔的降采样和上采样层数根据图像最小边的尺寸动态计算function fused pyramidFusion(imgs, weights) n length(imgs); [H, W, ~] size(imgs{1}); lvl floor(log2(min(H, W))) - 2; for i 1:n gp_img{i} gaussianPyramid(imgs{i}, lvl); lap_img{i} laplacianPyramid(gp_img{i}); gp_w{i} gaussianPyramid(weights{i}, lvl); end for L 1:lvl1 fused_lap{L} zeros(size(lap_img{1}{L})); for i 1:n fused_lap{L} fused_lap{L} ... lap_img{i}{L} .* gp_w{i}{L}; end end fused reconstructLaplacian(fused_lap); fused max(min(fused, 1), 0); endgaussianPyramid、laplacianPyramid和reconstructLaplacian是三个辅助函数逻辑很标准高斯金字塔每一层用imresize(prev, 0.5)降采样拉普拉斯金字塔的每一层由相邻两层高斯图像上采样后相减得到重建时从顶层开始逐层上采样并累加拉普拉斯细节。在MATLAB里也可以用自带的impyramid函数但我实测imresize配合双线性插值速度更快结果也更稳定。2.3 MATLAB实现里的几个真坑第一个坑是类型问题。读入图像是uint8如果忘了转doubleimfilter、std这些函数会按整型规则计算结果要么溢出要么全零。建议读图后立刻用im2double统一处理后面所有运算都是double最后输出前再转换回uint8写图。第二个坑是权重图归一化的位置这一点前面已经强调必须等所有权重计算完成后统一归一化。第三个坑是拉普拉斯金字塔重建后的clip操作因为加权融合时可能出现0到1之外的数值不裁剪的话输出图上会有明显的灰色或白色噪斑。还有一个容易被忽略的细节fspecial(laplacian, 0.5)里的0.5是alpha参数控制拉普拉斯算子的响应强度。取值越大滤波器对对角方向的纹理响应越强但不要超过0.8否则权重图会出现明显的棋盘格噪声。处理夜景素材时我通常把alpha降到0.2到0.3权重图更干净融合结果也少一些颗粒感。3. Python实现OpenCV一行调用与手动金字塔3.1 OpenCV Mertens融合最快出图方案Python版本最简单的方式是直接用OpenCV自带的Mertens融合器。代码非常短import cv2 import numpy as np img_list [cv2.imread(fexp_{i}.jpg) for i in range(1, 6)] merger cv2.createMergeMertens(contrast_weight1.0, saturation_weight1.0, exposure_weight0.0) fused_hdr merger.process(img_list) fused_8u np.clip(fused_hdr * 255, 0, 255).astype(np.uint8) cv2.imwrite(fused_opencv.jpg, fused_8u)注意返回结果是浮点型像素值在0到1之间直接保存会偏暗要乘255再clip成uint8。一个值得留意的参数是exposure_weightOpenCV默认是0.0意味着只考虑对比度和饱和度不做过曝欠曝惩罚。如果融合结果整体发灰多半是这个权重调得偏高或者输入图的曝光范围本身没拉开。OpenCV内部实现的就是Mertens论文里的金字塔加权融合对于不想重复造轮子的人来说非常省事但它也有一个不太友好的地方结果色调偏“算法味”偏向中性灰平衡不像自己手写版本那样可以精细控制三个测度的指数。3.2 手动numpy金字塔把算法逻辑吃透如果想让Python版本和MATLAB版本的行为完全对齐可以用numpy手动实现一遍。权重计算部分和MATLAB几乎一一对应def quality_weight(img): img img.astype(np.float32) / 255.0 sat np.std(img, axis2) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) lap cv2.Laplacian(gray, cv2.CV_32F) con np.abs(lap) well np.exp(-(gray - 0.5) ** 2 / (2 * 0.2 ** 2)) return con * sat * well 1e-12金字塔部分可以用cv2.pyrDown和cv2.pyrUp或者自己写高斯核做卷积下采样。有一个细节要知道cv2.Laplacian返回的拉普拉斯图和MATLAB的fspecial(laplacian)并不完全一致MATLAB默认是4邻域模板OpenCV默认是带对角线的8邻域模板结果会有细微差异但不影响整体效果。如果追求严格一致可以用自定义核np.array([[0,1,0],[1,-4,1],[0,1,0]], dtypenp.float32)配cv2.filter2D。手动版的好处是每一层的金字塔结果都能拿出来看做算法对比实验时特别方便。3.3 两个Python版本的取舍与性能OpenCV版代码短、速度快处理一张1200万像素的图、5张输入大概在80到200毫秒这得益于底层C实现和SIMD优化。手动numpy版速度慢一些约0.5到1.5秒主要开销在逐层pyrDown和数组广播上但换来的是灵活性和可调试性。如果只是大批量出图我建议直接用OpenCV版如果是写论文或者做实验手动版更合适随时改权重公式就行。还有一个容易踩的坑process()函数要求输入列表里的图像尺寸完全一致。相机导出的包围曝光照片尺寸一般没问题但从RAW里用不同参数导出时很可能出现尺寸不一致OpenCV会直接报错或者静默出错。手动版处理时最好以第一张图为基准其余图统一用cv2.resize对齐缩小用INTER_AREA放大用INTER_CUBIC不然最后融合出来图是全黑的。4. 两套程序结果对比与实用化优化4.1 结果一致性验证两套程序跑完一定要做结果对比确认算法逻辑没有偏差。我的验证方法是对同一组5张包围曝光图分别跑MATLAB手写版、Python手动版、Python OpenCV版用SSIM指标两两对比。实测下来MATLAB版和Python手动版因为算法步骤几乎一致SSIM通常在0.98以上和OpenCV版对比SSIM大概在0.90到0.95之间差异主要来自拉普拉斯核和曝光度权重参数设置不同。对比项MATLAB手写版Python手动版Python OpenCV版代码量约150行约100行约10行单次耗时5张1200万像素0.8~2s0.5~1.5s0.08~0.2s结果可定制性高高低SSIM以MATLAB版为参考1.00.980.90~0.95依赖Image Processing Toolboxopencv-python, numpyopencv-python如果你做一个“可复现研究”最稳妥的做法是让MATLAB版和Python手动版共用同一套权重公式和金字塔参数这样跨语言结果几乎可以画等号。OpenCV版则更适合当作快速验证工具先看效果再决定要不要上自研流程。4.2 高动态场景的常见问题鬼影、偏色、边缘伪影多曝光融合最普遍的失败案例是鬼影。只要场景里有移动物体——行人、车辆、树枝——融合结果就会出现半透明重影。原因是权重测度逐像素独立计算它并不知道哪些像素属于同一个物体基础Mertens算法也没有去鬼影机制。规避方法主要靠拍摄阶段把AEB连拍间隔缩短或者后期只保留参考帧上有运动的区域其余帧用蒙版遮掉。偏色问题多出现在大光比夜景。不同曝光帧的色彩平衡可能有差异融合时如果某区域权重在相邻帧间跳变色彩色相也会跟着跳。缓解方案是融合前先做一个简单的全局白平衡对齐让所有输入帧的平均色温尽量一致。边缘伪影则通常与高斯核太小有关权重金字塔最顶层如果太锐利融合结果在强边缘附近会出现亮边这时候把金字塔层数加1到2层或者对权重图做一次轻微高斯模糊都能有效缓解。4.3 批量处理的工程化建议如果只是处理几张图写脚本一把梭就行。但如果要处理几十上百组包围曝光我建议把流程工程化写一个批处理脚本自动读取目录下按文件名前缀分组的所有曝光序列先做尺寸统一再调用融合函数输出到指定文件夹最后生成一个HTML对比页面把原图和融合图并排展示方便快速筛选。我用这个流程整理过一次影赛素材效率提升非常明显。另外一个非常实用的优化是在融合前加上对齐步骤。手持拍摄时包围曝光之间难免有微小抖动直接融合会在建筑边缘出现重影。Python里可以先用cv2.createAlignMTB()做对齐再送入mergerMATLAB里用imregcorr做基于相位的配准。注意这一步要放在计算权重之前否则权重图也会带上错位信息后续再怎么融合都对不齐。我在实际项目中经常被问到“到底用MATLAB还是Python”我的建议是算法研究阶段用MATLAB因为你能把每个金字塔层的中间结果可视化出来对理解多分辨率融合特别有帮助工程落地阶段用Python生态完整、性能好、容易接入现有流程。最后再分享一个小技巧不管用哪套程序融合前先把所有输入图的直方图看一遍如果某张图暗部或亮部完全没有信息说明曝光间距拉太大了拍摄时把EV步长从1.0改成0.7融合质量会明显提升。这也是我踩过不少次坑之后摸索出来的经验。本文还有配套的精品资源点击获取