ImageMagick 示例 —— 镜头校正
- 基本方法
- 使用 Hugin 确定镜头参数集
- 定义点集
-
镜头畸变校正(PDF) 拍摄照片时,生成的图像实际上会同时受到镜头效应和球面透视效应的影响而产生畸变。如果打算使用照片,通常需要校正这些效应,本节讨论的正是这一点。本页的大部分内容由 Wolfgang Hugemann 贡献。
镜头校正简介
鱼眼镜头和低价广角镜头(或者说设置为短焦距的变焦镜头)通常会产生明显的桶形畸变。不过,通过对数码照片施加合适的算法变换,这种畸变大多可以得到校正。最常用的镜头校正算法之一由 Panorama Tools 引入并被 PTlens 采用,ImageMagick 也以 桶形校正畸变法(Barrel Correction Distortion Method) 的形式提供了它。在这一处理思路中,畸变由四个变换参数 a, b, c, d 控制,为了校正特定镜头(或者说设置为某一焦距的变焦相机)产生的畸变,必须合理地选择这些参数。这些参数的合适取值几乎无法靠反复试错找到。下面,我们介绍如何借助 Hugin 有效地确定该模型的镜头校正参数;Hugin 是 Panorama Tools 的一个免费图形用户界面,可在多种操作系统上使用。如果你不想深入镜头校正的细节,可以跳过本页的其余部分,直接购买 PTlens——它(借助庞大的镜头数据库)以合理的价格为数量众多的数码相机和镜头提供了高级的镜头校正。如今,一些数码相机(例如 Nikon P7000)甚至在其内部的图像处理步骤中集成了镜头校正。对于用不具备此功能的相机拍摄的照片,ImageMagick 可以让你把镜头校正作为更大的图像处理脚本中的一个步骤加以整合。以下文字是论文 镜头畸变校正(PDF)(讨论其在事故重建中的应用)的删节版。这里给出的说明更偏重实操,聚焦于获取适当镜头校正参数的方法。
非缩放约束
正如 桶形畸变 中所述,桶形畸变由以下数学公式定义:
R = ( a * r^3 + b * r^2 + c * r + d ) * r
其中 r 是到数码照片几何图像中心的距离,R 是原始图像中的等效半径。与此类映射一贯的情形一样,上面的方程定义了一种“颜色查找函数”,即到哪里去寻找半径 r 处像素的颜色。半径 r 和 R 以图像较短一边的一半(即通常是图像的高度)进行归一化,使得图像上下边缘的中点处 r = R = 1。校正数码照片时,我们应当注意非缩放约束
a + b + c + d = 1
显然,当 r = 1 时它给出 R = 1。Panorama Tools 通过以下公式由其他参数计算参数 d:
d = 1 - a - b - c
这样就只剩下三个自由的模型参数,因此参数 d 通常被省略。如果省略了 d,ImageMagick 会依据非缩放约束自动计算它。所以,一条用于镜头校正的典型 ImageMagick 命令行大致如下所示
magick input.jpg -distort barrel '0.06335 -0.18432 -0.13008' output.jpg
从而把 d 的计算交给 ImageMagick。我们这里所说的 Panorama Tools 镜头校正方法假定镜头的光轴与图像的中心重合,而实际上(由于制造公差)这并不严格成立。此外,它还忽略了诸如 胡须畸变(mustache distortion) 之类的效应。尽管如此,它在实践中似乎工作得精确得惊人。
正如曲线(a = 0.05, b = -0.25, c = 0.05)所示,这一关系通常用于 0 到 1.5 的范围(宽高比 3:2),经过点 (0,0) 和 (1,1),并且在 r > 1 时必须是递减的。
现成的参数集
PTlens 当前的镜头数据库作为程序的“精髓”被加密,只能由 PTlens 自身读取。不过,在 2006 年 2 月之前,PTlens 的数据库以 XML 格式编码,即一种易于编辑的文本格式。这一 2006 年版的 PTlens XML 数据库至今仍(合法地)可在 Hugin 的 SourceForge 网站 上获取,并为许多较旧的相机型号提供了数据。当 PTlens 的数据库被加密后,Hugin 的作者们试图建立一个免费的 XML 编码镜头数据库作为替代。这个数据库名为 LensFun,可供下载。它附带完整的编程接口,但基本上你所需要的只是 XML 文件中关于你相机的信息。举例来说,曾经流行的 Nikon Coolpix 995 的镜头校正参数可在文件 compact-nikon.xml 中找到,该文件位于目录 \data\db 中。可以用文本编辑器或 XML 查看器查看此文件:
<lens>
<maker>Nikon</maker>
<model>Standard</model>
<mount>nikon995</mount>
<cropfactor>4.843</cropfactor>
<calibration>
<distortion model="ptlens" focal="8.2" a="0" b="-0.019966" c="0" />
<distortion model="ptlens" focal="10.1" a="0" b="-0.010931" c="0" />
<distortion model="ptlens" focal="13.6" a="0" b="-0.002049" c="0" />
<distortion model="ptlens" focal="18.4" a="0" b="0.003845" c="0" />
<distortion model="ptlens" focal="23.4" a="0" b="0.006884" c="0" />
<distortion model="ptlens" focal="28.3" a="0" b="0.008666" c="0" />
<distortion model="ptlens" focal="31" a="0" b="0.009298" c="0" />
</calibration>
</lens>
正如从相机的技术数据表中可以看到的,Nikon Coolpix 995 的变焦范围为 8.2 – 31.0 mm,相当于 35 mm 胶片相机的 38 – 152 mm。由此得到 152 / 31 = 4.90 的裁切系数,这大致对应于 XML 文件中给出的 4.843。桶形畸变校正的系数针对六个焦距给出,即 8.2 mm、10.1 mm、13.6 mm、18.4 mm、23.4 mm、28.3 mm 和 31.0 mm。对于这枚镜头,系数 a 和 c 被设为零,也就是说畸变仅由二阶项 b 描述。请注意,许多镜头的 a 和 c 参数也会有取值,这些参数同样应以类似方式进行插值。如果我们有一张用设置为最短焦距的 Nikon Coolpix 995 拍摄的照片 DSCN0001.jpg,可以用 ImageMagick 这样校正它
magick DSCN0001.jpg -distort barrel '0.0 -0.019966 0.0' DSCN0001_pt.jpg
(文件名扩展 _pt 是 PTlens 用来标记已校正图像的。)对于所提供的六个焦距,校正系数 b 可从 XML 文件中读取。对于其他焦距,可通过在两个相邻焦距之间插值来确定合适的值。作为替代,b 对焦距 f 的依赖关系可用以下多项式近似
b = 0.000005142 * f^3 - 0.000380839 * f^2 + 0.009606325 * f - 0.075316854
因此,第一步先用(从 EXIF 信息读取的)焦距来计算镜头校正参数 b,然后第二步再以该值作为 b 参数执行镜头校正(即桶形畸变)。Windows 一节展示了一个 VBScript 示例,其中用到了上述方程,焦距通过 magick identify 从一张 Nikon Coolpix 995 照片中提取。
从零开始校准
基本方法
在确定镜头参数时,所有程序都依赖同一种范式:理想的透视映射应当把现实世界中的直线映射为图像中的直线。因此,如果已知一组现实世界的点 P0, P1, ..., Pn 位于一条直线上,那么它们的像 p0, p1, ..., pn 也必须落在一条直线上。任何偏离此规则的现象都必须归因于镜头畸变。要确定定义一条直线的两个参数(例如斜率和 y 轴截距),我们需要两个点。每多提供一个点,就会给出另一个用于确定镜头校正参数的方程。因此,如果我们的函数式方法只有一个自由参数 b(如上文的 Nikon Coolpix 995),那么为了确定所求的镜头校正参数 b,就必须在一条现实世界的直线及其像上提供至少三个点。更具体地说:畸变模型只使用参数 b,也就是说,校正后图像的坐标 X1, Y1 可由数码照片的坐标按下式计算
r = s * sqrt(x1^2 + y1^2)
X1 = [(1-b) + b r^2] * x1
Y1 = [(1-b) + b r^2] * y1
Y1 = k1 * X1 + k2
对于同一条直线上提供的每个点,这都会得到一个方程
[(1-b) + b r^2] * y1 = k1 * [(1-b) + b r^2] * x1 + k2
with: r = s * sqrt(x1^2 + y1^2)
因此,三个现实世界的点及其对应的像点就足以确定描述直线和镜头畸变的参数 k1, k2, b。实际上,现实世界点的坐标很少是已知的,因此需要多于三个点才能确定所求的参数。大多数校准软件使用由直线构成的矩形网格(常为棋盘格)来生成一组方程,然后通过非线性最小二乘拟合计算映射参数。有些程序自行生成控制点集,通常使用预定义的模板;另一些程序则要求用户从校准图像中选取控制点。
使用 Hugin 确定镜头参数集
下面,我们将演示如何借助 Hugin 确定一组镜头校正参数。Hugin 的网站上也有一份现成的“Simple Lens Calibration Tutorial”,但在撰写本文时(2014 年),它似乎过于简单,无法提供日后可用于大量校正的可靠参数。首先,你需要弄到一个合适的测试图案。基本上,一个约 10 × 7 个方格、印在 ISO 216 A3 或类似纸张上的棋盘格图案就足够了,也是常用的做法。不过,低价变焦镜头(所谓的 可变焦镜头)在校准时应设为无限远对焦,因为在近距离对焦时,它们的真实焦距可能与 EXIF 中嵌入的值相差很大。对于定焦镜头,你同样可以使用棋盘格测试图案,这在校准鱼眼镜头时尤为可取,因为可能很难找到一个大到足以覆盖其视场的现实世界物体。所以,尤其是在校准变焦镜头/变焦相机时,你不如按照 PTlens 网站 所建议的那样,拍摄一张现代建筑的照片。请按照那里给出的说明操作。照片可能会呈现透视畸变:
启动 Hugin,在第一个标签页上点击「Add images ...」按钮,打开校准图像。(Hugin 界面的截图可参见 hugin.sourceforge.net。)在该标签页的底部,把「Optimise」设为「Custom parameters」(这会添加一个名为「Optimiser」的新标签页,否则你是看不到它的)。在「Stitcher」标签页上,把「Projection」设为「Rectilinear」。在「Control Points」标签页上,你会看到测试照片出现两次,通过在照片的两个版本中挑选这些点组,就可以定义位于同一条直线上的点集。
但不要在两个版本中挑选完全相同的点,使得两幅图像中的点一模一样,因为这会误导优化器走捷径,去确定一对一对应关系的参数。相反,你应当在图像的两个版本中挑选同一条线上的不同点。出于测试目的,你可以定义几组这样的点集,最好靠近图像边缘,那里的直线畸变更大。你会发现,在 Hugin 中定义这样的点集是一件相当繁琐的事情(这或许也是 lensfun 数据库如此之小的原因之一)。
然后切换到「Optimiser」标签页,按住 ctrl 键左键点击来选择要优化的参数。(参见该标签页顶部的提示。)我建议优化「Yaw(y)」「Pitch (p)」以及镜头参数「a」「b」和「c」。水平视场「Hfov (f)」由测试图像中的 EXIF 数据、借助 FocalLengthIn35mmFilm 条目 f 计算得出:
Hfov = 2 × arctan (18 mm / f)
其中 18 mm 是 35 mm 底片(尺寸为 36 × 24 mm)宽度的一半。然后按「Optimize now!」按钮。得到的参数「a」「b」和「c」,对于广角镜头应低于 0.01,对于鱼眼镜头应低于 0.1。如果数值更大,优化很可能失败了。若是如此,请检查「Control Points」标签页上的点集:控制点很可能顺序错乱,或没有正确地与对应的线关联。优化器似乎还对所提供的初始集(用数学的话说:初始向量)很敏感,也就是说,把所有参数都设为零可能是错误的选择。你可以通过在「Optimiser」标签页上双击数值,或勾选标签页右下角的复选框「Edit script before optimising」来编辑初始向量。这会在优化之前弹出一个文本框,让你编辑 Hugin 工程文件的相应部分。在重新启动优化器之前,把初始向量 a, b, c 重设为 a0.0 b0.0 c0.0(或其他合适的值)。经验表明,把「a」设为某个正值可能会有帮助,对于鱼眼镜头尤其如此。对于配备定焦镜头的相机,这项校准做一次便一劳永逸。对于带变焦镜头的相机,则必须在约五个不同焦距上进行校准,以覆盖整个焦距范围。确定了这样一组参数后,在 ImageMagick 中这样测试一下
magick calibration_image.jpg -distort barrel '_a b c_ ' flat.jpg
把值 _a b c_ 替换为刚刚确定的那些值。输出图像中的线应当完全笔直,否则优化就失败了,需要用不同的初始向量或修正后的控制点集重新执行。
定义点集
进行认真的校准时,建议手动编辑 Hugin 工程文件,并以其他方式定义点坐标和点集。工程文件是扩展名为 PTO 的纯文本文件,你可以用一个简单的文本编辑器打开它并提供一份点列表。其 # control points 一节中的单独一行看起来像这样:
c n0 N0 x175.0 y87.8 X1533.3 Y62.6 t3
其中 x, y 是源图像(标签页左侧图像)中的像素坐标,X, Y 是目标图像(标签页右侧图像)中的像素坐标——在这个特殊情形下,它们实际上是同一幅图像的两个版本。(通常这会是全景图中并排放置的两幅不同图像。)开头的 c n0 N0 是标准代码,末尾的 t3 是所关联直线的编号,从索引 3 开始。正如从上面的例子中可以看出的,像素坐标可以带有小数部分。当然,x, y 和 X, Y 必须位于同一条直线上。但它们不能完全相同,因为在这种情况下优化器会拒绝工作(见上文)。保证这一点最简单的办法,是在两幅图像中使用相同的点,但目标坐标采用相反的顺序,例如在左图中使用 p1, p2, p3, p4,在右图中使用 P4, P3, P2, P1。用一个取点工具确定源图像中的像素坐标。你可以用任何图像查看器来做这件事,即那种能保存此类数据的查看器。一个跨平台的此类工具是 Fiji。我(在 Windows 下工作)用了 WinMorph 中的折线来完成。取点时你应遵循一种预先定义的策略,例如在每条(大致)水平的线上取相同数量的点,从左到右进行(即在整幅图像中沿之字形线前进,就像电视显像管里的阴极射线束一样)。这样的策略会简化目标点坐标的排序。定义源点和目标点坐标的文本文件行随后可以手动建立,或借助软件工具建立。(我用一个 Excel VBA 子程序来完成这项任务。)准备好后,把点列表复制到 PTO 文件的相应一节,保存它并用 Hugin 重新打开。结果应当如下所示:
一个现成的示例——既包含校准图像,也包含对应的 Hugin 工程——由 ZIP 文件 olympus_c2500l.zip 提供。 从相机生成的缩略图逆向推算有几种情形,我们已经拥有一对图像,一幅是桶形畸变的,另一幅已经过校正。这次校正可能是用其他某种镜头校正软件完成的,它并不告诉我们校正参数。此外,如今许多相机(2019 年)可以对 JPEG 图像在内部执行镜头校正。不过,此功能通常不施加于 RAW 图像。(嗯,毕竟是 RAW。)尽管如此,RAW 图像中包含一个 JPEG 预览,相机已对其施加了校正。ImageMagick 可以借助 dcraw 读取 RAW 图像。因此,我们可以在不做镜头校正的情况下用 magick 把 RAW 图像转换为 JPEG,再把结果与内部校正过的 JPEG 缩略图进行比较。在这样一对图像的情形下,校正参数可以直截了当地计算出来。通过在两幅图像中挑选对应的点对,我们可以直接建立 r 与 R 之间的关系。 ![[RAW Image]](../static/img/lens/raw_marks.jpg)
RAW 图像(桶形畸变) ![]()
已校正的缩略图(待逆向推算) |
---|---
正如上面的例子所示,我们可以自由选择挑选哪些点对;它们既不必沿着直线,甚至也不必位于几何图案上。我们只需用 r 与 R 的对应值填满一张表,然后计算回归曲线,例如借助 电子表格 图表。
于是 ImageMagick 的命令行为
magick barrel.jpg -distort barrel '0.0099 -0.0678 0.0014 1.0511' flat.jpg
你也可以把同一条命令直接应用于 DNG 相机格式图像「barrel.dng」。
镜头校正示例
露营车
左侧那张露营车的原始照片不得不在黄昏时分、从相当近的距离拍摄,因为摄影师身后是一道陡坡,空间受限。(糟糕的光照条件解释了那层蓝色调,它源自后期处理中的强力提亮。)原始照片呈现明显的桶形畸变,尤其在图像顶部附近的水平条带以及建筑物的后角处可见。这次拍摄所用的 Nikon Coolpix 995 收录在 PTlens 的数据库中,因此畸变很容易得到校正,如中间一图所示。右侧图像显示两张照片灰度版本之间的差分,其计算方式是先将二者相减,再取反,然后进行极端剪裁和伽马校正。同样,校正的效果在顶部的水平条带上表现得最为清楚。白色的圆圈(表示差分为零)源于非缩放约束:位于直径等于图像较短一边的圆上的点保持不变。
GoPro 平坦化
GoPro 相机镜头会产生明显的桶形畸变,这似乎是其品牌特色的一部分。例如,GoPro Hero 3+ 银色版配有一枚焦距固定为 2.77 mm 的鱼眼镜头,若使用整个感光区域,则相当于 35 mm 胶片下 16 mm 的焦距。GoPro Hero 3+ 有三种拍照模式:
- 1000 万像素 = 3680 × 2760 像素 广角(35 mm 胶片下 16 mm)
- 700 万像素 = 3072 × 2304 像素 广角(35 mm 胶片下 16 mm)
- 500 万像素 = 2624 × 1968 像素 中等视角(35 mm 胶片下 23 mm)
GoPro Hero 3+ 配备一块 1/2.3" 传感器,其裁切系数为 5.64。(这使得 35 mm 胶片下的焦距仅为 15.62 mm,EXIF 信息提供的 16 mm 大概是四舍五入的结果。)前两种模式似乎使用整个感光区域,分辨率的降低显然是通过降采样实现的。因此畸变参数相同,这一点可在实践中得到证明。500 万像素模式显然只使用感光区域的一部分,因为 3680 / 2624 × 16 ≈ 23。镜头参数可确定为
- 广角:
- a = 0.06335
- b = -0.18432
- c = -0.13009
- 中等视角:
- a = 0.01359
- b = -0.06034
- c = -0.10618
理论上,第二种模式的参数可由第一种推导出来,因为半径 ri 与 Ri 通过比例因子 κ = 3680 / 2624 = 1.402 相耦合,由此得到:
- a2 = a1 / κ³
- b2 = b1 / κ²
- c2 = c1 / κ
上述由一次独立优化得到的参数,与这些理论值相差不大。相应地,各种视频模式的参数既可以通过优化得到,也可以从该模式所使用的传感器区域部分推导出来。对于视频,水平视场无法从 EXIF 数据推导。它可以根据所使用的感光区域计算,可以(在受控条件下拍摄素材后)由视频本身确定,也可以干脆与其他参数一起估计,即在优化中作为自由参数保留。HD 视频(1920 × 1080)的参数为: * a = 0.030530 * b = -0.124312 * c = -0.038543
这些参数可用于在 ImageMagick 中进行逐帧校正。作为替代,也可以用它们通过 AVIsynth 插件 DeBarrel 来「平坦化」整个视频,该插件同样使用 Panorama Tools 的镜头校正模型。
两台键盘(由 el_supremo 提供)
我拍摄的两台键盘的照片中有非常明显的桶形畸变,因为它是在 17 mm 焦距下拍摄的。
这种畸变可以用例如 Canon 的 Digital Photo Professional 来校正(我有一台 Canon 50D 相机)。其他单反相机制造商通常也会提供软件来对自家镜头做这类校正,但我想看看上述示例在这张照片上效果如何。第一步是前往 LensFun 网站 下载最新版本的相机数据库。解压该软件包(winzip 在 Windows 上可以解压 .tar.gz 文件),然后在「lensfun/data/db」目录中查找与你相机制造商对应的文件。就我而言,我查看了「slr-canon.xml」,它可以用任何文本编辑器编辑。现在我找到我所用的特定镜头的信息,在本例中是一枚「EF-S 17-85mm」。那枚镜头的信息如下所示:
<lens>
<maker>Canon</maker>
<model>Canon EF-S 17-85mm f/4-5.6 IS USM</model>
<mount>Canon EF-S</mount>
<cropfactor>1.6</cropfactor>
<calibration>
<distortion model="ptlens" focal="17" a="0.021181" b="-0.055581" c="0" />
<distortion model="ptlens" focal="20" a="0.019344" b="-0.043786" c="0" />
<distortion model="ptlens" focal="22" a="0.015491" b="-0.026682" c="0" />
<distortion model="ptlens" focal="28" a="0.008084" b="-0.007472" c="0" />
<distortion model="ptlens" focal="30" a="0.005522" b="-0.001763" c="0" />
<distortion model="ptlens" focal="35" a="0.003149" b="0.002207" c="0" />
<distortion model="ptlens" focal="44" a="0" b="0.008269" c="0" />
<distortion model="ptlens" focal="53" a="0" b="0.008792" c="0" />
<distortion model="ptlens" focal="61" a="0" b="0.00738" c="0" />
<distortion model="ptlens" focal="72" a="0" b="0.006226" c="0" />
<distortion model="ptlens" focal="78" a="0" b="0.007095" c="0" />
<distortion model="ptlens" focal="85" a="0" b="0.007288" c="0" />
</calibration>
</lens>
这些校准条目给出了从 17mm 到 85mm 一系列焦距的畸变值。如果我所需的焦距介于其中两个值之间,我可以选择最接近的那个,也可以对这些值进行插值。由于我要校正的照片是在 17mm 下拍摄的,我需要校准信息中第一行的信息。它给出的值为:a="0.021181" b="-0.055581" c="0" 这就是用于校正镜头畸变的三个参数。不过,对于某些较旧版本的 IM,桶形畸变校正需要第四个参数 d。幸运的是,用这个简单公式就能轻松地从其他三个参数计算出 d 的值:d = 1-a-b-c 也就是说:d="1.0344"。 |
如果未把「d」作为畸变参数提供,IM 会自动算出它的值,但某些较旧版本的 IM 并不这么做。 |
|---|---|
| 于是,用来校正镜头畸变的实际 桶形畸变 命令如下…… |
magick keyboards.jpg \
-distort barrel "0.021181 -0.055581 0" \
keyboards_ptlens.jpg
| 当然,由于 JPEG 是有损压缩,在完全处理完图像之前,你不应把它保存为 JPEG。
---|---
在原始照片中,畸变在谱架底部以及上面那台键盘沿线尤为明显。这些畸变在输出照片中几乎完全消失了。把这一结果与用 Canon 软件得到的结果作视觉比较,二者本质上一致。El-Supremo
![[IM Output]](../static/img/img_photos/building_1.gif)
![[IM Output]](../static/img/img_photos/building_2.gif)
![[IM Output]](../static/img/img_photos/hugin.jpg)
![[Calabration]](../static/img/lens/calibration_points.gif)
![[Regression]](../static/img/lens/regression_curve.gif)
![[IM Output]](../static/img/img_photos/campmobile.jpg)
![[IM Output]](../static/img/img_photos/campmobile_pt.jpg)
![[IM Output]](../static/img/img_photos/campmobile_comp.jpg)
![[IM Output]](../static/img/img_photos/keyboards.jpg)
![[IM Output]](../static/img/lens/keyboards_ptlens.jpg)