ImageMagick 示例 -- 图像卷积
- Convolve
- 卷积核缩放
- 核归一化(自动缩放)
- 零和归一化
- 将核与 Identity 核混合
-
模糊图像(低通滤波)
- Gaussian 核与 Blur 核
- 柔化模糊(与原图混合)
-
用模糊对图像进行“反锐化”(从原图中减去)
边缘检测卷积(高通滤波)
-
用边缘检测锐化图像(增强原图边缘)
方向性卷积(斜率、罗盘滤波)
-
Convolve 与 Correlate(非对称核的效果)
- 相关与形状搜索
- 生命游戏
卷积使用像素的局部“邻域”来修改图像。它会合并并平均每个像素周围的所有颜色值,从而模糊图像、突出边缘和边界,以及锐化图像。卷积的一个变体“相关”(Correlation)还可用于扫描并搜索特定图案,生成一幅表示匹配程度的图像。
卷积简介
'Convolve' 方法以及与其关系密切的 'Correlate' 方法,在许多方面都与形态学非常相似。事实上,它们的工作方式几乎完全一样:在每个位置匹配一个邻域“核”,因此也可以看作形态学中的另一种特殊“方法”。它们实际上也复用了很多相同的代码,甚至使用了基本核和用户定义核中定义的同一套核定义。对于专门为这个运算符设计的更多核(数量很多),请参见模糊核和边缘检测核。其中最重要的是 '[Gaussian](../static/img/convolve/gaussian)' 核。不过,卷积比形态学古老得多,它生成的更多是灰度渐变效果,而不是形态学通常生成的二值形状分析效果。因此,它常常被视为一种与形态学非常不同、相互独立,并且在图像处理中更核心的操作。基本上,卷积或相关会对指定邻域中的所有像素执行“加权平均”。也就是说,它把每个相邻像素的值乘以核中给定的权重,然后把这些值相加得到最终结果。因此,最终图像中的每个像素通常都会包含源图像中它局部周围其他像素的一小部分。换个角度看,图像中每个像素的颜色,会按照所用核的定义,被加入到(模糊)或从(锐化/边缘检测)其附近邻居的颜色中减去。'convolve' 和 'correlate' 除了一个很小但很重要的差别外,是同一种操作;对于下面要看的示例和控制方式,你基本可以把它们当作同一回事。稍后(见 Convolution 与 Correlation)我们会仔细说明这两个运算符究竟有什么不同,以及为什么这个差别如此细小。但在大多数情况下,它们是同一种方法。
Convolve()
如上所述,'Convolve' 方法会根据核中的浮点值,对局部邻域中的每个像素加权。然后这些加权值会被简单相加,生成结果图像中新的替代像素。例如,让我们使用一个很小的用户定义卷积核来卷积单个像素。我还设置了特殊的 Show Kernel 设置,这样你就可以看到所定义和使用的核的细节(显示的图像已被放大)。
magick xc: -bordercolor black -border 5x5 pixel.gif
magick pixel.gif -define morphology:showKernel=1 \
-morphology Convolve '3x3: 0.0, 0.5, 0.0
0.5, 1.0, 0.5
0.0, 0.5, 0.0' pixel_spread.gif
可以看到,图像中的单个像素现在已经扩展,在周围生成了 50% 灰度的像素。也就是说,当核的“原点”(本例中是中心)位于原图中这个单像素旁边时,只有这个单像素具有非零值。随后该像素值会乘以核中的 '0.5' 权重,所得的“半亮”像素被加入到结果图像中。类似地,当核的原点正好位于原始像素上方时,它会得到 '1.0' 的值,从而复现原始像素,而其周围邻域中的其他值(黑色)不会向结果贡献任何成分。请注意,任何值为 '0.0' 的核元素都不会参与最终计算。零值实际上不属于“邻域”,就像形态学核中的任何 'Nan' 值都不参与一样。因此这个核由 5 个元素的邻域组成。在许多方面,'[Convolve](#convolve)' 方法与形态学的 '[Dilate](morphology.html#dilate)' 方法非常相似;不过 '[Dilate](morphology.html#dilate)' 只是把核当作一种位图蒙版,用来在邻域中寻找最大值。另一方面,'[Convolve](#convolve)' 是邻域中所有值的加权和,因此每个核元素的值都会影响整体结果。卷积操作的语法是……
**-morphology Convolve {_convolution_kernel_}
**
不过你也可以使用较旧、更直接的运算符……
**-convolve {_convolution_kernel_}
**
| _在 IM v6.5.9 之前,较旧的 "[-convolve](https://imagemagick.org/command-line-options/#convolve)" 并不理解 morphology 核定义。它只接受“旧式”的用户自定义核,也就是只用逗号分隔值组成的字符串,生成某个奇数尺寸的方形核。现在它会接受“新式”的卷积核定义。
不过它仍然被限制为“奇数尺寸”的方形核。在它开始使用新的“形态学”卷积方法之前,这个限制会一直存在。
_
---|---
| _较旧的 "[-convolve](https://imagemagick.org/command-line-options/#convolve)" 运算符与较新的形态学 '[Convolve](#convolve)' 方法并不完全相同。下面列出这两种操作的差异……
_
- _旧运算符实现为Correlation,而不是真正的卷积。这意味着核不会以反射后的形式叠加到源图像上。它对结果的影响见 Convolve 与 Correlate。
_ * _它只接受奇数尺寸的方形核。morphology 版本允许任意矩形数组,并且可以把数组中的任意点声明为原点。
_ * _旧运算符总是会归一化核,用户无法控制核的核缩放。新的方法不会自动归一化,需要你明确请求。不过大多数生成出来的核已经预先归一化。
_ * 你不能使用任何形式的与 Identity 核混合,不过输出输出偏置会照常执行。 * _不过,如果主机具备相应能力,它会使用快速的“GPU”代码。Morphology 目前还没有启用这一点。
_
* _目前其他与卷积相关的运算符,例如 "[-gaussian-blur](https://imagemagick.org/command-line-options/#gaussian-blur)"、"[-blur](https://imagemagick.org/command-line-options/#blur)"、"[-sharpen](https://imagemagick.org/command-line-options/#sharpen)"、"[-unsharp](https://imagemagick.org/command-line-options/#unsharp)",使用的是旧版本运算符。
_
* _默认情况下,旧命令只会对颜色通道执行卷积(由 "[-channel](https://imagemagick.org/command-line-options/#channel)" 设置定义)。如果使用 "[-channel](https://imagemagick.org/command-line-options/#channel) RGBA" 设置进行卷积,它还会用 alpha 通道对核值加权,以确保透明度相关的模糊处理正确。
形态学 'convolve' 方法默认会自动处理颜色通道的透明度加权。也就是说,用它模糊图像时,透明颜色会被当作透明处理,因此默认可以避免模糊透明度缺陷。
不过如果用户修改了默认的 "[-channel](https://imagemagick.org/command-line-options/#channel)" 设置(不包含特殊的 'Sync' 标志),那么它会把卷积作为纯粹基于通道的灰度运算符来处理。
更多信息请参见 "[-channel](https://imagemagick.org/command-line-options/#channel)" 设置文档,或查看以同样方式使用同一标志的图像通道数学运算。
_
_
最终,随着相关功能合并到较新的形态学 '[Convolve](#convolve)' 方法中,上述大多数差异都会发生变化。
_
---|---
如果你想看一些展示 '[Convolve](#convolve)' 实际工作方式的优秀示例,我也建议看看 EECE \ CS 253 Image Processing, Lecture 7, Spatial Convolution。Wikipedia, Convolve 条目中也有一些很好的卷积过程一维动画。
卷积核缩放
上面的示例对于单像素这种几乎全黑的图像效果很好,但如果把它应用到真实图像上,就会遇到问题……
magick logo: -resize 50% -crop 80x80+150+60 +repage face.png
magick face.png \
-morphology Convolve '3x3: 0.0,0.5,0.0 0.5,1.0,0.5 0.0,0.5,0.0' \
face_spread.png
可以看到,结果图像比原图亮得多(实际上亮了 3 倍)。原因是每个像素被分摊了 3 次:四周的 4 × '0.5',再加上原始像素的一份完整副本。也就是说,核中所有值相加为 3,使结果图像变成三倍亮!如果回头看上面的 'showKernel' 输出,你会看到它列出该核的“卷积输出范围为 0 到 3”。这说明这个核通常会把图像变亮 3 倍。要修正这一点,需要把核中的所有值都除以 3。也就是说,值 '0.5' 实际上应约为 '0.1667',中心值 '1.0' 应约为 '0.3333'。这个过程称为“核归一化”。例如,下面是手动“归一化”后的结果和核定义……
magick face.png -define morphology:showKernel=1 \
-morphology Convolve \
'3x3: 0.0,.1667,0.0 .1667,.3333,.1667 0.0,.1667,0.0' \
face_spread_norm.png
可以看到,你得到的是人脸图像一个非常轻微模糊的版本,因为每个像素都扩散到了它的直接邻居上。 | _上面显示的“核图像”(使用特殊的 Kernel 2 Image 脚本生成)也展示了归一化后的核。可以看到,核本身现在非常暗,因为其中所有值也都很暗,不过它们合起来仍为 '1.0'。
从这里开始,所有显示出来的卷积核图像都会经过调整,使最大值显示为白色;否则你通常只会看到一张很暗、基本没用的“核图像”。
_
---|---
自己归一化核并不愉快,而且如你所见,它会让得到的核定义难懂得多。因此提供了替代方法。从 IM v6.5.9-2 起,特殊的专家选项 "[-define](https://imagemagick.org/command-line-options/#define) convolve:scale={_kernel_scale_}' 允许你为核指定全局缩放因子,从而调整整体结果的亮度。 |
magick face.png -define convolve:scale=0.33333 \
-morphology Convolve '3x3: 0.0,0.5,0.0 0.5,1.0,0.5 0.0,0.5,0.0' \
face_spread_scale.png
![[IM Output]](../static/img/convolve/face_spread_scale.png)
实际上它所做的是调整核结果的整体强度。你将在后面的示例中看到,有时你可能希望让卷积结果更强或更弱。这个 'kernel_scale ' 因子就能做到这一点。
核归一化(自动缩放)
你不必像上面那样自己算出缩放因子,只需给 IM 一个特殊的 '!' 归一化标志,让它在内部计算这个“归一化缩放因子”。 |
magick face.png -define convolve:scale=\! \
-morphology Convolve '3x3: 0,1,0 1,2,1 0,1,0' \
face_spread_normalize.png
![[IM Output]](../static/img/convolve/face_spread_normalize.png)
| 字符 '!' 有时也会被各种 UNIX 命令行 shell 用于特殊目的。因此即使放在引号中,也可能需要用反斜杠转义该字符。请谨慎使用。
---|---
请注意,由于核现在会被归一化,我可以用整数以更简单的方式定义它。归一化后的核仍然与前面“缩放”过的核相同。通常你总是会希望归一化核,因此更简单的 "[-convolve](https://imagemagick.org/command-line-options/#convolve)" 变体会自动执行这个归一化。你也可以让 IM 先归一化核,然后再按给定量进一步缩放它,以调整输出范围。为了更方便,你还可以把缩放因子指定为百分比。例如,这里我归一化核,然后把数值重新缩放到计算大小的 50%,从而生成更暗的结果。 |
magick face.png -define convolve:scale=50%\! \
-morphology Convolve '3x3: 0,1,0 1,2,1 0,1,0' \
face_spread_norm_half.png
![[IM Output]](../static/img/convolve/face_spread_norm_half.png)
请注意,使用 '!' 值实际上等同于使用 '1!',甚至 '100%!'。如果想翻转核中的正值和负值,你甚至可以使用负缩放因子。相关示例请参见用模糊对图像进行“反锐化”。如果核以这种方式归一化,Show Kernel 输出会告诉你它已经归一化。
归一化的工作方式
'核归一化 ' 的实际工作方式,是把所有核值相加(也包括可能存在的负值)。如果结果非零,就缩放所有值,使它们的总和等于一('1.0')。请注意,如果存在负值,这实际上可能生成一个值大于一的核元素,通常出现在原点处。这种情况特别会发生在 Un-Sharp 核中。不过重要的是,整个核的总和为 '1.0',因此最终图像不会因为卷积操作而变暗或变亮。如果相加结果为零('0.0'),那么该核会被视为一种特殊的零和核。在这种情况下,核会被缩放,使所有正值相加等于 '1.0',同理,所有负值相加则等于 '-1.0'。这类核在边缘检测技术中尤其常见。如果核属于这种形式,Show Kernel 输出也会说明它是零和的;即使它实际上不是归一化后的零和核,也可以从显示出的其他数值中很容易看出来。大多数由数学确定的核都是预先归一化的。这包括数学推导出的核:'[Unity](#unity)'、'[Gaussian](#gaussian)'、'[LoG](#log)'、'[DoG](#dog)'、'[Blur](#blur)'、'[Comet](#comet)'。不过,离散常量核没有预先归一化,因此你必须使用上面的核归一化设置来完成。这包括这些核:'[Laplacian](#laplacian)'、'[Sobel](#sobel)'、'[Roberts](#roberts)'、'[Prewitt](#prewitt)'、'[Compass](#compass)'、'[Kirsch](#kirsch)'、'[FreiChen](#freichen)'。请注意,'[FreiChen](#freichen)' 核有一些子类型,已经为更具体的用途预先加权。FreiChen 核不应归一化,而应直接使用。
零和归一化
并非所有卷积核都只使用正值。也可以有同时使用正值和负值的核,而且这些核的值通常意在相加为零,从而形成零和核。这类核对于更高级的图像卷积非常重要,因为它们提供了边缘检测和图像锐化技术。如上一节所述,普通的归一化标志 '!' 也能用于这类核。但有时由于特殊情况,你希望确保核仍然保持“零和”。特殊的 '^' 归一化方法正是为下面这些情况提供一种确保核为“零和”的方式……
- 用户的核定义不够精确,无法确保零和。例如,你不能把 '
1/3' 或任何其他 3 的分数因子表示为精确的浮点小数。 - 数学曲线被核大小(半径)“截断”,因此可能不再是零和。例如,基于无限响应曲线的 '
[LoG](#log)' 或 '[DoG](#dog)' 核就会出现这种情况。正因为如此,IM 在这些核内部实际使用了这种特殊归一化。 - 确保 Correlation 的“形状蒙版”为零和,这样在搜索时,IM 可以同等地寻找正匹配和负匹配。见下文的相关形状搜索。
其做法是把核中的所有正值和负值作为两个独立部分分别归一化。也就是说,所有负值会被缩放到相加为 '-1.0',所有正值会被缩放到相加为 '+1.0'。结果就是整个核可以保证相加为零。请注意,如果你把这种归一化方法用于像 'Gaussian' 这样的全正值核,仍然会得到正确归一化的核。因此这种归一化形式仍可用于模糊核。不过,它不应直接用于归一化明确定义的锐化核,甚至反锐化核,因为这类核可能包含负值,但要求总和为一(使用普通归一化方法)。
将核与 Identity 核混合
核缩放设置的完整语法可以是……
**-define convolve:scale='{_kernel_scale_}[!^] [,{_origin_addition_}] [%]'
-set option:convolve:scale '{_kernel_scale_}[!^] [,{_origin_addition_}] [%%]'**
请注意,使用 "[-set](https://imagemagick.org/command-line-options/#set)" 时,百分号字符需要写成两个。可选的归一化标志 '!' 或 '^" 会先应用到用户定义核或内置核上(如果请求了的话)。之后,核会按 'kernel_scale ' 因子缩放,以增大或减小卷积对结果的有效“强度”。默认缩放因子是 '1.0'。最后,逗号后的数值会被加到核的“原点”值上。默认的 'origin_addition ' 是 '0.0'。这最后一步实际上是向前面生成的、已归一化并缩放的核中“加入”一个给定“缩放”的 Unity 核。这样可以生成能够……的核。
请注意,如果给出百分号('%')标志,那么该百分比会同时应用到 'kernel_scale ' 因子和 'origin_addition ' 上。当涉及小数时,这能让缩放值更容易阅读和理解。下面是核缩放 define 的用法示例……
-define convolve:scale='!50%,100%' -morphology Convolve Laplacian:2
会生成所请求的 '[Laplacian:2](#laplacian_2)' 核……
| 0 | -1 | 0 |
|---|---|---|
| -1 | 4 | -1 |
| 0 | -1 | 0 |
对它进行归一化('!' 标志)
| 0 | -0.25 | 0 |
|---|---|---|
| -0.25 | 1 | -0.25 |
| 0 | -0.25 | 0 |
按 50% 缩放
| 0 | -0.125 | 0 |
|---|---|---|
| -0.125 | 0.5 | -0.125 |
| 0 | -0.125 | 0 |
加入一个 Unity 核(向原点值加上 100%)
| 0 | -0.125 | 0 |
|---|---|---|
| -0.125 | 1.5 | -0.125 |
| 0 | -0.125 | 0 |
现在你就可以使用 '[Laplacian:2](#laplacian_2)' 作为锐化核进行卷积,但锐化强度只有 '50%。请记住,只要在缩放设置中的任何位置给出 '%' 标志,两个值都会被视为百分比。如果没有该标志,两个值都只是简单的直接乘数。例如,下面这些缩放选项都是等价的。
50,100% 50%,100 %50,100 .5,1 0.5,1.0
两个归一化标志也是如此。它们可以出现在卷积缩放设置中的任何位置,但总会在任何其他缩放发生之前先应用。
输出结果偏置控制
当你处理包含负值的核时,结果图像中的某些像素本应被赋予负值。对于零和核(见下文)尤其如此。遗憾的是,除非你使用特别构建的 HDRI 版 ImageMagick 来保留生成的负值,否则任何负结果都会被裁剪为零(黑色)。你只能得到卷积的正结果。普通图像格式无法保存这些负值,因此你只会得到一半结果。你可以构建 HDRI 版 ImageMagick 来保留生成的负值,然后提取所需信息。另一种方法是使用负缩放因子让核取反。例如使用……
-define convolve:scale='-1'
不过这样你只会得到负结果,而正结果会被裁剪。通过使用 IM 设置 "[-bias](https://imagemagick.org/command-line-options/#bias)",你仍然可以同时保留正结果和负结果。非 HDRI 版本 IM 可使用的设置是……
-define convolve:scale=50%\! -bias 50%
第一个设置会把输出缩放到通常结果(归一化后)的一半大小,从而为正结果和负结果都留出空间。然后它会在把结果保存回图像之前,向像素输出添加 50% 灰度。使用这些设置时,任何“零”结果都会变为纯灰,负结果比它更暗,正结果比它更亮。黑色表示 '-1.0',白色表示 '+1.0'。下方相关形状搜索示例中展示了这种做法。
模糊图像(低通滤波)
IM 示例中的另一个章节,具体说是模糊与锐化图像,实际上讨论了这个主题的实用方面。这里我们关注更具体的细节。不过首先,我们会说明基本核以及如何不加修改地直接使用它们。稍后再看如何修改模糊效果以生成其他效果。
模糊核
Unity
这是一个实际上什么也不做的特殊核。它只指定了一个核元素,因此每个像素都会被它自身替换,不发生变化。例如,下面是一个无操作的卷积……
magick face.png -morphology Convolve Unity face_unity.png
从 IM v6.6.9-4 起,该核可以接受一个参数,作为核专用的缩放参数。这允许你用它来乘以图像的数值,例如让图像变亮或变暗。 |
magick face.png -morphology Convolve Unity:0.5 face_dimmed.png
![[IM Output]](../static/img/convolve/face_dimmed.png)
这看起来可能不是很有用,但它可用于生成柔化模糊和反锐化效果,或者用于无法使用核缩放或核 Identity 混合的多核序列。同样的单元素核也可以用 '[Disk:0.5](morphology.html#disk)' 生成,它也允许你在生成核时指定额外的缩放参数。(例如上一个示例可写作 'Disk:0.5,0.5')。类似的(用于卷积的)核也可以由 '[Gaussian](#gaussian)' 核生成器以 'sigma ' 为 '0.0' 生成。不过那只能生成一个很小的 3x3 核,其中中心为 '1.0',周围 8 个值为 '0.0'。
使用形状核进行 Mean 或 Average 滤波
虽然下面定义的大多数卷积核通常都会以某种方式使用 Gaussian 曲线,但你仍然可以使用前面某个形态学形状核,简单地对给定(较大)区域内的像素取平均。当然,你需要对核进行归一化,这样才会真正生成平均值,而不是只得到邻域值的总和。例如,这里我使用一个较小的 '[Octagon](morphology.html#octagon)' 形状核,对每个像素周围圆形区域内找到的所有像素值取平均。
magick face.png -define convolve:scale=! \
-morphology Convolve Octagon:2 face_mean.png
结果是,每个像素的值会均匀扩散到所定义邻域中的全部 25 个像素上。也就是说,它等价于在给定形状上的“均值”或“平均”滤波器。如果你想把原始像素排除在平均之外,只使用周围像素,则可以使用 '[Ring](morphology.html#ring)' 核(只提供一个半径)。其他形状核也可以同样使用,例如在 '[Diamond](morphology.html#diamond)'、'[Square](morphology.html#square)' 或较大的 '[Disk](morphology.html#disk)' 形状上,以任意大小对像素值取平均。不过,虽然在某个形状区域上进行常量平均确实会模糊图像,它也容易在结果图像中产生异常效果(特别是混叠伪影)。更具体地说,使用“平坦”的平均核往往会把锐利边缘转换为较厚的线性斜坡,并在加厚边缘处突然改变斜率。结果的厚度为核的 'radius*2-1'。不同边缘角度如何影响斜坡厚度和斜坡线性,取决于这个“平坦”或平均核的形状。
magick -size 80x80 xc: -draw 'polygon 15,15 15,65 60,15' shape.png
magick shape.png \
-define convolve:scale=! -morphology Convolve Square:5 \
shape_mean_square.png
magick shape.png \
-define convolve:scale=! -morphology Convolve Disk:5 \
shape_mean_disk.png
请注意,上面的对角线模糊在方形核和圆盘核下并不相同。生成方形“线性斜坡”模糊的另一种方式,是使用指定半径和非常大的 sigma。例如,上面的方形核卷积也可以用 -blur 5x65535 实现。在形态学可用之前,Fred Wienhaus 常在他的脚本中使用这种方法。
Gaussian 核(二维 gaussian 模糊)
你可能已经看出来,'Gaussian' 核是最常用于卷积图像的核。这是用于模糊效果的数学理想核。例如,下面是一个小型 'Gaussian' 核的 Show Kernel 输出(它们会很快变得非常大)……
magick xc: -define morphology:showKernel=1 \
-morphology Convolve:0 Gaussian:0x0.8 null:
我实际上并不想对上面的图像应用卷积,只是想显示它将要使用的核。因此我使用了 ':0' 迭代次数,让它什么也不做。同样,我用特殊的 '[null:](files.html#null)' 文件格式丢弃结果图像输出。你可以从卷积输出范围看到,'Gaussian' 核已经为你归一化(缩放)好了。不过你也会注意到,它仍然是一个相当大的核,里面填满了很小的小数值。如果仔细看,会发现最大值(2.48678,也列在第一行)位于中心,最小值位于边缘和角落附近(约为 .000000194)。下面是使用卷积的典型 Gaussian 模糊……
magick face.png -morphology Convolve Gaussian:0x2 face_gaussian.png
核的语法很直接……
Gaussian:[{_radius_}]x{_sigma_}
这些参数实际上与 "[-gaussian-blur](https://imagemagick.org/command-line-options/#gaussian-blur)" 运算符所用的完全相同,后者实际上就是使用这个核执行卷积。第一个数值与大多数形态学核一样,是 'radius ',也就是核的大小。它只是一个整数,最小值为 1,因此最小可能核为 3x3 元素。最佳做法通常是指定为零,让 ImageMagick 根据提供的 'sigma ' 值计算合适半径。第二个更重要的参数是 'sigma ',它定义每个像素应该变得多模糊或“扩散”多远。值越大,图像越模糊。它是一个浮点值。必须提供 sigma 值。如果给出 sigma 值 '0.0',你最终会得到一个相当没用的 '[Unity](#unity)' 核(给定半径,或半径为 1,因此生成一个中心为 '1.0'、周围为 '0.0' 的 3x3 核)。如你上面所见,使用任何类型的 '[Unity](#unity)' 核进行卷积都对图像_没有任何作用_!如果你确实指定 'radius ',通常最好让它至少是 'sigma ' 的两倍;IM 通常会计算约为 3 倍的半径(实际上是仍能提供有意义结果的最大半径),不过这取决于你所安装 IM 的编译时质量。关于 'Gaussian' 核参数的影响,以及图像模糊的一般信息,请参见……模糊图像。
Blur 核(一维 gaussian 模糊)
'Blur' 核与 Gaussian 核非常相似,甚至接受相同的参数(见下文)。不过 gaussian 是二维曲线,而 'Blur' 核生成的是一维曲线。也就是说,它会生成一条细长的单行数值。下面是一个小型 'Blur' 核的 Show Kernel 输出。 | |
magick xc: -define morphology:showKernel=1 \
-morphology Convolve:0 Blur:0x0.8 null:
上图显示的是默认 'Blur' 核的实际剖面。它是使用 Kernel Image 脚本 "[kernel2image](../static/img/scripts/kernel2image)" 创建的,然后再用 "[im_profile](../static/img/scripts/im_profile)" 脚本绘制成图。它清楚展示了这个核所代表的“Gaussian 钟形曲线”。下面是使用该核水平模糊图像的示例。 |
magick face.png -morphology Convolve Blur:0x4 face_blur.png
![[IM Output]](../static/img/convolve/face_blur.png)
该核的语法与 '[Gaussian](#gaussian)' 完全相同,只是额外多了一个可选的旋转角度。
Blur:[{_radius_}]x{_sigma_}[,{_angle_}]
和前面一样,第二个值 'sigma ' 是必需的;如果设为零,你会得到线性等价的 '[Unity](#unity)' 核。'angle ' 允许你把核旋转 90 度,从而垂直模糊图像。 |
magick face.png -morphology Convolve Blur:0x4,90 face_blur_vert.png
![[IM Output]](../static/img/convolve/face_blur_vert.png)
目前只支持 90 度旋转。这在 ImageMagick 的后续版本中可能会改变。这个核的目的实际上是创建一种更快的二维图像模糊形式,用来产生 '[Gaussian](#gaussian)' 核所生成的效果。关于这是如何完成的,请参见下文 Gaussian 核与 Blur 核。
Comet 核(半个一维 gaussian 模糊)
'Comet' 核几乎与 '[Blur](#blur)' 核完全相同,但实际上它只有半个 blur 核。
magick xc: -define morphology:showKernel=1 \
-morphology Convolve:0 Comet:0x1.0 null:
请注意,定义的原点位置在左侧边缘,而不在核的中心。对于卷积核来说这非常少见,因此会产生非常特别的结果。它会朝一个方向把图像抹开,就像手指抹过未干的画面,在表面留下颜色拖痕。有点像彗星的尾巴,或流星、坠星留下的轨迹。 |
magick face.png -morphology Convolve Comet:0x5 face_comet.png
![[IM Output]](../static/img/convolve/face_comet.png)
请注意,前景色和背景色都被“拖抹”了。如果你只想模糊前景色,可以把背景设为透明,并在前景模糊后再把背景加回去。你还可以提供第三个 angle 参数,让核围绕其“原点”按任意 90 度倍数旋转。 |
magick face.png -morphology Convolve comet:0x5+90 face_comet_vert.png
![[IM Output]](../static/img/convolve/face_comet_vert.png)
这个核实际上与专门的 Motion Blur 运算符使用的是同一个核,不过那个运算符还做了一些相当复杂的坐标查找处理,使模糊可以在任意角度工作。只是它做得并不好,在较大角度(如 45 度)会产生颜色“团块”。希望以后能实现真正的核旋转,以便在非 90 度倍数的角度上创建更好的运动模糊类效果。
Gaussian 核与 Blur 核
如前所述,'[Gaussian](#gaussian)' 与 '[Blur](#blur)' 核关系非常密切,实际上可以完成同样的工作。两者都是 Gaussian 曲线的表示,前者是二维表示,后者是一维表示。例如,下面重复执行 "-gaussian-blur 0x2",它等价于 "-morphology Convolve Gaussian:0x2" 操作。 |
magick face.png -gaussian-blur 0x2 face_gaussian-blur.png
![[IM Output]](../static/img/convolve/face_gaussian-blur.png)
这可以用两个彼此旋转 90 度的独立线性或一维模糊操作来替代(顺序实际上也无关紧要)…… |
magick face.png -morphology Convolve Blur:0x2 \
-morphology Convolve Blur:0x2+90 face_blur_x2.png
magick face.png -morphology Convolve 'Blur:0x2;Blur:0x2+90' face_blur_x2.png
默认情况下,IM 会按多核组合设置的定义,把第一个卷积核的结果再用第二个(以及之后的)卷积核“重新迭代”。你甚至可以进一步简化上面的写法:使用 '>' 让 IM 把一个核展开成旋转核列表,也就是生成 90 度旋转列表(本例中为两个核)。例如……
magick face.png -morphology Convolve 'Blur:0x2>' face_blur_x2.png
上面所有示例彼此等价,这也正是 "[-blur](https://imagemagick.org/command-line-options/#blur)" 运算符的工作方式。 |
magick face.png -blur 0x2 face_blurred.png
![[IM Output]](../static/img/convolve/face_blurred.png)
这体现了 "[-blur](https://imagemagick.org/command-line-options/#blur)" 与 "[-gaussian-blur](https://imagemagick.org/command-line-options/#gaussian-blur)" 运算符之间真正的区别。后者使用一个大的二维核,而前者使用两个小的一维核。从速度上看,"[-blur](https://imagemagick.org/command-line-options/#blur)" 运算符通常会快一个数量级,因为它使用的是两个小得多的核,而不是一个很大的核。模糊参数越大(sigma 参数的大小越大),核就越大,两种操作之间的速度差也越大。因此通常推荐使用 "[-blur](https://imagemagick.org/command-line-options/#blur)" 运算符。两个运算符在结果上的唯一区别,是很小的量子舍入效果(除非你使用 HDRI)以及边缘效果(取决于虚拟像素设置)。这两者都是因为在两次独立的 'blur' 卷积之间保存中间图像而产生的信息损失。这个差异通常小到不可见,对任何实际用途都不重要。
柔化模糊(与原图混合)
你可以通过把任何形式的模糊结果与一部分原图混合,来柔化模糊的影响。尤其是在应用很强的模糊时。例如……
magick face.png -morphology Convolve Gaussian:0x3 face_strong_blur.png
magick face.png face_strong_blur.png \
-compose Blend -define compose:args=60,40% -composite \
face_soft_blur.png
这里使用了 '[Blend](compose.html#blend)' 合成方法,将 '60%' 的模糊图像(合成源图像)与 '40%' 的原始图像(合成目标图像)混合,从而在最终图像上得到“柔化模糊”效果。不过,你也可以使用同样比例,通过将核与 Identity 核混合直接完成同样的事。 |
magick face.png -define convolve:scale=60,40% \
-morphology Convolve 'Gaussian:0x3' face_soft_blur2.png
![[IM Output]](../static/img/convolve/face_soft_blur2.png)
请注意,缩放数字的顺序相同。第一个数字('60%')缩放给定核,以降低它对输出的影响;第二个数字('40%')加入足够的 '[Unity](#unity)'(或 'Identity')核,防止结果变暗。关键在于,对于模糊核,这两个数字相加为 '100%',就像合成混合中那样。你也可以使用更快的两遍模糊,但在这种情况下,我们不能把 'Blend' 直接并入核中,因为两个独立的卷积无法干净地“分离”。因此我们仍需要在之后再次执行 Blend 合成。 |
magick face.png \( +clone -blur 0x3 \) \
-compose Blend -define compose:args=60 -composite \
face_soft_blur3.png
![[IM Output]](../static/img/convolve/face_soft_blur3.png)
请注意,你只需要给出想要与原始图像混合的模糊(源)图像比例。因此值 '100' 会得到模糊图像,而 '0' 会得到原始图像。 | 请记住,"[-blur](https://imagemagick.org/command-line-options/#blur)" 运算符完全等价于使用更快的两遍模糊核。
---|---
使用模糊“反锐化”图像(从原图中减去)
进一步使用这种核混合,当你开始使用负缩放时,就可以从原始图像中减去模糊效果。得到的技术称为 'unsharp'。关于它为何得到这样一个不太幸运的名字,请参见 Unsharp, Wikipedia。 |
magick face.png -define convolve:scale=-100,200% \
-morphology Convolve 'Gaussian:0x2' face_unsharp.png
![[IM Output]](../static/img/convolve/face_unsharp.png)
请注意,虽然使用了负的核缩放因子,这两个数字仍然相加为 '100%',与上面完全一样。你也可以用合成混合做到同样的事。上面的示例实际上正是命名不佳的 "[-sharpen](https://imagemagick.org/command-line-options/#sharpen)" 运算符的工作方式,只是它只提供 'sigma ' 模糊控制,而没有其他操作控制。混合方式与上面给出的完全相同。你可以使用更快的两遍一维模糊核,但同样需要把混合操作作为单独步骤执行。 |
magick face.png \( +clone -blur 0x2 \) \
-compose Blend -define compose:args=-100,200 -composite \
face_unsharp_fast.png
![[IM Output]](../static/img/convolve/face_unsharp_fast.png)
你可能已经注意到,这几乎与柔化模糊完全相同,只是模糊图像是从原始图像中减去,而不是加上去。这是一种称为外推混合的混合方式,也就是超出普通 0 到 100 百分比范围的混合。同样,你可以简单指定要从原始图像中减去多少模糊图像。例如,让我们过度反锐化这幅图像,造成一些混叠和颜色失真。 |
magick face.png \( +clone -blur 0x2 \) \
-compose Blend -define compose:args=-200 -composite \
face_unsharp_200.png
![[IM Output]](../static/img/convolve/face_unsharp_200.png)
完整的 "[-unsharp](https://imagemagick.org/command-line-options/#unsharp)" 运算符提供了另一种控制。具体来说,它提供差异阈值,使锐化只在给定差异较大时应用,例如图像中真实边缘附近。该阈值可用于避免“锐化”细小缺陷,比如皱纹或相机噪声。图像反锐化通常在调整尺寸或扭曲图像后,配合非常小的模糊(sigma 约为 0.75)使用,以改善最终结果。相关示例请参见锐化缩放后的图像。用于图像锐化的另一种替代方案,是实际定位图像边缘并利用它们来锐化图像。详情见下文用边缘检测锐化图像。不过它通常被认为更慢,虽然实际上也慢不了多少。
边缘检测卷积(高通滤波)
边缘检测是卷积被大量使用的另一个领域。这里的任务是以各种方式突出或增强图像边缘。它可以用于尽可能准确地定位边缘,也可以用于确定每条边缘的角度或斜率方向。不过,如果图像中存在噪声,这项工作会困难得多,例如扫描仪、数码相机产生的噪声,甚至 JPEG 图像文件格式有损压缩造成的噪声。一般来说,较大的核能更好地处理噪声,但会牺牲边缘定位准确性;较小的核能产生锐利的边缘定位结果,但也会因为图像噪声产生更多伪结果。已有许多小型、知名的核被开发并用于边缘检测研究。它们大多以研究其数学原理或开发特定核类型的数学家命名。因此你会看到 '[Laplacian](#laplacian)'、'[Sobel](#sobel)' 和 '[Prewitt](#prewitt)' 等核。这些“命名”核通常很小,并使用整数定义,因此可以为了速度被内置到专门设计的优化软件和硬件中。也就是说,它们被称为“离散”核。因此,在使用它们时,你需要对核进行缩放或归一化。边缘检测还有一个副作用:它提供了锐化图像边缘的方法。
零和核
所有边缘检测核都有一个共同特征:它们都是零和的。这意味着它们包含负值,但核中所有值相加为零。对于平滑、平坦的彩色图像,使用这类核进行卷积会生成“零”图像,也就是黑色图像。不过对于其他任何图像,结果都会同时包含负值和正值。例如,这里我在一幅包含一些基本形状的图像上应用离散的 '[Sobel](#sobel)' 边缘检测器……
magick -size 80x80 xc:black \
-fill white -draw 'rectangle 15,15 65,65' \
-fill black -draw 'circle 40,40 40,20' shapes.gif
magick shapes.gif -define convolve:scale='!' \
-morphology Convolve Sobel shapes_sobel.gif
如果查看结果,你会看到这个核具有方向性,因为它只找到了垂直边缘(由零角度的 '[Sobel](#sobel)' 核定义)。不过它只找到了一组边缘,也就是从左到右由黑到白的“正”斜率。要得到“负”斜率,你需要使用核缩放设置让核取反。例如…… |
magick shapes.gif -define convolve:scale='-1!' \
-morphology Convolve Sobel shapes_sobel_neg.gif
![[IM Output]](../static/img/convolve/shapes_sobel_neg.gif)
对于 '[Sobel](#sobel)' 核,你也可以把它旋转 180 度,以得到与“缩放取反”相同的结果,但并非所有核都以这种方式对称。另一种解决方案是给结果添加输出偏置。也就是向结果图像添加 50% 灰度,使负值比它更暗,正值比它更亮。不过,你还需要缩放核,以确保结果不会被图像的“黑”和“白”范围裁剪。 |
magick shapes.gif -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Sobel shapes_sobel_bias.gif
magick shapes.gif -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Sobel -solarize 50% -level 50,0% \
shapes_sobel_abs.gif
![[IM Output]](../static/img/convolve/shapes_sobel_abs.gif)
关于更多结果处理技巧,尤其是涉及方向判定的技巧,请参见 '[Sobel](#sobel)' 核。使用输出偏置的另一个替代方案,是构建特殊的 HDRI 版 ImageMagick。它在内存中用浮点值存储图像,这意味着图像值不会因为使用整数而被“裁剪”或“舍入”。不过,即使使用这种特殊版本的 IM,在保存为普通图像文件格式之前仍需要对结果进行后处理,或者需要使用支持浮点的特殊图像文件格式。不过你不必担心中间图像结果中的裁剪或舍入效果,因此处理起来会更容易。
边缘检测核
LoG: Gaussian 的 Laplacian
LoG:{_radius_},{_sigma_}
'LoG',即 “Laplacian of a Gaussian”,是你能得到的最佳边缘检测核之一。它也被称为 “Mexican Hat” 核。基本上,它是一个经过 gaussian 模糊平滑的 '[Laplacian](#laplacian)' 微分(斜率)运算符。这又会去除图像中大部分噪声影响,而这种影响可以通过 'sigma ' 设置调整。该核包含负值,这些负值在强中心峰周围形成一个环。在上面显示的“核图像”中,负值显示为深色(接近黑色),并在边缘附近衰减到零(深灰色)。下面是它的效果,展示了它如何突出图像边缘。
magick face.png -bias 50% -morphology Convolve LoG:0x2 face_log.png
Laplacian 核没有方向性,但会在边缘两侧同时产生正值和负值的脊。要定位边缘,需要在正负脊之间寻找过零点,这种技术称为 Marr and Hildreth 边缘检测。这个核也非常适合锐化图像。
DoG: Gaussian 差分
DoG:{_radius_},{_sigma1_}[,{_sigma2_}]
这会生成一个 'DoG',也就是 “Difference of Gaussians” 核,其中由 'sigma1 ' 生成的 gaussian 会减去由 'sigma2 ' 生成的 gaussian。通常 'sigma2 ' 较大,这样核的“中心峰”为正。交换这两个数实际上会使得到的核取反。Gaussian 的 Laplacian 的一个主要批评点是,由于它是一条很特殊的数学曲线,实现起来很困难。它也不是一条文档非常充分的曲线。另一方面,它不能像 Gaussian 那样“分离”为更快的两遍方案(见 Gaussian 核与 Blur 核)。不过,通过生成两个 sigma 值略有不同(比例约为 1.6)的 '[Gaussian](#gaussian)' 核,并将它们相减,实际上可以生成接近 Gaussian 的 Laplacian 的近似结果。因此,'[DoG](#dog)' 比 '[LoG](#log)' 核更容易在硬件中生成。例如,这里我把核图像中的 '[LoG](#log)' 和 '[DoG](#dog)' 核并排放置以供比较。
如果查看 Difference of Gaussian, Wikipedia 页面,你会看到一些图表,它们也比较了 '[LoG](#log)'(或 “Mexican Hat”)与 '[DoG](#dog)' 的剖面,显示出匹配曲线之间非常非常细微的差异。关于如何把 LoG 的 sigma 映射为近似等价的 'DoG',还需要更多信息。如果你知道,请按页脚地址发邮件给我。 应用后的结果也非常相似。 |
magick face.png -bias 50% -morphology Convolve DoG:0,1.8,2.4 face_dog.png
![[IM Output]](../static/img/convolve/face_dog.png)
请注意,两个 'sigma ' 值都应定义,并且至少一个应为非零。任一 sigma 分量为零时,都等价于一个 '[Unity](#unity)' 核,意思是它保持图像不变。如果两个值都为零,两个 Gaussian 都会是 '[Unity](#unity)' 核,相减后会产生完全为零或黑色的结果(再加上任何偏置值)。当参数为 'Dog:0,0,_non-zero_ 时,DoG 会变成一个简单的高通滤波器,其定义为 'Unity' 核(产生原图)减去低通滤波核(模糊图像)。在这种情况下,sigma1=0 只是 'Unity' 核,而 sigma2=non-zero 是 Gaussian 低通(模糊)滤波核。因此,下面会生成一幅滤波值为 sigma2=2 的高通滤波图像。 |
magick face.png -bias 50% -morphology Convolve DoG:0,0,2 face_dog_unity.png
![[IM Output]](../static/img/convolve/face_dog_unity.png)
Photoshop 中滤波值 radius=2 的高通滤波器会产生相同结果。请注意,使用 'DoG:0,2,0' 会返回上一幅图像的基本取反版本(围绕输出偏置)。这个技术也可用于生成 3x3 的“各向同性 Laplacian”核,也就是在所有方向上产生相同结果的 '[Laplacian](#laplacian)' 核,而不是带有不均等的对角偏置。例如,radius=1(用于 3x3 核)且 sigma 为 1 会生成…… | |
magick face.png -define morphology:showKernel=1 -bias 50% \
-morphology Convolve DoG:1,0,1 face_laplacian_isotropic.png
使用 “Difference of Gaussians” 的另一个要点是,你可以使用快得多的 "[-blur](https://imagemagick.org/command-line-options/#blur)" 运算符(其内部使用 '[Blur](#blur)' 核)来生成相同结果。不过要做到这一点,你需要分别生成两幅“模糊”图像,然后在加入合适缩放和偏置的情况下相减。例如…… |
magick face.png \
\( -clone 0 -blur 0x1.8 \) \( -clone 0 -blur 0x2.4 \) -delete 0 \
-compose Mathematics -define compose:args=0,-4,4,0.5 -composite \
face_diff_of_blurs.png
![[IM Output]](../static/img/convolve/face_diff_of_blurs.png)
上面使用了特殊的数学合成方法,以避免在非 HDRI 版 IM 中进行图像相减时出现“裁剪”问题。更多细节参见添加带偏置的渐变。唯一另一个因素,是在相减期间使用了更大的缩放因子(Mathematics Compose 参数中的两个 '4')。这是因为将两个已归一化的模糊结果相减,并不会产生与在 '[DoG](#dog)' 核中把两条相减的 gaussian 曲线一起归一化时相同的(增大后的)结果幅度。不过,除了幅度之外,上面的示例图像等价于第一个 '[DoG](#dog)' 核结果,只是生成更快,尤其是在 sigma 值较大时。这就是重点:即使工作更多,这种复杂方法也比直接使用 '[DoG](#dog)' 或 '[LoG](#log)' 核更快。
离散 Laplacian 核
Laplacian:{_type_}
许多科学研究论文中都发表过各种小型 “Laplacian 核”。这里我提供了在学术文献中能找到的较常见形式的内置版本。这些核基本上是使用 '[LoG](#log)' 核计算出来的,但经过缩放,以便在小型核数组中使用离散整数值。这样可以使用专门生成的快速图像滤波器,只用整数数学处理图像数据。不过 ImageMagick 是更通用的图像处理器,因此并不提供这种超快速专用滤波器。但人们喜欢使用这些核,因为它们更容易理解,所以其中很多已经内置到 IM 中。这里提供的核都不能旋转,并且大多数是“各向异性”的,也就是说它们并非完美圆形,尤其是在对角方向上。不过,生成真正“各向同性 3x3 Laplacian 核”的方法可参见上一节('[DoG](#dog)' 核)。前两个 'Laplacian:0' 和 'Laplacian:1' 核是目前最常用的“离散 Laplacian 核”形式。它们非常小,意味着能非常精确地定位边缘,但也容易增强图像噪声。请注意,并非所有 'type ' 编号都已定义,留下了一些空位供未来定义更多离散核。所使用的编号是为了更好匹配由该编号定义的核而选择的。
Laplacian:0(默认)
8 邻域 Laplacian。可能是最常见的离散 Laplacian 边缘检测核。这里我使用 Show Kernel 提取“离散”且“未归一化”的核,然后展示带有输出偏置的归一化核结果。 | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:0 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:0 face_laplacian_0.png
有时 Laplacian 会产生比预期更复杂的结果,无论是上个示例中的离散 Laplacian,还是生成的 '[LoG](#log)' 或 '[DoG](#dog)'。在这种情况下,生成无偏置图像(不使用任何输出偏置)效果会更好。因此我们不加偏置重复上面的操作,只保留较亮的“正”边缘。 |
magick face.png -define convolve:scale='!' \
-morphology Convolve Laplacian:0 \
-auto-level face_laplacian_positives.png
![[IM Output]](../static/img/convolve/face_laplacian_positives.png)
在这个例子中,我们有深色(黑色)线条位于较亮(白色)的颜色上。这会导致滤波器把边缘“加倍”,如结果中所示。对于这幅图像,使用负缩放因子(保留负边缘而不是正边缘)在测试图像上似乎效果更好。 |
magick face.png -define convolve:scale='-1!' \
-morphology Convolve Laplacian:0 \
-auto-level face_laplacian_negatives.png
![[IM Output]](../static/img/convolve/face_laplacian_negatives.png)
可以看到,对于这幅图像,使用负缩放会产生更强的边缘,而且没有正结果产生的“双生”效果。这是因为所用图像是在白色背景上使用了“黑色”边缘线。顺便说一句:黄色星星周围出现蓝色边缘,是因为“黄色”星星与“白色”背景之间的差异相当于减去了蓝色。如果背景是黑色,你会得到黄色边缘颜色。
Laplacian:1
4 邻域 Laplacian。也非常常用。 | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:1 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:1 face_laplacian_1.png
结果没有那么强,但通常比 8 邻域 Laplacian 更清晰。
Laplacian:2
3x3 Laplacian,中心:4 边:1 角:-2 | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:2 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:2 face_laplacian_2.png
Laplacian:3
3x3 Laplacian,中心:4 边:-2 角:1 | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:3 null:
magick face.png -define convolve:scale='400%!' -bias 50% \
-morphology Convolve Laplacian:3 face_laplacian_3.png
这个核会突出对角边缘,并倾向于让垂直和水平边缘消失。不过你可能需要缩放结果(如我上面所做),才能让任何结果可见。
Laplacian:5
5x5 Laplacian | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:5 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:5 face_laplacian_5.png
Laplacian 核的经验法则是:核越大,结果越干净,尤其是在存在误差时。不过你也会得到更少细节。
Laplacian:7
7x7 Laplacian | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:7 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:7 face_laplacian_7.png
Laplacian:15
离散 5x5 LoG(Sigma 约为 1.4) | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:15 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:15 face_laplacian_15.png
Laplacian:19
离散 9x9 LoG(Sigma 约为 1.4) | |
magick xc: -define morphology:showKernel=1 -precision 2 \
-morphology Convolve:0 Laplacian:19 null:
magick face.png -define convolve:scale='!' -bias 50% \
-morphology Convolve Laplacian:19 face_laplacian_19.png
使用边缘检测锐化图像(增强原图边缘)
'[LoG](#log)' 和 '[DoG](#dog)' 核也可用于锐化图像,与使用模糊反锐化图像相对应。基本上,你只需要把核结果(包括负结果)加到原始图像上。要做到这一点很简单,只需向缩放因子中加入 100% 加权的 '[Unity](#unity)' 或 “Identity” 核。这正是它被提供出来的原因。例如…… |
magick face.png -define convolve:scale='100,100%' \
-morphology Convolve 'Log:0x2' face_sharpen.png
![[IM Output]](../static/img/convolve/face_sharpen.png)
相比反锐化技术生成的结果(右侧所示),这是一种范围更宽、更平滑的图像锐化。也就是说,这是图像真正的锐化,而不是通过减去模糊来伪造出的锐化。 | ![[IM Output]](../static/img/convolve/face_unsharp.png)
---|---
和前面一样,当只执行单遍卷积时,可以直接使用混合核。例如,锐化弱一些…… |
magick face.png -define convolve:scale='50,100%' \
-morphology Convolve 'Log:0x2' face_sharpen_50.png
magick face.png -define convolve:scale='150,100%' \
-morphology Convolve 'Log:0x2' face_sharpen_150.png
![[IM Output]](../static/img/convolve/face_sharpen_150.png)
你可以使用 Gaussian 差分的两遍方法来生成更快的多步骤锐化操作,但如上所示,这种方案需要 4 次卷积和一次单独的混合操作才能达到相同结果。以后:添加这个示例 正是这种复杂性,使得 Unsharpen 更常成为锐化图像的首选方法。但如你所见,对于较重的锐化过程,使用真正的锐化核优于反锐化锐化。不过对于轻微锐化,例如锐化缩放后的图像,使用 unsharp 没有问题。
方向性卷积(斜率与罗盘)
与上面类似,这些核会寻找图像颜色强度中的斜率,不过它们寻找的不是任意斜率,而是特定方向上的斜率。从数学上说,这称为“导数”,其实只是“斜率”的一种更正式说法。不过,了解不同方向上的斜率信息也很有用,因为你可以据此确定斜率或图像边缘的角度,即“罗盘”方向。也就是图像中某个特定点处斜率的二维方向。斜率还用于称为图像“浮雕”和“着色”的图像处理技术。目前还没有可“生成”的核,只有“命名”的预定义核,例如 Sobel 和 Roberts。不过我确信,浮雕和着色核的生成函数未来某个时候会被移入形态学/卷积核集合中。所以我们先看看一些“命名”的方向性核。
方向性核
Sobel
Sobel:{_angle_}
我们已经在上面讨论零和核时见过 'Sobel ' 核。这个核是一个原始的方向性(一次导数)核,用于返回某个特定正交方向上的边缘斜率。默认情况下,它配合 'convolve' 操作用于从左到右的斜率检测。结果本质上就是图像的 X 导数(斜率)。
magick -size 60x60 xc:black xc:white +append slope_positive.gif
magick slope_positive.gif -morphology Convolve Sobel slope_sobel.gif
| _如果查看这个核,你可能会觉得它声明反了。从某种意义上说,你确实是对的。不过这是由 'Convolve' 实际工作的方式造成的。
关于这种“反转”的更多细节,可以参见下文的 Convolve 与 Correlate。
_
---|---
请注意,这个核也能产生“负斜率”指示,但除非在该卷积操作中同时使用 '50%' 的偏置,否则无法看到它。虽然前一个示例中没有负斜率,但下面这个示例有,因此我也加入了偏置设置,这样你就能看到它。
magick -size 40x60 xc:black xc:white xc:black +append slope_both.gif
magick slope_both.gif -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Sobel slope_sobel_bias.gif
| _如果把这个核与 'Correlate' 一起使用,你会发现斜率会“匹配”核定义的方式。在这种情况下,对于从左侧高值(白色值)到右侧低值(黑色值)的斜率,你会得到正结果。在上面的示例中,两条线会互换。
不过上面是卷积,而不是相关(即匹配核)。关于差异的更多细节,同样请参见 Convolve 与 Correlate。_
---|---
可以看到,当斜率从黑到白上升时,我们现在得到一条白线(正斜率);当从白回到黑下降时,得到一条黑线(负斜率)。下面是在 face 图像上使用默认 'Sobel' 核的结果。
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Sobel face_sobel.png
请注意,sobel 以及大多数其他边缘检测核,往往会沿非常强的边缘产生 2 像素宽的响应,并在单像素宽线条上产生 3 像素响应。这比 laplacian 边缘检测器强得多。你可以使用 'angle ' 参数旋转这个核,通常按 90 度倍数旋转。不过你也可以按 45 度倍数旋转,即使它并非为此设计。这对于获得 45 度量化的方向导数,或从所有 45 度旋转导数结果的最大值中得到梯度幅值很有用。下面再次使用它,但旋转 90 度(从上到下)。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Sobel:90 face_sobel_90.png
使用 '[Sobel](#sobel)' 核收集图像所有边缘的一种方法,是在所有方向上应用该核 4 次,并收集看到的最大值(使用 Lighten 数学合成)。这是对梯度幅值的近似。 |
magick face.png -define convolve:scale='!' \
\( -clone 0 -morphology Convolve Sobel:0 \) \
\( -clone 0 -morphology Convolve Sobel:90 \) \
\( -clone 0 -morphology Convolve Sobel:180 \) \
\( -clone 0 -morphology Convolve Sobel:270 \) \
-delete 0 -background Black -compose Lighten -flatten \
face_sobel_maximum.png
![[IM Output]](../static/img/convolve/face_sobel_maximum.png)
你可以利用 IM 形态学的多核处理功能来简化上面的操作。也就是说,你可以创建 '[Sobel](#sobel)' 核所有 90 度旋转的旋转列表。 |
magick face.png -define convolve:scale='!' \
-define morphology:compose=Lighten \
-morphology Convolve 'Sobel:>' face_sobel_maximum_2.png
![[IM Output]](../static/img/convolve/face_sobel_maximum_2.png)
如果你想准确看到上面的操作在做什么,可以加入 Show Kernel 设置和 Verbose 设置。生成梯度幅值的一种更高效技巧,是利用 180 度旋转只会产生与核取反相同的结果,因此也会使结果取反。这样,使用 X 和 Y 导数(旋转 90 度的卷积),再用一些技巧取得卷积的绝对值,就能用更少处理达到这种结果。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
\( -clone 0 -morphology Convolve Sobel:0 \) \
\( -clone 0 -morphology Convolve Sobel:90 \) \
-delete 0 -solarize 50% -level 50,0% \
-compose Lighten -composite face_sobel_maximum_3.png
![[IM Output]](../static/img/convolve/face_sobel_maximum_3.png)
这通常足以满足大多数用途。要提取所有斜率更精确的幅值,可以对 X 和 Y 两个导数进行向量相加(依据勾股定理)。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
\( -clone 0 -morphology Convolve Sobel:0 \) \
\( -clone 0 -morphology Convolve Sobel:90 \) \
-delete 0 -solarize 50% -level 50,0% \
+level 0,70% -gamma 0.5 -compose plus -composite -gamma 2 \
-auto-level face_sobel_magnitude.png
![[IM Output]](../static/img/convolve/face_sobel_magnitude.png)
| _上面使用的 "[-gamma](https://imagemagick.org/command-line-options/#gamma)" 函数用于对 '[Sobel](#sobel)' 结果返回的值执行数学上的“平方”和“平方根”。详情请参见 Power Of Maths Function。
额外的 "[+level](https://imagemagick.org/command-line-options/#level)" 确保 Plus 合成不会溢出图像量子范围。详情请参见量子效应:non-HDRI 与 HDRI。_
---|---
除了幅值之外,你还可以从两个边缘检测结果中提取斜率方向。
magick -size 30x600 xc:'#0F0' -colorspace HSB \
gradient: -compose CopyRed -composite \
-colorspace RGB -rotate 90 rainbow.jpg
magick shapes.gif -define convolve:scale='50%!' -bias 50% \
\( -clone 0 -morphology Convolve Sobel:0 \) \
\( -clone 0 -morphology Convolve Sobel:90 \) \
-delete 0 \
\( -clone 0,1 -fx '0.5+atan2(v-0.5,0.5-u)/pi/2' rainbow.jpg -clut \) \
\( -clone 0,1 -fx 'u>0.48&&u<0.52&&v>0.48&&v<0.52 ? 0.0 : 1.0' \) \
-delete 0,1 -alpha off -compose CopyOpacity -composite \
face_sobel_direction.png
第一个 "[-fx](https://imagemagick.org/command-line-options/#fx)" 表达式使用 'atan()' 函数把 X,Y 向量转换成角度。随后用外部的彩虹渐变图像作为颜色查找表进行着色。第二个 "[-fx](https://imagemagick.org/command-line-options/#fx)" 表达式创建阈值化的透明蒙版,使没有斜率的区域变为透明。不过,由于上面的技术没有考虑斜率幅值,在真实图像上往往会产生大量混乱信息。下面是另一个更复杂的版本。它几乎把所有计算都放在绿色 'G' 通道中,从而把所需图像处理量减少到三分之一。然后它使用 HSB 色彩空间来创建方向(色相)和幅值(亮度)。 |
magick face.png -colorspace Gray -channel G \
-define convolve:scale='50%!' -bias 50% \
\( -clone 0 -morphology Convolve Sobel:0 \) \
\( -clone 0 -morphology Convolve Sobel:90 \) \
-delete 0 \
\( -clone 0,1 -fx '0.5 + atan2(v-0.5,0.5-u)/pi/2' \) \
\( -clone 0 -fill white -colorize 100% \) \
\( -clone 0,1 -fx 'hypot(u-0.5,v-0.5)*2' \) \
-delete 0,1 -separate +channel \
-set colorspace HSB -combine -colorspace RGB \
face_sobel_magnitude_n_direction.png
Roberts
Roberts:{_angle_}
'Roberts' 核比前面的 '[Sobel](#sobel)' 核简单得多,并能产生更紧凑的边缘定位(低至 2 像素)。当然,这也使它更容易受到噪声影响。通常这个核由更小的 2x1,甚至 2x2 核表示;不过通过把它实现为 3x3 核,我就可以按 45 度增量“循环”旋转该核。例如,下面是 45 度结果,通常称为 'Roberts-Cross' 核。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Roberts:45 face_roberts.png
![[IM Output]](../static/img/convolve/face_roberts.png)
和 '[Sobel](#sobel)' 一样,你也可以使用多核处理从所有方向生成最大斜率。不过这一次我们会得到 8 个 45 度方向,而不只是 4 个。 |
magick face.png -define morphology:compose=Lighten \
-morphology Convolve 'Roberts:@' face_roberts_maximum.png
![[IM Output]](../static/img/convolve/face_roberts_maximum.png)
请注意,将这个核旋转 180 度并不会生成取反结果(因为存在偏移)。因此你不能像处理 '[Sobel](#sobel)' 那样简单地合并一半数量的卷积。基本上,仅由一次 '[Roberts](#roberts)' 卷积生成的斜率,与实际图像对齐时偏移了半个像素。也就是说,计算出的斜率位于 '+1' 和 '-1' 值之间、像素之间的点上,但被存储在中心的 '-1' 像素中。不过这也意味着,通过保存像素周围的所有斜率并把它们相加,可以得到更小、更锐利的边缘检测结果,只用 2 个像素(而不是 4 个像素)突出锐利边界。
Prewitt
Prewitt:{_angle_}
'Prewitt' 核与 '[Sobel](#sobel)' 非常相似,不过在特定边缘检测的精确方向上更宽松。因此结果会稍微更模糊。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Prewitt face_prewitt.png
Compass
Compass:{_angle_}
这是 'Prewitt Compass' 核,据说它比 '[Sobel](#sobel)' 有更强的方向感。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Compass face_compass.png
Kirsch
Kirsch:{_angle_}
这是另一种方向感很强的边缘检测器。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Kirsch face_kirsch.png
Frei-Chen
这个内置项提供三组核。第一组是 '[Sobel](#sobel)' 的“各向同性”(方向均匀)变体,其中 '2' 值被替换为 2 的平方根。
Frei-Chen:[{_type_},][{_angle_}]
上面的核是默认的未加权核,也是 'Frei-Chen' 核的核心。 |
magick face.png -define convolve:scale='50%!' -bias 50% \
-morphology Convolve Frei-Chen face_freichen.png
![[IM Output]](../static/img/convolve/face_freichen.png)
和 '[Sobel](#sobel)' 一样,这个核应使用 90 度倍数的角度来应用。为了方便,提供了两个核(权重相同):一个像上面那样用于正交方向,另一个用于对角方向。
Frei-Chen:1
第三组类型由 9 个专门设计并加权的核组成,不仅用于特定方向上的边缘检测,也用于确定锐利边缘的实际角度。这里的 'type ' 可以是从 '11' 到 '19' 的数字,使你能够提取该组 9 个核中的任意一个。不过如果给出 'type ' 值 '10',则会得到包含全部 9 个预加权核的多核列表。
每个核都会应用到原图像,然后将这些结果相加,
生成边缘检测结果。
最好使用 [HDRI](basics.html#hdri) 版本的
ImageMagick 来完成。
magick image.png \
\( -clone 0 -morphology Convolve FreiChen:11 \) \
\( -clone 0 -morphology Convolve FreiChen:12 \) \
\( -clone 0 -morphology Convolve FreiChen:13 \) \
\( -clone 0 -morphology Convolve FreiChen:14 \) \
\( -clone 0 -morphology Convolve FreiChen:15 \) \
\( -clone 0 -morphology Convolve FreiChen:16 \) \
\( -clone 0 -morphology Convolve FreiChen:17 \) \
\( -clone 0 -morphology Convolve FreiChen:18 \) \
\( -clone 0 -morphology Convolve FreiChen:19 \) \
-delete 0 -background Black -compose Plus -flatten \
result.pfm
如果给出类型 10,则会生成包含上面全部 9 个加权核的多核列表。
这样就可以使用多核合成来完成上面的操作,
写法也简单得多...
magick image.png -define morphology:compose=Plus \
-morphology Convolve FreiChen:10 \
result.pfm
不过我还没有弄清这些结果原本应当表示什么。如果
有人知道这在实际中如何使用,或者有相关经验,请
告诉我,这样我就可以把它补充到这里,供其他人参考。
Correlate()
[Convolve](#convolve) 方法基本用于图像处理,而 Correlate 方法则更偏向于模式匹配。也就是说,它会用图像与核执行“互相关”(Cross-Correlation),在图像中寻找与给定形状匹配的位置。实际上,[Convolve](#convolve) 和 Correlate 是同一种操作。两者唯一的差别其实非常小,也就是对核做 x 和 y 方向的反射(等价于旋转 180 度)。我找到的关于相关与卷积如何工作以及二者差异的最佳说明,是 Class Notes for CMSC 426, Fall 2005, by David Jacobs。
卷积与相关(非对称核的影响)
如上所述,[Convolve](#convolve) 和 [Correlate](#correlate) 这两个运算本质上是相同的。事实上,用户常常说“卷积”,但真正指的是相关。而且相关其实更容易理解。对于围绕中心“原点”对称的核来说(这也是很常见的情况),这两种方法实际上相同。只有在使用非对称或不均衡的核时,差异才会显现。例如,这里我对那个“单像素”图像使用一个“L”形的“flat”核。
magick pixel.gif \
-morphology Convolve '3: 1,0,0
1,0,0
1,1,0' convolve_shape.gif
可以看到,[Convolve](#convolve) 将中心的单个像素扩展成围绕它的“L”形,即使原点本身并不是“neighbourhood”的一部分。现在用 [Correlate](#correlate) 再重复这个例子。
magick pixel.gif \
-morphology Correlate '3: 1,0,0
1,0,0
1,1,0' correlate_shape.gif
可以看到,[Correlate](#correlate) 也扩展了单个像素,形成一个“L”形,但它是“旋转后”的“L”形。这本质上就是两种方法的唯一区别。[Correlate](#correlate) 方法会按“原样”应用核,因此单个像素会扩展为“旋转后”的形状。另一方面,[Convolve](#convolve) 实际使用的是旋转 180 度后的核,所以每个像素都会扩展为相同的、未旋转的形状。如果想看一些关于 [Convolve](#convolve) 实际如何工作的好例子,我也推荐查看 EECE \ CS 253 Image Processing, Lecture 7, Spatial Convolution。其中第 22 页的图示实际将“反射后”的核应用到单个像素上,正如我上面所做的那样。
这种旋转差异看起来可能不大,但从数学角度看,它意味着卷积操作(用星号(*)符号表示)是可交换的:如果把核和图像都只看成数值数组(或两幅图像),那么 F * G == G * F。它也意味着卷积是可结合的:( F * G ) * H == F * ( G * H )。更多信息参见 Convolution Properties, Wikipedia。[Correlate](#correlate) 操作既不可交换,也不可结合,即使它与卷积关系很近(只差核的旋转)。基本上,[Convolve](#convolve) 更像数学中的“乘法”,而 [Correlate](#correlate) 不是。所有这些麻烦的例外,是所用核旋转 180 度后仍然完全相同。也就是说,核关于“原点”对称。在这种特殊情况下,两种操作会生成等价结果。容易让人混淆的是,用于卷积的大多数核,例如 Gaussian Blurs、Laplacian 等都是对称的,因此你实际做的是卷积还是相关并没有太大关系。于是人们就会在这些术语上变得松散而模糊。只有当核不对称时,例如形状搜索(见下一节),或像 Sobel 这样的方向性核,差异才真正重要。
相关与形状搜索
Correlate 方法(不旋转、按“原样”应用核 neighbourhood)的真正用途,是一种旧但简单的方法,用于定位大致匹配所提供核中形状的对象。例如,如果我们用 Correlate 配合一个“L”形核,尝试搜索上面卷积方法示例创建出的图像,会得到...
magick convolve_shape.gif -define convolve:scale='1!' \
-morphology Correlate '3: 1,0,0
1,0,0
1,1,0' correlate.gif
_旁注:注意,上面核图像中的“黑色”区域表示零值。这个核中没有负值,只有用于匹配形状的正值。
_
注意,我使用了 IM 的核归一化,以防最终结果过亮,让“峰值”淹没在一片白点中。可以看到,[Correlate](#correlate) 方法在核“原点”与图像中相同形状精确匹配的位置产生了最大亮度。但在只得到部分形状匹配的位置,它也会产生较暗的结果。匹配到的形状越多,像素就越亮。不过我要提醒你,虽然 [Correlate](#correlate) 在这个例子中成功了,但它并不是特别好的方法。例如,在亮度很高的区域中,它可能产生大量误匹配。这个问题可以通过对那些应当匹配图像深色背景的区域使用负值来缓解。也就是说,不匹配背景的区域应当让结果像素变暗。
magick convolve_shape.gif -define convolve:scale='1^' \
-morphology Correlate '4x5+2+2: 0 -1 0 0
-1 +1 -1 0
-1 +1 -1 0
-1 +1 +1 -1
0 -1 -1 0 ' correlate_pattern.gif
_旁注:为了让核图像更清楚,我生成核图像时让正值(前景)显示为白色,负值(背景)显示为黑色,零值(不关心)显示为透明。不过实际使用的核在数值上是完整定义的,并且其“neighbourhood”是一个完整矩形。
_
可以看到,匹配峰值明显得多,因为现在不仅匹配前景像素,也同时匹配背景像素。注意上面使用了特殊的归一化标志 ^。这很重要,因为它会分别归一化核中的正值和负值。也就是说,你希望同等地搜索前景像素和背景像素。这意味着,使用 IM 的 HDRI 版本,或适当地使用输出偏置(见上文),就可以搜索给定形状的正匹配和负匹配。例如,这里我把“L”形搜索应用到一幅同时包含正“L”形和负“L”形的测试图像上。(显示的图像已放大)
magick test_morphology.gif -bias 50% -define convolve:scale='50%^' \
-morphology Correlate '4x5+2+2: 0 -1 0 0
-1 1 -1 0
-1 1 -1 0
-1 1 1 -1
0 -1 -1 0 ' correlate_bias.gif
输出偏置让搜索的普通输出成为中间灰色,而匹配的形状会根据实际匹配“shape kernel”的像素数量,被赋予更亮或更暗的颜色。如果只检查输出图像的实际值,会得到一个纯白像素和一个纯黑像素,表示完美匹配。不过也会有相当多的近似匹配。如果我对负匹配或“黑色”匹配不感兴趣,可以去掉输出偏置和 50% 缩放因子,使未匹配像素为黑色、完美匹配为白色。一旦得到一幅 [Correlate](#correlate) 匹配图像,就需要尝试找出匹配“峰值”。这可以用另一次相关来完成,但效果并不总是很好。更好的方法是使用更精确的模式匹配方法,即 [HitAndMiss](morphology.html#hitmiss) morphology,并配合专门为此创建的特殊 [Peaks](morphology.html#peaks)。它会寻找任何只被较暗像素包围的单个像素。其他 [Peaks](morphology.html#peaks) 核也可用于寻找“更宽松”的匹配。
magick correlate_bias.gif -morphology hitandmiss peaks:1.9 \
-auto-level correlate_peaks.gif
这里就可以轻松找到形状最佳匹配所在的位置,不过匹配程度已经丢失。你可能还会想看看图像比较与子图像搜索中的“峰值查找”部分。同时也可以看看 Fred Weinhaus 的脚本 "[maxima](http://www.fmwconcepts.com/imagemagick/maxima/)"。以后:使用快速傅里叶变换进行归一化互相关,以便在非常大的图像(源图像和子图像)上生成非常快速的图像相关结果。
Correlation 与 HitAnd Miss Morphology
| 如果把我表示出的核图像与 Hit-And-Miss Morphology Method 使用的核进行比较,你会发现它们实际上表示的是同一件事。 | '[HitAndMiss](morphology.html#hitmiss)' |
'[Correlate](#correlate)' |
|---|---|---|
| 前景 | 值为 '1.0' |
值为 '1.0'(归一化前) |
| 不关心 | 值为 'Nan' 或 '0.5' |
值为 'Nan' 或 '0.0' |
| 背景 | 值为 '0.0' |
值为 '-1.0'(归一化前) |
| 结果 | 从背景的最大值中减去前景的最小值。因此只有精确匹配才会产生正结果,阈值化后会得到二值匹配图像。 | 生成一个范围,用来表示图像与形状匹配的接近程度。只要整体模式存在,某些背景像素的值可以大于前景像素。定位特定“匹配”峰值可能比较困难。也可以找到负匹配。 |
可以看到,它们彼此是对应的。因此,一种方法的核可以转换成另一种方法的核。不过 [Hit-And-Miss](morphology.html#hitmiss) 只会找到前景与背景差异明确的完全精确匹配。因此,与 [Correlate](#correlate) 相比,它对噪声和近似匹配的容忍度要低得多。另一方面,[Correlate](#correlate) 可以通过线性图像处理来执行,更具体地说,可以使用快速傅里叶变换。这可以让较大模式和核的模式匹配快得多,尤其是在涉及多个模式时,可以省去把图像和模式转换到频域的成本。它也可以用于实际图像,不过可能还需要一些预处理,并使用 HDRI。使用哪一种实际上取决于你,以及你想要得到什么结果:只要完美匹配,还是接受有更多误差的近似匹配,并可能使用更快的算法。注意,在较大图像中寻找小型彩色图像的精确匹配时,"magick compare" 程序的子图像定位功能会比 [Hit-And-Miss](morphology.html#hmt) 或 [Correlate](#correlate) 方法提供好得多的方式。这是因为它使用“颜色向量差异的最小二乘”来执行子图像匹配,可以为匹配结果生成更好的度量。不过它同样很慢,尤其是对于大型图像。 |
邻域计数
卷积较少见的一种用途称为邻域计数。也就是计算图像中每个像素点周围特定区域内存在多少像素。
计算邻域像素
基本上,使用一个非常简单的卷积核,就可以创建一幅图像,其中包含二值图像中特定点周围邻域像素数量的计数。用尺寸为 1.5 的 Ring Kernel 进行卷积,就可以得到邻域计数。下面计算一个小区域中每个像素的邻域数量,并显示处理前后各个像素的放大图(使用 Enlarge Image Script 生成)...
magick area.gif -define convolve:scale=\! \
-morphology Convolve Ring:1.5 neighbour.gif
可以看到,所有像素的灰度级都表示它们拥有多少邻域像素,其中也包括边缘处的任何虚拟像素邻域。如果想把当前像素也计入统计,可以改用 Square Kernel。通过适当的转换(包括 level 调整),并使用 PbmPlus 文件格式,如果需要的话,你可以把上面的灰度级 magick 成实际数字。
magick neighbour.gif +depth +level 0,8 pgm: | pnmnoraw | tail -n +4
如果想排除位于实际形状内部的像素,可以使用一个中心像素为强负值的核,然后对任何负结果执行 Clamp(如果使用 IM 的 HDRI 版本)。生成这种“正 1 围绕一个较大负中心”的核,一个简单方法是对标准的 Descrete Laplacian Kernel 进行负缩放。
magick area.gif -define convolve:scale=-1\! \
-morphology Convolve Laplacian:0 -clamp neigh_edge.gif
生命游戏
1970 年,英国数学家 John Horton Conway 在 Scientific American 上发表了一种特殊模拟,后来变得非常流行。它现在被称为 Conway's Game of Life。它基于一个点网格,其中每个点不是“活”的,就是“死”的。某个“cell”在下一“generation”中被归类为“活”还是“死”,取决于一组非常简单的规则,这些规则纯粹基于它周围有多少个存活的邻近 cell。
- neighbourhood 是每个“cell”周围的 8 个像素。
- 如果一个“live”cell 有 2 或 3 个邻居,它会继续存活。
- 如果一个“dead”cell 恰好有 3 个邻居,它会变成“live”(出生)。
- 否则,该 cell 会变成或保持“dead”。
这些规则作用在二值模式上的结果非常惊人:你会得到一些“cell”簇,它们似乎会扩张、收缩、振荡,甚至在网格上缓慢移动。它也成了理论研究的重要对象,人们想看看是否甚至能生成“DNA”式的较大“life”模式复制。旁注:这似乎是可能的,但过于脆弱而不实用,这也让现实中的 DNA 生命更加令人惊叹。它还激发了人们对其他形式元胞自动机的研究和实现的浓厚兴趣:用非常小尺度上的极其简单规则来生成和研究大尺度效应。这很像化学在原子和分子层级发生的事情,只是复杂度更高。下面用 ImageMagick 实现“Life”。首先,为了简单起见,我们让“live”cell 为白色,“dead”cell 为黑色。这样我们只需要统计每个 cell 周围 8 像素 neighbourhood 中的“白色”像素。不过也可以把黑白互换来实现,只是那样更难理解其过程。但是规则强烈依赖中心 cell 是活还是死。因此,我们需要把“dead”cell 的邻域计数与“live”cell 的邻域计数分开。这可以通过给中心 cell 一个大于所有邻居总和的值来简单完成。值“10”很适合:它是个好处理的整数,并且大于最大邻域计数 8。这样,“Game of Life”卷积核就等价于...
'3: 1, 1, 1 1, 10, 1 1, 1, 1'
其结果会是每个像素周围 8 个邻居(为“白色”的邻居)的计数;如果中心像素是“live”或“white”,则再加上 10。因此,对于死像素,这个核的值会是 0 到 8;对于活像素,则会是 10 到 18。如果我们用 20 对这个核进行缩放(实际上是用 0.05 缩放来生成渐变,见下文),就会生成一幅具有 21 个可能灰度级的图像。也就是说,灰度级 0 会得到“黑色”值,灰度级 21 会得到白色值,尽管这个核实际上不能生成这样的值。现在可以把“Game of Life ”规则编码到一幅颜色查找表图像中,从而把上述核生成的邻域计数“灰度级”magick 成符合“Life Rules”的相应“生死”结果。
magick -size 21x1 xc:black -fill white \
-draw 'point 3,0 point 12,0 point 13,0' \
life_clut.gif
enlarge_image -25.3 -ml 'Life Rules' life_clut.gif life_clut_enlarged.png
这幅图像非常小,所以我使用 Enlarge Image Script 生成一个较大的版本显示在上面,并让每个像素清楚分隔。基本上,前 10 个像素表示对“dead cell”应做什么,接下来的 10 个像素表示对“live cell”应做什么。左侧第一个白色像素(dead cell 周围邻居计数 = 3)表示“birth”,而右侧两个白色像素(live cell 旁边邻居计数为 2 和 3)允许已有的“live cell”继续存活。任何其他结果都会让结果保持黑色(dead)。颜色查找表长度为 21 个像素,因为我会除以缩放因子 20,这意味着可能生成 0 到 20 范围内的值,也就是 21 个不同灰度级。我们其实可以给中心(cell 的前一状态)使用不同于 10 的值,也可以给缩放使用不同于 20 的值,但这些数值更容易处理。总结来说,我们把卷积核除以 20,并使用一幅长度为 21 像素的 CLUT(配合整数插值),把卷积结果(灰度级)匹配到正确的输出颜色值。旁注:这个“Life Rules”CLUT 可以看作通用的元胞自动机规则表。用于邻域计数的 neighbourhood 模式也是元胞自动机的一部分。Cellular Automata Rules Lexicon 的 Rules Tables 部分也概述了这种技术,将其作为定义所列元胞自动机中通用“limit-free”形式的一种方式。基本上,几乎任何自动机都可以用这种 neighbourhood/表组合来定义,虽然大多数自动机会用更简化的形式定义。下面把它多次应用到一幅包含“life”模式的图像上,看看模式如何从一代变化到下一代,并检查它是否按预期工作。
magick -size 15x15 xc:black -fill white \
-draw 'line 3,2 3,4 line 10,10 12,10 point 10,11 point 11,12' \
life_gen_000.gif
magick life_gen_000.gif -define convolve:scale=0.05 \
-morphology Convolve '3:1,1,1 1,10,1 1,1,1' \
life_clut.gif -interpolate integer -clut \
life_gen_001.gif
magick life_gen_001.gif -define convolve:scale=0.05 \
-morphology Convolve '3:1,1,1 1,10,1 1,1,1' \
life_clut.gif -interpolate integer -clut \
life_gen_002.gif
magick life_gen_002.gif -define convolve:scale=0.05 \
-morphology Convolve '3:1,1,1 1,10,1 1,1,1' \
life_clut.gif -interpolate integer -clut \
life_gen_003.gif
magick life_gen_003.gif -define convolve:scale=0.05 \
-morphology Convolve '3:1,1,1 1,10,1 1,1,1' \
life_clut.gif -interpolate integer -clut \
life_gen_004.gif
请记住,上面的图像已经放大;要查看生成的原始“微小”图像,请点击放大图。可以看到,“Life”模式表现如预期(如果你熟悉这些模式的话)。左上角的“blinker”来回翻转,而底部的“glider”在我们迭代生命规则的 4 个“generation”中,向它移动了 1 个对角步。
这里还有一个较大的例子,称为 Gosper's Glider Gun,我从一个特殊的生命模式生成了 60 帧动画。实际使用的图像尺寸显示在右侧,但我对结果动画进行了缩放,以便观看。
magick glider_gun.gif life_pattern.gif
for i in `seq 59`; do
magick life_pattern.gif -define convolve:scale=0.05 \
-morphology Convolve '3:1,1,1 1,10,1 1,1,1' \
life_clut.gif -interpolate integer -clut \
-write life_pattern.gif miff:-
done | magick - -scale 500% \
-set delay 10 -layers Optimize -loop 0 glider_gun_anim.gif
magick glider_gun.gif -scale 500% life_pattern.gif
注意,glider 在底部边缘“爆炸”的原因,是 convolve 使用的默认虚拟像素处理,以及生命信息在超出图像边界后丢失。更多生命模式图像可以在 Life Pattern Catalog 中找到,不过要在上面的生命处理器中使用时,需要对图像重新着色(取反)。我把“将上述内容放入脚本,并为某个特定输入图像生成生命序列”这件事留作练习。
这只是 IM 可以处理的整个“Cellular Automata ”范围中的一个例子。当然,对于“Life”和“Cellular Automata”,有许多更快的专用程序,它们通常做的也是同一件事,但我想展示 IM 也足够灵活,可以完成这件事。由于结果是简单的二值图像,你还可以使用 IM 的 Morphology 方法,例如 Hit and Miss Pattern Searching 或互相关,来搜索特定生命模式,使得用 IM 进行生命研究更实用,虽然速度较慢。
![[IM Text]](../static/img/convolve/spread_kernel.txt.gif)
![[IM Output]](../static/img/convolve/kernel_spread.gif)
![[IM Output]](../static/img/images/face.png)
![[IM Output]](../static/img/convolve/face_spread.png)
![[IM Text]](../static/img/convolve/kernel_spread_norm.txt.gif)
![[IM Output]](../static/img/convolve/kernel_spread_norm.gif)
![[IM Output]](../static/img/convolve/face_spread_norm.png)
![[IM Output]](../static/img/convolve/kernel_unity.gif)
![[IM Output]](../static/img/convolve/face_unity.png)
![[IM Output]](../static/img/convolve/face_mean.png)
![[IM Output]](../static/img/convolve/shape.png)
![[IM Output]](../static/img/convolve/shape_mean_square.png)
![[IM Output]](../static/img/convolve/shape_mean_disk.png)
![[IM Text]](../static/img/convolve/kernel_gaussian.txt.gif)
![[IM Output]](../static/img/convolve/face_gaussian.png)
![[IM Text]](../static/img/convolve/kernel_blur.txt.gif)
![[IM Text]](../static/img/convolve/kernel_comet.txt.gif)
![[IM Output]](../static/img/convolve/face_blur_x2.png)
![[IM Output]](../static/img/convolve/face_strong_blur.png)
![[IM Output]](../static/img/convolve/face_soft_blur.png)
![[IM Output]](../static/img/convolve/shapes.gif)
![[IM Output]](../static/img/convolve/shapes_sobel.gif)
![[IM Output]](../static/img/convolve/shapes_sobel_bias.gif)
![[IM Output]](../static/img/convolve/kernel_log.gif)
![[IM Output]](../static/img/convolve/face_log.png)
![[IM Text]](../static/img/convolve/face_laplacian_isotropic.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_isotropic.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_0.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_0.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_1.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_1.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_2.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_2.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_3.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_3.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_5.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_5.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_7.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_7.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_15.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_15.txt.gif)
![[IM Text]](../static/img/convolve/face_laplacian_19.png)
![[IM Text]](../static/img/convolve/kernel_laplacian_19.txt.gif)
![[IM Output]](../static/img/convolve/face_sharpen_50.png)
![[IM Text]](../static/img/convolve/kernel_sobel.txt.gif)
![[IM Output]](../static/img/convolve/slope_positive.gif)
![[IM Output]](../static/img/convolve/kernel_sobel.gif)
![[IM Output]](../static/img/convolve/slope_sobel.gif)
![[IM Output]](../static/img/convolve/slope_both.gif)
![[IM Output]](../static/img/convolve/slope_sobel_bias.gif)
![[IM Output]](../static/img/convolve/face_sobel.png)
![[IM Output]](../static/img/convolve/face_sobel_90.png)
![[IM Output]](../static/img/convolve/face_sobel_direction.png)
![[IM Output]](../static/img/convolve/face_sobel_magnitude_n_direction.png)
![[IM Text]](../static/img/convolve/kernel_roberts.txt.gif)
![[IM Text]](../static/img/convolve/kernel_prewitt.txt.gif)
![[IM Output]](../static/img/convolve/face_prewitt.png)
![[IM Text]](../static/img/convolve/kernel_compass.txt.gif)
![[IM Output]](../static/img/convolve/face_compass.png)
![[IM Text]](../static/img/convolve/kernel_kirsch.txt.gif)
![[IM Output]](../static/img/convolve/face_kirsch.png)
![[IM Text]](../static/img/convolve/kernel_freichen.txt.gif)
![[IM Text]](../static/img/convolve/kernel_freichen1.txt.gif)
![[IM Text]](../static/img/convolve/kernel_freichen2.txt.gif)
![[IM Output]](../static/img/convolve/convolve_shape.gif)
![[IM Output]](../static/img/convolve/correlate_shape.gif)
![[IM Output]](../static/img/convolve/test_mag.gif)
![[IM Output]](../static/img/convolve/correlate_bias_mag.gif)
![[IM Output]](../static/img/convolve/kernel_peaks.gif)
![[IM Output]](../static/img/convolve/correlate_peaks_mag.gif)
![[IM Output]](../static/img/convolve/area_enlarged.png)
![[IM Output]](../static/img/convolve/neighbour_enlarged.png)
![[IM Text]](../static/img/convolve/neighbour.txt.gif)
![[IM Output]](../static/img/convolve/neigh_edge_enlarged.png)
![[IM Output]](../static/img/convolve/life_clut_enlarged.png)
![[IM Output]](../static/img/convolve/life_gen_000_mag.gif)
![[IM Output]](../static/img/convolve/life_gen_001_mag.gif)
![[IM Output]](../static/img/convolve/life_gen_002_mag.gif)
![[IM Output]](../static/img/convolve/life_gen_003_mag.gif)
![[IM Output]](../static/img/convolve/life_gen_004_mag.gif)
![[IM Output]](../static/img/convolve/life_pattern.gif)
![[IM Output]](../static/img/convolve/glider_gun_anim.gif)