1. 从一张拍不清的照片说起:为什么需要图像融合
前阵子帮朋友处理一组室内场景的照片,同一机位拍了两张:一张对焦在近处的摆件上,细节锐利但背景虚化;另一张对焦在远处的墙面上,背景清晰但前景糊了。朋友问我能不能把两张“拼”成一张全都清楚的图。这个需求听起来像是修图软件的活儿,但本质上它属于图像融合的范畴——把同一场景的多幅图像信息整合成一幅信息更完整的图像。
图像融合在工业检测、医学影像、遥感观测里用得非常多。比如红外与可见光图像的融合,红外能穿透烟雾看到热源,可见光能提供纹理细节,两者一合,既能看到目标又能看清轮廓。再比如多聚焦图像融合,就是我朋友遇到的那种情况,不同焦距各拍一张,融合后得到全焦图像。而实现融合的方法有很多种,从最简单的加权平均到复杂的深度学习模型都有,但如果你想要一个原理清晰、代码量可控、效果稳定的方案,基于Harr小波分解的融合方法是一个非常值得上手的起点。
这篇文章面向的是有一定OpenCV基础、想动手实现图像融合的开发者。我会把Harr小波分解的原理讲透,把融合规则的设计逻辑说清楚,然后给出完整的OpenCV实现代码,最后分享我在调试过程中踩过的坑和总结的参数调节经验。你不需要有信号处理的背景,只要会写Python、装过OpenCV,就能跟着做出来。
2. 为什么选Harr小波:方案选型背后的逻辑
2.1 图像融合的常见技术路线对比
在动手写代码之前,先搞清楚市面上有哪些融合方法,以及为什么Harr小波值得选。我把常见的几类方法列出来做个对比:
| 方法类别 | 代表算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 空间域方法 | 加权平均、主成分分析 | 实现简单、速度快 | 容易产生鬼影、对比度下降 | 对融合质量要求不高的快速预览 |
| 金字塔方法 | 拉普拉斯金字塔 | 多尺度融合、效果较好 | 分解层数受限、重构有误差 | 多聚焦图像融合 |
| 小波变换方法 | Harr小波、Daubechies小波 | 多尺度+多方向、信息保留好 | 对位移敏感、计算量中等 | 多聚焦、红外可见光融合 |
| 深度学习方法 | CNN、GAN | 效果最好 | 需要大量训练数据、模型大 | 有标注数据的专业场景 |
从表里能看出来,小波变换方法处在一个“甜点区”:效果比简单的空间域方法好一大截,实现难度又比深度学习低得多。而Harr小波又是所有小波里最简单的一种,它的滤波器系数只有四个值,计算过程就是加减法,非常适合用来理解小波分解的本质。
2.2 Harr小波分解到底在做什么
很多人一听到“小波”就觉得玄乎,其实Harr小波的核心思想用一句话就能概括:把信号拆成低频的“概貌”和高频的“细节”。
拿一维信号举例,假设有四个像素值[10, 12, 8, 6]。Harr小波分解的第一步是两两配对求平均和差值:
- 平均值:
(10+12)/2 = 11,(8+6)/2 = 7 - 差值:
(10-12)/2 = -1,(8-6)/2 = 1
这样原来的四个数就变成了[11, 7, -1, 1]。前两个是低频分量(概貌),后两个是高频分量(细节)。如果继续对低频分量做同样的操作,就得到了多级分解。
放到二维图像上,情况稍微复杂一点。图像是二维信号,所以要做行方向和列方向各一次分解,一共得到四个子带:
- LL(低频-低频):图像的近似概貌,包含了大部分能量
- LH(低频-高频):水平方向的细节,对应垂直边缘
- HL(高频-低频):垂直方向的细节,对应水平边缘
- HH(高频-高频):对角线方向的细节
注意:不同教材对LH和HL的命名可能相反,这里约定第一个字母代表行方向处理结果,第二个字母代表列方向。实际写代码时不用纠结名字,看子带对应的物理意义就行。
这个分解过程可以递归进行。对LL子带再做一次二维Harr分解,就得到二级分解结果。分解级数越多,频率划分越细,但计算量也越大。对于一般的图像融合任务,2到3级分解就够用了。
2.3 融合规则的设计思路
分解完之后,关键问题来了:怎么把两张图的子带合并成一张图的子带?
低频子带包含的是图像的亮度和整体结构,高频子带包含的是边缘和纹理。这两类信息的重要性不同,所以要用不同的融合策略:
- 低频子带:常用加权平均。因为低频信息差异不大,简单平均就能得到平滑的结果。但纯平均会降低对比度,所以有时候会用基于局部能量的加权,让能量大的区域占更大权重。
- 高频子带:常用绝对值取大。高频系数绝对值越大,说明该位置的边缘越强、细节越丰富,所以取绝对值大的那个系数,能最大程度保留清晰区域的细节。
这个“低频平均+高频取大”的组合是Harr小波融合里最经典也最稳定的策略。我试过其他变体,比如低频也取大、高频用阈值收缩,效果都不如这个组合稳定。
3. 核心细节拆解:从原理到可运行的代码
3.1 环境准备与依赖确认
动手之前先把环境理清楚。这个项目对环境的依赖非常轻量:
pip install opencv-python numpy matplotlibopencv-python:负责图像的读取、显示和基础处理numpy:负责矩阵运算,小波分解的加减法全靠它matplotlib:可选,用来对比显示融合前后的图像
版本方面没有特殊要求,OpenCV 4.x 和 NumPy 1.20 以上都能跑。我实测在 Python 3.8 到 3.11 上都正常。如果你用的是 Anaconda,这些库基本都自带了,直接跳过安装步骤。
提示:不要用
pip install cv2,这个包名是错的,正确的包名是opencv-python。我见过不少新手在这里卡住。
3.2 Harr小波分解的手动实现
OpenCV 本身没有提供小波变换的函数,所以这部分需要自己写。好在Harr小波的实现非常直接,核心就是切片和加减法。
先看一维分解的实现思路。假设输入是一个长度为N的一维数组,我们要把它变成N/2个低频系数和N/2个高频系数:
import numpy as np def harr_1d_decompose(signal): """一维Harr小波分解""" signal = np.asarray(signal, dtype=np.float32) n = len(signal) # 保证长度为偶数 if n % 2 != 0: signal = np.append(signal, signal[-1]) n += 1 # 偶数索引和奇数索引配对 even = signal[0::2] odd = signal[1::2] # 低频:平均值;高频:差值的一半 low = (even + odd) / 2.0 high = (even - odd) / 2.0 return low, high这里有个细节值得说明:高频系数为什么除以2?因为Harr小波的规范正交基要求能量守恒,除以2(或者说除以根号2的平方)是为了让分解后的总能量和原信号一致。如果你不关心能量守恒,只做融合,其实不除也能用,但重构的时候要对应处理。
二维分解就是在一维的基础上,先对每一行做分解,再对结果的每一列做分解:
def harr_2d_decompose(img): """二维Harr小波分解,返回LL, LH, HL, HH四个子带""" img = np.asarray(img, dtype=np.float32) h, w = img.shape # 处理奇数尺寸 if h % 2 != 0: img = np.vstack([img, img[-1:, :]]) h += 1 if w % 2 != 0: img = np.hstack([img, img[:, -1:]]) w += 1 # 行方向分解 row_low = np.zeros((h, w // 2), dtype=np.float32) row_high = np.zeros((h, w // 2), dtype=np.float32) for i in range(h): low, high = harr_1d_decompose(img[i, :]) row_low[i, :] = low row_high[i, :] = high # 列方向分解 LL = np.zeros((h // 2, w // 2), dtype=np.float32) LH = np.zeros((h // 2, w // 2), dtype=np.float32) HL = np.zeros((h // 2, w // 2), dtype=np.float32) HH = np.zeros((h // 2, w // 2), dtype=np.float32) for j in range(w // 2): low, high = harr_1d_decompose(row_low[:, j]) LL[:, j] = low LH[:, j] = high low, high = harr_1d_decompose(row_high[:, j]) HL[:, j] = low HH[:, j] = high return LL, LH, HL, HH这段代码里用循环逐行逐列处理,逻辑清晰但速度一般。如果你要处理大图或者视频流,可以用NumPy的向量化操作重写,把循环去掉。不过对于单张图像的融合任务,这个速度完全够用,一张1080p的图分解一次大概几十毫秒。
3.3 融合规则的具体实现
分解完成后,对两张图的子带分别应用融合规则:
def fuse_coefficients(LL1, LL2, LH1, LH2, HL1, HL2, HH1, HH2): """低频平均,高频绝对值取大""" # 低频:加权平均 LL_fused = (LL1 + LL2) / 2.0 # 高频:绝对值取大 LH_fused = np.where(np.abs(LH1) >= np.abs(LH2), LH1, LH2) HL_fused = np.where(np.abs(HL1) >= np.abs(HL2), HL1, HL2) HH_fused = np.where(np.abs(HH1) >= np.abs(HH2), HH1, HH2) return LL_fused, LH_fused, HL_fused, HH_fused低频用平均是最稳妥的选择。我试过用最大值、用局部方差加权,结果要么对比度异常,要么出现块状伪影。平均虽然简单,但胜在稳定,融合后的图像过渡自然。
高频用绝对值取大,背后的逻辑是:高频系数的绝对值代表该位置边缘的强度。两张图在同一位置,一张清晰一张模糊,清晰那张的边缘强度一定更大,所以取绝对值大的系数,就相当于自动选择了清晰区域的信息。
3.4 小波重构:把子带还原成图像
融合完子带后,需要做逆变换把系数还原成像素图像。重构是分解的逆过程:
def harr_1d_reconstruct(low, high): """一维Harr小波重构""" n = len(low) signal = np.zeros(2 * n, dtype=np.float32) signal[0::2] = low + high signal[1::2] = low - high return signal def harr_2d_reconstruct(LL, LH, HL, HH): """二维Harr小波重构""" h, w = LL.shape # 列方向重构 row_low = np.zeros((2 * h, w), dtype=np.float32) row_high = np.zeros((2 * h, w), dtype=np.float32) for j in range(w): row_low[:, j] = harr_1d_reconstruct(LL[:, j], LH[:, j]) row_high[:, j] = harr_1d_reconstruct(HL[:, j], HH[:, j]) # 行方向重构 img = np.zeros((2 * h, 2 * w), dtype=np.float32) for i in range(2 * h): img[i, :] = harr_1d_reconstruct(row_low[i, :], row_high[i, :]) return img重构的公式推导很简单:分解时low = (even + odd) / 2,high = (even - odd) / 2,那么反解出来就是even = low + high,odd = low - high。这就是为什么重构代码里是加和减。
注意:如果分解时对奇数尺寸做了填充,重构后图像会比原图大一行或一列,需要裁剪回原始尺寸。这个细节很容易被忽略,导致融合结果尺寸对不上。
4. 完整实操流程:从读图到保存结果
4.1 主流程代码组织
把前面的函数串起来,形成一个完整的融合流程:
import cv2 import numpy as np def wavelet_fuse(img1_path, img2_path, output_path, levels=2): """基于Harr小波分解的图像融合主函数""" # 读取图像并转为灰度图 img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE) img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE) if img1 is None or img2 is None: raise ValueError("图像读取失败,请检查路径") # 统一尺寸 h = min(img1.shape[0], img2.shape[0]) w = min(img1.shape[1], img2.shape[1]) img1 = cv2.resize(img1, (w, h)).astype(np.float32) img2 = cv2.resize(img2, (w, h)).astype(np.float32) # 多级分解 coeffs1 = decompose_multilevel(img1, levels) coeffs2 = decompose_multilevel(img2, levels) # 逐级融合 fused_coeffs = [] for (LL1, LH1, HL1, HH1), (LL2, LH2, HL2, HH2) in zip(coeffs1, coeffs2): fused_coeffs.append(fuse_coefficients(LL1, LL2, LH1, LH2, HL1, HL2, HH1, HH2)) # 多级重构 result = reconstruct_multilevel(fused_coeffs) # 裁剪回原始尺寸并保存 result = result[:h, :w] result = np.clip(result, 0, 255).astype(np.uint8) cv2.imwrite(output_path, result) return result多级分解和重构的辅助函数:
def decompose_multilevel(img, levels): """多级二维Harr小波分解""" coeffs = [] current = img for _ in range(levels): LL, LH, HL, HH = harr_2d_decompose(current) coeffs.append((LL, LH, HL, HH)) current = LL # 对低频子带继续分解 return coeffs def reconstruct_multilevel(coeffs): """多级二维Harr小波重构""" # 从最深层开始逐级重构 LL, LH, HL, HH = coeffs[-1] current = harr_2d_reconstruct(LL, LH, HL, HH) for i in range(len(coeffs) - 2, -1, -1): _, LH, HL, HH = coeffs[i] current = harr_2d_reconstruct(current, LH, HL, HH) return current这里有个容易搞混的地方:多级分解时,每一级只对上一级的LL子带继续分解,但融合和重构时需要把每一级的LH、HL、HH都保留下来。重构时从最深层往上走,用当前重构结果作为下一级的LL,配合该级保存的LH、HL、HH继续重构。
4.2 参数选择与效果验证
分解级数levels是唯一需要手动调的参数。我的经验是:
- 1级分解:融合效果最基础,适合差异不大的图像,速度快
- 2级分解:最常用的选择,在效果和速度之间取得平衡
- 3级分解:适合大尺寸图像(2000像素以上),能捕捉更粗粒度的结构信息
- 4级以上:收益递减明显,而且容易在图像边缘产生振铃效应
验证融合效果时,我习惯把原图1、原图2、融合结果并排显示:
import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 3, figsize=(15, 5)) axes[0].imshow(cv2.imread('img1.jpg', 0), cmap='gray') axes[0].set_title('Source 1') axes[1].imshow(cv2.imread('img2.jpg', 0), cmap='gray') axes[1].set_title('Source 2') axes[2].imshow(result, cmap='gray') axes[2].set_title('Fused') for ax in axes: ax.axis('off') plt.tight_layout() plt.show()看融合结果时重点关注两个地方:一是原本模糊的区域是否变清晰了,二是清晰区域和模糊区域的交界处有没有明显的拼接痕迹。如果交界处有突兀的亮线或暗线,说明融合规则在该区域选择不一致,需要调整策略。
4.3 彩色图像的处理方式
前面的代码都是针对灰度图的。彩色图像的处理有两种思路:
思路一:分通道处理。把BGR三个通道拆开,每个通道单独做小波融合,最后合并。这种做法实现简单,但可能引入颜色失真,因为三个通道的融合决策可能不一致。
思路二:转换到YCrCb空间。只对Y通道(亮度)做小波融合,Cr和Cb通道(色度)用简单的平均或取第一张图的色度。这种做法能更好地保持颜色一致性,是我更推荐的方式。
def wavelet_fuse_color(img1_path, img2_path, output_path, levels=2): """彩色图像融合:Y通道小波融合,色度通道平均""" img1 = cv2.imread(img1_path) img2 = cv2.imread(img2_path) h = min(img1.shape[0], img2.shape[0]) w = min(img1.shape[1], img2.shape[1]) img1 = cv2.resize(img1, (w, h)) img2 = cv2.resize(img2, (w, h)) # 转换到YCrCb ycbcr1 = cv2.cvtColor(img1, cv2.COLOR_BGR2YCrCb).astype(np.float32) ycbcr2 = cv2.cvtColor(img2, cv2.COLOR_BGR2YCrCb).astype(np.float32) # Y通道小波融合 y1, cr1, cb1 = ycbcr1[:,:,0], ycbcr1[:,:,1], ycbcr1[:,:,2] y2, cr2, cb2 = ycbcr2[:,:,0], ycbcr2[:,:,1], ycbcr2[:,:,2] coeffs1 = decompose_multilevel(y1, levels) coeffs2 = decompose_multilevel(y2, levels) fused_coeffs = [] for c1, c2 in zip(coeffs1, coeffs2): fused_coeffs.append(fuse_coefficients(*c1, *c2)) y_fused = reconstruct_multilevel(fused_coeffs)[:h, :w] # 色度通道平均 cr_fused = (cr1 + cr2) / 2.0 cb_fused = (cb1 + cb2) / 2.0 # 合并并转回BGR ycbcr_fused = np.stack([y_fused, cr_fused, cb_fused], axis=2) ycbcr_fused = np.clip(ycbcr_fused, 0, 255).astype(np.uint8) result = cv2.cvtColor(ycbcr_fused, cv2.COLOR_YCrCb2BGR) cv2.imwrite(output_path, result) return result色度通道用平均而不是取大,是因为人眼对色度细节不敏感,色度上的微小差异不会影响观感,但色度不一致反而会让人察觉出颜色异常。
5. 常见问题与排查技巧实录
5.1 融合结果出现块状伪影
这是最常见的问题,表现为融合图像上出现规则的方块状纹理。根本原因是高频系数选择不一致:在某个局部区域,有些位置选了图1的系数,有些位置选了图2的系数,导致重构后出现不连续的跳变。
解决办法有两个。一是改用基于窗口的融合规则,不逐像素比较绝对值,而是比较以该像素为中心的3x3或5x5窗口内的能量和:
def fuse_high_freq_window(H1, H2, window_size=3): """基于局部窗口能量的高频融合""" kernel = np.ones((window_size, window_size), dtype=np.float32) energy1 = cv2.filter2D(np.abs(H1), -1, kernel) energy2 = cv2.filter2D(np.abs(H2), -1, kernel) return np.where(energy1 >= energy2, H1, H2)二是对融合后的高频系数做一次一致性验证:如果某位置的系数来自图1,但周围大多数系数来自图2,就把它改成图2的系数。这个操作叫“一致性滤波”,能有效消除孤立的错误选择。
5.2 融合图像整体偏暗或偏亮
如果融合结果比原图明显偏暗,通常是低频融合策略的问题。平均操作会拉低高亮区域的亮度,因为另一张图在对应位置可能是暗的。
改进方法是基于局部能量的加权平均:
def fuse_low_freq_weighted(LL1, LL2, window_size=3): """基于局部能量的低频加权融合""" kernel = np.ones((window_size, window_size), dtype=np.float32) energy1 = cv2.filter2D(LL1 ** 2, -1, kernel) energy2 = cv2.filter2D(LL2 ** 2, -1, kernel) total = energy1 + energy2 + 1e-6 w1 = energy1 / total w2 = energy2 / total return w1 * LL1 + w2 * LL2这样能量大的区域(通常也是更清晰的区域)会获得更大的权重,融合结果的亮度更接近清晰图像。
5.3 图像尺寸不一致导致报错
两张输入图像尺寸不同时,直接做小波分解会在矩阵运算时报错。我的处理方式是统一缩放到较小尺寸,而不是填充到较大尺寸。因为填充会引入人工边界,在边界处产生虚假的高频响应,影响融合质量。
如果两张图的长宽比差异很大,缩放会导致明显变形,这时候应该先做中心裁剪,裁成相同的长宽比,再统一尺寸。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方法 |
|---|---|---|---|
| 融合结果有块状伪影 | 高频系数选择不一致 | 检查高频融合规则 | 改用窗口能量比较+一致性滤波 |
| 整体偏暗/偏亮 | 低频平均拉低对比度 | 检查低频融合策略 | 改用局部能量加权平均 |
| 边缘出现亮线/暗线 | 分解重构边界处理不当 | 检查奇数尺寸填充逻辑 | 统一尺寸后再分解,重构后裁剪 |
| 颜色失真 | 三通道独立融合 | 检查是否分通道处理 | 转YCrCb空间,只融合Y通道 |
| 运行速度慢 | 逐像素循环 | 检查是否用了Python循环 | 改用NumPy向量化操作 |
| 重构后尺寸不对 | 奇数尺寸填充未裁剪 | 检查重构后裁剪逻辑 | 记录原始尺寸,重构后裁剪 |
5.5 实操心得:参数调节的优先级
调参的时候不要一上来就改分解级数。我的经验是,先调融合规则,再调窗口大小,最后才动分解级数。
融合规则决定了信息选择的基本逻辑,如果规则本身不合理,调多少级分解都救不回来。窗口大小影响融合的平滑程度,窗口越大越平滑但细节保留越少,一般3x3或5x5就够了。分解级数影响的是频率划分的粒度,对最终效果的影响反而是最小的。
还有一个容易被忽略的点:输入图像的质量。如果两张源图本身就很模糊,或者曝光差异极大,再好的融合算法也出不来好结果。融合的前提是源图之间有互补信息,如果两张图的信息高度重叠,融合的意义就不大了。
6. 性能优化与扩展方向
6.1 向量化加速
前面展示的分解和重构代码用了Python循环,逻辑清晰但速度受限。如果要把这个方案用到批量处理或实时场景,需要做向量化改造。核心思路是用NumPy的切片和广播操作替代循环:
def harr_2d_decompose_fast(img): """向量化版本的二维Harr小波分解""" img = np.asarray(img, dtype=np.float32) h, w = img.shape if h % 2 != 0: img = np.vstack([img, img[-1:, :]]) if w % 2 != 0: img = np.hstack([img, img[:, -1:]]) # 行方向:利用切片一次性处理所有行 even_cols = img[:, 0::2] odd_cols = img[:, 1::2] row_low = (even_cols + odd_cols) / 2.0 row_high = (even_cols - odd_cols) / 2.0 # 列方向:同样用切片 even_rows_low = row_low[0::2, :] odd_rows_low = row_low[1::2, :] LL = (even_rows_low + odd_rows_low) / 2.0 LH = (even_rows_low - odd_rows_low) / 2.0 even_rows_high = row_high[0::2, :] odd_rows_high = row_high[1::2, :] HL = (even_rows_high + odd_rows_high) / 2.0 HH = (even_rows_high - odd_rows_high) / 2.0 return LL, LH, HL, HH向量化版本的速度比循环版本快10倍以上,处理一张4K图像只需要几毫秒。重构函数也可以用同样的方式改造。
6.2 扩展到其他小波基
Harr小波的优点是简单,缺点是频率划分不够精细,在图像平滑区域容易产生“方块效应”。如果对融合质量有更高要求,可以换成Daubechies小波(db2、db4)或Symlet小波。这些正交小波的滤波器系数更长,分解效果更好,但实现复杂度也更高。
一个折中的方案是使用双正交小波,比如Cohen-Daubechies-Feauveau小波,它的分解和重构滤波器不同,但具有线性相位特性,能避免重构时的相位失真。OpenCV虽然没有内置小波函数,但可以用cv2.filter2D配合自定义的滤波器核来实现。
6.3 与深度学习方法结合
如果手头有标注数据,可以考虑用浅层CNN来学习融合规则,用小波分解做预处理。具体做法是:先对源图做小波分解,把LL、LH、HL、HH四个子带作为CNN的输入通道,让网络学习如何组合这些子带。这样既保留了小波变换的多尺度优势,又利用了深度学习的拟合能力。
不过这种方案需要大量的训练数据,而且模型的可解释性不如纯小波方法。对于大多数应用场景,纯小波融合已经足够好了。
6.4 批量处理与自动化
实际项目中往往需要处理大量图像对。我通常会写一个批处理脚本,自动遍历文件夹里的图像对,按命名规则配对,然后调用融合函数:
import os import glob def batch_fuse(input_dir, output_dir, levels=2): """批量融合:按文件名前缀配对""" os.makedirs(output_dir, exist_ok=True) img1_list = sorted(glob.glob(os.path.join(input_dir, '*_A.*'))) for path1 in img1_list: path2 = path1.replace('_A.', '_B.') if not os.path.exists(path2): print(f"跳过:找不到配对图像 {path2}") continue name = os.path.basename(path1).replace('_A.', '_fused.') output_path = os.path.join(output_dir, name) try: wavelet_fuse(path1, path2, output_path, levels) print(f"完成:{name}") except Exception as e: print(f"失败:{name},原因:{e}")批处理时要注意异常处理,单张图像失败不应该中断整个批次。另外建议加一个日志记录,方便回溯哪些图像处理失败了。
7. 一些踩坑之后的个人体会
这个方案我从第一次写到现在,前后改了好几版。最开始用的是逐像素循环,一张图跑好几秒,后来改成向量化才把速度提上来。融合规则也试过很多种,低频取大、高频平均、基于梯度的选择,最后发现还是“低频平均+高频绝对值取大”这个最朴素的组合最稳。
有一个坑我印象很深:早期版本没有处理奇数尺寸,结果遇到一张1921x1081的图,分解重构后尺寸变成了1922x1082,保存的时候和原图对不上,排查了半天才发现是填充逻辑的问题。后来我在分解函数里统一做了偶数化处理,重构后再裁剪回原始尺寸,这个问题就再也没出现过。
还有一个经验是关于分解级数的。我一开始觉得级数越多效果越好,直接上了4级,结果融合图像边缘出现了明显的振铃,而且处理时间翻了好几倍。后来降到2级,效果反而更自然。所以参数这个东西,不是越大越好,够用就行。
如果你打算把这个方案用到实际项目里,我的建议是先把灰度图的流程跑通,确认融合效果符合预期,再扩展到彩色图像。彩色图像的处理虽然只多了几行代码,但颜色空间转换和通道合并的细节很容易出错,先把基础打牢再往上加功能,会省很多调试时间。