【问题标题】:ImageMagick: combine multiple 'montage' commands into one to gain performanceImageMagick:将多个“蒙太奇”命令合并为一个以获得性能
【发布时间】:2015-04-19 08:58:23
【问题描述】:

我有一个脚本,它拍摄 4 张照片并复制它们以在一张中生成 8 张小照片。该脚本还会为输出添加背景。

这是我的期望:

http://img11.hostingpics.net/pics/831624stack.png

我有一个运行良好的代码。但需要保存多张临时图片。

所以我在寻找是否可以合并脚本的命令以仅获得一个图像保存操作。目标是让脚本更快地完成。

//Make image smaller:
    convert /home/pi/images/*.png -resize 487x296 -flop \
      -set filename:f "/home/pi/imagesResized/%t%p" '%[filename:f]'.png

//Make one column from 4 images:
    montage /home/pi/imagesResized/*.png -tile 1x4 \
      -geometry +0+15 -background none  /home/pi/line.png

//Duplicate to two columns:
    montage /home/pi/line.png /home/pi/line.png -tile 2x1 \
      -geometry +45+0 -background none /home/pi/photos.png

//Add the background image:
    suffix=$(date +%H%M%S)
    montage /home/pi/photos.png -geometry +20+247 \
         -texture /home/pi/data/background_photo.png \
          /home/pi/photo_${suffix}.jpg

【问题讨论】:

  • 您的第二个命令的注释是 "Make one col from 4 images",但您的代码显示 -tile 2x4 将组成 2 列(最多 8 个图像)。
  • 感谢您的关注。只是复制错误。
  • 我刚刚注意到另一个问题:您的'%[filename:f].png 有一个不平衡的'-quote...我会解决它。
  • 我不确定你想用-resize 487x296 实现什么。如果你想要完全那个尺寸,无论如何,你需要使用-resize 487x296\!——我在我的回答中假设是这样......否则,你给它的调整大小将保持方面比例,并且结果大小可能符合您的规范所说的宽度 - 以使结果适合为准 487x296 像素!
  • 不知道,谢谢库尔特

标签: performance merge imagemagick montage


【解决方案1】:

首先,让我这样说: 如果您的输入图像非常大,则将单独的命令放入一个 ImageMagick 命令链中,您只会节省大量的处理时间。 但是,您可以通过跳过写出和读入中间结果图像的需要来节省磁盘 I/O 时间。

您的代码使用两个不同的montage 命令加上一个convert 命令来实现第一个蒙太奇。 最后使用montage 将之前的结果放在背景上。

从我的脑海中,我可以快速想出一种将前三个命令组合成一个命令的方法。 将中间结果放在背景上的最后一个蒙太奇步骤并不容易正确,而且很可能也不会节省太多时间。 因此,我暂时将其保持打开状态。

很遗憾,您没有提供任何指向源图像的链接。 为了回答这个问题,我必须创建自己的。 它们还可以用来证明我的答案的有效性。

为了创建四个 800x600 像素大小的 PNG,我在命令行中使用了 Ghostscript 和一些 PostScript 代码:

for i in 1 2 3 4 ; do 
   gs -o t${i}.png                  \
      -g800x600                     \
      -sDEVICE=pngalpha             \
      -c "0.5 setgray"              \
      -c "0 0 800 600 rectfill"     \
      -c "1 0 0 setrgbcolor"        \
      -c "3 setlinewidth"           \
      -c "10 10 780 580 rectstroke" \
      -c "0 setgray"                \
      -c "/Helvetica-Bold findfont" \
      -c "560 scalefont setfont"    \
      -c "230 60 moveto"            \
      -c "(${i}) show "             \
      -c "showpage" ; 
done

然后我首先用我的图像测试了你的代码。 这是 OP 命令的结果。结果是完整的,包括背景图像上的蒙太奇来自我自己的股票更新),使用受 Mark Setchell 回答启发的命令创建:

convert -size 200x200 xc:gray +noise gaussian background.png

合并前两个命令:

以下是我为提出单个命令而进行的第一次尝试。 它应该与您输出line.png前两个 命令实现相同的结果。 我已经知道它在某些方面不会完全按预期工作,但我仍然尝试过。 我尝试了它以查看命令的其他位置是否会显示我没有预期的问题。 不用担心,完整的最终代码的解释将在答案的末尾。 阅读完整答案后,您可以尝试弄清楚以下命令的工作原理:

_col1=blue ;
_col2=red  ;
convert t*.png -flop -resize 487x296\! \
   \( -size 15x15                      \
      -clone 0 xc:${_col1}             \
      -clone 1 xc:${_col1}             \
      -clone 2 xc:${_col1}             \
      -clone 3                         \
      -append                          \
      +write f.png                     \
   \) null:

这是我的命令的结果(右)与第二个命令之后的中间结果(左)的比较:

所以,我预料到的一件事发生了:每张图片之间都有一个蓝色间隔。 出于调试原因,我对其进行了着色。 这可以通过将颜色变量设置为none(透明)来解决。

没有预料到的事情,我是在打开生成的图像f.png后才发现的:

  • 我的背景是白色而不是透明。 这可以通过在正确的位置添加-background none 来解决。

  • 我的列中单个图像的间距太窄,只有 15 个像素。 这是因为在 OP 的中间文件line.png 中,间距不是 15 像素,而是 30 像素。 他的参数-geometry +0+15 用于创建列的montage 确实在每个图像的顶部添加了15 个像素。 我的命令(使用convert ... -append 而不是montage)不允许使用具有相同效果的其他-geometry 设置。 但他可以通过在我的命令中添加更多 xc:{_col1} 间隔来修复。

合并前三个命令:

所以这是下一次迭代。 它集成了来自 OP 的第三个命令的效果。 这是通过添加+duplicate 来复制第一个创建的列来实现的。 然后它添加+append 以附加重复的列水平-append 将垂直这样做):

_col1=blue ;
_col2=red  ;
convert t*.png -flop -resize 487x296\! \
   \( -size 15x15                      \
      -background none                 \
       xc:${_col1}                     \
      -clone 0 xc:${_col1} xc:${_col1} \
      -clone 1 xc:${_col1} xc:${_col1} \
      -clone 2 xc:${_col1} xc:${_col1} \
      -clone 3                         \
       xc:${_col1}                     \
      -append                          \
      +duplicate                       \
      -size 45x45 xc:${_col2}          \
      +append                          \
      +write f2.png                    \
   \) null:

预料到的一件事又发生了:

  • 两列之间的红色垫片位于右侧,而不是位于两列之间。 我们可以通过交换获得+append-ed 的最后两个图像来解决这个问题。 这可以通过在正确的位置添加+swap 运算符来完成。

  • 此外,与第一列中的图像间间距相同的事情将应用于列之间的间距:我必须加倍。

  • 我现在不在乎没有将相同的空间(45 像素)添加到左侧和右侧的 +append-ed 列中。

所以这里还有一个迭代:

_col1=red ;
_col2=blue ; 
convert t*.png -flop -resize 487x296\! \
   \( -background none                 \
      -size 15x15                      \
       xc:${_col1}                     \
      -clone 0 xc:${_col1} xc:${_col1} \
      -clone 1 xc:${_col1} xc:${_col1} \
      -clone 2 xc:${_col1} xc:${_col1} \
      -clone 3                         \
       xc:${_col1}                     \
      -append                          \
      +duplicate                       \
      -size 90x90 xc:${_col2}          \
      +swap                            \
      +append                          \
      +write f3.png                    \
   \) null:

结果如下:

  • 右边是第三条命令后OP代码创建的中间photos.png
  • 左边是我的命令创建的图像蒙太奇。

现在缺少的是对我打包到单个命令中的各个操作的分解的解释:

  • \( :
    这会打开一些图像的“横向”处理。 然后将这种横向处理的结果再次插入到主 ImageMagick 进程中。 反斜杠是必需的,因此 shell 不会尝试解释它们。
  • \) :
    这将关闭横向处理。
  • -size 15x15 :
    这将设置接下来要填充的画布的大小。
  • xc:${_col1} :
    这用指定的颜色填充画布。 xc: 只是 canvas: 的别名,但打字速度更快。
  • -clone 0 :
    这将创建当前在加载的图像堆栈中的第一个图像的副本。 在这种情况下,它是t1.png 的副本。 -clone 1 复制 t2.png-clone 2 复制 t3.png 等。 -clone+clone 在横向处理链中工作得最好,因此前面解释过使用 \(\)
  • -append :
    此运算符垂直附加所有当前加载的图像。 在这种情况下,这些是 t1.png、...t4.png 的 4 个副本。
  • +duplicate :
    此运算符类似于+clone。 它复制当前加载的图像堆栈中的最后一个图像。 在这种情况下,最后一个图像(并且仅剩下一个在横向管道内)是先前-append 操作的结果。 此操作创建了第一列 4 张图像,由红色垫片隔开。
  • +append :
    该运算符水平地附加所有当前加载的图像。 目前共有三个图像:-append 操作的结果、由+duplicate 创建的副本以及90x90 大小的xc:-canvas。
  • +swap :
    此运算符交换当前加载堆栈上的最后两个图像。
  • +write :
    此运算符从当前加载的堆栈中写出 所有 图像。 当有多个图像时,它将使用给定的名称写入这些图像,但附加一个数字。 它是调试复杂 ImageMagick 命令的好工具。 这很棒,因为在+write 操作完成后,之前加载的图像都保留在堆栈上。 这些图像保持不变,并且可以继续处理。 但是,我们现在已经完成,在这种情况下不会继续。 因此,我们使用\) 关闭侧向进程。
  • null :
    现在我们关闭了 sideway 进程,ImageMagick 再次将 sideway 生成的图像放入主管道。 请记住,+write 没有完成处理,它向磁盘写入了一个文件,该文件旨在成为 中间 结果。 在主管道中,现在还有原来的t1.png ...t4.png加上侧边处理的结果。 但是,我们不会对他们做任何事情。 我们将来自+write 的中间结果作为我们的最终结果。 但是convert 命令现在希望看到一个输出文件名。 如果它没有看到,它会抱怨并向我们显示一条错误消息。 因此,我们告诉它注销所有已加载并从堆栈中丢弃所有图像。 为此,我们使用null: 作为输出文件名。

    (如果您喜欢冒险,请使用 out.png 作为文件名而不是 null:。 你会看到 ImageMagick 实际上创建了多个out-0.pngout-1.png、...out-3.png 文件名。 你会发现out-4.pngf.png一样,out-{0,1,2,3}.png和输入图片一样。 -- 您也可以将null: 替换为-append output.jpg,然后看看会发生什么...)

更新

现在是速度比较...

对于第一个粗略的基准测试,我确实在一个循环中运行了 OP 的前三个命令,并进行了 100 次迭代。 然后我也运行了自己的命令 100 次。

结果如下:

  • OP 前三个命令,100 次:61.3 秒
  • 我的单个命令替换了这些,100 次:48.9 秒

所以我的单个命令比来自 OP 的原始命令节省了大约 20% 的时间。

鉴于我的磁盘 I/O 性能可以假设为与旋转硬盘相比相当快(测试系统具有 SSD),合并命令的速度增益(避免过多的临时文件写入/读取) 在磁盘较慢的系统上可能会更加明显。

为了检查命令的一点重新架构(没有那么多加载的图像在最后被简单地丢弃,如null: 输出文件名所示)是否会获得更多改进,我也试过这个:

convert t*.png -flop -resize 487x296\! \
      -background none   \
      -size 0x15         \
       xc:red            \
      -duplicate 7 \
      -insert 0    \
      -insert 2    \
      -insert 3    \
      -insert 5    \
      -insert 6    \
      -insert 8    \
      -insert 9    \
      -append      \
      \( +clone -size 45x0 xc:blue +swap \) \
       +append \
       f4.png

这个命令的架构有点不同。

  1. 首先,它会加载所有图像。它-flops 他们和它-resizes 他们。

  2. 其次,它会创建一个 15x15 像素的画布,然后将其放置在图像堆栈上。

  3. 第三,它会创建该画布的 7 个额外副本。 现在堆栈上有 12 个图像: 4 个输入文件、1 个xc:-canvas 和 7 个画布副本。

  4. 然后,它使用一系列-insert N 操作。 -insert N 操作操纵图像堆栈的顺序。 它从堆栈中删除最后一个图像并将其插入图像索引位置N。 当-insert系列操作开始时,堆栈上有4个图像(t1t2t3t4),加上8个“间隔符”(s)。 这是他们原来的顺序:

    index:   0   1   2   3   4   5   6   7   8   9  10  11
    image:  t1  t2  t3  t4   s   s   s   s   s   s   s   s  
    

    我以某种方式选择了索引号,以便在所有 -insert N 操作完成后,从上面的原始订单更改的新订单是:

    index:   0   1   2   3   4   5   6   7   8   9  10  11
    image:   s  t1   s   s  t2   s   s  t3   s   s  t4   s
    

    由于所有的间隔 s 都是 15 像素宽,这实现了与我的初始命令相同的间距。

  5. 接下来,会发生类似的横向处理:这次是 +clone 先前 -append 操作的结果并创建水平间隔。

  6. 最后,+append(对前一个 -append 和 sideway-process 的结果进行运算)创建最终结果 f4.png

当我对最后一条命令进行基准测试时,每个命令重复 100 次后得到以下结果:

  • 我更新前的第一个命令,100 次: 48.3 秒
  • 更新后我的上一条命令解释了 100 次: 46.6 秒

所以速度提升不是那么显着,如果你想相信这些数字,大约会提高 3%。

(应该注意,我确实在同一台机器上并行运行了两个循环,以在基准测试比赛中创造更多的公平性。 通过并行运行,它们都必须处理相同的 CPU 和 I/O 负载,这可能是由它们自己以及机器上同时发生的其他进程引起的。)

【讨论】:

  • 哇。传说中的答案。这么多信息,只有 2 票!
  • @deweydb:谢谢 ;-) 请查看我的其他[imagemagick] 答案(甚至我的[ghostscript][pdf] 答案并对其进行投票,以弥补这种低估). ;-)
【解决方案2】:

因为我没有你的图片、你的纹理或它们的尺寸,所以我会给你展示一些类似的东西让你适应......

制作一些输入图像:

convert -size 500x400 xc:black 1.png
convert -size 500x400 xc:red 2.png
convert -size 500x400 xc:green 3.png
convert -size 500x400 xc:blue 4.png

和背景纹理:

convert -size 2000x2000 xc:gray +noise gaussian background.png

现在按照你的要求做,但没有中间文件到磁盘:

montage [1234].png -background none -tile 1x4 -geometry +0+15 miff:- |
  convert -background none :- -size 15 xc:none -clone 0 +append png: | 
  convert -gravity center background.png :- -composite z.png

第一行将 4 个图像依次排列,并通过管道将组合结果以MIFF(多图像文件格式)发送到下一个命令。第二个命令从管道读取图像并附加一个 15 像素宽的“间隔”,然后复制第一列图像(使用clone)并通过另一个管道作为PNG 写入下一个命令。最后的命令读取 8 个组合的小图像并将它们放在背景上,居中。

如果图像的右侧列应该是反射的左侧列 - 很难从显示扁平黑框的糟糕示例中分辨出来 - 您可能需要将命令的第二行从

  convert -background none :- -size 15 xc:none -clone 0 +append png: | 

  convert -background none :- -size 15 xc:none \( -clone 0 -flop \) +append png: | 

【讨论】:

  • 我认为您的代码可能比我的更快(与 OP 的代码相比,它的速度仅提高了约 20%)。比较会很有趣......但是,我的笔记本电脑在今天偶尔崩溃+重启时遇到问题。因此,当它再次发生时,我的答案还没有完成,我现在必须填补缺失的部分......
  • @KurtPfeifle 你是对的——当然。我必须更具战术性。我会更有战术性!
  • 我这样做的主要动机不是“战术”。我只是用一个论点作为辅助论点:)
  • 我一直在运行您的代码(使用 my 图像),将蒙太奇跳过到嘈杂的背景上。它需要大约相同的时间——但您没有将输入图像的大小调整为 OP 所需的 487x296 像素!
  • 天哪……我没想到会有这么好的答案。非常感谢大家所做的一切。我是 ImageMagick 的新手,所以我必须阅读并测试你所说的一切。再次感谢您!
【解决方案3】:

这是另一个答案。它从 Mark Setchell 的方法中汲取灵感。在这里,我提出了一个新的命令管道。不同的是,我...

  • ...在管道中仅使用 3 个 montage 命令(马克在他的管道中使用了 1 个 montage 和 2 个 converts),我
  • ...准确再现 OP 的 3 个不同命令的输出。

作为输入,我在另一个答案t{1,2,3,4}.png 中使用了由 Ghostscript 命令创建的四个图像。

为了创建合适的背景图像,我使用了 Mark 的命令,但经过修改:我将图像缩小了一点,以便 OP 中的 -texture 运算符可以有意义地使用:

convert -size 200x200 xc:gray +noise gaussian background.png

然后我对所有三个命令/脚本进行了基准测试,...

  1. ...作为 shell 脚本的 OP 命令,
  2. ...Mark 的方法,尽管如上所述进行了修改,
  3. ...我最初的方法,通过添加背景图像进行了修改。

对于基准测试,我确实运行了 100 次重复。 这样做时,我大致同时在单独的终端窗口中启动每个脚本/命令。 测试机上的 CPU 有 4 个核心。 因此,当命令并行运行时,它们必须分别处理相同的 CPU 和 I/O 负载,相互竞争资源。 这是我能想到的最“公平”的临时性能测试设置。

我还确认 3 个测试(photo_test1.jpgms+kp_test2.jpgkp_test3.jpg)创建的输出文件在像素上(几乎)相同。 如果输出切换为 PNG(而不是 JPEG,OP 要求的方式),那么这 3 种方法之间的这些小差异也会消失。

结果如下:

使用原始帖子 (OP) 中的 4 个命令编写脚本:

mkdir ./imagesResized
time for i in {1..100}; do
    convert t*.png -resize 487x296\! -flop                   \
      -set filename:f "./imagesResized/%t%p" '%[filename:f]'.png    
    montage ./imagesResized/*.png -tile 1x4 -geometry +0+15  \
            -background none line.png    
    montage line.png line.png -tile 2x1 -geometry +45+0      \
            -background none photos.png

    montage photos.png -geometry +20+247                     \
            -texture background.png                          \
             photo_test1.jpg    
done

结果:

真实的 2m13.471s
用户 1m54.306s
系统 0m14.340s

大约 133 秒的实时时间。

将此作为 100% 的时间消耗。

受马克回答启发的修改命令管道:

time for i in {1..100}; do
  montage t[1234].png -resize 487x296\! -flop -background none \
         -tile 1x4 -geometry +0+15 miff:-                      \
    | montage :- -background none -clone 0 -tile 2x1 -geometry +45+0 miff:- \
    | montage :- -geometry +20+247 -texture background.png ms+kp_test2.jpg ;
done

结果:

真正的 1m50.125s
用户 1m32.453s
系统 0m16.578s

大约 110 秒。

与原始命令相比,耗时约 83%。

我原来的命令,现在完成了缺少背景合成:

time for i in {1..100}; do
    convert t*.png -flop -resize 487x296\! \
          -background none     \    
          -size 0x15           \    
           xc:white            \    
          -duplicate 7         \    
          -insert 0            \    
          -insert 2            \    
          -insert 3            \    
          -insert 5            \    
          -insert 6            \    
          -insert 8            \    
          -insert 9            \    
          -append              \    
          \( +clone -size 90x0 xc:white +swap \) \ 
          +append              \    
          -transparent white   \
           miff:-              \    
    | montage :- -geometry +65+247 -texture background.png kp_test3.png
done 

结果:

真正的 1m34.786s
用户 1m20.595s
系统 0m13.026s

大约 95 秒。

与原始命令相比,耗时约 72%。

【讨论】:

  • 非常感谢您的所有努力!
  • @MrChristo:我的版本是v6.9.0-0。可以更新吗?您能否使用粘贴服务,例如 Debian'sGitHub's 或其他将您的输出复制到? (另请参阅我的下一条评论...)
  • @MrChristo:您使用的是 Unix、Linux 还是 MacOSX?如果是这样,请确保在每个行继续标记“\”之后没有 any 空格。或者,将所有内容写入一行(但随后跳过行继续标记“\”!)。
  • @MrChristo:如果您使用的是 Windows,请使用 ^ 作为行继续标记。也使用 ( ... ) 来分隔横向处理,而不是 \( ... \)
  • 谢谢,我在 linux 上,我确实检查了空白但没有看到任何空白。我将把所有内容放在一行中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多