简介:图像景深融合处理源代码是一套基于C#与OpenCvSharp的图像融合实现,面向需要快速集成景深合成或多图融合作业的开发者和研究人员。资源核心将融合算法打包为可直接调用的DLL,支持CV_8UC1灰度图与CV_8UC3彩色图两种Mat格式,测试工程演示了直接打开JPG或BMP图片并转为Mat后完成融合的完整流程,调用方只需关注业务逻辑,且在多核环境下可获得更快的融合速度。压缩包共54个文件,以DLL运行库、CS源码工程、EXE测试程序为主,其中DLL为算法和OpenCvSharp运行依赖,CS为调用封装代码,EXE为演示入口,另有XML配置说明、PDB调试符号及项目文件,便于二次调试与工程迁移。包体约89.19MB,已有204人浏览学习。可从中拿到现成的算法封装、完整的OpenCvSharp调用示例和可运行测试程序,适合作为图像融合模块的代码参考或在现有C#视觉项目中直接引用。
1. 图像景深融合处理源代码:从对焦栈到合焦图的一站式工程
拍微距最常见的翻车现场:大光圈下对焦点锁住了,花瓣锐利、背景虚化得很干净,可你想拍的两根蕊须总有一根落在焦外。硬缩光圈到 F11 能把景深拉出来,但暗光下高 ISO 的噪点立刻毁掉画质。摄影圈子共同的做法,是拍一组不同焦点平面的原片,进后期做景深合成。这份「图像景深融合处理源代码」就是把这条后期链路完整开源出来的工程——输入同一机位下不同焦点的原图,自动做像素级清晰度评估并按权重融合,输出一张全图合焦的画面。适用人群很明确:微距摄影后期党、显微与电子元件检测的机器视觉入门者,以及想在图像处理里跑通“清晰度度量—权重图生成—多尺度融合”完整闭环的开发者。工程不依赖商业插件,全部用基础图像处理手段实现,可改可跑。
2. 技术选型与总体架构:Python + OpenCV 的融合管线怎么搭
2.1 技术栈与依赖:为什么选 Python 而不是更底层的方案
打开这份源码的第一个观感,是它没有引入深度学习依赖,核心库只有 numpy 和 OpenCV。景深融合当前主要有两条路线:传统图像处理派,用像素级清晰度度量加多尺度融合解决;深度学习派,训练回归网络去估计每一处的散焦程度。这份工程走的是传统路线,好处在落地环节非常明显——不需要 GPU、不需要标注数据、普通笔记本上就能跑完一组样例,而且中间过程的清晰度图和权重图都可以直接导出,方便调试和二次开发。
requirements.txt 里的依赖写得比较克制:
numpy>=1.24,<2.0 opencv-python>=4.8.0,<5.0 natsort>=8.4.0numpy 版本上限写成 2.0 是个实用细节:OpenCV 4.8 系列对 numpy 2.x 的二进制兼容处理还不到位,直接升级会让 cv2.cvtColor 在某些场合抛 ABI 不匹配的警告。natsort 用来做输入文件名的自然排序,否则frame_10.jpg会排在frame_9.jpg前面,焦点栈顺序一乱,后续对齐和融合都会跟着跑偏。
我一般建议在工程里把 numpy 的版本上限写死,毕竟这种项目对版本敏感度不高,稳定优先。OpenCV 的 API 从 4.5 到 4.10 基本延续同一套调用约定,pyrDown、Laplacian、boxFilter 这些核心函数的签名没有破坏性变化,只要锁住 cv2 的读取方式,后续维护压力很小。这套选型在工程上还有一个优势:OpenCV 的 Python 接口直接暴露的是 C++ 实现,cv2.Laplacian 底层是卷积算子,几百张图跑下来比纯 numpy 手写快一个量级。景深融合要逐帧做多尺度金字塔,每帧快 0.2 秒,在 12 帧的输入上就是 2 秒级差距,批量跑几十组时体验差异很明显。
2.2 工程目录结构与输入输出约定
源码目录按职责分得比较清楚,五个 Python 文件各管一段:
focusstacking/ ├── src/ │ ├── cli.py # 命令行入口,负责参数解析和流程编排 │ ├── io_utils.py # 图片扫描、自然排序、读取与写盘 │ ├── focus_measure.py # 清晰度度量(拉普拉斯能量 / Tenengrad) │ ├── weighting.py # 权重图生成,包含 softmax 与羽化 │ ├── fusion.py # 拉普拉斯金字塔融合与重建 ├── samples/ # 示例焦点栈,按文件名自然排序读取 ├── output/ # 默认输出目录 └── README.md # 参数说明与使用示例cli.py 是流程编排者,它调用 io_utils.py 扫描输入目录,把图片按文件名自然排序读进内存;再依次调用 focus_measure 计算每张图的清晰度图,weighting 把清晰度图转成权重图,最后 fusion 把原图和权重一起送入金字塔融合流程。每层之间都用 numpy 数组传递,没有中间文件落盘,调试时可以临时把函数返回值存成 TIFF 来观察。
目录里没有模型目录或权重文件夹,因为这个工程是纯传统图像处理,不需要离线模型文件。整个工程不依赖 GPU,后续如果你打算做深度学习版本,再加 pytorch 也不迟——当前这套代码已经把传统链路拆成了独立模块,换掉 focus_measure 就可以接一个 CNN 输出的清晰度热力图,接口都不用变。输入约定只有一个硬性要求:所有图片必须来自同一机位、同一场景,焦点不同。文件格式支持 JPG、PNG、TIFF,推荐用 TIFF 或 PNG 作为中间文件,因为 JPEG 压缩带来的边缘振铃会直接影响清晰度度量的准确性。输出目录默认为 output/,格式由输出文件的后缀决定,写.tif会保留 16bit 精度,写.jpg则会压缩到 8bit。
2.3 处理管线:从焦点堆栈到全清晰图
整个融合管线分五步走:读取排序、可选对齐、清晰度度量、权重生成、金字塔融合。前两步属于准备阶段,核心价值在第三步之后的像素级决策。
对齐这一步容易被人忽视,但手持拍摄的照片通常有 2 到 10 个像素的随机平移,不去对齐直接做逐像素融合,边缘处必然出现双线或重影。工程里默认用相位相关法估计全局平移量,相位相关比特征点匹配对这种输入更稳——焦点栈的画面内容基本相同,只是局部模糊程度不一样,特征点检测在这种输入上不稳定。相位相关在频域里找出互功率谱峰值,直接得到整幅图的位移量,对光照变化也不敏感。
对齐之后是核心的“评估—加权—融合”三段式。先对每张原图计算清晰度图,得到每个像素位置“当前帧有多清楚”的数值;再把 N 张清晰度图放一起做 softmax,变成 N 个在空间上互相竞争的权重图;最后把原图和权重图同时送入拉普拉斯金字塔,在 5 个尺度上逐层加权融合。之所以要金字塔,是因为直接在像素域做权重乘加会在焦点过渡处留下明显的接缝,而这种空间域接缝在频域里往往集中在几个尺度上,多尺度融合可以把过渡摊到不同频率层,肉眼基本看不出来。
不过这里有一个容易误用的点:对齐不是万能的。相位相关法对旋转和尺度变化没有处理能力,如果拍摄时云台松动导致画面有轻微旋转,哪怕只有 0.3 度,融合边缘也会出现重影样式的伪影。碰到这种输入,先把图像手动转正,或者改用 ORB 特征点匹配做过一次预对齐,再走默认融合管线。总体原则是:对齐阶段宁可少做,不可做错,错对齐比不对齐更糟。像素级评估是这份工程与传统后期合成软件最大的区别——后者多数时候也生成权重蒙版,但过程不透明;这里每一张权重图都可以单独导出、单独检查。我检视结果时一般同时开三张图:原始焦点栈、清晰度热力图、权重图。清晰度热力图用来判断哪些区域评估错了,权重图用来判断过渡带宽不够。这两个中间产物是调参的向导,输出对不对看一眼就知道。
3. 核心模块拆解:清晰度度量、权重图与拉普拉斯金字塔融合
3.1 清晰度评估:拉普拉斯能量与 Tenengrad 的代码实现
清晰度度量是整个融合的“眼睛”。一张图里,焦点清晰的区域高频信息丰富,拉普拉斯算子响应强烈;散焦区域则高频成分被削弱,拉普拉斯响应趋于平缓。实际上,一般做法不是对整幅图算一个标量,而是逐像素计算局部窗口内的拉普拉斯能量,再把结果当成一张热力图传给下一环节。
import cv2 import numpy as np def focus_measure(image, window_size=25, method="laplacian"): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if method == "laplacian": lap = cv2.Laplacian(gray, cv2.CV_64F, ksize=3) energy = lap * lap elif method == "tenengrad": gx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) gy = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) energy = gx * gx + gy * gy else: raise ValueError(f"不支持的方法: {method}") measure = cv2.boxFilter(energy, cv2.CV_64F, (window_size, window_size)) return measure这段代码有两个要点。第一,gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)里的 BGR 顺序是 OpenCV 的老规矩,换 RGB 会导致后续颜色通道错位;第二,cv2.boxFilter用盒式滤波做局部窗口累加,等价于一个均值卷积核,把单像素的高频响应聚合成 25×25 邻域的“区域清晰度”。window_size 定多少很微妙——窗口太小,纹理稀疏的区域会被当成低清晰度;窗口太大,焦点边界会被磨平。默认 25 对 2000 万像素级别的照片是个稳妥起点。
Tenengrad 和拉普拉斯能量,实际效果差距不大,拉普拉斯对噪点更敏感,Tenengrad 对边缘方向更敏感。做 PCB 检测时,焊点边缘是有方向性的,Tenengrad 更容易把方向性细节保留住;而微距花卉这类边缘方向随机的场景,拉普拉斯更稳。选哪个不是性能瓶颈,多跑一遍对比也不费事。
3.2 权重图生成:softmax 与边缘羽化
有了 N 张清晰度图,下一步是把“谁更清晰”变成“融合时权重各占多少”。朴素做法是逐像素取最大值对应的帧,得到一个硬索引图。但硬索引在焦点过渡带上会产生一像素宽的突变,融合出来的画面会出现台阶感。正规做法通常用 softmax 把清晰度值映射成权重,让过渡带变成渐变。
def generate_weights(measures, temperature=8.0): measures = measures.astype(np.float64) max_val = np.max(measures, axis=0) exp_map = np.exp((measures - max_val) * temperature) weight_sum = np.sum(exp_map, axis=0) weights = exp_map / np.clip(weight_sum, 1e-12, None) return weights这是带温度系数的 softmax,核心逻辑是先减去每帧同一位置的最大值再做指数,防止 exp 溢出。temperature 控制权重曲线的陡峭程度:温度取 8 到 12 之间时,权重图接近硬切,过渡带只有 10 来个像素;取 1 到 3 时,过渡带能拉到几十像素宽,焦点切换处更柔,但也可能把边缘细节“抹”掉一部分。
我一般建议先跑默认值 8,如果发现焦点边界特别明显的重影或描边,再把温度调低到 4 左右重跑一次。权重图可以直接导出,显示成灰度图之后,白色区域就是当前帧主导融合的区域,黑白交界越柔和,最后结果越自然。
3.3 拉普拉斯金字塔融合:核心链路与重建
权重图已经算完,最后一步是融合。这里要解决的核心问题,是直接做sum(image_i * weight_i)会出现接缝。原因是图像的高频和低频部分对权重的敏感度不同:低频亮度过渡可以在小权重偏差下平滑衔接,但高频边缘只要权重偏差一变换就会出现“断线”或双层边。
拉普拉斯金字塔的思路是把每张原图分解成不同尺度的高频细节层,每一层都按照该尺度的权重图去加权求和,最后再逐层重建。分解出来的每一层都是原始图的带通分量,融合后重建就等于“每个尺度上选了最合适的内容拼起来”。
def build_laplacian_pyramid(image, levels): current = image.astype(np.float32) gaussian = [current] for _ in range(levels): gaussian.append(cv2.pyrDown(gaussian[-1])) laplacian = [] for i in range(levels): up = cv2.pyrUp(gaussian[i + 1]) up = cv2.resize(up, (gaussian[i].shape[1], gaussian[i].shape[0])) laplacian.append(gaussian[i] - up) laplacian.append(gaussian[-1]) return laplaciangaussian[i] - up就是当前尺度的高频残差,最后一层直接存放最小的低频基底。levels 设 5 表示把图像下采样 5 个等级,覆盖从 1/2 到 1/32 的分辨率范围,对过渡带几十像素的权重变化足够平滑。超过 7 层会明显增加内存占用,收益却很小,因为最低层的分辨率往往不足 200×200 像素,细节早已被模糊掉。
融合主流程要同时处理原图金字塔和权重金字塔:
def fuse_with_pyramid(images, weights, levels=5): n, h, w, c = images.shape if h % 2: h -= 1 if w % 2: w -= 1 images = images[:, :h, :w].astype(np.float32) weights = np.stack([ cv2.resize(weights[i], (w, h)).astype(np.float32) for i in range(n) ]) lap_pyramids = [build_laplacian_pyramid(images[i], levels) for i in range(n)] weight_pyramids = [] for i in range(n): wp = [weights[i]] for _ in range(levels): wp.append(cv2.pyrDown(wp[-1])) weight_pyramids.append(wp) fused_pyramid = [] for level in range(levels + 1): acc = np.zeros((h // (2 ** level), w // (2 ** level), c), dtype=np.float32) wsum = np.zeros((h // (2 ** level), w // (2 ** level), 1), dtype=np.float32) for i in range(n): layer_img = lap_pyramids[i][level] layer_w = cv2.resize(weight_pyramids[i][level], (layer_img.shape[1], layer_img.shape[0])) acc += layer_img * layer_w[..., None] wsum += layer_w[..., None] fused_pyramid.append(acc / (wsum + 1e-12)) result = fused_pyramid[-1] for level in range(levels - 1, -1, -1): result = cv2.pyrUp(result) result = cv2.resize(result, (fused_pyramid[level].shape[1], fused_pyramid[level].shape[0])) result = result + fused_pyramid[level] return np.clip(result, 0, 255).astype(np.uint8)参数说明:images的形状是 (N, H, W, 3),N 是输入帧数;weights的形状是 (N, H, W),由上一节生成,每个像素处 N 个值之和为 1。尺寸裁剪是必要的,OpenCV 的 pyrDown 在奇数尺寸下会自动丢掉最后一行或一列,提前在入口统一裁剪能保证所有层级形状对齐。wsum + 1e-12是保护项,防止某层权重全零时出现除零的 NaN。最后的金字塔重建,是从顶层低频开始逐级放大,再加回高频残差,得到的就是完整融合结果。
这套实现里最容易翻车的地方是layer_w[..., None]的广播。layer_w是单通道权重,layer_img是三通道彩色图,numpy 广播到 (H, W, 1) 乘 (H, W, 3) 时会把权重复制到每个颜色通道上,这是符合预期的。如果忘了这个维度扩展,控制台会直接报广播错误,属于一眼能定位的问题。
4. 运行与参数调优:命令行入口与三种典型用法
4.1 环境准备与依赖安装
拿到源码后先把环境搭起来。建议用虚拟环境隔离,不要直接装在系统解释器里,后续改依赖版本时不会被别的项目干扰。
cd focusstacking python -m venv .venv source .venv/bin/activate # Windows 下执行 .venv\Scripts\activate pip install -r requirements.txt安装完成后,先用 samples 目录里自带的例子跑一遍默认流程。样例一般是一组 12 张左右的花卉微距照片,名字按frame_01.jpg到frame_12.jpg排列,能覆盖从近点到远点的整个对焦行程。
4.2 命令行参数解析与参数表
cli.py 的参数解析集中在 build_parser 函数里:
def build_parser(): parser = argparse.ArgumentParser(description="图像景深融合处理") parser.add_argument("--input", required=True, help="输入目录或 glob 模式") parser.add_argument("--output", default="output/fused.jpg", help="输出路径") parser.add_argument("--align", action=argparse.BooleanOptionalAction, default=True) parser.add_argument("--method", choices=["laplacian", "tenengrad"], default="laplacian") parser.add_argument("--blend-levels", type=int, default=5) parser.add_argument("--temperature", type=float, default=8.0) parser.add_argument("--window", type=int, default=25) return parser这里每个参数对应一个调优节点:--blend-levels控制金字塔层数,--temperature控制权重过渡陡峭度,--window控制清晰度评估的局部窗口大小。跑通以后再调这三个参数,效果变化最明显。
| 参数 | 默认值 | 作用说明 |
|---|---|---|
| --input | 必填 | 输入目录路径,或带通配符的 glob 模式 |
| --output | output/fused.jpg | 输出路径,后缀决定编码格式 |
| --align | True | 是否做全局平移对齐,三脚架固定机位可关掉 |
| --method | laplacian | 清晰度度量方式,可选 laplacian / tenengrad |
| --blend-levels | 5 | 拉普拉斯金字塔层数,越大过渡越平滑 |
| --temperature | 8.0 | softmax 温度,越大权重过渡越硬 |
| --window | 25 | 清晰度评估窗口边长,奇数为佳 |
4.3 三种典型运行场景与参数选择
第一种是微距花卉的后期融合,这也是最常见的使用场景。直接用默认参数,把输入指向花朵焦点栈目录:
python src/cli.py --input samples/flower --output output/flower_fused.jpg跑完看一眼焦点过渡区域有没有明显的白边或硬线,如果过渡太硬,把--temperature降到 4 重跑一次;如果细节偏软,把--blend-levels加到 7。对比这两次输出的差值图,可以直观看到温度参数到底改了哪些区域的权重。
第二种是 PCB 或电子元件的检测图。这类场景通常放在支架上拍摄,机位不会移动,可以关掉对齐省一点时间,同时把度量方式换成 tenengrad,因为焊点和走线多为方向性边缘:
python src/cli.py --input samples/pcb --output output/pcb_fused.png \ --align off --method tenengrad --window 15window 从 25 改成 15,是因为 PCB 特征小而密集,窗口太大会把相邻焊点的清晰度信息混在一起,评估失真。这里输出的格式选了 PNG,因为检测图后续可能还要做二值化或边缘提取,PNG 的无损编码更能保住细节。
第三种是批量处理多个焦点栈,比如连续拍摄了好几组不同角度的产品图。这时候用 shell 循环逐个调用即可:
for dir in samples/set_*; do python src/cli.py --input "$dir" --output "output/$(basename "$dir").jpg" done这种用法没有魔法,主要注意点是输出名别重,多个目录共用一个输出路径会把前面的结果覆盖掉。批量跑之前建议先用单个样例验证参数,确认后再开循环,否则一组图全部废掉就后悔药都找不到了。跑批量时把逐帧日志打开,能看到每张图的处理耗时,一旦某张图异常卡住,日志会直接告诉你卡在哪个文件上。
5. 避坑指南与常见问题:重影、偏色、权重撕裂的排查记录
5.1 融合结果出现重影、双轮廓
现象:合成的图在花瓣边缘或叶片边缘有半透明的双层影子,像没对齐的多重曝光。原因基本集中在两类,一是输入图片之间存在几像素的平移且对齐失败,二是画面里的物体发生了几何移动,比如风吹动的花瓣。相位相关只能估计全局平移,对局部形变无能为力。
解决:先关掉对齐重跑一次,看重影是否来自对齐引入的错误——如果关掉后依然重影,说明是物体运动,建议回到拍摄环节把焦点栈改成连拍模式,或者放慢节奏减少运动。如果确认是对齐问题,打开对齐后观察对齐输出的可视化结果,相位相关在低纹理画面上有时会算出错误位移,这时候手动裁剪边缘再对齐也能改善。
5.2 输出画面整体偏色,尤其边缘带绿紫边
现象:融合结果里有彩色的边缘描边,绿边紫边混合,跟镜头的色差很像。这个现象最常见的来源,是把 BGR 当 RGB 用了。OpenCV 读图默认是 BGR 顺序,如果权重图或中间数组按 RGB 假设做数值计算,融合时通道错位会直接产生颜色偏移。特别在金字塔残差计算里,通道错位不会被平滑修正,反而会一层层放大。
解决:检查所有cv2.cvtColor和cv2.imread的通道顺序,确认统一为 BGR。如果是从其他库读取的 RGB 图像,在进入 pipeline 前强制加一次cv2.cvtColor(img, cv2.COLOR_RGB2BGR)。彩色描边还可能是权重过渡带太窄导致的,这种情况把 temperature 调低并增加金字塔层数即可缓解。排查这类问题时打印第一张图的第一个像素的三通道值,能快速判断通道顺序是否已经错位。
5.3 中途报“图像尺寸不一致”或“尺寸不是偶数”
现象:脚本报错,提示两幅图的宽高对不上,或 pyrDown 输出尺寸无法对齐。原因很实际——输入图片尺寸不统一,或者宽度是奇数。OpenCV 的下采样要求每层尺寸为偶数,奇数宽高的图在某个融合层级会出现行或列跨尺度不一致。
解决:在进入融合前统一裁剪或缩放到同一尺寸。工程里 fuse_with_pyramid 已经做了 2 的整除处理,但只在单组输入内有效;如果你换了一批不同分辨率的图片,需要在 io_utils 读取后先cv2.resize到目标尺寸。一般建议输入长边不超过 6000 像素,超过的部分先降采样,否则后续内存压力会很大。
5.4 权重图撕裂或过渡带出现横竖线
现象:权重图可视化时出现明显的横线或竖线,融合结果在这些位置出现亮度跳变。原因通常出在金字塔的上采样尺寸不匹配——cv2.pyrUp会把尺寸翻倍,但当原尺寸不是 2 的倍数时,补边可能导致一行错位。这类问题看起来像黑匣子,只在特定输入尺寸下复现。
解决:统一入口尺寸的裁剪逻辑,让所有图片先经过同一尺寸对齐函数再进金字塔。我一般会在 build_laplacian_pyramid 里打印每一层的尺寸,确认逐层尺寸减半关系没有异常跳变。拿不准时直接把输入裁剪成 2 的幂倍数,问题会完全消失。这个方法治标又治本,尺寸统一后金字塔每一层都能严格对齐。
5.5 内存占用过高或批量处理时崩溃
现象:融合 12 张 4000×3000 的图片时内存占用轻松超过 8GB,批量处理中途被系统杀掉。原因是金字塔融合要对每张图保存 N 层浮点数组,且每层都要参与加权计算;12 张图同时驻留内存,浮点中间结果的体积远大于原始 JPG 文件。
解决:两个思路。一是降低输入分辨率,长边压到 3000 以内,这对大多数后期输出足够;二是按块融合——把图像切成若干有重叠的块分别处理,再用渐变带拼接。切块时重叠区留 32px 以上,否则块与块之间会看到接缝。工程默认没做分块,二次开发时可以往这个方向切入。
提示:如果内存还不够,把
images数组从 float32 降到 float16 能省一半空间,但金字塔重建时的精度会略微下降,建议只在内存吃紧时启用。
6. 二次开发方向:把融合结果做得更自然的三个切入点
6.1 用相位一致性度量替换拉普拉斯
拉普拉斯能量对焦内高频响应高、焦外响应低,但遇到纹理非常平坦的区域时,两种状态的高频差异都很小,算法会随机选一帧,结果就是平坦区域出现颗粒感。相位一致性度量可以弥补这一点,它基于频域的局部相位一致性判断边缘,对纹理缺失更鲁棒。实现上可以用 PyWavelets 做局部频率分解,替换 focus_measure 里的 boxFilter 分支。替换后额外收益是它对光照不均的容忍度更高。
6.2 融合前加一步亮度归一化
同一组焦点栈的照片之间,曝光往往存在轻微差异,尤其在自然光下快速连拍时,直接融合会出现亮度斑块。做法是在对齐完成后、清晰度度量前,统计每张图的全局平均亮度,以中间帧为基准,给其他帧乘以一个亮度系数。这个步骤的坑是:直接用全局系数会在边缘处留下亮度差,最好在重叠区域分块估计局部亮度增益,再用平滑过的增益矩阵去乘原图。我一般用中值滤波估计局部亮度比,精度足够且实现简单。
6.3 引入 GPU 加速或分块处理
如果输入的是高像素工业相机照片,CPU 跑金字塔融合一轮可能要几分钟。最快的改造是用cv2.UMat让 OpenCV 自动调度到 GPU 后端,代码改动很小——把cv2.imread的返回值转成 UMat,pyrDown 和 pyrUp 会自动走 OpenCL。另一个更可控的做法是 ROI 分块,把大图切成若干小尺寸块,块与块之间重叠 64 像素,融合后沿重叠中心做线性渐变拼回。
以上三处都是改动范围小、收益明显的点。任何改动都要先把输出和原融合结果做一次像素级差值检查,确认没有引入新的接缝或偏色。
我在这个项目上踩过最久的坑,是某一次换新数据集后融合图边缘反复出紫边,检查所有参数都没结果,最后发现是读取图像时用了另一个库的 RGB 顺序,OpenCV 管道全程没察觉,直到输出才爆发。从那以后,我每次换一组输入,都会先打印第一张图的通道顺序和数值范围,确认 BGR 与 8bit 再往下跑。这个习惯后来帮我挡掉了很多玄学问题。希望帮到你。
本文还有配套的精品资源,点击获取