首先,让我这样说:如果您的输入图像非常大,则通过将单独的命令放入一个 ImageMagick 命令链中,您只会节省大量的处理时间。但是,您可以通过跳过写出和读入中间结果图像的需要来节省磁盘 I/O 时间。
您的代码使用两个不同的montage
命令加上一个convert
命令来实现第一个蒙太奇。最后,您使用 onemontage
将先前的结果放在背景上。
从我的脑海中,我可以快速想出一种将前三个命令组合成一个命令的方法。将中间结果放在背景上的最后一个蒙太奇步骤并不容易正确,而且很可能也不会节省太多时间。因此,我暂时将其保持打开状态。
不幸的是,您没有提供任何指向源图像的链接。为了回答这个问题,我必须创建自己的。它们还可以用来演示我的答案的有效性。
为了创建四个 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
。这可以通过+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:
结果如下:
- 右边是
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
在横向处理链中工作得最好,因此前面解释了使用\(
and \)
。
-append
:这个操作符垂直
附加所有当前加载的图像。在这种情况下,这些是, ...的 4 个副本。t1.png
t4.png
+duplicate
:
这个运算符类似于+clone
. 它复制当前加载的图像堆栈中的最后一个图像。在这种情况下,最后一个图像(并且仅在横向管道内剩余的一个)是前一个-append
操作的结果。该操作创建了 4 个图像的第一列,由红色垫片隔开。
+append
:此操作符水平
附加所有当前加载的图像。当前有三个图像:操作的结果、由 创建的副本和大小的-canvas。-append
+duplicate
90x90
xc:
+swap
:
此运算符交换当前加载堆栈上的最后两个图像。
+write
:该操作符从当前加载的堆栈中
写出所有图像。当有多个图像时,它将使用给定的名称写入这些图像,但附加一个数字。它是调试复杂 ImageMagick 命令的好工具。这很好,因为在+write
操作完成后,之前加载的图像都保留在堆栈上。这些图像保持不变,并且可以继续处理。但是,我们现在已经完成,在这种情况下不会继续。因此,我们用\)
.
null
:
现在我们关闭了sideway进程,ImageMagick再次将sideway生成的图像放入主管道。请记住,+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
,看看会发生什么......)
更新
现在进行速度比较...
对于第一个粗略的基准测试,我确实在一个循环中运行了 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
这个命令的架构有点不同。
首先,它加载所有图像。是-flop
他们,也是-resize
他们。
其次,它创建一个 15x15 像素的画布,然后将其放置在图像堆栈上。
第三,它创建了该画布的 7 个额外副本。现在堆栈上有 12 个图像:4 个输入文件、1 个xc:
-canvas 和 7 个画布副本。
然后,它使用一系列-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
我以某种方式选择了索引号,以便在所有-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 像素宽,这实现了与我的初始命令相同的间距。
接下来,发生类似的横向处理:这次+clone
是前一个-append
操作的结果并创建水平间隔。
最后,+append
(它对前一个-append
和侧向过程的结果进行操作)创建最终结果,f4.png
。
当我对最后一个命令进行基准测试时,我得到了每个命令重复 100 次的以下结果:
- 我更新前的第一个命令,100次: 48.3秒
- 我更新后的最后一条命令解释,100次: 46.6秒
所以速度提升并不是那么显着,如果你想相信这些数字,大约会提高 3%。
(需要注意的是,我确实在同一台机器上并行运行了两个循环,以在基准测试竞争中创造更多的公平性。通过并行运行,它们都必须处理相同的 CPU 和 I/O 负载,这可能会导致本身以及机器上同时发生的其他进程。)