计算摄影学

图像去噪

理论

之前已经看到了许多图像平滑技术,例如高斯模糊,中值模糊等,它们在某种程度上可以消除少量噪声。在这些技术中,围绕一个像素取一个小邻域,并进行了一些操作,例如高斯加权平均值,值的中位数等来替换中心元素;简而言之,在像素处去除噪声是其邻域的局部现象。
通常认为噪声是零均值的随机变量。考虑一个有噪声的像素, p = p 0 + n p = p_0 + n p=p0+n,其中 p 0 p_0 p0是像素的真实值,n是该像素中的噪声。可以从不同的图像中获取大量相同的像素(例如N)并计算其平均值。理想情况下,由于噪声的平均值为零,因此应该得到 p = p 0 p = p_0 p=p0
可以通过简单的设置自己进行验证。将静态相机固定在某个位置几秒钟,这将提供很多帧或同一场景的很多图像;然后编写一段代码,找到视频中所有帧的平均值;比较最终结果和第一帧,会看到噪声减少。不幸的是,这种简单的方法对摄像机和场景的运动并不稳健;通常,只有一张嘈杂的图像可用。
因此想法很简单,需要一组相似的图像来平均噪声。考虑图像中的一个小窗口(例如5x5窗口)。很有可能同一修补程序可能位于图像中的其他位置。有时在它周围的一个小邻域中。一起使用这些相似的补丁并找到它们的平均值怎么办?对于那个特定的窗口,这很好。请参阅下面的示例图片:
在这里插入图片描述图像中的蓝色补丁看起来很相似。绿色补丁看起来很相似。因此,获取一个像素,在其周围获取一个小窗口,在图像中搜索相似的窗口,对所有窗口求平均,然后用得到的结果替换该像素。此方法是“非本地均值消噪”,与之前看到的模糊技术相比,它花费了更多时间,但是效果非常好。
对于彩色图像,图像将转换为CIELAB色彩空间,然后分别对L和AB分量进行降噪。

OpenCV中的图像去噪

OpenCV提供了此方法的四个变体:

  1. cv.fastNlMeansDenoising() 处理单个灰度图像
  2. cv.fastNlMeansDenoisingColored() 处理彩色图像。
  3. cv.fastNlMeansDenoisingMulti() 处理在短时间内捕获的图像序列(灰度图像)
  4. cv.fastNlMeansDenoisingColoredMulti() 与3相同,但用于彩色图像。
    常用参数为:
  • h:决定滤波器强度的参数。较高的h值可以更好地消除噪点,但同时也可以消除图像细节(可以设为10)。
  • hForColorComponents:与h相同,但仅用于彩色图像(通常与h相同)。
  • templateWindowSize:应为奇数(建议设为7)。
  • searchWindowSize:应为奇数(建议设为21)。

cv.fastNIMeansDenoisingColored()示例

它用于消除彩色图像中的噪点(噪声可能是高斯的)。
fastNIMeansDenoisingColored.py

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

img = cv.imread('./OpenCV/data/die.png')
dst = cv.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)
plt.subplot(121)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.subplot(122)
plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB))
plt.show()

输入图像的高斯噪声为 σ = 25 σ= 25 σ=25,查看结果:
在这里插入图片描述

cv.fastNlMeansDenoisingMulti() 示例

将对视频应用相同的方法。

  • 第一个参数是噪声帧列表。
  • 第二个参数imgToDenoiseIndex 指定需要去噪的帧;为此,在输入列表中传递帧的索引。
  • 第三个参数是temporalWindowSize,它指定要用于降噪的附近帧的数量。

这应该很奇怪,在这种情况下,总共使用temporalWindowSize帧,其中中心帧是要被去噪的帧。例如,传递了一个5帧的列表作为输入,令imgToDenoiseIndex = 2,temporalWindowSize =3;然后使用第 1 帧、第 2 帧和第 3 帧对第 2 帧进行降噪。
看下面示例fastNlMeansDenoisingMulti.py

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

cap = cv.VideoCapture('./OpenCV/data/vtest.avi')
# 创建5个帧的列表
img = [cap.read()[1] for i in range(5)]
# 将所有转化为灰度
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img]
# 将所有转化为float64
gray = [np.float64(i) for i in gray]
# 创建方差为25的噪声
noise = np.random.randn(*gray[1].shape)*10
# 在图像上添加噪声
noisy = [i + noise for i in gray]
# 转化为uint8
noisy = [np.uint8(np.clip(i, 0, 255)) for i in noisy]
# 对第三帧进行降噪
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)
images = [gray[2], noisy[2], dst]
titles = ['Original', 'Noisy', 'Denoisy']
for i in range (3):
    plt.subplot(1, 3, i + 1)
    plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述
计算需要花费大量时间。结果中第一个图像是原始帧,第二个是噪声帧,第三个是去噪图像。

图像修补

基础

大多数人家里都会有一些旧化的照片,上面有黑点,一些笔触等。你是否曾经想过将其还原?不能简单地在绘画工具中擦除它们,因为它将简单地用白色结构代替黑色结构,这是没有用的。在这些情况下,将使用一种称为图像修复的技术;基本思想很简单:用附近的像素替换那些不良区域,使其看起来和邻近的区域协调。考虑下面显示的图像(摘自Wikipedia):
在这里插入图片描述
基于此目的设计了几种算法,OpenCV提供了其中两种,两者都可以通过相同的函数cv.inpaint()进行访问。

  1. 第一种算法基于Alexandru Telea在2004年发表的论文“基于快速行进方法的图像修补技术”。它基于快速行进方法,考虑图像中要修复的区域;算法从该区域的边界开始,并进入该区域内部,首先逐渐填充边界中的所有内容;在要修复的邻域上的像素周围需要一个小的邻域;该像素被附近所有已知像素的归一化加权总和所代替。权重的选择很重要,那些位于该点附近,边界法线附近的像素和那些位于边界轮廓线上的像素将获得更大的权重。修复像素后,将使用快速行进方法将其移动到下一个最近的像素。FMM确保首先修复已知像素附近的那些像素,以便像手动启发式操作一样工作。通过使用标志cv.INPAINT_TELEA启用此算法。
  2. 第二种算法基于Bertalmio,Marcelo,Andrea L. Bertozzi和Guillermo Sapiro在2001年发表的论文“ Navier-Stokes,流体动力学以及图像和视频修补”。该算法基于流体动力学并利用了 偏微分方程,基本原理是启发式的。它首先沿着边缘从已知区域移动到未知区域(因为边缘是连续的);它延续了等距线(线连接具有相同强度的点,就像轮廓线连接具有相同高程的点一样),同时在修复区域的边界匹配梯度矢量;为此,使用了一些流体动力学方法。获得它们后,将填充颜色以减少该区域的最小差异。通过使用标志cv.INPAINT_NS启用此算法。

有关修复的交互式示例

在OpenCV中,samples/python/inpaint.py示例

from __future__ import print_function
import cv2 as cv
import numpy as np
from common import Sketcher

def main():
    import sys
    try:
        fn = sys.argv[1]
    except:
        fn = './OpenCV/data/messi.png'

    img = cv.imread(cv.samples.findFile(fn))
    if img is None:
        print('Failed to load image file:', fn)
        sys.exit(1)

    img_mark = img.copy()
    mark = np.zeros(img.shape[:2], np.uint8)
    sketch = Sketcher('img', [img_mark, mark], lambda:((255, 255, 255), 255))

    while True:
        ch = cv.waitKey()
        if ch == 27:
            break
        if ch == ord('1'):
            res = cv.inpaint(img_mark, mark, 3, cv.INPAINT_TELEA)
            cv.imshow('inpaint1', res)
        if ch == ord('2'):
            res = cv.inpaint(img_mark, mark, 3, cv.INPAINT_NS)
            cv.imshow('inpaint2', res)
        if ch == ord('r'):
            img_mark[:] = img
            mark[:] = 0
            sketch.show()

    print('Done')

if __name__ == '__main__':
    print(__doc__)
    main()
    cv.destroyAllWindows()

在这里插入图片描述
在img窗口中,使用鼠标在要修复处涂画线条后,按1键使用cv.INPAINT_TELEA生成inpaint1窗口显示处理后的图片,按2键使用cv.INPAINT_NS生成inpaint2窗口显示处理后的图片,按r键重新涂画,按esc键离开窗口。
出现错误:

 from common import Sketcher
ImportError: cannot import name 'Sketcher' from 'common' (D:\Anaconda\lib\site-packages\common\__init__.py)

需在OpenCV中找到samples/python/common.py,将common.py放入inpaint.py同一文件夹下即可。

高动态范围

理论

高动态范围成像(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. 将曝光图像加载到列表中。
  2. 将曝光合成HDR图像。在此阶段,将曝光序列合并为一张HDR图像,显示了OpenCV中的两种可能性。 第一种方法是Debevec,第二种方法是Robertson。 请注意,HDR图像的类型为float32,而不是uint8,因为它包含所有曝光图像的完整动态范围。
  3. 色调图HDR图像。将32位浮点HDR数据映射到[0…1]范围内。实际上,在某些情况下,该值可以大于1或小于0,因此请注意,稍后将必须裁剪数据以避免溢出。
  4. 使用Mertens融合曝光。在这里展示了一种替代算法,用于合并曝光图像,而不需要曝光时间。也不需要使用任何色调映射算法,因为Mertens算法已经为我们提供了[0…1]范围内的结果。
  5. 转为8-bit并保存。为了保存或显示结果,需要将数据转换为[0…255]范围内的8位整数。
    hdr.py
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 将曝光图像加载到列表中
img_fn = ['./OpenCV/data/img' + str(i) + '.png' for i in range(4)]
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)
# 将曝光合成HDR图像
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())
# 色调图HDR图像
tonemap1 = cv.createTonemap(gamma=2.2)
res_debevec = tonemap1.process(hdr_debevec.copy())
tonemap2 = cv.createTonemap(gamma=6.6)
res_robertson = tonemap2.process(hdr_robertson.copy())
# 使用Mertens融合曝光
merge_mertens = cv.createMergeMertens()
res_mertens = merge_mertens.process(img_list)
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')
images = [res_debevec_8bit, res_robertson_8bit, res_mertens_8bit]
titles= ['ldr_debevec', 'ldr_robertson', 'fusion_mertens']
for i in range(3):
    plt.subplot(1, 3, i + 1)
    plt.imshow(cv.cvtColor(images[i], cv.COLOR_BGR2RGB))
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()
cv.imwrite('./OpenCV/data/ldr_debevec.jpg', res_debevec_8bit)
cv.imwrite('./OpenCV/data/ldr_robertson.jpg', res_robertson_8bit)
cv.imwrite('./OpenCV/data/fusion_mertens.jpg', res_mertens_8bit)

在这里插入图片描述

估计相机响应函数

摄像机响应功能(CRF)可以将场景辐射度与测量强度值联系起来。CRF在某些计算机视觉算法(包括HDR算法)中非常重要。在这里,估计逆相机响应函数并将其用于HDR合并。在hdr.py中添加如下代码:

# 估计相机响应函数(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())
x = np.arange(256)
plt.subplot(121)
plt.plot(x, crf_debevec[:, 0, 0], c='r')
plt.plot(x, crf_debevec[:, 0, 1], c='g')
plt.plot(x, crf_debevec[:, 0, 2], c='b')
plt.ylim(0, 35)
plt.title('Debvec Inverse Camera Response Function')
plt.xlabel('Measured Intensity'), plt.ylabel('Calibrated Intensity')
plt.subplot(122)
plt.plot(x, crf_robertson[:, 0, 0], c='r')
plt.plot(x, crf_robertson[:, 0, 1], c='g')
plt.plot(x, crf_robertson[:, 0, 2], c='b')
plt.ylim(0, 10)
plt.title('Robertson Inverse Camera Response Function')
plt.xlabel('Measured Intensity'), plt.ylabel('Calibrated Intensity')
plt.show()

相机响应功能由每个颜色通道的256长度向量表示。 对于此序列,得到以下估计:
在这里插入图片描述

学习来源:OpenCV-Python中文文档
OpenCV55: 高动态范围成像|High Dynamic Range (HDR)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值