<路径 clip-rule="evenodd" d="M33.377 4.574a3.508 3.508 0 0 0-2.633-1.126c-1 0-1.993.67-2.604 1.334l.002-1.24-1.867-.002-.02 10.17v.133l1.877.002.008-3.18c.567.611 1.464.97 2.462.973 1.099 0 2.022-.377 2.747-1.117.73-.745 1.1-1.796 1.103-3.002.003-1.232-.358-2.222-1.075-2.945Zm-3.082.55c.637 0 1.176.23 1.602.683.438.438.663 1.012.66 1.707-.003.7-.22 1.33-.668 1.787-.428.438-.964.661-1.601.661-.627 0-1.15-.22-1.6-.666-.445-.46-.662-1.086-.662-1.789.003-.695.227-1.27.668-1.708a2.13 2.13 0 0 1 1.596-.675h.005Zm5.109-.067-.008 4.291c-.002.926.263 1.587.784 1.963.325.235.738.354 1.228.354.376 0 .967-.146.967-.146l-.168-1.564s-.43.133-.64-.01c-.198-.136-.296-.428-.296-.866l.008-4.022 1.738.002.002-1.492-1.738-.002.005-2.144-1.874-.002-.005 2.143-1.573-.002 1.57 1.497ZM20.016 1.305h-9.245l-.002 1.777h3.695l-.016 8.295v.164l1.955.002-.008-8.459 3.621-.002V1.305Z" fill="#262D3D" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M10.06 5.844 7.277 3.166 4.015.03 2.609 1.374l2.056 1.978-4.51 4.313 6.065 5.831 1.387-1.327-2.073-1.994 4.526-4.331ZM4.274 8.7a.211.211 0 0 1-.124 0c-.04-.013-.074-.03-.15-.102l-.817-.787c-.072-.069-.092-.104-.105-.143a.187.187 0 0 1 0-.12c.013-.039.03-.07.105-.143L5.76 4.938c.072-.07.108-.09.15-.099a.21.21 0 0 1 .123 0c.041.012.075.03.15.101L7 5.727c.072.07.093.104.103.144.013.04.013.08 0 .119-.013.04-.03.072-.106.143L4.422 8.601a.325.325 0 0 1-.147.099Z" fill="#204ECF" fill-rule="evenodd"><路径 clip-rule="evenodd" d="M24.354 4.622a3.94 3.94 0 0 0-2.876-1.149 4.1 4.1 0 0 0-2.829 1.084c-.804.725-1.214 1.733-1.217 2.992-.002 1.26.405 2.267 1.207 2.995a4.114 4.114 0 0 0 2.832 1.094c.04.002.082.002.123.002a3.967 3.967 0 0 0 2.75-1.138c.538-.532 1.183-1.473 1.186-2.938.002-1.465-.637-2.408-1.176-2.942Zm-.59 2.94c-.003.73-.228 1.334-.671 1.794-.441.458-.99.69-1.633.69a2.166 2.166 0 0 1-1.614-.697c-.43-.45-.65-1.057-.65-1.797s.222-1.344.655-1.795a2.17 2.17 0 0 1 1.617-.69c.64 0 1.189.235 1.63.698.443.46.668 1.064.665 1.797ZM41.15 6.324c0-.458.25-1.465 1.632-1.465.49 0 .768.159 1.003.347.227.18.34.626.34.994v.174l-2.282.341C40.035 6.98 39 7.913 38.993 9.28c-.002.708.266 1.314.777 1.76.503.438 1.191.67 2.004.673 1.023 0 1.792-.354 2.341-1.084.003.31.003.621.003.91h1.903l.013-5.246c.002-.856-.289-1.685-.864-2.14-.567-.449-1.31-.679-2.386-.681h-.015c-.82 0-1.69.208-2.274.695-.689.572-1.027 1.478-1.027 2.178l1.682-.02Zm.864 3.814c-.676-.002-1.115-.371-1.112-.938.003-.589.43-.933 1.346-1.081l1.875-.305v.017c-.005 1.36-.87 2.307-2.102 2.307h-.008Zm4.917-8.712-.018 10.058v.044l1.684.005.018-10.06v-.045l-1.684-.002Zm2.654 9.491c0-.173.062-.322.19-.445a.645.645 0 0 1 .462-.186c.18 0 .338.062.465.186a.596.596 0 0 1 .193.445.583.583 0 0 1-.193.443.644.644 0 0 1-.465.183.634.634 0 0 1-.461-.183.59.59 0 0 1-.191-.443Zm.108 0c0 .146.052.273.158.376a.54.54 0 0 0 .389.154.539.539 0 0 0 .547-.53.498.498 0 0 0-.16-.373.531.531 0 0 0-.387-.156.531.531 0 0 0-.387.155.497.497 0 0 0-.16.374Zm.702.344-.176-.3h-.118v.3h-.109v-.688h.292c.144 0 .23.082.23.196 0 .096-.076.168-.176.188l.178.304h-.121Zm-.294-.596v.21h.167c.093 0 .14-.034.14-.104 0-.072-.047-.106-.14-.106h-.167Z" fill="#262D3D" fill-rule="evenodd">作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

拉杜Balaban

Radu是一位资深的问题解决者,拥有超过15年的行业经验. 他目前专注于机器学习.

专业知识

以前在

节奏

计算摄影就是用计算来增强摄影过程. 虽然我们通常倾向于认为这只适用于后期处理的最终结果(类似于照片编辑), 由于计算可以在摄影过程的每个步骤中启用,因此可能性要丰富得多——从场景照明开始, 继续看镜头, 最后甚至是在拍摄图像的显示上.

这是很重要的,因为它允许做更多,以不同的方式比什么可以实现与普通相机. 这也很重要,因为现在最流行的相机——移动相机——与它的兄弟(数码单反相机)相比并不是特别强大。, 然而,它通过利用设备上可用的计算能力,成功地完成了这项工作.

我们将看两个例子,其中计算可以更准确地增强摄影, 我们将看到如何简单地拍摄更多的照片,并使用一点Python来组合它们,可以在移动相机硬件不真正发光的两种情况下创造出不错的结果-低光和高动态范围.

光线暗的摄影

假设我们想要拍摄一个场景的弱光照片, 但这款相机的光圈(镜头)很小,曝光时间有限. 这是手机摄像头的典型情况, 给定一个低光场景, 可以产生这样的图像(用iPhone 6相机拍摄):

一对玩具在弱光环境下的图像

如果我们尝试提高对比度,结果如下,这也是相当糟糕的:

与上图相同的图像,更明亮,但有一个分散注意力的视觉噪音

会发生什么? 这些噪音是从哪里来的?

答案是噪音来自传感器——这个装置试图确定光线何时照射到它以及光线的强度. 在弱光下, 然而, 它必须大大提高灵敏度才能记录任何东西, 高灵敏度意味着它也开始检测假阳性——根本不存在的光子. (作为旁注, 此问题不仅影响设备, 还有我们人类:下次你在一个黑暗的房间里, 花点时间注意一下出现在你视野中的噪音.)

Some amount of noise will always be present in an imaging device; 然而, 如果信号(有用信息)强度高, 噪声可以忽略不计(高信噪比). 当信号较弱时,例如在弱光下,噪声就会突出(低信噪比)。.

仍然, 我们可以克服噪音问题, 即使有摄影机的限制, 为了得到比上面更好的镜头.

要做到这一点, 我们需要考虑到随着时间的推移会发生什么:信号将保持不变(同样的场景,我们假设它是静态的),而噪声将完全随机. 这意味着, 如果我们多拍几张照片, 它们会有不同版本的噪音, 但是同样有用的信息.

So, 如果我们取一段时间内拍摄的许多图像的平均值, 噪音将被抵消,而信号将不受影响.

下面的插图显示了一个简化的例子:我们有一个受噪声影响的信号(三角形), 我们试图通过对受不同噪声影响的相同信号的多个实例取平均值来恢复信号.

三角形的四面板演示, 一个带有附加噪声的代表三角形的散射图像, 一种代表50个实例平均值的锯齿三角形, 1000个实例的平均值, 和原来的三角形几乎一样.

我们看到了, 尽管在任何情况下,噪声都足以完全扭曲信号, 通过平均逐步降低噪声,恢复原始信号.

让我们看看这个原则如何应用于图像:首先, 我们需要在相机允许的最大曝光范围内为多张拍摄对象的照片. 为了获得最佳效果,请使用允许手动拍摄的应用程序. 重要的是要在同一个地方拍摄,所以一个(临时的)三脚架会有帮助.

更多的镜头通常意味着更好的质量, 但确切的数字取决于具体情况:有多少光, 这相机多灵敏啊, 等. 一个好的范围是在10到100之间.

一旦我们有了这些图像(如果可能的话是原始格式),我们就可以在Python中读取和处理它们.

对于那些不熟悉Python图像处理的人, 我们应该提到,图像是用字节值(0-255)的2D数组表示的, 单色或灰度图像. 一个彩色图像可以被认为是三个这样的图像的集合, 每个颜色通道(R, G, B), 或者实际上是根据垂直位置索引的3D数组, 水平位置和颜色通道(0, 1, 2).

我们将使用两个库:NumPy (http://www.numpy.org/)和OpenCV (http://opencv.org/). 第一个允许我们非常有效地在数组上执行计算(使用令人惊讶的短代码), 而OpenCV在这种情况下处理图像文件的读/写, 但更有能力, 提供许多高级图形过程—其中一些将在本文后面使用.

进口操作系统
导入numpy为np
进口cv2

文件夹= 'source_folder'

我们从源文件夹中获取所有的图像文件
文件= list([os ..路径.Join (folder, f) for f在OS中.listdir(文件夹)])

我们通过把图像加起来计算平均值
#从显式设置为浮点数开始,以便强制
#从图像中转换8位值,否则会溢出
平均= cv2.imread(文件[0]).astype (np.浮动)
For files中的file [1:]:
    Image = cv2.imread(文件)
    # NumPy添加两个图像元素,像素一个像素/通道一个通道
    平均值+=图像
 
# Divide by count(再次划分每个像素/通道)
平均/= len(文件)

#正常化图像,将像素强度分散到0..255
#这将使图像变亮而不丢失信息
输出= cv2.normalize(average, None, 0,255, cv2 ..NORM_MINMAX)

#保存输出
cv2.imwrite(“输出.png”,输出)

结果(应用了自动对比度)显示噪声消失了, 比原始图像有了很大的改进.

玩具的原始照片, 这一次既明亮又清晰得多, 几乎听不到噪音

然而,我们仍然注意到一些奇怪的人工制品,如绿色的框架和网格状的图案. 这一次,它不是一个随机的噪音,而是一个固定模式的噪音. 发生了什么事?

上图左上角的特写

左上角的特写,显示绿色的框架和网格图案

同样,我们可以把它归咎于传感器. 在这种情况下, 我们看到传感器的不同部分对光的反应不同, 形成一个可见的图案. 这些图案中的一些元素是规则的,很可能与传感器衬底(金属/硅)以及它如何反射/吸收入射光子有关. 其他元素, 比如白色像素, 仅仅是传感器像素有缺陷吗, 哪些可能对光过于敏感或过于不敏感.

幸运的是,也有一种方法可以消除这种噪音. 它叫做 暗帧减法.

要做到这一点, 我们需要一个图案噪声本身的图像, 如果我们拍摄黑暗,就可以得到这个. 是的, 没错,只要盖上相机孔,用最大曝光时间和ISO值拍很多照片(比如100张), 并按上述方法处理它们.

当对许多黑色帧(实际上不是黑色的)进行平均时, 由于随机噪声),我们将以固定模式噪声结束. 我们可以假设这个固定的噪声将保持不变, 所以这一步只需要一次:生成的图像可以在未来所有的弱光拍摄中重复使用.

下图是iPhone 6右上方的图案噪点(对比度调整后):

前一图像中显示的帧部分的图案噪声

再一次,我们注意到网格状的纹理,甚至看起来是一个卡住的白色像素.

一旦我们有了这个暗帧噪声的值(在 average_noise 变量),在归一化之前,我们可以简单地从目前的镜头中减去它:

Average -=平均值噪声

输出= cv2.normalize(average, None, 0,255, cv2 ..NORM_MINMAX)
cv2.imwrite(“输出.png”,输出)

这是我们的最后一张照片:

这张照片的另一张照片,这次绝对没有证据表明是在弱光下拍摄的

高动态范围

小型(移动)相机的另一个限制是它的动态范围小, 这意味着它能捕捉细节的光强范围相当小.

换句话说, the camera is able to capture only a narrow band of the light intensities from a scene; the intensities below that band appear as pure black, 而它上面的强度看起来是纯白色的, 这些区域的任何细节都丢失了.

然而, 相机(或摄影师)可以使用一个技巧,那就是调整曝光时间(传感器暴露在光线下的时间),以控制到达传感器的光总量, 有效地上下移动范围,以便捕捉给定场景的最合适范围.

但这是一种妥协. 很多细节都没有出现在最后的照片中. 在下面的两张图片中, 我们看到用不同曝光时间拍摄的相同场景:非常短的曝光(1/1000秒), 中曝光(1/50秒)和长曝光(1/4秒).

同一幅花的三个版本, 它太黑了,以至于照片的大部分都是黑色的, 一个普通, 虽然灯光有点不太好, 第三张照片的光线调得很高,以至于很难看到前景中的花朵

如你所见, 这三张图片都不能捕捉到所有的细节:灯丝只在第一张照片中可见, 一些花的细节在中间或最后一个镜头中可见,但不是两者都可见.

好消息是,我们可以对此做些什么, 同样,它涉及到用一些Python代码构建多个镜头.

我们将采用的方法是基于Paul Debevec等人的工作.他在论文中描述了这种方法 在这里. 该方法是这样工作的:

首先,它需要用不同的曝光时间拍摄同一场景(静止)的多个镜头. 再一次。, 和前面的例子一样, 我们需要一个三脚架或支架来确保相机不会移动. 我们还需要一个手动拍摄应用程序(如果使用手机),这样我们就可以控制曝光时间,防止相机自动调整. 所需的拍摄次数取决于图像中存在的强度范围(从三次起)。, 曝光时间应该在这个范围内间隔,这样我们想要保留的细节至少可以在一张照片中清晰地显示出来.

下一个, 采用一种基于不同曝光时间内相同像素的颜色重构相机响应曲线的算法. 这基本上让我们建立一个地图之间的真实场景的亮度点, 曝光时间, 以及相应像素在捕获图像中所具有的值. 我们将使用来自OpenCV库的Debevec方法的实现.

#用OpenCV读取所有文件
文件= ['1 . '.jpg”、“2.jpg ', ' 3.jpg ', ' 4.jpg”、“5.jpg ']
Images = list([cv2 ..Imread (f) for f in files []
#以秒为单位计算曝光时间
暴露= np.float32 ([1. / t表示[1000,500,100,50,10]])

#计算响应曲线
校准= cv2.createCalibrateDebevec ()
响应=校准.过程(图片,曝光)

响应曲线看起来是这样的:

将响应曲线显示为像素曝光(对数)除以像素值的图形

在纵轴上, 我们有一个点的场景亮度和曝光时间的累积效应, 而在水平轴上,我们有相应像素的值(每个通道0到255).

这条曲线允许我们执行反向操作(这是该过程的下一步)-给定像素值和曝光时间, 我们可以计算场景中每个点的真实亮度. 这个亮度值称为辐照度, 它测量落在传感器单位面积上的光能. 不像图像数据, 它使用浮点数表示,因为它反映了更广泛的值范围(因此, 高动态范围). 一旦我们有了辐照度图像(HDR图像),我们可以简单地保存它:

#计算HDR图像
合并= cv2.createMergeDebevec ()
合并.处理(图像、曝光、响应)

#保存到磁盘
cv2.imwrite(“hdr_image.hdr, hdr)

对于我们这些有幸拥有HDR显示器的人(HDR显示器正变得越来越普遍), 也许可以直接想象出这幅图像的辉煌. 不幸的是, HDR标准仍处于起步阶段, 因此,不同的显示器的处理过程可能有所不同.

对我们其他人来说, 好消息是,我们仍然可以利用这些数据, 虽然普通显示要求图像具有字节值(0-255)通道. 虽然我们需要放弃一些丰富的辐照度地图, 至少我们可以控制如何去做.

这个过程叫做 贴图色调 它涉及到将浮点辐照度图(具有高范围的值)转换为标准字节值图像. 有一些技术可以做到这一点,以便保留许多额外的细节. 给大家举个例子, 想象一下,在我们将浮点范围压缩成字节值之前, 我们增强(锐化)存在于HDR图像中的边缘. 增强这些边缘将有助于在低动态范围图像中保留它们(以及隐含的它们提供的细节).

OpenCV提供了一组这样的音调映射操作符,如Drago、Durand、Mantiuk或Reinhardt. 下面是如何使用这些运算符之一(Durand)以及它产生的结果的示例.

Durand = cv2.createTonemapDurand(γ= 2.5)
LDR = durand.过程(hdr)

# Tonemap操作符创建值为0的浮点图像..1范围内
#这就是为什么我们在保存之前将图像乘以255
cv2.imwrite(“durand_image.Png ', LDR * 255)

上述计算的结果显示为图像

如果需要对过程进行更多的控制,还可以使用Python创建自己的操作符. 例如, 这是使用自定义运算符获得的结果,该运算符在将值范围缩小到8位之前移除以很少像素表示的强度(随后是自动对比度步骤):

按照上述过程生成的图像

下面是上述运算符的代码:

def countTonemap(hdr, min_fraction=0.0005):
	计数,范围= np.直方图(hdr, 256)
	Min_count = min_fraction * HDR.大小
	Delta_range = ranges[1] - ranges[0]

	图像= HDR.副本()
	For I in range(len(counts)):
    	if counts[i] < min_count:
        	image[image >= ranges[i + 1]] -= delta_range
        	range -= delta_range

	返回cv2.normalize(image, None, 0,1, cv2.NORM_MINMAX)

结论

我们已经看到了如何使用一些Python和一些支持库, 为了提高最终效果,我们可以突破物理相机的极限. 我们讨论的两个例子都使用了多个低质量的镜头来创造更好的东西, 但是对于不同的问题和限制,还有许多其他的方法.

虽然许多照相手机都有存储或内置的应用程序来解决这些特殊的例子, 显然,手工编写这些程序并享受可以获得的更高层次的控制和理解并不困难.

如果您对移动设备上的图像计算感兴趣,请查看 OpenCV教程:在iOS中使用MSER进行实时对象检测 同为顶级精英的人 OpenCV开发人员 Altaibayar Tseveenbayar.

了解基本知识

  • 相机的传感器是如何工作的?

    传感器将入射光转换成电信号, 然后被放大和数字化. 它的表面被划分为像素,像素进一步被划分为通道. 因此, 传感器产生三个对应红色的数字, 场景中每个点的绿色和蓝色光强度.

  • 图像处理的含义是什么?

    图像处理是指在传感器捕获原始图像后所采取的任何计算步骤, 为了增强或修改它.

  • 图像处理是如何工作的?

    图像处理在软件中通过对图像数据进行数值运算来完成. 大多数常见的图像处理技术都有坚实的数学背景.

  • 什么是Python, NumPy和OpenCV?

    Python是一种非常适合科学计算的编程语言. NumPy是一个Python库,它简化了对数组的数值操作. OpenCV是一个专门的库,专注于图像处理和计算机视觉.

  • 为什么光线很弱的照片会有噪音?

    在弱光和有限曝光下, 有非常少量的光能落在传感器上. 传感器试图通过放大信号来进行补偿, 但最终也放大了自己的电子噪音.

  • 什么是HDR摄影?

    高动态范围(HDR)摄影是指更准确地捕捉场景中的物理光强度. 传统的数码摄影只使用少量的亮度等级(通常是256)。.

  • 什么是音调映射?

    色调映射是将HDR图像转换为常规图像的技术, 保留大部分细节,使图像可以在非hdr显示器上显示.

聘请Toptal这方面的专家.
现在雇佣
拉杜Balaban

位于 巴亚马雷,马拉穆鲁斯县,罗马尼亚

成员自 2017年7月11日

作者简介

Radu是一位资深的问题解决者,拥有超过15年的行业经验. 他目前专注于机器学习.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

专业知识

以前在

节奏

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

世界级的文章,每周发一次.

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.