⚠️ 这是一个非官方翻译网站,与 ImageMagick Studio LLC 无关。准确信息请参阅原文(https://usage.imagemagick.org/compare/index.html)

ImageMagick 示例 -- 图像比较

ImageMagick 示例前言与索引
图像比较的方法 -- 有什么不同?

图像比较的方法

Compare 程序

"magick compare" 程序提供了一种简单方式,用来比较两幅相似图像,并判断图像究竟有多“不同”。例如,这里有一个动画“袋子”的两帧,我把它们交给 "magick compare",用来高亮显示发生变化的区域。

  magick compare bag_frame1.gif bag_frame2.gif  compare.gif

[IM Output] [IM Output] [IM Output]

如你所见,会得到一幅白色和红色的图像,其中带有第二幅图像的“阴影”。它清楚地显示出两幅图像之间发生变化的三个区域。你当然也可以不保存这幅“compare”图像,而是直接查看它;我觉得这样更方便,可以输出到特殊的 "[x:](files.html#x)" 输出格式,或者使用 "[display](basics.html#display)" 程序。例如……

  magick compare bag_frame1.gif bag_frame2.gif  x:
  magick compare bag_frame1.gif bag_frame2.gif  miff:- | display

从 IM v6.4 开始,你可以把差异颜色从红色改成其他更有意思的颜色…… |

  magick compare bag_frame1.gif bag_frame2.gif \
          -highlight-color  SeaGreen  compare_color.gif

[IM Output]
从 IM v6.4.2-8 开始,你也可以指定另一种颜色。 |

  magick compare bag_frame1.gif bag_frame2.gif \
          -highlight-color  SeaGreen  -lowlight-color PaleGreen \
          compare_colors.gif

[IM Output]
如果你不想要第二幅图像的那层“阴影”,从 IM v6.4.2-8 开始,可以在选项中加入 "-compose src" 来去掉它。 |

  magick compare bag_frame1.gif bag_frame2.gif \
          -compose Src compare_src.gif

[IM Output]
同时使用这三个额外设置,就可以生成变化像素的灰度遮罩…… |

  magick compare bag_frame1.gif bag_frame2.gif \
          -compose Src -highlight-color White -lowlight-color Black \
          compare_mask.gif

[IM Output]
不过要注意,这个遮罩表示的是任何差异,哪怕是最微小的差异。例如,你可以看到把图像保存为有损 JPEG 格式所产生的所有细小差异……

  magick bag_frame1.gif  bag_frame1.jpg
  magick compare bag_frame1.gif bag_frame1.jpg   compare_lossy_jpeg.gif

[IM Output] [IM Output] [IM Output]

如你所见,虽然你几乎看不出这幅图像的 GIF 版本和 JPEG 版本之间有什么差别,"magick compare" 仍然报告了大量差异。通过使用较小的模糊系数,可以让 IM 忽略两幅图像之间这些细微差异。 |

  magick compare -metric AE -fuzz 5% \
          bag_frame1.gif bag_frame1.jpg   compare_fuzz.gif

[IM Text]

[IM Output]
这表明,实际差异中的大部分都只是很小的差别。"[-metric](https://imagemagick.org/command-line-options/#metric)" 设置的特殊值 'AE'("Absolute Error" 计数的缩写)会把当前模糊系数下被遮罩的实际像素数报告出来(输出到标准错误)。

差异图像

如果想更准确地了解图像到底有多不同,通常最好取得更精确的 '[difference](compose.html#difference)' 合成图像…… |

  magick composite bag_frame1.gif bag_frame1.jpg \
            -compose difference  difference_jpeg.gif

[IM Output]
如你所见,"magick compare" 显示 JPEG 在两幅图像之间造成了许多差异,但 '[difference](compose.html#difference)' 合成图像相当暗,说明所有差异都相对较小。如果结果图像黑得难以看出差异,可以考虑对图像进行正规化(使用数学上更正确的 "[-auto-level](https://imagemagick.org/command-line-options/#auto-level)"),以增强结果。 |

  magick difference_jpeg.gif  -auto-level  difference_norm.gif

[IM Output]
这仍然显示大多数差异都很轻微,最大的差异出现在图像的锐利边缘附近,而 JPEG 图像文件格式并不擅长处理这些位置。另一方面,如果取得动画原始两帧之间的差异图像,即使不做任何增强,也能看到两幅图像之间非常明显的差异。 |

  magick composite bag_frame1.gif bag_frame2.gif \
            -compose difference  difference_frames.gif

[IM Output]
注意,由于 '[difference](compose.html#difference)' 合成方法是结合的,上面示例中两幅图像的顺序并不重要。不过与 "magick compare" 不同,你可以比较尺寸不同的图像,此时目标图像会决定最终差异图像的大小。difference 方法和 "magick" 程序一起使用时更有用,因为你可以在保存或显示结果之前继续处理结果图像。例如,你可以对每个色彩通道做阈值处理并合并它们,生成一幅遮罩,用来表示两幅图像之间任何颜色发生变化的像素。 |

  magick bag_frame1.gif bag_frame2.gif -compose difference -composite \
          -threshold 0 -separate -evaluate-sequence Add \
          difference_mask.gif

[IM Output]
这基本上就是 "magick compare" 程序所做的事情,只是可以更细致地控制颜色和输出样式。不过你也能看到,它往往会找出两幅图像之间最小的变化。如果图像来自有损图像文件格式,例如 JPEG,或者是需要减色和抖动(颜色量化)的 GIF 图像,那么它很可能会把整幅图像都标记为发生变化。因此它通常并不太有用。为了得到更好的结果,你可以尝试弄清像素颜色究竟相差多少。例如,我们可以把结果转成灰度,以取得比彩色结果更适合比较的图像。 |

  magick bag_frame1.gif bag_frame2.gif -compose difference -composite \
          -colorspace Gray   difference_gray.gif

[IM Output]
现在,与 "magick compare" 不同,差异图像会在最终结果中显示两幅图像混合后的样子。例如,看看猫额头上似乎出现的那个奇怪“护符”。它原本是第一幅图像中袋子的提手。这种合并会让你很难准确判断自己看到的究竟是什么差异,因为图像中的新增和移除都会混在一起。由于这些细节会让人困惑,对我们人类来说,"magick compare" 通常是更好的查看方式;而 'difference' 图像则更适合后续处理。不过,对差异图像做灰度化只是把 RGB 距离求平均(实际上是加权平均)。结果是,单个比特的颜色差异可能会因为量子舍入效应而丢失。如果图像之间哪怕最小的差异也很重要,更好的方法是把差异图像中分离出来的色彩通道相加,确保捕获所有差异,包括最微小的差异。 |

  magick bag_frame1.gif bag_frame2.gif -compose difference -composite \
          -separate -evaluate-sequence add   difference_add.gif

[IM Output]
上面产生的差异值称为“曼哈顿距离”度量。也就是说,在只能沿正交(或轴向)方向移动时,两幅图像中对应颜色之间的距离。不过请注意,较大的差异可能会被裁剪(或烧掉),因为除非使用 HDRI 版本的 IM,否则它可能超过像素数据的“Quantum Range”或整数限制。进一步来说,你可以通过平方和平方根实现毕达哥拉斯距离或欧氏距离,从而得到颜色向量距离。 |

  magick bag_frame1.gif bag_frame2.gif -compose difference -composite \
           -evaluate Pow 2 -separate -evaluate-sequence Add -evaluate Pow 0.5 \
           difference_vector.gif

[IM Output]
这实际上类似于 'fuzz' 系数在阈值处理过程中实际测量的内容(不涉及透明度时)。不过 'fuzz' 还会在相加前把平方值除以 3,以确保结果不超过图像颜色范围限制。这样做意味着,只有在相反的原色和二次色之间存在差异时,例如蓝色像素和黄色像素之间,结果中才会得到纯“白”像素。那么我们也做一下这个缩放…… |

  magick bag_frame1.gif bag_frame2.gif -compose difference -composite \
          -evaluate Pow 2 -evaluate divide 3 -separate \
          -evaluate-sequence Add -evaluate Pow 0.5 \
          difference_vector_scaled.gif

[IM Output]
这实际上与你通过 "-colorspace Gray' 差异图像(如上)得到的结果非常相似,但它对颜色差异的表示要准确得多。你也可以省略第二个 'Pow 0.5' 修改,这样会得到一幅平方差异图像。还有其他颜色距离度量,可以在 Color Difference, Wikipedia 页面阅读。它们大多会生成向量差异(见上一个例子),但使用不同的色彩空间,例如 LAB 或 LUV。不过,这在比较真实世界中的颜色差异时会更重要(例如人类视觉差异的度量)。另请参见背景移除,其中使用类似上面的差异图像来进行背景移除。你也可以看看这个关于变化检测的外部页面,作为实际用例。

闪烁比较

查看图像差异的另一种替代 "magick compare" 程序的方法,是让相似图像以相当快的速度交替闪烁进行比较。 |

  magick -delay 50 bag_frame1.gif bag_frame2.gif -loop 0 flicker_cmp.gif

[IM Output]
为了让这件事更容易,我写了一个名为 "**[flicker_cmp](../static/img/scripts/flicker_cmp)**" 的脚本,用来显示两幅给定图像之间来回切换的动画,就像上面的示例一样。它还会在显示图像的底部添加标签,说明任意时刻你正在看的具体是哪一幅图像。

比较动画

你也可以使用一种特殊的“胶片条”技术,比较两个已合并帧动画中的差异。类似的“append”技巧见并排 Append。基本做法是把所有动画帧追加到一起,形成一幅大而长的图像。然后比较这两幅图像,再把动画重新裁剪成单独的帧来创建新动画。例如……

    magick \( anim1.gif -coalesce -append \) \
            \( anim2.gif -coalesce -append \) miff:- | \
      magick compare - miff:- |\
        magick - -crop 160x120 +repage anim_compare.gif

结果是一段“compare”图像的动画:第二个动画的“变暗”版本上叠加了高亮,显示出不同的部分。注意,要让它工作,"[-crop](https://imagemagick.org/command-line-options/#crop)" 的尺寸必须匹配动画的原始尺寸。另外,动画原本可能具有的可变时间延迟会丢失,转而使用基于原始动画第一帧的固定时间延迟。另一种对动画有用的图像比较技术,是定位动画中所有发生变化的区域,从而把动画中互不相连的部分分开。这样你就可以把一个大动画拆成多个较小的动画。参见拆分动画


比较统计

两幅图像究竟有多不同?

施工中

来自差异图像的统计……

  以下命令会输出详细信息,并只抽取其中包含图像通道统计的
  那一节……

    magick image1 image2 -compose Difference -composite \
            -colorspace gray -verbose  info: |\
       sed -n '/statistics:/,/^  [^ ]/ p'

  括号中的数字(如果存在)是归一化到 0 到 1 之间的值,
  因而不依赖你的 IM 的 Q 级别。
  如果没有这些数字,你应该考虑升级 IM。

  要以百分比取得平均(mean)灰度级,可以使用这个
  命令……

     magick image1 image2 -compose Difference -composite \
           -colorspace gray -format '%[fx:mean*100]' info:

  如果不需要百分比,可以使用更简单的……

     magick image1 image2 -compose Difference -composite \
           -colorspace gray -format '%[mean]' info:


Compare 程序统计……

   使用 -metric 可以得到实际的平均差异值

     magick compare -metric MAE image1 image2 null: 2>&1

   添加 -verbose 会为每个单独通道
   提供更具体的信息。

      magick compare -verbose -metric MAE rose.jpg reconstruct.jpg null: 2>&1

      Image: rose.jpg
      Channel distortion: MAE
        red: 1884 (0.028748)
        green: 1532.34 (0.023382)
        blue: 1691.25 (0.0258068)
        all: 1702.53 (0.025979)

   有许多不同的度量可供选择。
   使用同一组测试图像(大致相同)

   像素数
      AE ...... Absolute Error 不同像素数量的绝对误差计数(0=相等)

                这个值可以使用 -fuzz 设置进行阈值化,
                只统计大于阈值的像素。

                从 IM v6.4.3 开始,-metric AE 计数会受 -fuzz 影响。
                因此你可以把“细微”差异从这个计数中排除。

                magick -metric AE -fuzz 10% image1.png image2.png null:

                哪些像素不同,可以通过输出图像查看
                (上面的命令会忽略输出图像)。

                这是唯一会受 'fuzz' 影响的度量。

   最大误差(任意一个像素)
      PAE ..... Peak Absolute Error   (在通道内,用于 3D 色彩空间)
      PSNR .... Peak Signal to noise ratio(用于图像压缩论文)
                平均平方差与任意两幅图像之间可能存在的最大
                平均平方差之比,
                用分贝值表示。

                PSNR 越高,图像越接近;
                最大差异发生在 1。PSNR 为 20 表示
                差异是最大值的 1/100。

   平均误差(覆盖所有像素)
      MAE ..... Mean absolute error    (平均通道误差距离)
      MSE ..... Mean squared error     (平均平方误差距离)
      RMSE .... (平方)均方根误差 -- 即:  sqrt(MSE)


   专用度量
      MEPP .... 归一化平均误差 AND 归一化最大误差
                对没有透明度的图像来说,这些应该与 '-fuzz' 系数
                直接相关。

                有透明度时会变得困难,因为遮罩应该影响被比较的
                像素数,从而影响 'mean',
                但目前并没有这样做。

      FUZZ      考虑透明度的 fuzz 系数差异

      NCC       归一化互相关(1 = 相似)

   我在测试图像上得到了以下结果……

    _metric_|__low_Q_jpeg__|__black_vs_white__
     PSNR   | 29.6504      | 0
     PAE    | 63479        | 65535
     MAE    | 137.478      | 65535
     MSE    | 4.65489e+06  | 4.29484e+09
     RMSE   | 2157.52      | 65535


   第一列数字是对带有低质量 JPEG 差异的图像进行比较,
   其中测试图像被读入后
   用非常低的 -quality 设置重新保存。

   第二列 "black vs white" 是一幅纯黑图像
   和一幅纯白图像的比较。如果比较时忽略
   图像的“平均颜色”,那么结果值会非常小。
   这似乎只适用于 PSNR 度量,
   因为其他所有度量都产生了最大差异值。

   e+06 是科学记数法,表示小数点需要移动多少位。
   例如:   4.65489e+06  -->  4,654,890.0
   因而它大约等于 400 万,也是 2157.52 的平方

   警告:数字依赖编译时设置的 IM Quality (Q) 级别。
   质量越高,数字越大。只有 PSNR 应该
   不受此影响。因此 IM 也会给出一个
   不受编译时质量设置影响的“归一化”结果,
   不过仍可能有轻微的“量子”或“整数舍入”影响。

   我还没有弄清现有 "-define" 选项中是否有任何选项
   可用于 "compare" 功能。


   注意,对于不透明颜色,AE -fuzz 与 RMSE 距离是等价的。
   但是,当涉及透明颜色时,AE fuzz 系数测试
   会把两种不同的完全透明颜色视为相同,
   而 RMSE 会把它们视为不同!

   例如……
   对 AE 来说,完全透明的白色和完全透明的黑色是相同的。

     magick compare -metric AE xc:#0000 xc:#FFF0 null:
     0 (0)

   对 RMSE 来说,它们也相同,因为它们都是完全透明的颜色

     magick compare -metric RMSE xc:#0000 xc:#FFF0 null:
     0 (0)

Dissimilarity-threshold

  如果得到 'too different' 错误,可以用下面的方式禁用它……
      -dissimilarity-threshold 1.0

  但这个阈值究竟是什么?

更多信息请参见我非常古老的原始文本笔记…… Image Comparing, Tower of Computational Sorcery


子图像和形状匹配

施工中

使用 "compare -subimage-search" 选项……

  magick compare -subimage-search  large_image.png  sub-image.png  results-%d.png

  这会生成两幅图像
    results-0.png
        显示匹配位置
    results-1.png
        是一个可能的左上角位置映射,显示子图像在该位置
        匹配得有多好。

  注意,第二幅图像更小,因为它只包含左上角位置。
  因此它的尺寸是   large_image - small_image + 1

  不过,这种搜索基于颜色向量差异,所以会产生非常准确的
  颜色比较。

  这个搜索基本上会在大图中的每一个可能位置比较小图。
  因此它很慢!**非常非常慢。**。

  最好的思路是先比较非常非常小的子图像
  来找出可能位置,再用这些位置逐一做差异比较,
  以得到更准确的匹配。

  请看看这个脚本
    ../static/img/scripts/overlap
  以及相关讨论
    [Overlapped Images](https://magick.imagemagick.org/viewtopic.php?f=1&t=22526&p=95286)
  它讨论了如何定位一幅图像中的“高熵”子图像,并在第二幅
  图像中搜索可能匹配,从而发现两幅图像之间的重叠偏移量,
  并把这些图像合并成一幅更大的图像。

  另一段讨论使用子图像搜索在更大的图像中寻找平铺图案,
  目标是生成可平铺图像
    [Stitching image over a canvas](https://magick.imagemagick.org/viewtopic.php?f=1&t=22860)


  使用 RMSE 和新的 -grayscale 函数,将分离的颜色差异通道
  结果合并到最终图像中的示例

    magick large_image.png small_image.png miff:- |
      magick compare -metric RMSE -subimage-search - miff:- |
        magick - -delete 0 -grayscale MS show:


相似度阈值

  很多时候,人们只关心第一个匹配到的结果。
  一旦找到这个“好”匹配,就没有必要继续
  搜索另一个匹配。-similarity-metric 定义了你
  认为什么算是好匹配。

  "-similarity-threshold 0.0" 会在找到第一个“完美”匹配时
  中止,而 "-similarity-threshold 1.0"(默认值)永远不会匹配,
  会搜索每一个可能点。介于两者之间的值会设置一个只针对颜色的
  'fuzz' 系数,用来表示你认为可接受的匹配。

  注意,如果子图像搜索被中止,第二幅“映射”图像
  将只包含部分结果,
  (只显示 compare 中止搜索之前的结果)。


一些基本的子图像搜索示例……

  抓取终端窗口的屏幕截图("screen.png"),
  并裁剪出单个字母或单词的图像("letter.png")。

  只报告第一个匹配……为了速度,
  找到第一个匹配后立即中止。
  不输出不完整的图像结果。

     magick compare -subimage-search -metric AE -similarity-threshold 1.0 \
                   screen.png letter.png null: 2>&1

  注意,速度会高度依赖
  第一个匹配在图像中的位置。

  找出与该图像完全相同的所有出现位置,
  并作为图像输出(匹配处为白点,其他地方为黑色)

     magick compare -subimage-search -metric AE \
                   screen.png letter.png miff:- 2>/dev/null |
       magick - -delete 0 show:

  抽取所有匹配字母(白点)的坐标列表
  (作为枚举像素列表,忽略任何黑色内容)

     magick compare -subimage-search -metric AE \
                   screen.png letter.png miff:-  2>/dev/null |
       magick - -delete 0 txt:- | grep -v '#000000'

  只要坐标列表

     magick compare -subimage-search -metric AE \
                   screen.png letter.png miff:-  2>/dev/null |
       magick - -delete 0 txt:- | sed -n '/#FFFFFF/s/:.*//p'



非 ImageMagick 的子图像搜索解决方案……

  "xautomation" 包中的 "visgrep"。

    这是一个简单得多的子图像搜索程序,只输出匹配位置的
    坐标列表(甚至可以是多个子图像匹配)。
    因为它简单得多(适合近似精确匹配),也不会试图生成
    用于进一步研究的“结果图像”,所以速度也快得多。

    例如……

      visgrep screen.png letter.png

    计时结果
      使用 "compare" 只取得第一个匹配        0.21 seconds
      使用 "compare" 取得一幅“结果图像”       1.56 seconds
        同上,但抽取坐标列表                  1.76 seconds
      使用 "visgrep" 取得所有匹配坐标          0.09 seconds



子图像搜索的其他方法……

HitAndMiss 形态学

  这本质上是一种二值匹配,你定义哪些像素
  必须是“背景”,哪些必须是前景。
  不过它也允许你定义一些区域,
  在这些区域中,结果是前景还是背景都无所谓。

  基本上是一种二值模式搜索方法。

Correlate(Convolve 的变体)

  这类似于 Hit and Miss,但使用灰度值。前景使用正值,
  背景使用负值,不关心的部分使用零。
  不过它仅限于灰度图像。

  参见[相关与形状搜索](convolve.html#correlate_search)。

  这两者基本上都和前面的子图像比较一样慢,
  但在颜色方面不那么准确。不过,由于它能
  为子图像指定形状(不关心区域),
  因此作为搜索方法很有用。

  不过你需要用 magick 将子图像转换成一个“核”,也就是
  浮点值数组,而不是实际图像。


FFT Convolve(NCC)

  快速傅里叶变换是一个较慢的运算符,
  但通常比前面两种方法快好几个数量级。
  原因是频域中的卷积只是逐像素的
  直接乘法。

  只要把要搜索的子图像旋转 180 度,
  'Convolve' 方法就可以转换成 'Correlate'。
  参见 [Correlate](convolve.html#correlate)。

  基本上,通过把图像转换到“频率”域,你可以非常非常快速地
  进行子图像搜索,尤其是在子图像较大、甚至可能和原图一样大时,
  相比前面的方法优势很明显!

  我认为这已经作为 NCC compare 度量加入了。


<a id="peak_finding"></a>
峰值查找与抽取(用于近似部分匹配)……

  比较图像之后,通常会得到某种“概率映射”,
  用来定义匹配有多“完美”。

  现在要做的是找到最佳匹配,或者图像中的多个匹配。
  也就是说,你要定位结果映射中的主要“峰值”,
  并抽取实际位置。

  * 使用拉普拉斯卷积核

    要得到结果,你需要找到图像中的“峰值”,
    它们也未必就是最亮的点。
    可以通过卷积图像来做到这一点:
    从中心像素中减去周围像素的平均值。
    因为我们只需要正结果,所以用 bias 去掉负结果。

      magick mandril3_ncc1.png \
              -bias -100% -convolve Laplacian:0 result.png

    做阈值处理并把它作为遮罩,就能只抽取那些像素。

      magick mandril3_ncc1.png \
              \( +clone -bias -100% -convolve Laplacian:0 -threshold 50% \) \
              -compose multiply -composite \
              txt:- | grep -v black

    问题是你可能在峰值处得到一簇点,
    而不是一个明确的像素,
    尤其是在两个峰值像素被很低的值包围时。

  * 使用 Peaks Hit and Miss 形态学核

      magick mandril3_ncc1.png \
              -morphology HMT Peaks:1.5 result.png

    问题是,如果得到两个值完全相同的峰值像素
    (前景和背景之间没有间隙),它可能不会产生结果

    不过还有其他“peak”核
    仍然可以定位这样的峰值簇。

  * Dilate 并比较

    对图像 Dilate(扩展最大值)3 次,然后与原始图像比较。
    任何位于膨胀核尺寸区域内的峰值
    (7 像素见方)都会保持同样的值。
    把所有显示出差异的像素设为零。

    HugoRune 的方法(IM 讨论主题 14491)

  * 循环匹配并移除。

    基本上就是找到最高像素值,记下它。然后遮住该峰值周围
    一片区域中的所有像素,并重复,直到达到某个限制
    (点数或阈值)。

    这个方法的 shell 脚本实现见 Fred Weinhaus 的脚本
    "[maxima](http://www.fmwconcepts.com/imagemagick/maxima/)"

    它没有尝试寻找由近似相等像素组成的大型“簇”的中心,
    不过这在真实图像中应该非常少见。

  * 子像素定位

    如果峰值不是一个精确像素,
    而可能是子像素位置(位于像素之间),
    那么对峰值区域进行某种模式匹配(高斯曲线拟合)
    可能让你把峰值定位到子像素坐标。

    这在全景拼接的图像配准中可能更重要,
    尤其是当你没有使用大量点
    来得到透视叠加的最佳拟合平均值时。

  * 在图像中寻找平铺图案

    当你拥有所有点之后,搜索重复图案
    (多个峰值之间相似的向量距离)
    应该能指出某种平铺结构。


改进子图像匹配……

  Correlate(或同样事情的快速 FFT correlate)的主要问题是,
  它完全不了解颜色。

  Correlation(或 convolve)纯粹是一种数学技术,
  用于一组值。对图像而言,
  这意味着它只应用于图像的各个单独通道,
  而不是颜色向量距离。


  而 compare 会真正比较颜色向量。它比 correlate 更善于找到
  形状,但慢得多得多。

  因此,为了正确利用 correlate,你应该用 magick 将图像
  (为速度可预先处理,或在结果之后处理)转换成能突出
  图像中色彩差异的灰度“相关”图像。

  顺带一提:使用 -channel 把操作限制在一个灰度通道上会提升速度。
  在 IMv7 中,灰度化会把图像减少为一个通道,
  因而会自动获得速度提升。

  例如,与其使用强度,不如提取图像的 Hue,
  也许能得到更好的前景/背景区分。
  不过,如果要搜索的子图像中有很多红色,
  你可能需要旋转色相。

  要查看这个问题,请参见 HSL 和 HSB 的通道分离示例。
    https://usage.imagemagick.org/color_basics/#separate

  另一种应该效果很好的灰度化方法,
  是对两幅图像做边缘检测。
  它会突出边界和形状,而这些通常比图像中的
  平滑渐变或颜色变化重要得多。

  边缘检测方法示例见
    https://usage.imagemagick.org/convolve/#edgedet

  你也可以看看方向性或罗盘型边缘检测。

  基本上,任何能在你的具体场景中增强形状的做法都是好主意。
  只要在进行相关之前把它应用到两幅图像上即可。


缩放与旋转不变匹配……

  * 位置无关……
  * 匹配旋转后的子图像(角度无关)
  * 匹配调整尺寸后的子图像(尺寸无关)
  * 同时与尺寸和角度无关


--------------

其他更具体的图像匹配……

匹配直线……

  Hough 算法

匹配圆……

  Hough 算法变体

匹配人脸

  上述方法的组合。

查找重复图像

相同文件

文件是否在二进制层面相同,也就是说它们是否完全是同一个文件,可能只是彼此的精确副本。这不需要 ImageMagick。不要忽视这一点。你可以用这种方式非常非常快速地比较大量文件。我找到的最好方法是使用 MD5 校验和。

  md5sum * | sort | awk {'print $2 " " $1'}  | uniq -Df 1

这样会列出相同图像的 md5。使用这项技术,我创建了一些脚本,可以生成并比较文件的 md5sum 列表,返回 md5 相同的文件。不过要注意,除了直接复制之外,对图像文件做任何改动,都会被这种方法归类为不同,即使图像数据本身相同也一样。文件中的日期变化或其他细微元数据差异,就足以让图像变成不同文件。

IM 图像签名

你可以让 IM 为每幅图像生成一个“签名”……

  magick identify -quiet -format "%#" images...

这会生成类似 MD5 和 SHA256 的哈希字符串。不过与后两者不同,它使用实际图像数据来生成签名,而不是图像元数据。因此,如果你有同一张图片的两个副本,但创建/修改时间戳不同,那么两个文件应该得到相同签名;而 MD5 和 SHA256 即使图像本身相同,也会产生两个签名。警告:读取并写出 JPEG 图像会生成不同的图像数据,因此也会生成不同签名。这只是因为 JPEG 图像格式使用的是有损压缩。

直接比较

如果两幅图像尺寸相同,你可以直接比较它们(使用 "magick compare" 程序),看看它们匹配得有多好。(见上文)这非常慢,根据我的经验,对全尺寸图像使用时并不太有用,因为实在太慢。不过,它可能是了解两幅图像究竟有多相似的最佳方法。

图像分类

在我尝试比较图像的过程中,我发现彩色图像、卡通风格图像和草图彼此的比较表现都非常不同。尤其是线稿和灰度图像,几乎在每种比较方法中,差异都倾向于小于彩色图像。基本上,由于颜色全都位于一条线上,任何颜色度量都会倾向于把这类图像放得近 3 倍(1 维色彩空间相对于 3 维色彩空间)。这基本意味着,把图像至少分成这两组,可能是任何认真查找重复或高度相似图像工作中的一个非常重要的第一步。其他主要分类或图像类型也能让图像比较更容易,只要减少需要比较的图像数量即可。参见下方的图像分类。

缩略图比较

让程序(在内存中)创建大量待比较图像的小缩略图(比如 64x64 像素),然后通过直接比较来查找重复。这通常是人们(包括我自己)首先尝试的事情,实际上大多数图像比较程序(例如照片管理软件)也都使用这种技术。它确实有效,并能找到完全匹配的图像。另外,只要稍微模糊并放宽差异阈值,它甚至能找出被轻微裁剪和调整尺寸的图像。不过,试图在内存中存放 10,000 张这样的缩略图,常常会让普通计算机开始频繁换页,变得非常慢。另一种做法是存储所有这些缩略图(除非程序出于用户浏览目的本来就这样做),但这会占用大量磁盘空间。改善磁盘抖动问题的一种方法,是只在内存中保留较少数量的图像。也就是说,按组比较图像,而不是把一幅图像与所有其他图像比较。自然的分组方式是按目录,并把每个目录中的图像与其他目录中的图像比较。事实上这相当不错,因为图像往往会被分组在一起,而这一组图像通常会与类似的一组匹配。按目录对输出匹配图像也算是一个额外好处。此外,两幅图像达到多相似才算可接受,也取决于它们的图像类型。比较两幅线稿时,需要很小的“阈值”来排除不同图像;而比较有大面积色块的图像时,通常需要大得多的阈值,才能抓住被裁剪过的相似图像。真实世界图像有更大的问题:纹理只要有很轻微的偏移,就可能在图像之间产生非常严重的累加差异。因此,你可能需要用中值滤波、模糊、减色或颜色分割,把这类图像简化为大致的颜色区域。经过这样的处理后,真实世界图像通常可以用类似卡通图像的方法来比较。

图像度量

为每幅图像创建一个小度量,是线性阶(O)的操作。而把所有图像与所有其他图像比较,则是平方阶(O^2)的操作。度量并不是为了真正找出匹配图像,而是把相似(可能匹配)的图像分组起来,好让你能在较小的组上做更深入的比较。因此,任何度量比较都应该宽松,并接受匹配概率较低(但仍有概率)的图像。但它也不能宽松到包含太多误匹配。你也可以考虑多个度量,因为有些度量可能会匹配到另一种度量“刚好错过”的图像,原因是它们落在不同的相邻区域(阈值不匹配)。下一节(度量)中列出了一些我实验过或设想过的不同 IM 生成度量,包括:平均颜色、主导颜色、前景背景、边缘颜色、颜色矩阵等。Günter Bachelier 也报告了使用更特殊度量进行图像比较的可能性,例如:傅里叶描述子、分形维数、凸面积、长/短轴长度和角度、圆度、凸性、卷曲度、实心度、形状方差、方向、欧拉数、边界描述子、曲率、弯曲能量、总绝对曲率、面积、几何中心、质心、紧凑度、离心率、关于中心的矩等。我目前的工作是生成并使用简单的 3x3 平均颜色矩阵来表示图像(见下方颜色矩阵度量)。当这些度量被生成(或请求)时,会与其他文件信息一起缓存到每个目录中的特殊文件里。这样,只有当没有可用缓存度量,或者图像发生变化时,我才需要重新生成某个特定度量。

相似度或距离

两幅图像的度量(或实际图像)可以用多种不同方法比较,通常会生成一个单一的距离度量或“相似度度量”,可用于把“相似”图像聚类在一起。

  • 直接阈值,或最大差异(切比雪夫距离)
    只根据任一度量中的最大差异来比较图像。
    该阈值会在多维度量空间中生成一个相似图像的超立方体。当然,这个图像差异只基于一个度量,而不是覆盖所有度量。
  • 平均差异(均值距离,平均曼哈顿距离)
    把所有差异相加,并可选择除以度量数量。
    这也称为两个度量之间的曼哈顿距离,因为它等同于你在城市网格中行走需要覆盖的距离。所有度量都同等贡献,结果会让对象显得比你预期的更“近”。在空间中,这个度量的阈值会产生类似菱形的形状。
  • 欧氏(毕达哥拉斯)差异
    或者说度量空间中度量之间的直接向量距离。
    涉及的度量越多,这个值往往越大。不过,如果某一个度量产生很大的差异,它往往会比其他度量贡献更多。阈值会在度量空间中产生一个球形体积。
  • 数学误差/数据拟合,或(转动惯量???)
    把所有差异的平方相加,然后取平方根
    这更常用于计算数学曲线与某一组数据的拟合程度,但也可以用于比较图像度量。
    它似乎提供了最好的非向量距离度量。
  • 向量角
    找出由图像度量创建的向量空间中心出发的两条线之间的角度。这应该可以去除可能应用到两幅图像上的任何对比度或图像增强效果。
    尚未测试
  • 向量距离
    对于线稿或灰度图像,如果一个度量中的所有单独颜色向量都朝同一方向,那么这些度量相对于图像平均颜色的相对距离可能更重要。相对于最大距离进行归一化,可能会减小对比度的影响。
    也就是说,这是一种线稿图像比较方法。
    尚未测试
  • 聚类分析
    绘制所有度量,并在多维空间中归组为相似簇。优秀的聚类包甚至可能发现并排除不会产生聚类的度量。
    尚未测试

目前我发现,“数学误差”技术似乎对灰度和颜色度量都有效,使用的是简单的 3x3 平均 "颜色矩阵度量"(见下方)。

人工验证

计算机完成寻找匹配图像的尝试之后,实际确认图像是否匹配就要由用户来完成。把匹配结果呈现给用户也可能很困难,因为他们大概会需要这些能力……

  • 并排查看图像
  • 在两幅图像之间非常非常快速地切换,以原始尺寸查看,并可选使用共同的“缩放”尺寸。
  • 在不同缩放和平移后的图像之间切换或叠加,尝试把图像对齐。
  • 查看与匹配图像位于同一目录(来源)或可能属于同一簇(其他近似匹配)的其他图像,以便处理整个组,而不是逐张处理。
  • 在两个(或更多)目录之间重命名、移动、替换、删除、复制图像,以整理图像并排除其他图像。
  • 等等……

。目前我把匹配结果分组成集合,并使用一组程序,在用户控制下处理它们。这些程序包括 IM 的 "magick display" 和 "magick montage",以及图像查看器 "XV" 和 "GQview"。不过,我也欢迎其他程序建议,只要它们能同时打开两个或更多目录,并显示来自多个目录的集合或图像组。通过其他程序或脚本进行远程控制可能非常关键,因为这允许以最适合用户查看和处理的方式设置并呈现图像组。还没有程序满足我的需求。例如,"gqview" 有集合和单目录视图,但不允许多个目录视图,也不支持对呈现进行远程/命令行控制。不过,这些集合并不会显示每幅图像来自哪个目录,也不能把单目录视图切换到其他目录。它也没有远程程序控制。另一方面,非常古老的 "xv" 确实允许多个目录视图(它使用多个 'visual schnauzer' 窗口),并在控制窗口中提供集合列表,但一次只能查看一幅图像,而且命令行只能打开并定位一个目录。当然,它也没有远程控制。这些是我找到的最好的人类验证程序,我使用脚本为每个图像组、匹配对或所有组匹配图像进行设置并启动。但没有一个让人很满意。在我看来,光桌及其相关软件似乎是整理图像的更好方法,但为此你需要更大的触摸屏,而这就意味着高昂成本。

跨类型图像比较

我想做的较难事情之一,是找出由另一幅图像创建出来的图像。例如,我想把一幅线稿与别人给它上色或绘制后得到的卡通甚至超写实图像匹配起来。背景也可能被添加进去。这些事情非常困难,我使用边缘检测技术做的实验,到目前为止还没有定论。在这里找到合适的度量是关键,因为人类能更好地建立“相似性”联系,但你仍然必须找到可能的匹配结果并呈现给用户。

查找重复图像小结

总之,我目前查找和处理重复图像的流程,是一条用来寻找和整理“相似”图像的程序流水线。

   Generate/Cache  Image Types and Metrics
     -> Compare metrics and cluster images.
       -> compare images in cluster for matches
         -> group into sets of matching images (by source directory)
           -> human verification

如你所见,我正在考虑一种高度分阶段的方法。请把你的想法发邮件给我!!!


按类型给图像分类

判断图像属于哪种类型很重要,因为大多数图像比较方法只适用于特定类型的图像。例如,把一幅文本图像与艺术家的素描比较没有意义。对几乎纯白的图像(素描)使用彩色图像比较方法也没有用。通常,比较图像时首先要做的是判断图像是哪种类型,或者说它使用了哪种“色彩空间”。图像的基本分类可以包括……

  • 黑白线稿或文本图像(几乎全是一种颜色)
  • 由两种基本颜色均匀组成的图像 - (图案图像?)。
  • 灰度艺术绘图(大量色阶)
  • 线性色彩图像(颜色形成渐变,但不是从黑到白)
  • 大面积纯色块的卡通风格彩色图像。
  • 带有阴影色彩区域的真实照片
  • 图像包含一些注释文字或 logo 叠加。(单个颜色尖峰)

有了基本类别后,你也可以尝试使用各种图像度量继续给图像排序,例如…… * 整幅图像的平均颜色 * 图像中的主导颜色 * 图像的前景/背景颜色。

更糟的是,JPEG 或调整过尺寸的图像往往也会有色彩失真,使这类分类困难得多,因为颜色不会完全保持原样。灰色不会是纯灰,线条也可能不够锐利清晰。关于按类型给图像分类的长期讨论,见 IM 用户论坛…… How to check image color or black and white

灰度图像

检查一幅图像是否为灰度图像,最简单的方法是查看图像的颜色饱和度级别。也就是说,把图像转换到 'Hue' 图像色彩空间,并取得颜色(通常是绿色)通道的平均值和最大值即可。例如……

  magick rose: granite: -colorspace HCL \
          -format '%M  avg=%[fx:mean.g] peak=%[fx:maxima.g]\n' info:

[IM Text]

这些数字被归一化到 0 到 1 的范围。如你所见,"rose" 色彩非常丰富(平均 30%),并有很强的峰值(接近 1)。而 "granite" 图像的饱和度很低(约 2%),峰值也很低。虽然它不是纯灰度,但非常接近。低平均值和高峰值表示存在小块强烈颜色。对同一个通道做阈值处理,可以生成图像中色彩区域的遮罩。问题:上面的做法找不到线性色彩的图像。也就是只包含形成线性色彩渐变的颜色的图像,例如发黄的(棕褐色调)照片或蓝图。这些本质上是带颜色的灰度图像。参见下一种图像类型。

图像是否为线性色彩

另一种技术是对图像中的所有颜色(或简化的颜色矩阵度量)直接做一条 3 维直线的“最佳拟合”。拟合误差(通常是误差平方的平均值)能很好地指示图像对这条线的拟合程度。把一条线拟合到 3 维图像,通常会涉及一些向量数学。结果不仅能告诉你图像是否使用近似“线性”的一组颜色,而且适用于任何颜色尺度,不只是从亮到暗,也包括黄色纸张上的偏灰线条。结果还可以用来用 magick 将图像处理成更简单的“灰度”图像(或者只把一组颜色度量转换成灰度度量),从而进行更简单的比较,并更好地寻找匹配。我的试验测试程序甚至不使用整幅图像来做这个判断,而是用下方简单的 9 种颜色(27 个值)的颜色矩阵度量来表示图像。不过请注意,这个测试通常不能很好地区分没有阴影的线稿。这类图像几乎完全是单一背景色(通常是白色),因此可能不表现出任何形式的线性色彩渐变。它们应先通过不同测试分离出来(见下一节,实际上要容易得多)。如果感兴趣,请发邮件告诉我你尝试过什么。

纯黑白图像

要判断一幅图像是否接近纯黑白,几乎没有任何颜色甚至灰色(由于抗锯齿),我们可以巧妙使用 "[-solarize](https://imagemagick.org/command-line-options/#solarize)" 选项(见 Solarize 的 IM 示例)。把这个操作应用到任何图像上,都会让所有明亮颜色变成暗色(被取反)。因此,任何接近白色的颜色都会变成接近黑色的颜色。对这样的图像做简单统计分析,就能判断图像是否纯粹(或几乎纯粹)为黑白。

   magick wmark_dragon.jpg  -solarize 50% -colorspace Gray  wmark_bw_test.png
   magick identify -verbose -alpha off wmark_bw_test.png | \
       sed -n '/Histogram/q; /Colormap/q; /statistics:/,$ p'  > wmark_stats.txt

[IM Output] [IM Output] | | [IM Text]

如果查看上面的统计,你会看到颜色 'mean' 非常接近纯黑('0'),而 'standard deviation' 也非常小,但大于 'mean'。因此这幅图像必然大多是纯黑白,只有极少颜色或中间调灰色。对一般灰度和彩色图像来说,'mean' 会大得多,而 'standard deviation' 通常小于 mean。发生这种情况时,表示 solarized 图像中几乎没有接近纯黑的内容。也就是说,几乎不存在纯黑或纯白颜色。让我们用内置 granite 图像重复这个测试。

   magick granite: granite.jpg
   magick granite.jpg -solarize 50% -colorspace Gray  granite_bw_test.png
   magick identify -verbose -alpha off granite_bw_test.png | \
     sed -n '/Histogram/q; /Colormap/q; /statistics:/,$ p' > granite_stats.txt

[IM Output] [IM Output] | | [IM Text]

注意,'mean' 现在大得多,接近颜色范围的中部,而 'standard deviation' 远小于 'mean' 的大小。从 IM v6.4.8-3 开始,你还会看到另外两个统计值,可能有助于判断图像类型。'Kurtosis' 和 'Skewness' 在第一幅黑白图像中也都相对较大(且为正),这反映了与灰度图像相比,其中涉及的灰色很少。不过,出于比较目的,'mean' 与标准差可能仍然是更好的指标。注意,这种比较不会区分“白底黑字”或“黑底白字”,但一旦你知道它并不是真正的灰度图像,对图像正常 mean 做简单检查,就能告诉你背景色到底是什么。

点状彩色图像

这类图像无法通过上面的灰度测试,但它们仍然是黑白图像,只是其中有一小块区域或颜色斑块。小块颜色很容易被大图的整体平均吞没,可能被误分类为灰度图像。我们不关心只有比如单个彩色像素的图像,因为那很可能是比特错误,或者这类像素在图像中零星散布。但如果图像有一个彩色箭头或小的彩色对象,那就不同了。换句话说,是一块集中的颜色。在 IM 论坛讨论 False positive for greyscale images using the "saturation test" 中,有人想到把图像分成更小的区块,然后在其中任一区域寻找高饱和度。这引出了下面的方法。

  • 用 magick 将图像转换到带有 Saturation 或 Chroma 通道的色彩空间
  • 按 1:50(2%)比例缩小图像(例如颜色“斑点尺寸”)
  • 对最大饱和度/色度值进行阈值处理

单独或非常小的斑点会被移除,但较大的颜色斑点会在调整尺寸后的图像中至少留下一个彩色像素。

中间调着色图像

[IM Output] 棕褐色调图像,或把中间调灰色着成某种高亮颜色的图像(例如右侧图像),可能会更难区分。这类图像很容易生成,如中间调颜色着色所示,不过并不常见。颜色仍然会在色彩空间中形成颜色渐变(直线),但该渐变落在一条弯曲路径上,通常是某种位于平面内的抛物线。不过,区分这类图像可能非常困难。一种技术是获取所有色相的标准差,但排除饱和度极低的颜色。中间调彩色图像中的所有色相应该非常相似,即使数量不多。这项技术在 How to check image color or back and white 的特定帖子中提出。提醒一下,Hue 是循环值,会在颜色“红色”处回绕。要正确测试,可能需要做两次,并把色相平移 180 度。另外,对任何饱和度非常低的颜色(灰色),Hue 都没有真正意义,因此在测试色相标准差时应忽略这些颜色。

文本 vs 线稿

如果你有一幅几乎完全是单一颜色(通常是白色)的图像,可以尝试判断其内容应分类为文本还是线稿。文本会有大量小而不相连的对象,通常归组成水平行。另一方面,线稿应该大体上整体相连,并包含许多不同角度。注意,卡通风格的彩色图像也可以转成线稿,以便更简单地比较图像,因此拥有一种线稿比较方法会很有用。有人愿意试试吗?要进一步了解文本,IM 论坛中讨论过许多技术:Check if image contains text

真实照片 vs 卡通风格

基本上,卡通图像有非常明确的色块和边界锐利的区域,常常还通过分隔用黑线让边界更清晰。它们通常也只有很少的渐变或阴影效果。而真实照片有大量柔和边缘效果、颜色渐变和纹理,并使用许多不同颜色。当然这并不总是正确。真实照片也可能具有很强的卡通感,尤其是使用了很高对比度时;有些现代卡通又逼真到很难把它们归类为卡通。一般而言,真实照片和卡通之间的主要差别是纹理和渐变。因此,要判断它属于哪种图像类型,需要把该图像与去除细尺度纹理后的同一图像比较。较大的差异表示图像更像“写实”和“真实世界”,而不是“卡通化”或“平面”。还要记住,线稿、艺术家素描和文本也可能在风格上非常卡通,但它们带有足够细的纹理和细节,可能让上述方法把图像当成真实世界图像。因此,线稿和素描应提前分离出来。
Jim Van Zandt 提供了这个解决方案……

  • 写出每个像素的颜色
  • 按颜色排序
  • 写出每种颜色的像素数
  • 按像素数排序
  • 沿列表前进,直到已覆盖图像中一半像素。
  • 如果 #pixels >>> #colors,那么它就是卡通风格。

初始部分可归类为直方图。参见 "[histogram:](files.html#histogram)" 示例。
如果你创建了某种图像分类方案……即使只是粗略方案,也请告诉我们你的结果,让其他人(包括我自己)也能受益。


处理特定图像类型

这里是关于更具体图像判定技术的一些笔记和信息。

糟糕的扫描或打印件

在真实世界中,事情从来不会像你希望的那样完美。扫描仪会有坏传感器,打印机滚筒会有划痕。这两个问题通常都会让扫描图像和打印件中出现长竖线。不过,判断图像是否存在这些竖线相当容易。思路是把图像所有行的像素一起求平均。任何“故障”都会在最终像素行中表现为尖锐突起,你可以使用该像素行的“阈值直方图”来计数。

FUTURE -- image example needed for testing
    magick bad_printout.png -crop 0x1+0+0 -evaluate-sequence mean \
            -threshold 50% -format %c histogram:info:-

faster method but needs image height (assumed to be 1024)
    magick bad_printout.png -scale 1024x1 \
            -threshold 50% -format %c histogram:info:-

当你从传真、打印件或扫描件中判定并移除了这样的“坏线”后,就可以继续进行其他测试,而不必担心这类真实世界故障。

空白传真

首先,你需要用 "[-shave](https://imagemagick.org/command-line-options/#shave)" 削掉传真可能添加到页面上的任何页眉和页脚。然后可以做一个“阈值直方图”(见前文),看看有多少单独的黑色像素。

FUTURE -- image example needed for testing
    magick blank_fax.png -threshold 50% -format %c histogram:info:-

或者,你可以做一次带噪声 Trim,看看图像中是否确实包含任何更明显的实体区域或值得注意的对象。

FUTURE -- image example needed for testing

垃圾信息图像

垃圾信息图像通常会在图像颜色直方图中显示一个主导纯色尖峰。检查图像中的这个颜色,通常会发现它位于图像的某个角落。不过,这对卡通风格图像不起作用。

EMail 垃圾信息图像

这些图像被设计用来绕过各种垃圾邮件过滤器。基本上,广告文本会使用各种颜色隐藏在图像中,并加入额外“污点”和其他噪声,使其更难检测。虽然它们很难与例如公司邮件页眉中的 logo 区分开来,但通常也比典型邮件 logo 大得多。一种发现技术是对图像使用较大的中值滤波器。EMail 垃圾信息文本通常会消失,而 logo 或图像仍会保持很鲜艳。


图像度量,快速找到要比较的图像

度量表示一种“指纹”,用非常小的内存量来表示图像。相似图像应该得到相似度量。不过要注意,度量并不是为了真正找出匹配图像,而是尝试排除那些肯定不匹配的图像。也就是说,一个好的度量能让你在进一步比较中忽略大多数图像,从而减少搜索所有图像所需的时间。

图像的平均颜色

你可以使用 -scale 得到图像的平均颜色,
不过我还建议先去掉图像外部边框,
以减少图像周围可能添加的任何“杂边”的影响。

    magick image.png  -gravity center -crop 70x70%+0+0 \
            -scale 1x1\! -depth 8 txt:-

另一种做法是,要得到基于颜色聚类的“加权质心”颜色,
而不是平均值,可以使用 -colors

    magick rose: -colors 1 -crop 1x1+0+0 -depth 8 -format '%[pixel:s]' info:-
    rgb(146,89,80)

这通常会匹配被调整尺寸、轻微裁剪、旋转或平移过的图像。但它也会匹配许多关系并不密切的图像。最大的问题是,这种度量通常会忽略被调亮、调暗或整体色相改变过的图像。另外,虽然它对彩色和真实世界图像是很好的度量,但对灰度图像完全没用。所有这类图像通常都会被归在一起,在该类型内部没有进一步聚类。这反过来说明,先对图像类型进行初步分类,对良好的图像排序和匹配可能非常关键。

图像的主导颜色

图像的主导颜色略有不同。你不想要把背景色和前景合并起来的平均值,而是想找出最常见的前景颜色,也许还要知道图像中有多大比例由这种主导颜色组成。因此你不能只对图像取直方图,因为图像可能使用大量单独的颜色阴影,而不是某一种具体颜色。这可以通过低层量化函数 -segment 完成,然后再取直方图。与直接使用 -colors 相比,这有一个优点:它不会试图合并距离较远(从颜色上说)的颜色簇,尽管结果可能更难判断。

 FUTURE example

之后,直方图会给出每种主导颜色的数量。不过,卡通或线稿中的主导颜色通常是图像背景色。因此它实际上只对真实生活图像有用。另一方面,你也许可以通过把它与图像平均边界颜色比较,来判断图像是否有真正的背景。请注意,一张图片的主导颜色更可能强烈受图像背景色影响,而不是受关注对象影响。通常关注对象位于图像中心或接近中心。

边界颜色

通过反复裁去图像四条边中的每一条(最多 2 到 3 像素),并计算边界平均颜色,你可以判断图像是否带框,以及框有多深。也可以判断图像是否有明确背景,或者整体图像中是否有某种天空/陆地、近景/远景的颜色分离。通过把平均边缘颜色与图像中心平均颜色比较,你可以发现图像是否没有中心主题或主体、整体均匀,例如一张空旷风景照片。

直方图 - 一般颜色匹配

对于与图像中能找到哪些类型颜色相关的度量,会使用某种直方图。这通过创建一个“颜色桶”数组,并在发现颜色时递增每个“桶”的计数来完成。现在,我可看不出你会为每幅图像存储大型直方图!所以你要么只在直方图中存储最主要的颜色,要么使用数量少得多的桶(每个桶中包含更多像素)。普通的“颜色桶”直方图其实效果并不好。原因是每种颜色总是落入一个桶。也就是说,每个像素都会以全有或全无的方式加入某个桶,完全不考虑该颜色距离桶边缘有多近。这反过来不会形成好的度量。一种解决方案是创建带有重叠桶的直方图。也就是说,每种颜色(也许黑色或白色除外)都会落入两个颜色桶。之后比较图像时,接近的颜色至少会匹配这些桶中的一个。另一种做法是创建直方图时,让每种颜色根据它距离桶中心有多近来贡献给每个“桶”。也就是说,位于一个桶边缘的颜色实际上会把自己分摊到两个桶中。这会生成一种模糊的、或插值后的直方图,但这种直方图会更准确地表示图像,尤其是在只使用很少数量颜色“桶”时。另外,传统上直方图要么只是图像的灰度分量,要么是三个分离的 RGB 分量。但这并不是很好的表示。你可以尝试改用 Hue、Saturation 和 Luminance 直方图来更好地表示图像。或者,为什么要把自己限制在一维直方图中?把颜色映射到跨整个色彩空间的一组真实颜色如何!也就是说,与其只给“red”值分桶,为什么不在 3 维颜色桶中计数(使用最适合的任何色彩空间)?那会生成一个真正表示图像内颜色的直方图。这样的 3 维直方图度量可以是一个简单数组,比如 8x8x8 或 2048 个桶。也就是一个 2Kbyte 的度量。颜色搜索随后会定位正确数量的附近桶,并取得附近桶的插值计数。这将表示图像中有多少颜色“接近”该颜色!

前景/背景颜色分离

使用 -colors,你可以尝试通过把图像减少到只有两种颜色,把图像分成前景和背景部分。先使用 -median 滤波器,可以去除图像中可能存在的细小细节、线条边缘和噪声的影响。当然,这对大多为白色的素描类图像并不好。

  magick rose: -median 5 +dither -colors 2 \
          -depth 8 -format %c histogram:info:-

这显示出红色和灰色是图像中的主导颜色。随后裁剪/裁切到图像中心,应能判断什么是前景、什么是背景。

  magick rose: -median 5 +dither -colors 2 \
          -trim +repage  -gravity center -crop 50% \
          -depth 8 -format %c histogram:info:-

这显示红色的 "rose" 颜色是主导前景色。注意,风景图像可能会以不同方式分离,因为你会得到下方的地面颜色和上方的天空颜色。因此,大致查看颜色是如何分离的,对判断图像类型可能非常有用。另外,带有某些文本“垃圾信息”的图片,往往会在某个角落显示一团颜色,比图像其余部分显眼得多。如果发现这种情况,就用 3 种颜色重做,然后在进行最终测试之前,用找到的最常见“背景”颜色擦除那个区域。这项技术可能是把图像分成“肤色”“绿植”“风景”等类别的好方法。

平均颜色矩阵

三乘三矩阵颜色方案("-scale 3x3\!")是一种合理的颜色分类方案。它能很好地分离并归组相似图像。例如,素描(几乎全白)、灰度、风景、海景、房间、人脸等,理论上都会被分成基本而相似的组。这也是为生成照片马赛克而索引图像时可以使用的合理度量。NetPBM 图像格式的输出特别适合生成这种度量,因为它可以只把像素值输出为文本数字。请记住,这会产生一个 27 维结果(3x3 个颜色,每个颜色 3 个值),因此可能需要多维聚类算法。你知道好的 3d 聚类程序/算法吗? 例如,下面是 IM logo 的 3 x 3 RGB 颜色(depth 8)。

  magick logo: -scale 3x3\! -compress none -depth 8 ppm:- |\
    sed '/^#/d' | tail -n +4

  251 241 240 245 234 231 229 233 236 254 254 254
  192 196 204 231 231 231 255 255 255 211 221 231
  188 196 210

上面的做法可以通过使用 16 位值来改进,并且可以裁掉边界的 10%,以去除可能添加的 logo 和边框杂物……

  magick logo: -gravity center -crop 80% -scale 3x3\! \
          -compress none -depth 16 ppm:- |   sed '/^#/d' | tail -n +4

  63999 59442 58776 62326 58785 58178 51740 54203 54965 65277 65262 65166
  45674 47023 49782 56375 55648 55601 65535 65535 65535 52406 55842 58941
  44635 48423 52881

当然,与前面的平均颜色度量一样,它在匹配经过颜色修改的图像时也会有问题,例如色相或亮度变化。(见下一节)另外,这个度量也能在其分组中分离线稿,虽然方式非常粗略。这类图画仍然会更多按背景“纸张”的颜色,而不是按内容来归组,并且通常需要比彩色图像更小的相似性“阈值”。

颜色差异矩阵

直接把颜色用作度量的最大问题,是你把图像绑定到了某种整体颜色上。这意味着任何被调亮、调暗或改变色相的图像都不会被归到一起。一个解决方案是以某种方式从度量中减去图像的主导颜色或平均颜色,而使用颜色矩阵能让这一点成为可能。这里,例如,我从矩阵中所有周围颜色中减去了中间或中心平均颜色。

  magick logo: -gravity center -crop 80% -scale 3x3\! -fx '.5+u-p{1,1}' \
          -compress none -depth 16 ppm:- | sed '/^#/d' | tail -n +4

  51093 45187 41761 49419 44529 41163 38834 39947 37950 52371 51007 48152
  32767 32767 32767 43469 41393 38587 52629 51279 48521 39500 41587 41926
  31729 34168 35867

注意,我给差异加上 .5,因为图像中无法保存负的颜色值。另外,使用较慢的 "[-fx](https://imagemagick.org/command-line-options/#fx)" 运算符是可以接受的,因为只处理 9 个像素。注意,中心像素(上面第二行开头的 "32767 32767 32767")不会有太大变化(任何变化都只是轻微舍入误差造成的),可以从结果中移除,把度量减少到 24 维(值)。另一种做法是,从全部 9 个颜色值中减去图像的平均颜色。

  magick logo: -scale 3x3\! \( +clone -scale 1x1 \) -fx '.5+u-v.p{0,0}' \
          -compress none ppm:- | sed '/^#/d' | tail -n +4

  38604 35917 34642 37011 33949 32441 32839 33841 33649 39447 39259 38369
  23358 24377 25436 33538 33174 32426 39612 39434 38605 28225 30576 32319
  22271 24381 27021

这也可以由度量比较器完成,而不是由度量生成器完成。无论整体颜色或亮度怎样变化,这个度量仍能很好地分离和聚类彩色图像,并把相似图像放得非常接近。不过它仍对对比度变化敏感。事实上,这种度量修改可以在比较过程中完成,因此原始颜色矩阵度量仍可作为标准图像度量来收集、缓存和比较。这正是我自己现在进行大规模图像比较时采用的方法。与直接颜色平均不同,你可以用这个度量区分不同线稿图像。不过,由于线稿使用线性色彩尺度(所有颜色都落在度量空间中的一条线上),图像之间的差异约为彩色图像的 1/3。因此,比较线稿时需要完全不同的阈值。所以,最好仍然先把线稿和灰度图像与彩色图像分开。换句话说,这是我目前为彩色图像找到的最佳度量之一。只要务必先判断哪些图像是线稿,并使用低得多的阈值单独比较它们。幸运的是,度量本身可以用来把图像分成灰度线性色彩图像。欢迎建议。

近邻差异

上面生成的是一个 3x3 矩阵,减去中心像素,并把所有值偏移到完美灰色。不过,更好的方法不是保存各个单元的颜色,而是生成每个单元与其邻居(8 个邻居)之间的差异。也就是说,不保存左上角的颜色,而是保存该角与上中、中心和左中之间的差异。当然,即使只有一个小的 3x3 数组,你最终也会得到包含 12 个差异的签名,不过你不需要编码完整差异,只需要一般的差异级别,例如相等,或大/小的正/负差异值。这更有可能在包含完全不同颜色的图像之间找到匹配,因为实际颜色完全不参与签名。'libpuzzle' 图像比较库正是这样做的,不过它使用 9x9 矩阵,并只把每个单元的中心像素平均在一起。它也把自己限制在图像的灰度版本上。这项技术在一篇 postscript 论文 Image Signature for Any Kind of Image 中有完整定义。该论文还深入讨论了如何把签名存储到数据库中,以及如何实际执行查找,以找出具有相似(不一定相同)签名的图像。这是我发现的第一篇真正详细说明如何做这件事的论文。:-)

感知哈希

把图像缩小到 8x8,并计算平均强度。64 位哈希中的每一位,如果该像素高于平均值则为 1,如果低于平均值则为 0。要比较两幅图像之间的相似度,只需逐位比较这些位哈希,并返回汉明距离。汉明距离越近,图像越相似。任何超过 21 / 64 的结果都被认为不相似。pHash 似乎使用 YCbCr 编码。有人讨论过直接使用 JPEG 中的 DCT,而最有希望的方法处理幅度/相位,并把它映射到对数极坐标系统。


更好地匹配图像

这里是一些杂项笔记和技术,我要么尚未尝试,要么发现它们在为了更精确匹配而比较较大图像时效果并不好。

分割颜色

如你所见,上面许多度量都会使用模糊/中值滤波,然后接减色技术,作为简化图像、让图像更易分类的基本尝试。不过,颜色量化运算符并不是为这个目的设计的。它的职责是减少颜色,以突出图像的重要细节。但在图像比较中,我们并不真正想突出这些特征,而是想突出比较上感兴趣的区域。这是一种相关颜色技术的工作,称为分割……顺带一提,来自 Leptonica:图像分割就是把图像划分为具有不同属性的区域。这个运算符会块化相似颜色区域,去除这些区域中的细节。这样,当你比较两幅图像时,比较的是区域,而不是图像中的低层细节。IM 实现了一个分割算法 "[-segment](https://imagemagick.org/command-line-options/#segment)",其实现细节见 SegmentImage()。示例:

  magick logo: -median 10 -segment 1x1 \
          +dither -scale 100x100\! segment_image.gif

一个问题是 -segment 非常慢,而且似乎只适用于较大的图像。小图像(如 rose: 或缩放到 100x100 的 logo:)似乎只会产生单一颜色。这可能是一个 bug。当然,你仍然可以像上面那样在分割之后缩放图像。这样就能在内存中存储更多图像,彼此比较。另外,与 Leptonica 提供的图像分割算法相比,结果分割似乎效果并不好。参见 Leptonica: Color Segmentation。不过,IM 分割的替代方案是误用颜色量化函数来寻找相似颜色区域。示例:

  magick logo: -scale 100x100\! -median 3 \
          -quantize YIQ +dither -colors 3 segment_image.gif

缺点是,-color 会限制图像中可能存在的颜色区域数量,而 segment 试图保留相似区域,不管图像中实际存在多少区域(或者至少它应该这样做)。

无色边缘比较

图像颜色出了名地不可靠,尤其是卡通风格图像。不同用户很容易重新给这类图像上色、添加不同颜色背景,甚至拿一张草图来上色。匹配这类图像的一种方法,是先按上面的方法做一些基本减色,但随后不要根据结果颜色比较图像,而是执行边缘检测和进一步处理,只把最重要颜色变化的轮廓用于图像的度量和比较。例如……

  magick logo: -scale 100x100\! -median 3 \
          -quantize YIQ +dither -colors 3 -edge 1 \
          -colorspace gray -blur 0x1   outline_image.gif

另一种方案可能是使用 -lat(局部区域阈值)进行边缘检测,这也许能提供更好的控制……

  magick logo: -scale 100x100\! -median 3 \
          -quantize YIQ +dither -colors 3 \
          -lat 3x3-5% -negate \
          -colorspace gray -blur 0x1  outline_image.gif

当然,比较时你会使用线稿比较方法。
??? 如何以可行方式比较线稿 ???
把图像相乘,看看结果图像是增加还是降低了线条强度。不匹配的线会变黑。


网络摄像头

固定摄像头中发生了什么变化

施工中

Walter Perry 报告说……我正在做的项目涉及处理监控摄像头发送的 20 张图像一组的数据,这些图像是在摄像头感测到运动后发送的。这些摄像头位于远程位置,一旦检测到运动,图像就会发送到本地服务器。到达本地服务器后,我希望能够“过滤”掉那些不包含引发事件之物的图像。我使用 PerlMagick 比较序列中的第一幅图像(它始终不会包含正常背景以外的任何东西)与其余图像。我为所有图像取得一个“平均”差异,然后如果单幅图像的差异大于平均差异,就保留该图像,因为其中有某些内容。无论白天还是夜晚、无论照明条件如何,这种方法都非常有效。我最初尝试只使用高于第一幅图像的百分比差异,但那并不太可靠,而且非常依赖照明条件。基于这种比较,我随后会判断哪些图像有“内容”,哪些图像没有任何运动、是空的。一旦我只取得那些包含“运动”的图像。