ImageMagick:将多个"蒙太奇"命令合并为一个以获得性能

MrC*_*sto 1 performance merge imagemagick montage

我有一个脚本,可以拍摄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
Run Code Online (Sandbox Code Playgroud)

Kur*_*fle 5

首先,让我这样说:如果您的输入图像非常大,您将通过将单独的命令放入单个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
Run Code Online (Sandbox Code Playgroud)

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

convert -size 200x200 xc:gray +noise gaussian background.png
Run Code Online (Sandbox Code Playgroud)

合并前两个命令:

以下是我的第一次拍摄,以便提出一个命令.它应该与前两个命令输出结果相同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:
Run Code Online (Sandbox Code Playgroud)

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

 

所以,我曾经预料到的一件事是:每幅图像之间都有一个蓝色的间隔物.我出于调试原因将其着色.这可以通过将颜色变量设置为none(透明)来解决.

想到的事情,以及我在打开结果图片后才发现的事情f.png:

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

  • 列中各个图像的间距太窄,仅为15像素.这是因为在line.pngOP 的中间文件中,间距不是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:
Run Code Online (Sandbox Code Playgroud)

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

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

  • 此外,与第一列中的图像间间隔相同的内容将适用于列中的间距:我必须将其加倍.

  • 我不会在意的是,+append左边和右边的-ed列没有添加相同的空格(45像素).

所以这是一个迭代:

_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:
Run Code Online (Sandbox Code Playgroud)

结果如下:

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

 

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

  • \(:
    这会打开一些图像的"侧面"处理.然后将该横向处理的结果再次插入到主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...... 的4份副本t4.png.
  • +duplicate:
    这个运算符类似于+clone.它复制当前加载的图像堆栈中的最后一个图像.在这种情况下,最后一个图像(并且只有横向管道内的剩余图像)是前一个-append操作的结果.此操作创建了第一列4个图像,由红色间隔物间隔开.
  • +append:
    此运算符水平附加所有当前加载的图像.目前有三个图像:-append操作的结果,由其创建的副本+duplicate以及90x90大小的xc:-canvas.
  • +swap:
    此运算符交换当前加载的堆栈上的最后两个图像.
  • +write:
    此运算符从当前加载的堆栈中写出所有图像.当有多个图像时,它将使用给定的名称写入这些图像,但附加一个数字.它是调试复杂ImageMagick命令的绝佳工具.这很棒,因为在+write操作完成后,先前加载的图像全部保留在堆栈中.这些图像保持不变,处理可以继续.但是,我们现在已经完成,在这种情况下不会继续.因此,我们用a关闭边路过程\).
  • null:
    现在我们关闭了侧面过程,ImageMagick再次将生成的图像从侧面放入主管道.记住,+write没有完成处理,它写了一个文件到磁盘,这是一个中间结果.在主要管道中,现在仍然有原始t1.png... t4.png加上横向处理的结果.但是我们不会对它们做任何事情.我们将把中间结果+write作为最终结果.但该convert命令现在需要查看输出文件名.如果没有看到,它会抱怨并向我们显示错误消息.因此,我们告诉它注销它已加载的所有内容并丢弃堆栈中的所有图像.为此,我们使用null:输出文件名.

    (如果你喜欢冒险,用out.png作为文件名,而不是null:你会看到,实际上ImageMagick的创建多个.out-0.png,out-1.png... out-3.png文件名你会发现,out-4.png是一样的f.png,并且out-{0,1,2,3}.png是相同的输入图像-你可以同时更换null:-append output.jpg,看到那么会发生什么......)

更新

现在进行速度比较......

对于第一个粗略的基准测试,我确实在100次迭代的循环中运行了OP的前三个命令.然后我也做了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
Run Code Online (Sandbox Code Playgroud)

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

  1. 首先,它加载所有图像.它-flopš他们,这-resizeŞ他们.

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

  3. 第三,它创建了该画布的7个附加副本.现在堆栈上有12个图像:4个输入文件,1个扫描图像xc:和7个画布副本.

  4. 然后,它使用一系列-insert N操作.该-insert N操作操纵图像堆栈的顺序.它从堆栈中删除最后一个图像并将其插入图像索引位置N.当-insert一系列的操作开始时,有4幅图像(t1,t2,t3,t4)在栈上,再加上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  
    
    Run Code Online (Sandbox Code Playgroud)

    我已经以某种方式选择了索引号,以便在-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
    
    Run Code Online (Sandbox Code Playgroud)

    由于所有垫片s的宽度均为15像素,因此与初始命令的间距相同.

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

  6. 最后,+append(对前一个-append和侧面过程的结果进行操作)创建最终结果f4.png.

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

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

所以速度增益并不那么引人注目,如果你想信任这些数字,大约要好3%.

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