在聊FFMPEG计算PSNR之前,我们先来大概了解一下视频质量的评估方法。
1. 视频质量评估方法的分类
视频质量评估简称IQA - Image Quality Assessment。视频质量评估分为两大类:视频主观质量评价(Subjective Quality Assessment, SQA)和视频客观质量评价(Objective Quality Assessment, OQA)。直接让观测者对视频质量做出直观判断的主观质量评估方法是最为准确的评估方法,但该方法相对复杂且结果易受多种因素的影响,因此,在实际应用中通常使用客观的、易于实现的视频客观质量评估方法。
根据对原始视频的依赖程度,客观质量评估方法又分为三种类型:Full Reference (FR)、No reference (NR)和Reduced Reference (RR)。
FR — 同时提供原始图像和失真图像;
RR — 只有失真图像而没有参考图像,但部分参考图像的信息是已知的。
NR — 不存在参考图像。
全参考客观视频质量评价方法是指把原始参考视频与失真视频在每一个对应帧中的每一个对应像素之间进行比较。实际上,这种方法得到的是失真视频相对于原始视频的相似程度或保真程度,而不是真正的视频质量,但由于该方法实现起来简单直接,因此使用最广泛。
常见的全参考视频质量评价方法有如下三种:
1. MSE — Mean Squared Error,均方差。MSE = sum((f(i,j) - f'(i,j)) ^ 2) / (M x N)
M,N分别为图像的行数和列数。
2. PSNR — Peak Signal-to-Noise Ratio,峰值信噪比。
PSNR = 10 x log(((2 ^ K - 1) ^ 2) / MSE)
log为以10为底,K表示每个灰度像素的比特数,通常情况下,K = 8,因此,PSNR = 10log(255 ^ 2 / MSE)。
3. SSIM — Structural SIMilarity,结构相似性。该模型旨在比较参考图像和受损图像之间的结构信息,研究感知结构损伤,而不是感知误差。该方法计算较复杂,但其值可以很好地反应人眼主观感受。SSIM的一般取值范围为0~1,值越大,视频质量越好。
2. FFMPEG计算PSNR存在的问题
PSNR通过均方误差的对数值来表示图像的失真程度,是客观视频评价中常用的评估方法。
FFMPEG中提供了PSNR的计算,除了在编码过程中实时计算PSNR之外,也提供了比较转码前后两个视频的PSNR的功能,本篇只描述计算两个离线文件PSNR的内容。使用的命令行为-lavfipsnr="stats_file=psnr.log",或-filter_complex psnr="stats_file=psnr.log" ,其中,stats_file=psnr.log是将每一对对比帧的Y、U、V分量的mse和psnr以及mse和psnr的平均值打印到名为psnr.log(文件名可自定义)中。
实验中用到的完整命令行如下:
ffmpeg -s 1280x720 -i src.yuv -s 1280x720 -i dst.yuv -lavfi psnr="stats_file=psnr.log" -fnull -
以及,
ffmpeg -i src.ts-i dst.ts -lavfi psnr="stats_file=psnr.log" -f null -
其中src.ts和dst.ts是要比较的两个转码前后的ts文件,而src.yuv和dst.yuv则是从src.ts和dst.ts抽取出来的YUV裸视频数据,理论上来说,以上两个命令行的运行结果应该是相同的。下面我们来看一下,实际的运行结果是否与理论相符。
下面是两个YUV视频的运行结果:
以及psnr.log中的详细信息:
再来看看两个TS文件的比较结果:
为了定位原因,又做了两个实验:
(1) 由于前面两个实验中,yuv文件不含音频分量,而ts文件含有音频分量,因此我们我们用FFMPEG把src.ts和dst.ts两个文件的音频分量去掉,得到src_an.ts和dst_an.ts,再用FFMPEG来计算这两个文件的PSNR值,命令行如下:
ffmpeg -i src_an.ts -i dst_an.ts -lavfi psnr="stats_file=psnr.log" -f null -
得到的最终结果如下:
该结果与计算两个yuv视频的结果基本一致!因此,此处可推断,FFMPEG在计算带音频的两个视频文件的PSNR时出了差错。
(2) 为了进一步确认FFMPEG计算YUV文件和不带音频的ts文件的PSNR值是正确的,使用了另外一个开源工具Evalvid来计算两个YUV文件的PSNR,Evalvid的具体使用方法此处不再赘述,感兴趣的各位可以上网搜索或者查看参考文档:http://www2.tkn.tu-berlin.de/research/evalvid/
使用的命令行如下:
psnr 1920 1080 420 src.yuvdst.yuv > ref_psnr.txt
得到的结果:
由于Evalvid只计算两个对比视频Y分量的PSNR,而UV分量没有考虑在内,因此,得到的值只能是个近似值。从Evalvid给出的结果来看,得到的PSNR值与FFMPEG计算YUV文件和不带音频的TS文件得到的PSNR值基本一致,因此更进一步确认了FFMPEG计算带音频的TS文件的PSNR值是存在bug的。
3. 问题分析
对该问题的分析,我这里有之前在FFMPEG-3.1.1上开发时绘制的一幅函数调用关系图。
FFMPEG通过比较两视频中各帧的PTS来确定做对比的两帧数据,只有PTS相同的帧才可用来做对比,那么,如果两个视频文件中对应视频帧的PTS不同,则用来做对比的数据也就不是原来的对应帧。通过跟踪定位,发现根本原因在于FFMPEG对输入两个视频中音视频流的处理上,具体的代码分析如下:
当主视频以音频起始时间为基准来调整各帧的PTS,而辅视频以视频起始时间为基准来调整各帧的PTS,就会出现调整后两个输入文件对应视频帧的PTS不同,例如,主视频以音频流起始时间为基准,调整后,其视频帧的PTS为-7200,-3600,0,3600,7200,...而辅视频则以视频流起始时间为基准,调整后,其视频帧的PTS为0,3600,7200,10800,14400,...导致主视频的前两帧找不到对比帧,而第3帧对比的是辅视频的第1帧,主视频第4帧对比的是辅视频的第2帧,等等。
4. 解决方法针对以上问题分析,就很容易找到应对方案了。我采用的方法是,在类似场景下,强制将两个视频的起始时间都设置为视频流的起始时间。修改后,得到的stc.ts和dst.ts之间的PSNR值如下:
结果正常!
最后,说一下采用以上方法计算PSNR的注意事项,一是对比的两个视频帧率要一致,主要是为了PTS同步;另外一个就是,不能使用avoid_negative_ts选项,因为该选项会强制采用音视频流中起始时间戳较小的一个作为文件的起始时间戳。