OpenCV 官翻6 - Computational Photography
文章目录
- 图像去噪
- 目标
- 理论
- OpenCV中的图像去噪
- 1、cv.fastNlMeansDenoisingColored()
- 2、cv.fastNlMeansDenoisingMulti()
- 附加资源
- 图像修复
- 目标
- 基础概念
- 代码
- 补充资源
- 练习
- 高动态范围成像(HDR)
- 目标
- 理论基础
- 曝光序列HDR
- 1、将曝光图像加载到列表中
- 2、将曝光序列合并为HDR图像
- 3、色调映射HDR图像
- 4、使用Mertens融合算法合并曝光图像
- 5、转换为8位并保存
- 结果
- 德贝维克方法:
- 罗伯逊方法:
- Mertenes 融合算法:
- 估计相机响应函数
- 补充资源
- 练习
图像去噪
https://docs.opencv.org/4.x/d5/d69/tutorial_py_non_local_means.html
目标
在本章中,
- 你将学习使用非局部均值去噪算法来消除图像中的噪声。
- 你将了解不同的函数,如
cv.fastNlMeansDenoising()
、cv.fastNlMeansDenoisingColored()
等。
理论
在前面的章节中,我们已经了解了许多图像平滑技术,如高斯模糊、中值模糊等,这些技术在去除少量噪声方面有一定效果。在这些技术中,我们选取像素周围的小邻域,并通过高斯加权平均、取中值等操作来替换中心像素。简而言之,像素点的去噪过程仅局限于其邻近区域。
噪声具有一个特性:通常被视为均值为零的随机变量。假设一个含噪像素为 \(p = p_0 + n\),其中 \(p_0\) 是像素的真实值,\(n\) 是该像素的噪声。若从不同图像中获取大量相同像素(比如 \(N\) 个)并计算其平均值,理想情况下应得到 \(p = p_0\),因为噪声的均值为零。
你可以通过简单实验验证这一点:用固定相机对准某场景拍摄几秒钟,这将获得大量帧(即同一场景的多张图像)。然后编写代码计算视频中所有帧的平均值(这对现在的你应该非常简单),最后将结果与第一帧对比,即可观察到噪声的减少。但遗憾的是,这种简单方法无法应对相机和场景的运动,且通常我们只有一张含噪图像可用。
因此,核心思路是:我们需要一组相似图像来消除噪声。假设在图像中取一个小窗口(如5x5像素),很可能在图像的其他位置(有时在其邻近区域)存在相似的图像块。若能将这些相似块集中并计算平均值,对该特定窗口而言效果会很好。参考下图示例:
图中蓝色块看起来相似,绿色块也彼此相似。具体操作是:选取一个像素,在其周围取小窗口,在图像中搜索相似窗口,对所有窗口取平均值后替换原像素。这种方法称为非局部均值去噪(Non-Local Means Denoising)。虽然比之前介绍的模糊技术耗时更长,但效果显著。更多细节和在线演示可参考附加资源中的第一个链接。
对于彩色图像,会先转换到CIELAB色彩空间,然后分别对L分量和AB分量进行去噪处理。
OpenCV中的图像去噪
OpenCV提供了该技术的四种变体。
1、cv.fastNlMeansDenoising()
- 处理单张灰度图像
2、cv.fastNlMeansDenoisingColored()
- 处理彩色图像
3、cv.fastNlMeansDenoisingMulti()
- 处理短时间内捕获的图像序列(灰度图像)
4、cv.fastNlMeansDenoisingColoredMulti()
- 同上,但适用于彩色图像
常用参数说明:
- h : 决定滤波强度的参数。较高的h值能更好去除噪声,但也会损失图像细节(推荐值10)
- hForColorComponents : 与h相同,但仅适用于彩色图像(通常与h值相同)
- templateWindowSize : 应为奇数(推荐值7)
- searchWindowSize : 应为奇数(推荐值21)
更多参数详情请参阅附加资源中的第一个链接。
我们将在此演示第2和第3种方法,其余方法留给读者自行探索。
1、cv.fastNlMeansDenoisingColored()
如上所述,该方法用于去除彩色图像中的噪声(假设噪声服从高斯分布)。参见以下示例:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltimg = cv.imread('die.png')dst = [cv.fastNlMeansDenoisingColored`](https://docs.opencv.org/4.x/d1/d79/group__photo__denoise.html#ga03aa4189fc3e31dafd638d90de335617)(img,None,10,10,7,21)plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(dst)
plt.show()
以下是放大后的结果。我的输入图像带有标准差为 \(\sigma = 25\) 的高斯噪声。
查看效果:
2、cv.fastNlMeansDenoisingMulti()
现在我们将同样的方法应用于视频处理。第一个参数是含噪声的帧列表。第二个参数imgToDenoiseIndex指定需要去噪的帧,这里我们传入输入列表中该帧的索引值。第三个参数temporalWindowSize表示用于去噪的邻近帧数量,该值应为奇数。此时将使用共计temporalWindowSize帧,其中中心帧就是待去噪的帧。例如,你传入包含5帧的输入列表,设imgToDenoisingIndex = 2且temporalWindowSize = 3,那么将使用frame-1、frame-2和frame-3来对frame-2进行去噪。下面来看具体示例。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltcap = cv.VideoCapture('vtest.avi')# create a list of first 5 frames
img = [cap.read()[1] for i in range(5)]# convert all to grayscale
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img]# convert all to float64
gray = [np.float64(i) for i in gray]# create a noise of variance 25
noise = np.random.randn(*gray[1].shape)*10# Add this noise to images
noisy = [i+noise for i in gray]# Convert back to uint8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy]# Denoise 3rd frame considering all the 5 frames
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)plt.subplot(131),plt.imshow(gray[2],'gray')
plt.subplot(132),plt.imshow(noisy[2],'gray')
plt.subplot(133),plt.imshow(dst,'gray')
plt.show()
下图展示了我们获得结果的放大版本:
该计算过程需要耗费大量时间。在结果中,第一张是原始帧图像,第二张是添加噪声后的图像,第三张则是去噪后的图像。
附加资源
1、http://www.ipol.im/pub/art/2011/bcm_nlm/(包含详细资料、在线演示等,强烈推荐访问。我们的测试图像即源自此链接)
2、Coursera在线课程(第一张图像取自此处)
本文档由 doxygen 1.12.0 生成于 2025年4月30日 星期三 23:08:43,适用于 OpenCV
图像修复
https://docs.opencv.org/4.x/df/d3d/tutorial_py_inpainting.html
目标
在本章中,
- 我们将学习如何通过一种称为"修复"的方法去除旧照片中的小噪点、划痕等
- 我们将了解OpenCV中的图像修复功能
基础概念
想必大家家中都保存着一些老旧褪色的照片,上面可能布满黑点或划痕。你是否曾想过如何修复它们?我们无法简单地用绘图工具擦除这些瑕疵,因为这样只会将黑色结构替换为白色结构,毫无实际意义。针对这种情况,就需要使用一种称为图像修复(image inpainting)的技术。其核心思想很简单:用邻近像素替换这些瑕疵区域,使其与周围环境自然融合。如下图所示(图片来自维基百科):
为此人们设计了多种算法,OpenCV提供了其中两种实现。两者都可通过同一个函数 cv.inpaint()
调用。
第一种算法基于Alexandru Telea 2004年发表的论文**《基于快速行进法的图像修复技术》**,其核心是快速行进法(Fast Marching Method)。算法从待修复区域的边界开始,逐步向内推进,优先填充边界区域。对于每个待修复像素,算法会取其周围小邻域内的已知像素,用归一化加权和进行替换。权重分配至关重要:离目标点越近、越接近边界法线方向、或位于边界轮廓上的像素会获得更高权重。完成当前像素修复后,通过快速行进法移动到下一个最近像素。这种机制确保已知像素附近的区域优先修复,模拟人工修复的启发式过程。可通过标志位 cv.INPAINT_TELEA
启用该算法。
第二种算法基于Bertalmio、Marcelo、Andrea L. Bertozzi和Guillermo Sapiro 2001年发表的论文**《纳维-斯托克斯方程、流体力学与图像视频修复》**,采用流体动力学和偏微分方程方法。其基本原理是启发式的:首先沿着已知区域到未知区域的边缘行进(因为边缘具有连续性),通过保持等照度线(连接相同强度点的线条,类似等高线连接相同海拔点)的同时匹配修复区域边界的梯度向量。这一过程运用了流体动力学中的方法,获得这些信息后,通过填充颜色使该区域方差最小化。可通过标志位 cv.INPAINT_NS
启用该算法。
代码
我们需要创建一个与输入图像大小相同的掩码,其中非零像素对应需要修复的区域。其他部分都很简单。我的图像被一些黑色笔划(我手动添加的)破坏了。我使用画图工具创建了相应的笔划。
import numpy as np
import cv2 as cvimg = cv.imread('messi_2.jpg')
mask = cv.imread('mask2.png', cv.IMREAD_GRAYSCALE)dst = cv.inpaint(img,mask,3,cv.INPAINT_TELEA)cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()
查看下方结果。第一张图显示的是降质输入图像。第二张图是掩膜。第三张图是第一种算法的处理结果,最后一张图是第二种算法的处理结果。
补充资源
1、Bertalmio, Marcelo, Andrea L. Bertozzi, 和 Guillermo Sapiro. “Navier-stokes, fluid dynamics, and image and video inpainting.” 收录于《计算机视觉与模式识别,2001年CVPR会议论文集》,第1卷,第I-355页。IEEE,2001年。
2、Telea, Alexandru. “An image inpainting technique based on the fast marching method.” 《图形工具杂志》9.1 (2004): 23-34.
练习
1、OpenCV 自带了一个关于图像修复的交互式示例 samples/python/inpaint.py,请尝试运行它。
2、几个月前,我观看了一个关于 Content-Aware Fill 的视频,这是 Adobe Photoshop 中使用的一种高级图像修复技术。经过进一步搜索,我发现 GIMP 中也有相同的技术,只是名称不同,叫做 “Resynthesizer”(需要单独安装插件)。我相信你会喜欢这个技术。
生成于 2025年4月30日 星期三 23:08:43,由 doxygen 1.12.0 为 OpenCV 生成
高动态范围成像(HDR)
https://docs.opencv.org/4.x/d2/df0/tutorial_py_hdr.html
目标
在本章中,我们将:
- 学习如何从曝光序列生成并显示HDR图像。
- 使用曝光融合技术合并曝光序列。
理论基础
高动态范围成像(HDRI 或 HDR)是一种用于图像和摄影的技术,能够再现比标准数字成像或摄影技术更宽的亮度动态范围。虽然人眼可以适应各种光照条件,但大多数成像设备每个通道仅使用8位,因此我们被限制在256个亮度级别内。当我们拍摄真实场景时,明亮区域可能会过度曝光,而黑暗区域则可能曝光不足,因此无法通过单次曝光捕捉所有细节。HDR成像使用每通道超过8位(通常为32位浮点值)的图像,从而实现了更宽的动态范围。
获取HDR图像有多种方法,最常见的是使用不同曝光值拍摄同一场景的照片。为了合并这些曝光图像,了解相机的响应函数非常有用,并且有多种算法可以对其进行估计。HDR图像合并后,需要将其转换回8位才能在普通显示器上查看,这一过程称为色调映射。如果场景中的物体或相机在拍摄过程中移动,会带来额外的复杂性,因为不同曝光的图像需要进行配准和对齐。
本教程将展示两种算法(Debevec、Robertson),用于从曝光序列生成并显示HDR图像,同时介绍一种称为曝光融合(Mertens)的替代方法,该方法可直接生成低动态范围图像且无需曝光时间数据。此外,我们还将估计相机响应函数(CRF),这对许多计算机视觉算法具有重要价值。HDR流程的每个步骤都可以采用不同的算法和参数实现,具体可参考相关手册了解全部选项。
曝光序列HDR
本教程我们将分析以下场景,其中包含4张不同曝光的图像,曝光时间分别为:15秒、2.5秒、1/4秒和1/30秒。(您可以从Wikipedia下载这些图像)
1、将曝光图像加载到列表中
第一阶段只需将所有图像加载到一个列表中。此外,常规HDR算法还需要曝光时间参数。需特别注意数据类型:图像应为单通道或三通道8位格式(np.uint8),而曝光时间需为float32类型且以秒为单位。
import cv2 as cv
import numpy as np# Loading exposure images into a list
img_fn = ["img0.jpg", "img1.jpg", "img2.jpg", "img3.jpg"]
img_list = [cv.imread(fn) for fn in img_fn]
exposure_times = np.array([15.0, 2.5, 0.25, 0.0333], dtype=np.float32)
2、将曝光序列合并为HDR图像
在此阶段,我们将曝光序列合并为一张HDR图像,展示OpenCV中提供的两种方法。第一种是Debevec算法,第二种是Robertson算法。需要注意的是,HDR图像的类型是float32而非uint8,因为它包含了所有曝光图像的全部动态范围。
# Merge exposures to HDR image
merge_debevec = cv.createMergeDebevec()
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy())
merge_robertson = cv.createMergeRobertson()
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy())
3、色调映射HDR图像
我们将32位浮点HDR数据映射到[0…1]范围内。实际上,在某些情况下数值可能大于1或小于0,因此请注意后续需要对数据进行裁剪以避免溢出。
# Tonemap HDR image
tonemap1 = cv.createTonemap(gamma=2.2)
res_debevec = tonemap1.process(hdr_debevec.copy())
4、使用Mertens融合算法合并曝光图像
这里我们展示一种替代算法来合并曝光图像,该算法无需知道曝光时间。我们也不需要任何色调映射算法,因为Mertens算法直接给出了[0…1]范围内的结果。
# Exposure fusion using Mertens
merge_mertens = cv.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
5、转换为8位并保存
为了保存或显示结果,我们需要将数据转换为[0…255]范围内的8位整数。
# Convert datatype to 8-bit and save
res_debevec_8bit = np.clip(res_debevec*255, 0, 255).astype('uint8')
res_robertson_8bit = np.clip(res_robertson*255, 0, 255).astype('uint8')
res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')cv.imwrite("ldr_debevec.jpg", res_debevec_8bit)
cv.imwrite("ldr_robertson.jpg", res_robertson_8bit)
cv.imwrite("fusion_mertens.jpg", res_mertens_8bit)
结果
可以看到不同的效果,但请注意每个算法都有额外的参数需要调整才能获得理想结果。最佳实践是尝试不同方法,看看哪种最适合您的场景。
德贝维克方法:
罗伯逊方法:
Mertenes 融合算法:
估计相机响应函数
相机响应函数(CRF)建立了场景辐射度与测量强度值之间的关联关系。CRF在某些计算机视觉算法(包括HDR算法)中具有重要作用。本文我们将估计逆相机响应函数,并将其用于HDR图像合成。
# Estimate camera response function (CRF)
cal_debevec = cv.createCalibrateDebevec()
crf_debevec = cal_debevec.process(img_list, times=exposure_times)
hdr_debevec = merge_debevec.process(img_list, times=exposure_times.copy(), response=crf_debevec.copy())
cal_robertson = cv.createCalibrateRobertson()
crf_robertson = cal_robertson.process(img_list, times=exposure_times)
hdr_robertson = merge_robertson.process(img_list, times=exposure_times.copy(), response=crf_robertson.copy())
相机响应函数通过一个256维向量表示每个颜色通道。针对该序列,我们得到以下估计结果:
补充资源
1、Paul E Debevec 和 Jitendra Malik。从照片中恢复高动态范围辐射图。收录于 ACM SIGGRAPH 2008 课程,第 31 页。ACM,2008 年。[68]
2、Mark A Robertson、Sean Borman 和 Robert L Stevenson。通过多重曝光改善动态范围。收录于《图像处理,1999 年国际会议论文集》,第 3 卷,第 159–163 页。IEEE,1999 年。[228]
3、Tom Mertens、Jan Kautz 和 Frank Van Reeth。曝光融合。收录于《计算机图形与应用,2007 年太平洋会议》,第 382–390 页。IEEE,2007 年。[189
]
4、图片来自 Wikipedia-HDR
练习
- 尝试所有色调映射算法:
cv::TonemapDrago
、cv::TonemapMantiuk
和cv::TonemapReinhard
- 尝试修改HDR校准和色调映射方法中的参数
生成于 2025年4月30日 星期三 23:08:43 由 doxygen 1.12.0 生成