ImageMagick 示例 -- 视频处理
ImageMagick 并不是特别适合处理数字视频,但它常常被用于此目的,尤其是在 Linux 环境中。这里我探讨一些专门用于处理真实拍摄(以及光线追踪生成)视频序列的技巧和示例。
视频转 GIF、优化总结
软件开发者 Benoit Rouleau 使用 IM 制作动态 GIF,他在与我讨论时给了我一段飞机从上方飞过的 AVI 视频,以帮助我们共同探讨 IM 的视频转换技巧。然而,尽管 AVI 本身相当小,未压缩的视频却有
字节这样庞大的体积,并且在
帧中包含了
种颜色。不过 IM 将这段视频转换成 GIF 动画完全没有什么困难。但要提醒的是,你很可能会遇到一些不受支持的 'AVI chunk' 错误,使用 "[-quiet](https://imagemagick.org/command-line-options/#quiet)" 控制设置 即可忽略它们。 |
magick -quiet -delay 1 plane.avi plane.gif
![[IM Output]](../static/img/video/plane.gif)
这里使用了 ImageMagick 默认的颜色量化与抖动方法,得到了对这段视频相当合理的转换结果。几乎没有什么颜色问题,因为视频本身一开始就只用了很少的颜色。这并不总是如此,尤其是因为 GIF 有每帧 256 色的限制。然而该动画文件有
字节大小,虽然由于颜色削减和 GIF 像素数据压缩,它只有原来的 1/5,但仍然相当大。此外,如果你进一步研究生成的动画,会发现图像中的
帧里,有
帧添加了各自独立的局部颜色表。也就是说,GIF 动画里的每一帧都需要自己的颜色索引表。换言之,虽然每帧的颜色都少于 256 色(受 GIF 格式限制),整个动画总共却用了
种颜色。遗憾的是 GIF 格式不会压缩颜色表,所以这些额外的颜色表最多可能占用:256 色 * 每色 3 字节 * 106 帧,即 81,408 字节的文件空间。对一段 1G 字节的视频来说不算多,但仍然是一笔可观的空间,尤其是当我们进一步优化视频时。除此之外,这段动画的 GIF 帧优化效果也不会很好。这不仅是因为背景在移动(由于摄像机向上摇移),也是因为 IM 使用了误差校正抖动(希尔伯特曲线抖动),它会生成一种逐帧各不相同的伪随机颜色图案。后面的例子会让这种 '抖动噪声' 变得更加明显。
共用的全局颜色表
这里我为视频的所有帧生成一张单一的全局颜色表。 |
magick -quiet -delay 1 plane.avi +remap plane_cgc.gif
这自然会得到
张局部颜色表,文件大小为
字节。 ![[IM Output]](../static/img/video/plane_cgc.gif)
如你所见,生成的动画没有多余的局部颜色表。取而代之的是,IM 基于动画中的所有帧生成了一张包含
种 '最佳' 颜色的单一全局颜色表。遗憾的是,这也导致像素数据不像之前压缩得那么好,因为需要更强的抖动。结果是动画看起来稍差,但大小与上一个大致相同。对于这段颜色有限的特定视频,我甚至可以把所用颜色进一步减少,比如只用 64 色而不会有太多问题,生成更小的动画文件。然而这在很大程度上取决于所用的视频序列,可能并不好看。你自己的视频可能结果更好,也可能更差,尤其是在处理使用更多颜色、可能还包含多个场景的视频时。
通用的全局颜色表
生成 '更小' GIF 动画的更好办法,是直接提供一组通用的颜色范围,而不是为动画生成 '最佳' 全局颜色表。使用一张无论原始视频中有什么颜色都能良好工作的颜色表。这么做的另一个理由是,你可以把视频做得更长,而不会严重损害颜色选择,也不必为每一帧使用局部颜色表。每一帧都抖动到同一张颜色映射表上,完全独立于动画中其他帧的内容。 这里我使用一张 '332' 颜色映射表,在不需要透明度时它通常被认为是一张非常好的标准颜色映射表。我经常看到这张颜色映射表(或者一张 219 色的 '网页安全' 颜色映射表)被广泛用于各种视频格式中。 |
magick -quiet -delay 1 plane.avi -remap colormap_332.png plane_ugc.gif
![[IM Output]](../static/img/video/plane_ugc.gif)
这段动画有
张局部颜色表,因此动画更小,为
字节大小。然而问题在于,你常常会在颜色恒定的区域看到明显且恼人的 '噪声'。这种噪声在之前所有的视频动画里其实都存在。只是因为现在使用了一张更通用、因而分布更广的颜色映射,它才第一次变得可见。这种噪声实际上是由重建图像时对削减后的颜色集合进行抖动造成的。但这会生成一种逐帧变化的伪随机颜色图案,结果使图像看起来像有背景噪声。关于为什么会发生这种情况的更多细节,请参阅 E 抖动的问题。 我们可以直接关闭颜色抖动来去除 '抖动噪声'…… |
magick -quiet -delay 1 plane.avi \
+dither -remap colormap_332.png plane_ugc_nd.gif
它有
张局部颜色表,大小为
字节。 ![[IM Output]](../static/img/video/plane_ugc_nd.gif)
生成的动画非常小,只有原始动画的 1/60,这通常是因为大片纯色区域带来了极好的像素压缩。但它虽然修正了抖动噪声、得到了非常小的文件大小,却换来了色带(颜色条纹),这通常被认为是一个非常糟糕的折中。
有序抖动视频
真正的解决办法是使用一种不会逐帧生成不同图案的颜色抖动技术。 例如,这里我使用利用色调分离色阶的有序抖动来对同一张通用的 '332' 颜色映射表进行抖动。 |
magick -quiet -delay 1 plane.avi \
-ordered-dither o8x8,8,8,4 +remap plane_od.gif
它有
张局部颜色表,大小为
字节。 ![[IM Output]](../static/img/video/plane_od.gif)
上面同样使用了 "[+remap](https://imagemagick.org/command-line-options/#remap)" 操作符,以确保所有图像使用完全相同的全局颜色映射(有序抖动已经把它削减到最多 256 色)。由于颜色数量已经是最优的,"[+remap](https://imagemagick.org/command-line-options/#remap)" 操作符不会进行任何抖动或颜色削减。生成的抖动图案不是随机的,从一帧到下一帧不会有太大变化。这样 '抖动噪声' 就从动画中被去除了,逐帧得到一个固定的颜色图案。这个图案还非常重复,从而允许更好的压缩。最后,由于颜色映射是固定的,它无论用于什么视频都应该能相当好地工作。
更高质量的有序抖动视频
不过这段特定的视频只用了一小段颜色范围,主要是各种深浅不同的蓝色,所以它实际上并没有用到通用均匀颜色映射所提供的大部分颜色。事实上,上一段视频动画中只用了
种颜色!这极其少,因此也相当明显。但这同时意味着,这段特定的动画可以从在有序抖动操作中使用大量 '色阶' 中获益,从而提升整体质量。不过首先,我们需要确定在达到 GIF 文件格式和全局颜色映射重映射两者共同施加的 256 色限制之前,动画能承受多少个色阶。然而棘手之处在于,你必须在把动画保存为受限的 GIF 格式 之前 就确定这些。下面就是我所用的命令……
magick -quiet plane.avi -ordered-dither o8x8,23 -append -format %k info:
基本上,我不断增减要使用的色阶数量,直到得到一个刚好落在所需 256 色限制之内的数值。 然后我就可以把找到的 '色阶' 选择应用到 plane 动画上。 |
magick -quiet -delay 1 plane.avi \
-ordered-dither o8x8,23 +remap plane_od2.gif
它有
张局部颜色表,大小为
字节,并有
种颜色。 ![[IM Output]](../static/img/video/plane_od2.gif)
如你所见,生成了一段质量非常高的有序抖动视频,它与我们先前生成的 '最佳颜色映射' 全局颜色映射版本不相上下,但同时体积小了 1/3,而 '抖动噪声' 现在也难看得多了。当然,由于质量高得多,它确实需要更大的文件,因为它不像低质量版本那样压缩得好。另一方面,你现在通过所用 '色阶' 的数量,真正实现了对质量与文件大小折中的良好控制。只要记住这个技巧是一个 特例 ,适用于不使用太多颜色的动画。而且通过添加更多帧把视频做长也会增加颜色,因此需要降低 '色阶' 的质量控制。在一般 GIF 动画的颜色优化方法中,这大概是我迄今见过的最佳方法。它去除了 '抖动噪声',提供了一定的质量控制,并保留了使用其他 GIF 动画优化方法(如帧优化)的能力。
压缩(透明度)优化
由于这段视频使用了摇移的摄像机,视频背景会逐帧变化。这意味着 GIF 动画的帧优化效果不会很好。 不过我们仍然可以使用简单的透明度优化来进一步减小 GIF 动画的最终大小。 |
magick plane_od2.gif -layers OptimizeTransparency +remap plane_opt.gif
结果大小为
字节,并有
种颜色。 ![[IM Output]](../static/img/video/plane_opt.gif)
也就是说,向图像添加了一种额外的颜色,即一个透明颜色索引,任何不改变当前所显示颜色的像素都被设为透明。这进而在原始动画中生成了大段的透明区域,以及相似像素序列的重复,从而在最终的 GIF 图像中带来更好的 LZW 压缩。还不错,动画现在只有直接转换为 GIF 时的一半大小,而且仍然保持着相当高的质量。 如果你想对上述内容有所补充、讨论进一步改进这些技术,请联系我或 IM 论坛。我非常乐意听取你的看法、技术与讨论,或者看看你可能遇到的某个特定视频/动画问题。 这样的一个讨论就是为动画 GIF 量化寻找“正确的色阶”。
Giflossy 压缩 LZW 优化
一个新工具 GifLossy 是原始 Gifsicle 程序的一个分支,它修改每一帧的颜色,以便让 LZW 能更强地压缩图像。 例如,这里我把它应用到原始的 GIF 动画上,要求它把颜色削减到单一的 256 色表。 |
gifsicle -O3 --lossy=80 --colors 256 plane.gif -o plane_giflossy.gif
它得到了一个绝对惊人的大小:
字节。它远不如我们用有序抖动所达到的质量那么高,但大小却不到一半。 ![[IM Output]](../static/img/video/plane_giflossy.gif)
受上述结果鼓舞,我决定把 GifLossy 用在我们得到的最佳有序抖动结果上,看看能否让它更小。 |
gifsicle -O3 --lossy=80 plane_od2.gif -o plane_od2_giflossy.gif
我们确实得到了一个更小的大小:
字节。遗憾的是,我们基本上失去了之前那么辛苦才达到的高质量有序抖动结果。这令人失望。
视频帧的去隔行处理
并非所有图像都来自数码相机。从非 CCD 摄像机的数字视频信号中提取图像是非常常见的。这些图像为了在电视上直接显示而被隔行处理,结果每隔一行就是图像的不同一帧(隔行)。对于物体没有移动的两帧,隔行通常不太明显。也许只会让图像产生轻微的边缘模糊。但当涉及快速移动的物体时,由于两帧被合并到了一起,生成的隔行图像会非常令人困惑。 Wolfgang Hugemann Auto@Hugemann.de(德国)遇到了这个问题,给我寄来了一张他自己拍摄的碰撞测试的快照。不过为了演示,我会使用从中裁剪出的一张较小的图像。这些技术同样适用于全尺寸图像。 |
magick video_frame.png -crop 100x100+200+470 +repage interlaced.png
![[IM Output]](../static/img/video/interlaced.png)
| Wolfgang Hugemann 对原始视频帧使用了 TIFF 格式,我为了在 IM Examples 中使用而把它转换成了 PNG。在处理完成之前,不要被诱惑去对这些图像使用 JPEG,因为它会破坏这一过程所需的低层质量。
---|---
如你所见,隔行显示出两个独立的帧,因为它来自一段隔行的 PAL 数字视频序列(每秒约 50 个半帧)。是的,汽车移动得非常快,摄像机使用了高速快门,生成了非常高质量的视频图像。生成的图像是两个交织在一起的半帧,在两个半帧之间 1/50 秒的间隔时间里,汽车的后视镜移动了相当大的一段距离。 这里我们只是用白色替换隔行半帧中的一个(每隔一行)。这是标准的去隔行方法,称为 'BoB' 滤波器。这是 Wolfgang 为 IM Examples 贡献的。 |
magick interlaced.png -fx "floor(j/2)==j/2 ? u : 1" deinterlace_1.png
![[IM Output]](../static/img/video/deinterlace_1.png)
不过 FX 操作符很慢,所以一种替代方案是创建一张 '条纹图像'。这样的图像可以从特殊的 "pattern:Horizontal2" 内置图像生成。 然后可以把该图像叠加到原图上,使用 '[Screen](compose.html#screen)' 合成方法叠加白线,或使用 '[Multiply](compose.html#multiply)' 叠加黑线。例如…… |
magick -size 100x100 pattern:Horizontal2 \
interlaced.png -compose Multiply -composite deinterlace_2.png
![[IM Output]](../static/img/video/deinterlace_2.png)
对图案取反,就可以用来选择隔行图像的另一半。或者,如果你把 'Multiply' 改成 'Screen',就可以提取出带白色背景的帧。 作为另一种方法,我尝试通过简单地复制前一行来填补缺失的帧行。 |
magick interlaced.png -fx "u.p{i,j-j%2}" deinterlace_3.png
![[IM Output]](../static/img/video/deinterlace_3.png)
你也可以使用像素化技术来缩小再放大图像,从而把每隔一行加倍。 |
magick interlaced.png -sample 100%x50% \
-sample 100%x200% deinterlace_4.png
![[IM Output]](../static/img/video/deinterlace_4.png)
稍作变化,你就可以在缩放放大的过程中把两侧的行结合起来,从而在垂直方向上平滑这张半帧图像。 |
magick interlaced.png -sample 100%x50% \
-resize 100%x200% deinterlace_5.png
![[IM Output]](../static/img/video/deinterlace_5.png)
结果是对隔行视频图像中一帧的特别漂亮的提取。 如果你想从图像中提取另一个半帧,可以调整 'sampling:offset'(自 IM v6.8.4-7 起)。 |
magick interlaced.png -define sample:offset=75 \
-sample 100%x50% -resize 100%x200% deinterlace_6.png
![[IM Output]](../static/img/video/deinterlace_6.png)
在这个 IM 版本之前,你需要把图像 "[-roll](https://imagemagick.org/command-line-options/#roll)" 一个像素,才能达到同样的结果。
![[IM Text]](../static/img/video/plane_od2_find.txt.gif)