3. Compare 2 videos with SSIM, PSNR and VMAF metrics

The classic metrics PSNR (Peak Signal to Noise Ratio) and SSIM (Structural SIMilarity) are implemented in C for improved performance.

[1]:
import math
import pathlib
import timeit

import cv2
import numpy as np
import tqdm

from cutcutcodec.core.io import read
from cutcutcodec.core.opti.parallel import starmap
from cutcutcodec.core.analysis.video.metric import psnr as compute_psnr, ssim as compute_ssim, vmaf as compute_vmaf

3.1. Preparing videos to be compared

Here we’ll compare the original video with a noisy version.

[2]:
from cutcutcodec.core.analysis.stream.rate_video import optimal_rate_video
from cutcutcodec.core.analysis.stream.shape import optimal_shape_video
from cutcutcodec.utils import get_project_root

file = get_project_root().parent / "media" / "video" / "intro.webm"
stream_ref = read(file).out_select("video")[0]
rate = optimal_rate_video(stream_ref)
shape = optimal_shape_video(stream_ref)
print(f"{file} at {rate} fps, of size {shape[1]}x{shape[0]}")
/home/rrichard/cutcutcodec_git/media/video/intro.webm at 30000/1001 fps, of size 1280x720
[3]:
from cutcutcodec.core.generation.video.noise import GeneratorVideoNoise

stream_comp = (stream_ref | GeneratorVideoNoise()).apply_video_equation("(9*r0+r1)/10", "(9*g0+g1)/10", "(9*b0+b1)/10").out_streams[0]

3.2. Compare each frame

3.2.1. Convert all streams into YUV in the same colorspace

Image comparisons are always made in yuv, not rgb.

[4]:
from cutcutcodec.core.filter.video.colorspace import FilterVideoColorspace
from cutcutcodec.config import Config  # to get the default used colorspace

dst_color = f"y'pbpr_{Config().target_trc}_{Config().target_prim}"
ref_yuv = FilterVideoColorspace([stream_ref], dst=dst_color).out_streams[0]
comp_yuv = FilterVideoColorspace([stream_comp], dst=dst_color).out_streams[0]
[5]:
def compare(stream1, stream2, time, shape):
    """Compare 2 images."""
    frame1, frame2 = stream1.snapshot(time, shape), stream2.snapshot(time, shape)
    psnr = compute_psnr(frame1, frame2, weights=(6, 1, 1), threads=1)  # factor 6 on y
    ssim = compute_ssim(frame1, frame2, weights=(6, 1, 1), data_range=1.0, threads=1)
    vmaf = compute_vmaf(frame1, frame2, threads=1)
    return psnr, ssim, vmaf

3.3. Compute metrics

[ ]:
times = np.arange(0, ref_yuv.duration, 1/rate).tolist()
all_psnr, all_ssim, all_vmaf = zip(*starmap(compare, ((ref_yuv, comp_yuv, t, shape) for t in tqdm.tqdm(times))))
 27%|██████████████████████████████████████▋                                                                                                           | 78/294 [01:53<01:22,  2.61it/s]
[ ]:
import matplotlib.pyplot as plt

plt.plot(times, all_psnr)
plt.xlabel("time (s)")
plt.ylabel("psnr (db)")
plt.show()

plt.plot(times, all_ssim)
plt.xlabel("time (s)")
plt.ylabel("ssim")
plt.show()

plt.plot(times, all_vmaf)
plt.xlabel("time (s)")
plt.ylabel("vmaf (db)")
plt.show()