FFmpeg针对图片的批量操作
在生活中,我们有时候会碰到这样一种场景:外出参加学术会议,回来整理PPT时发现所有的照片都需要裁剪;或者下载了一本扫描版的书,但扫描的图片对比度太低根本看不清字体。对于单张图片,我们可以很方便的使用各种编辑工具进行修改,但是如果图片一多,事情就要麻烦多了。
许多同学可能会提出,PhotoShop提供了批处理工具,可以实现对图片的批量操作。不过本文我们将探讨另一种可能性:使用ffmpeg在命令行下实现批量操作。
什么是FFmpeg
FFmpeg是一个开源的、跨平台的多媒体处理框架,全称为 Fast Forward MPEG (Moving Picture Experts Group),由神一般的开发者 —— 法国程序员法布里斯・贝拉(Fabrice Bellard)创建并开源。它以其强大的功能和灵活性在音视频处理领域独树一帜。这个工具集不仅包含了丰富的编解码库、容器处理库和滤镜库,还提供了命令行工具,用于录制、转换、编辑、合并、流化以及播放几乎所有的音频和视频格式, 当然也包括图片格式 。无论是MP3、AAC音频,还是H.264、H.265视频,FFmpeg都能轻松应对,实现高效的数据处理和转换。无怪乎有人称之为“多媒体处理的瑞士军刀”。(甚至有许多著名的播放器或转码工具都是基于FFmpeg二次开发的:迅雷,QQ影音,VLC player,格式工厂……)
作为一个跨平台的应用程序,FFmpeg提供了包括Linux、Windows、macOS在内的多种系统上的安装包,可以在官网的下载页面进行下载(如下图),当然也可以下载源代码包自行编译。
FFmpeg的核心应用程序包括三个可执行文件,分别是ffmpeg.exe
、ffplay.exe
、ffprobe.exe
。其中,ffmpeg是FFmpeg项目中最常用的命令行工具之一,主要用于音视频数据的编解码、转码、剪辑、合并、提取等操作;ffplay是FFmpeg项目中的一个简单而强大的视频播放器工具,不仅支持多种音视频格式的本地播放,还支持网络流媒体的播放,常用于快速预览音视频文件;ffprobe是一个多媒体流分析工具,用于检测多媒体文件的各种信息,如格式、编解码器、帧率、比特率等,广泛应用于音视频文件的质量检测。
在博客先前的文章中,我们介绍过如何使用ffmpeg工具将字幕嵌入视频中,其利用了ffmpeg的视频编辑功能。本文则将介绍ffmpeg的图片编辑功能。
FFmpeg的处理原理很容易理解,如上图所示,它其会把输入的内容看作一个输入流,并将用户指定的滤波器(包括视频滤波器-vf
和音频滤波器-af
)作用在这个输入流当中,最终把结果传进输出流。
图片的批量裁剪
裁剪图片的指令如下:
1 | ffmpeg -i <input> -vf crop=w:h:x:y <output> |
其中:
-i
参数指定输入的图片的文件路径。-vf
是”video filter”的缩写,意为视频滤波器(当然它可以处理单张图片)。其后接参数,用于指定对输入对象流的图像部分的处理逻辑。在这里,crop=w:h:x:y
意味着以坐标(x,y)
为起点,截取宽度和高度分别为(w,h)
的图片。另外一点需要注意,在图片处理中,坐标起点一般为左上角,并且x轴正方向是向右、y轴正方向是向下,这与数学中的坐标系有所不同。- 另外,如果要强制覆盖已经存在的图片,则需要加上
-y
参数
例如上面这些图片,尺寸为1414x794
,我想把PPT两侧的黑边给去掉。使用绘图工具测得黑边的坐标范围是 0~180px
和1240~1414px
,因此截图的坐标起点是(180,0)
,截图尺寸为(1060,794)
,我们可以使用指令 ffmpeg -i 01.jpg -vf crop=1060:794:180:0 output/01.jpg
进行转换。考虑到要进行批量转换,我们可以配合shell语句写出下面的指令:
1 | mkdir -p output # 防止没有创建文件夹导致报错 |
如果一切正常的话,屏幕上大概会滚过类似下图的信息
同时,在输出文件下也能找到新产生的图片文件
图片的大小调整
Windows PowerToy工具提供了图片大小调整器,但是非Windows用户想要批量调整图片大小依然很麻烦。但是FFmpeg也可以实现这一点,指令如下:
1 | ffmpeg -i <input> -vf 'scale=w:h' <output> |
和前面一样:
-vf
指定了视频滤波器参数。这里采用的滤波器是scale=w:h
,意味着将输入的图片大小重新调整为(w,h)
。- FFmpeg允许多个滤波器的串联,参数格式大概类似于
<filter1>,<filter2>,...
。例如我们想先裁剪图片,随后压缩图片画质,可以这么做:ffmpeg -i <input> -vf 'crop=w:h:x:y,scale=w:h' <output>
。
下面是一个例子。还是前面的那些图片,现在我们要裁剪掉黑边,并且把图片尺寸缩小到720x480px
:
1 | for f in `ls *.jpg`;do ffmpeg -i $f -vf 'crop=1060:794:180:0,scale=720:480' output/$f.resize.jpg -y ;done |
新产生的图片文件如下图(右边那一列)。注意,调整图片尺寸后,图像长宽比也会受到影响。
图片色彩调节(去色、亮度、对比度、锐化等)
对于扫描的书籍或笔记,有时候我们希望将颜色转为灰白以减小存储空间,此外对于一些不清晰或色彩过淡的页面,我们也希望能够调整色彩。这些工作FFmpeg都可以做。
图片去色(彩色图片转灰白)
在查询资料的时候,我看到了三种不同的滤波器参数组合,分别如下:
-vf lut=c1=128:c2=128
或者-vf lutyuv=u=128:v=128
:利用查找表(Lookup Table,lut
)修改像素点的YUV色彩空间属性,从而实现修改视频帧的颜色。这两种写法是等价的,都是将视频帧像素信息的色彩分量设置为某个固定值(而明亮度分量保持不变)以实现去色效果- YUV色彩空间是一种为了彩色电视信号传输而开发的色彩模型,其中第一个分量Y代表明亮度,后两个分量U和V编码色彩信号。
- 相比于RGB色彩空间,YUV在存储色彩信号分量时可以适当降低采样率(请注意只要Y信号还在,图像质量就不会受到影响),从而节省传输带宽。
-vf lutyuv=u=128:v=128
将色彩信号分量U和V全部设置为128,因此所有像素点全部不包含色彩信息,只有Y分量携带的明亮度信息还在,因此图片就成了黑白色。
hue=s=0
:hue
滤波器用于调整视频的色调、饱和度和亮度(基于HSV色彩模型)。s=0
将饱和度设置为0,这意味着去除所有颜色信息,只保留灰度信息。format=gray
:format
滤波器用于改变视频帧的格式。gray
将视频帧转换为灰度格式,这是最直接和有效的将彩色图像转换为灰度图像的方法(但是输出图片的体积在三种方法中最大)。
实际测试中,这三种滤波器组合都能够实现图片去色,但是输出的图片大小存在差异。经过比较发现,前两种参数组合的输出图片的色彩空间为 yuvj420p
,而format=gray
这种方法是 yuvj444p
。参考知乎文章 《YUV图解 (YUV444, YUV422, YUV420, YV12, NV12, NV21)》 ,我们可以知道yuvj420p
和 yuvj444p
分别代表了两种像素信息的采样方式,其中 yuvj444p
采样了更多的信息,这就是文件体积会更大一点的原因。由于我们将图片转灰白,根本目的是为了减小存储空间,因此format=gray
这种方法不可取。
最后总结一下:要实现图片去色,可以使用下面的指令实现:
1 | ffmpeg -i <input> -vf lut=c1=128:c2=128 <output> |
图像锐化
可以使用下面的指令实现:
1 | ffmpeg -i <input> -vf "cas=strength=0.95" <output> |
其中,cas
指定了一个对比度自适应锐化滤波器,其可调节的参数包括strength
(锐化强度)和planes
(作用的色彩通道)。
另一种指令是
1 | ffmpeg -i <input> -vf unsharp=5:5:1.5 <output> |
众所周知,图片的锐化或者模糊本质上是用一个卷积核对整幅图像进行卷积的结果,而卷积核的差异决定了效果是锐化还是模糊。这个滤波器名叫unsharp
,实际上既可以实现模糊也可以实现锐化。滤波器参数常用的有前三个,分别是luma_msize_x
、luma_msize_y
(这两个参数可以理解为卷积核的尺寸)、luma_amount
(滤波器数值。取值范围是[-1.5,1.5]
,其中负值表示模糊,正值表示锐化)。
上述两种滤波的实际效果:
色彩亮度、对比度调节
可以使用下面的指令实现:
1 | ffmpeg -i <input> -vf "eq=brightness=-0.2:contrast=1.6" <output> |
其中,eq
指定了一个调节亮度、对比度等信息的滤波器,其可调节的参数包括brightness
(亮度,取值范围 [-1.0,1.0]
,默认0)和contrast
(对比度,取值范围 [-1000.0,1000.0]
,默认1)。
上述滤波的实际效果: