视频质量-客观评估三剑客

943 阅读13分钟

前言:

伴随算法和算力的持续更新迭代,如何比较编码标准或编码器的视频质量?业界中有主观评价和客观评价两个流派。主观评价由于成本高、稳定性差,工程开发中应用并不广泛。客观评价分为FR(全参考)、RR(部分参考)和NR(无参考)三个方向,本文重点介绍最常见的三种评价视频质量的全参考方法PSNR、SSIM、VMAF。

一、原理:

1、PSNR

PSNR(Peak Signal-to-Noise Ratio,峰值信噪比)是一种用于衡量图像或视频质量的指标。它通过比较原始图像和压缩后图像之间的差异来评估图像的质量。

计算公式

PSNR 的计算公式如下:

PSNR = 10 * log10(MAX^2 / MSE)

其中:

  • MAX 是像素值的最大可能范围。对于 8 位图像,MAX 是 255。
  • MSE(Mean Squared Error,均方误差)是原始图像和压缩后图像之间差异的度量。计算公式为:MSE = (1 / MN) * Σ(Original(i, j) - Distorted(i, j))^2

取值范围

PSNR的单位是分贝(dB),值越高表示压缩后图像的质量越好,与原始图像的差异越小。 PSNR的取值范围理论上是无限的,但在实际中,它的值通常在 20 到 50 之间。以下是一些典型的 PSNR 值及其对应的图像质量:

  • 40-50 dB:非常好,几乎看不到噪声和失真。
  • 35-40 dB:很好,噪声和失真非常轻微。
  • 30-35 dB:较好,噪声和失真不太明显。
  • 25-30 dB:一般,有轻微的噪声和失真。
  • 20-25 dB:较差,有明显的噪声和失真。

限制

需要注意的是,PSNR 只能用于评估图像或视频的客观质量,而不能反映人类的主观感知。因此,PSNR 高的图像不一定看起来比 PSNR 低的图像更好。如从人们主观评测角度,有许多情况PSNR较高但被认为质量较差,这主要是因为人眼对不同类型的误差敏感性并不一致。

2、SSIM

SSIM(Structural Similarity Index,结构相似性指数)是一种衡量两幅图像相似度的指标,它考虑了图像的亮度(luminance)、对比度(contrast)和结构(structure)变化。与PSNR不同,SSIM 更接近人类视觉系统对图像质量的感知。

计算公式

SSIM 的计算公式如下:

SSIM(x, y) = [l(x, y)]α · [c(x, y)]β · [s(x, y)]γ

其中:

  • l(x, y) 是亮度比较函数,定义为 l(x, y) = (2μxμy + C1) / (μx² + μy² + C1),用于比较两幅图像的亮度。
  • c(x, y) 是对比度比较函数,定义为 c(x, y) = (2σxσy + C2) / (σx² + σy² + C2),用于比较两幅图像的对比度。
  • s(x, y) 是结构比较函数,定义为 s(x, y) = (σxy + C3) / (σxσy + C3),用于比较两幅图像的结构。
  • μx 和 μy 是图像 x 和 y 的平均值。
  • σx² 和 σy² 是图像 x 和 y 的方差。
  • σxy 是图像 x 和 y 的协方差。
  • C1、C2 和 C3 是为了避免分母为零而引入的常数,通常 C1 = (K1L)²,C2 = (K2L)²,C3 = C2/2,其中 L 是像素值的动态范围,K1 和 K2 是很小的常数。
  • α、β 和 γ 是权重因子,通常设置为 1。

取值范围

SSIM 的取值范围通常在 -1 到 1 之间,但实际应用中由于 C1 、C2和C3 常数的引入,其值通常落在 0 到 1 之间。值越接近 1 表示两幅图像越相似,值越接近 0 表示两幅图像差异越大。以下是一些典型的 SSIM 值及其对应的图像质量:

  • 在 0.9-1.0 之间:两幅图像非常相似,在视觉上几乎无法区分。
  • 在 0.8-0.9 之间:两幅图像有较高的相似性,但可能存在一些视觉上的差异。
  • 在 0.6-0.8 之间:两幅图像的相似性一般,存在明显的视觉差异。
  • 在 0.4-0.6 之间:两幅图像的相似性较低,视觉差异较大。
  • 低于 0.4:两幅图像的相似性很低,视觉差异非常明显。

限制

虽然 SSIM 比 PSNR 更接近人类视觉系统对图像质量的感知,但它仍然不能完全模拟人类视觉感知的复杂性。例如,对于包含大量纹理的图像,SSIM 可能会给出较高的分数,即使这些纹理对于图像的视觉质量来说并不重要。SSIM 可能无法准确评估某些类型的图像失真,如模糊、振铃效应等。

3、VMAF

VMAF(Video Multimethod Assessment Fusion)是一种基于机器学习的视频质量评估模型,它结合了多个质量评估指标,以提供更全面的视频质量评估。VMAF 的设计目标是比单一的质量评估指标更接近人类的主观感知。

原理

VMAF 通过结合多个低层次的视觉特征和人类视觉系统(HVS)的感知模型来预测视频的主观质量。

VMAF工作原理可以分为以下几个步骤:

1)特征提取

VMAF 从视频中提取多个低层次的视觉特征,在提取过程中会充分考虑人类视觉系统的特性。人类视觉系统对不同频率的图像信息敏感度不同,对低频信息(如大面积的背景)相对不敏感,而对高频信息(如边缘、细节)更为敏感,VMAF 会根据此特性对不同频率的图像信息进行加权处理,以更贴合人类的实际视觉感知。具体提取的特征包括:

  • 视觉信息保真度特征:即visual information fidelity(VIF)。主要关注像素级的差异。通过对比参考视频和测试视频中对应像素的数值,来衡量两者之间的差异程度。例如,在计算过程中会考虑像素值的亮度、颜色等方面的偏差,以此反映视频在基本信息传递上的准确性。
  • 细节损失特征:即detail loss measure(DLM)。着重于边缘和纹理的清晰度。边缘是图像中物体轮廓的体现,纹理则反映了物体表面的细节特征。VMAF 会采用一些图像处理算法,如梯度计算等,来检测视频中边缘和纹理的变化情况。当视频经过压缩或其他处理后,边缘可能会变得模糊,纹理也可能会丢失,这些变化都会在细节损失特征中得到体现。
  • 时间信息特征:即temporal information(TI)。主要涉及运动和闪烁情况。视频是具有时间维度的序列,物体在视频中的运动情况以及画面是否存在闪烁现象对观看体验有重要影响。VMAF 会分析视频帧之间的变化,检测物体的运动轨迹是否平滑,以及是否存在因帧率不稳定等原因导致的闪烁问题。

2)特征融合

提取的特征会被输入到机器学习模型中,如随机森林、支持向量机等。模型学习如何将提取到的各种特征进行融合,以预测视频的主观质量。在这个过程中,模型会根据不同特征对视频主观质量的影响程度,赋予不同的权重,综合考量各个特征的作用,得出一个更准确的评估结果。

3)模型训练

VMAF 模型是通过包含主观质量评分的视频数据集来训练的。这个数据集的构建方式是邀请大量观众对不同质量的视频进行评分,收集他们对视频清晰度、流畅度、色彩还原等多个方面的综合评价数据。在这个数据集上,模型学习如何根据提取的特征来预测视频的主观质量。通过不断调整模型的参数,使其预测结果与主观测试数据尽可能接近,从而提高模型的准确性和可靠性。

4)质量预测

模型被训练好后,就可以用来预测新视频的主观质量。VMAF 会对新视频进行特征提取和融合,并利用训练好的模型进行计算,最终输出一个分数,范围在 0 到 100 之间,分数越高表示视频质量越好。这个分数可以作为一个综合的指标,用于比较不同视频编码算法、传输方案或处理方法对视频质量的影响。

整体流程如下图所示:

0207.png

取值范围

VMAF 取值范围从0到100,代表了视频的感知质量。以下是VMAF取值与视频质量等级的大致对应关系:

  • 在 90-100之间:优秀,两个视频非常相似,在视觉上几乎无法区分。
  • 在 80-90 之间:良好,可能存在一些细微的差异,但对大多数观众来说是可接受的。
  • 在 70-80 之间:一般,可能存在一些明显的差异,但仍然可以观看。
  • 在 60-70 之间:较差,可能存在明显的模糊、失真或者噪点 。
  • 低于 60:非常差,视频质量可能无法正常观看。

限制

VMAF的视频质量评估效果虽然较PSNR或SSIM都好,但在实际使用时,仍存在一些限制和考虑因素:

1)个体主观性差异:VMAF虽然结合了多种指标并使用了机器学习来模拟人类视觉感知,但它仍然是一种客观质量评估工具。不同的人可能对同一视频内容的质量有不同的主观感受,而VMAF无法完全反映这种个体差异。

2)场景适应性:VMAF可能在某些特定类型的视频内容上表现不佳。例如,对于包含复杂纹理、快速运动或特定视觉效果的序列,VMAF的评估结果可能与人的主观感知存在差异。

3)模型训练的局限性:VMAF的模型训练基于特定的数据集和条件,例如使用1080p分辨率的视频在特定观看距离下进行标注。这意味着在不同观看条件或视频类型上,VMAF的表现可能会有所不同。

4)计算资源需求:VMAF的计算过程复杂,需要较多的计算资源,在某些机器上计算速度甚至低于1帧/秒。

二、应用:

接下来介绍如何在实际中使用PSNR、SSIM和VMAF。

1、PSNR

有多种方法可以计算PSNR,如使用FFmpeg、OpenCV、SciPy等,或者通过编写代码(Python、Java、C++)来计算 PSNR。在这里我通过Python+FFmpeg来计算:

1)安装Python、FFmpeg

网上有很多教程介绍 Python、FFmpeg 如何安装,这里暂做省略。安装完成后,可以通过命令行 "ffmpeg -version" 来确认 FFmpeg 是否已经正确安装(正常显示版本号表明已安装);通过命令行 "python3 --version" 来确认 Python 是否已经正确安装。

2)参考视频、测试视频

准备好参考视频(即视频源)和测试视频(即编码压缩后的视频),由于PSNR是全参考算法,需要确保参考视频和测试视频的帧率、分辨率相同。当测试视频的分辨率和参考视频不同时,通常通过 FFmpeg 将测试视频upscale或downscale到和参考视频相同的分辨率。此外,如果测试中使用的是直播流,还需要确保参考视频和测试视频首帧对齐,通常点播流不需要考虑首帧对齐的问题。

3) 计算PSNR

首先,通过命令行查看,FFmpeg 是否可以正确计算测试视频的 PSNR

ffmpeg -i input.mp4 -i output.mp4 -lavfi "[0:v][1:v]psnr" -f null -

计算完成后,通常会看到类似如下的输出信息

[Parsed_psnr_0 @ 0x7f79f2f0e3c0] PSNR y:41.339192 u:46.732488 v:48.948830 average:42.625172 min:32.968302 max:60.357141

上述步骤调试通过后,则可以尝试通过python调用FFmpeg计算 PSNR,代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# import start
import subprocess
import re
# import end


# function or class define start
def calculate_video_psnr(reference_video, test_video):
    try:
        # FFmpeg command
        command = [
            '/Users/huadao/Downloads/ffmpeg',
            '-i', reference_video,
            '-i', test_video,
            '-lavfi', 'psnr="stats_file=-"',
            '-f', 'null', '-'
        ]

        # run FFmpeg command and catch the output log
        result = subprocess.run(command, capture_output=True, text=True)

        # re match PSNR value, here is the average value
        # for example: "[Parsed_psnr_0 @ 0x7fade7806380] PSNR y:41.339192 u:46.732488 v:48.948830 average:42.625172 min:32.968302 max:60.357141"

        pattern = r"average.+?(\d+.\d+)"
        match = re.search(pattern, result.stderr)
        if match:
            psnr_value = float(match.group(1))
            return psnr_value

        return None

    except Exception as e:
        print(f"calc PSNR met error: {e}")
        return None

# function or class define end

if __name__ == '__main__':
    reference_video = '/Users/huadao/Downloads/input.mp4'
    test_video = '/Users/hudao/Downloads/output.mp4'
    psnr = calculate_video_psnr(reference_video, test_video)
    if psnr is not None:
        print(f"video PSNR value: {psnr} dB")

运行上述代码,会看到类似如下的输出: video PSNR value: 42.625172 dB

2、SSIM

和PSNR类似,SSIM也可以通过Python+FFmpeg来计算,只需要将上述的代码略做调整:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# import start
import subprocess
import re
# import end

# function or class define start
def calculate_psnr_ssim(reference_video, test_video):
    try:
        # FFmpeg command
        command = [
            '/Users/huadao/Downloads/ffmpeg',
            '-i', test_video,
            '-i', reference_video,
            '-lavfi', '[0:v][1:v]ssim;[0:v][1:v]psnr',
            '-f', 'null', '-'
        ]

        # run FFmpeg command and catch the output log
        result = subprocess.run(command, capture_output=True, text=True)

        print(result.stderr)
        # re match PSNR value, here is the average value
        psnr_pattern = r"average.+?(\d+.\d+)"
        psnr_match = re.search(psnr_pattern, result.stderr)
        psnr = float(psnr_match.group(1)) if psnr_match else None

        # re match SSIM value, here is the All value
        ssim_pattern = r"All.+?(\d+.\d+)"
        ssim_match = re.search(ssim_pattern, result.stderr)
        ssim = float(ssim_match.group(1)) if ssim_match else None

        return psnr, ssim

    except Exception as e:
        print(f"calc PSNR or SSIM error: {e}")
        return None, None


# function or class define end


if __name__ == '__main__':
    reference_video = '/Users/huadao/Downloads/input.mp4'
    test_video = '/Users/huadao/Downloads/output.mp4'
    psnr, ssim = calculate_psnr_ssim(reference_video, test_video)

    if psnr is not None and ssim is not None:
        print(f"PSNR value: {psnr} dB")
        print(f"SSIM value: {ssim}")
    else:
        print("failed to calc PSNR or SSIM value。")

运行上述代码,会看到类似如下的输出: PSNR value: 42.625172 dB SSIM value: 0.981796

3、VMAF

通过 VMAF 官方工具、FFmpeg 均可计算 VMAF。综合易用性、灵活性、自动化视角考量,推荐使用 Python+FFmpeg 的方式计算 VMAF

1)安装Python、FFmpeg

网上有很多教程介绍 Python、FFmpeg 如何安装,这里暂做省略。

2)参考视频、测试视频 同PSNR章节

3) 计算VMAF

首先,通过命令行查看,FFmpeg 是否可以正确计算测试视频的 VMAF

ffmpeg -i input.mp4 -i output.mp4 -lavfi "libvmaf=log_fmt=json:log_path=vmaf_log.json" -f null -

计算完成后,通常会看到类似如下的输出信息

[Parsed_libvmaf_0 @ 0x7faed0408f80] VMAF score: 92.452385ate=N/A speed=0.343x

同时生成的vmaf_log.json文件中包含每一帧的详细计算信息。

上述步骤调试通过后,则可以尝试通过python调用FFmpeg计算 VMAF,代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# import start
import subprocess
import json
# import end


# function or class define start
def calculate_vmaf(reference_video, test_video):
    try:
        # FFmpeg command
        command = [
            '/Users/huadao/Downloads/ffmpeg',
            '-i', reference_video,
            '-i', test_video,
            '-lavfi', 'libvmaf=:log_fmt=json:log_path=vmaf_log.json',
            '-f', 'null', '-'
        ]

        # run FFmpeg command
        result = subprocess.run(command, capture_output=True, text=True)

        # check command run success or not
        if result.returncode != 0:
            print(f"FFmpeg command run error: {result.stderr}")
            return None

        # read VMAF log
        with open('vmaf_log.json', 'r') as f:
            vmaf_log = json.load(f)

        # VMAF score per frame
        vmaf_scores = [float(frame['metrics']['vmaf']) for frame in vmaf_log['frames']]

        # calc average VMAF score
        average_vmaf = sum(vmaf_scores) / len(vmaf_scores)

        return average_vmaf

    except Exception as e:
        print(f"calc VMAF met error: {e}")
        return None

# function or class define end


if __name__ == '__main__':
    reference_video = '/Users/huadao/Downloads/input.mp4'
    test_video = '/Users/huadao/Downloads/output.mp4'

    vmaf_score = calculate_vmaf(reference_video, test_video)
    if vmaf_score is not None:
        print(f"average VMAF score: {vmaf_score}")

运行上述代码,会看到类似如下的输出: average VMAF score: 92.45238507313915

事实上在输出文件vmaf_log.json中,已经计算好了 VMAF 的最小值、最大值、均值、调和平均值:

"vmaf": {
      "min": 74.286021,
      "max": 100.000000,
      "mean": 92.452385,
      "harmonic_mean": 92.203532
    }

可以直接取用。

由于PSNR、SSIM和VMAF都有其限制和考虑因素,在实际应用中,需要结合三者来获得更全面的视频质量评估。