diff --git a/README.md b/README.md index 92473c5..c78e849 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ - [X] SSIM - [X] MSSSIM - [X] CIEDE2000 + - [X] MSE + - [X] RMSE ## Installation diff --git a/av_metrics/src/video/psnr.rs b/av_metrics/src/video/psnr.rs index 85e6bbb..65169fa 100644 --- a/av_metrics/src/video/psnr.rs +++ b/av_metrics/src/video/psnr.rs @@ -27,6 +27,32 @@ pub fn calculate_video_psnr( Ok(metrics.psnr) } +/// Calculates the MSE for two Videos. Lower is better +/// +/// MSE is Mean Square Error for a video. +pub fn calculate_video_mse( + decoder1: &mut D, + decoder2: &mut D, + frame_limit: Option, + progress_callback: F, +) -> Result> { + let metrics = Psnr.process_video(decoder1, decoder2, frame_limit, progress_callback)?; + Ok(metrics.mse) +} + +/// Calculates the RMSE for two Videos. Lower is better +/// +/// Root Mean Square MSE is Root of Mean Square Error for a video. +pub fn calculate_video_rmse( + decoder1: &mut D, + decoder2: &mut D, + frame_limit: Option, + progress_callback: F, +) -> Result> { + let metrics = Psnr.process_video(decoder1, decoder2, frame_limit, progress_callback)?; + Ok(metrics.rmse) +} + /// Calculates the APSNR for two videos. Higher is better. /// /// APSNR is capped at 100 in order to avoid skewed statistics @@ -66,6 +92,8 @@ pub fn calculate_frame_psnr( struct PsnrResults { psnr: PlanarMetrics, apsnr: PlanarMetrics, + mse: PlanarMetrics, + rmse: PlanarMetrics, } struct Psnr; @@ -121,7 +149,24 @@ impl VideoMetric for Psnr { .sum::() / metrics.len() as f64, }; - Ok(PsnrResults { psnr, apsnr }) + let mse = PlanarMetrics { + y: return_summed_mse(&metrics.iter().map(|m| m[0]).collect::>()), + u: return_summed_mse(&metrics.iter().map(|m| m[1]).collect::>()), + v: return_summed_mse(&metrics.iter().map(|m| m[2]).collect::>()), + avg: return_summed_mse(&metrics.iter().flatten().copied().collect::>()), + }; + let rmse = PlanarMetrics { + y: mse.y.sqrt(), + u: mse.u.sqrt(), + v: mse.v.sqrt(), + avg: mse.avg.sqrt(), + }; + Ok(PsnrResults { + psnr, + apsnr, + mse, + rmse, + }) } } @@ -144,6 +189,18 @@ fn calculate_summed_psnr(metrics: &[PsnrMetrics]) -> f64 { ) } +fn return_summed_mse(metrics: &[PsnrMetrics]) -> f64 { + return_mse( + metrics + .iter() + .fold(PsnrMetrics::default(), |acc, plane| PsnrMetrics { + sq_err: acc.sq_err + plane.sq_err, + sample_max: plane.sample_max, + n_pixels: acc.n_pixels + plane.n_pixels, + }), + ) +} + /// Calculate the PSNR metrics for a `Plane` by comparing the original (uncompressed) to /// the compressed version. fn calculate_plane_psnr_metrics( @@ -168,6 +225,10 @@ fn calculate_psnr(metrics: PsnrMetrics) -> f64 { - metrics.sq_err.log10()) } +fn return_mse(metrics: PsnrMetrics) -> f64 { + metrics.sq_err +} + /// Calculate the squared error for a `Plane` by comparing the original (uncompressed) /// to the compressed version. fn calculate_plane_total_squared_error(plane1: &Plane, plane2: &Plane) -> f64 { diff --git a/av_metrics_tool/src/main.rs b/av_metrics_tool/src/main.rs index 2a53fd4..20f8a1c 100644 --- a/av_metrics_tool/src/main.rs +++ b/av_metrics_tool/src/main.rs @@ -40,6 +40,8 @@ fn main() -> Result<(), String> { .takes_value(true) .possible_value("psnr") .possible_value("apsnr") + .possible_value("mse") + .possible_value("rmse") .possible_value("psnrhvs") .possible_value("ssim") .possible_value("msssim") @@ -186,6 +188,10 @@ struct MetricsResults { #[serde(skip_serializing_if = "Option::is_none")] psnr: Option, #[serde(skip_serializing_if = "Option::is_none")] + mse: Option, + #[serde(skip_serializing_if = "Option::is_none")] + rmse: Option, + #[serde(skip_serializing_if = "Option::is_none")] apsnr: Option, #[serde(skip_serializing_if = "Option::is_none")] psnr_hvs: Option, @@ -243,6 +249,18 @@ fn run_video_metrics( results.psnr = Psnr::run(input1, input2, progress_fn); } + if metric.is_none() || metric == Some("mse") { + progress.set_prefix("Computing MSE"); + progress.reset(); + results.mse = Mse::run(input1, input2, progress_fn); + } + + if metric.is_none() || metric == Some("rmse") { + progress.set_prefix("Computing RMSE"); + progress.reset(); + results.rmse = Rmse::run(input1, input2, progress_fn); + } + if metric.is_none() || metric == Some("apsnr") { progress.set_prefix("Computing APSNR"); progress.reset(); @@ -312,19 +330,24 @@ impl Report<'_> { .map_err(|err| err.to_string())?; } OutputType::CSV(w) => { - writeln!(w, "filename,psnr,apsnr,psnr_hvs,ssim,msssim,ciede2000") - .map_err(|err| err.to_string())?; + writeln!( + w, + "filename,psnr,apsnr,psnr_hvs,ssim,msssim,ciede2000,mse,rmse" + ) + .map_err(|err| err.to_string())?; for cmp in self.comparisons.iter() { writeln!( w, - "{},{},{},{},{},{},{}", + "{},{},{},{},{},{},{},{},{}", cmp.filename, cmp.psnr.map(|v| v.avg).unwrap_or(-0.0), cmp.apsnr.map(|v| v.avg).unwrap_or(-0.0), cmp.psnr_hvs.map(|v| v.avg).unwrap_or(-0.0), cmp.ssim.map(|v| v.avg).unwrap_or(-0.0), cmp.msssim.map(|v| v.avg).unwrap_or(-0.0), - cmp.ciede2000.unwrap_or(-0.0) + cmp.ciede2000.unwrap_or(-0.0), + cmp.mse.map(|v| v.avg).unwrap_or(-0.0), + cmp.rmse.map(|v| v.avg).unwrap_or(-0.0) ) .map_err(|err| err.to_string())?; } @@ -332,21 +355,23 @@ impl Report<'_> { OutputType::Markdown(w) => { writeln!( w, - "|filename|psnr|apsnr|psnr_hvs|ssim|msssim|ciede2000|\n\ - |-|-|-|-|-|-|-|" + "|filename|psnr|apsnr|psnr_hvs|ssim|msssim|ciede2000|mse|rmse|\n\ + |-|-|-|-|-|-|-|-|-|" ) .map_err(|err| err.to_string())?; for cmp in self.comparisons.iter() { writeln!( w, - "|{}|{}|{}|{}|{}|{}|{}|", + "|{}|{}|{}|{}|{}|{}|{}|{}|{}|", cmp.filename, cmp.psnr.map(|v| v.avg).unwrap_or(-0.0), cmp.apsnr.map(|v| v.avg).unwrap_or(-0.0), cmp.psnr_hvs.map(|v| v.avg).unwrap_or(-0.0), cmp.ssim.map(|v| v.avg).unwrap_or(-0.0), cmp.msssim.map(|v| v.avg).unwrap_or(-0.0), - cmp.ciede2000.unwrap_or(-0.0) + cmp.ciede2000.unwrap_or(-0.0), + cmp.mse.map(|v| v.avg).unwrap_or(-0.0), + cmp.rmse.map(|v| v.avg).unwrap_or(-0.0) ) .map_err(|err| err.to_string())?; } @@ -368,6 +393,8 @@ impl Report<'_> { Text::print_result(writer, "SSIM", cmp.ssim)?; Text::print_result(writer, "MSSSIM", cmp.msssim)?; Text::print_result(writer, "CIEDE2000", cmp.ciede2000)?; + Text::print_result(writer, "MSE", cmp.mse)?; + Text::print_result(writer, "RMSE", cmp.rmse)?; } } } @@ -440,6 +467,34 @@ impl CliMetric for Psnr { } } +struct Mse; + +impl CliMetric for Mse { + type VideoResult = PlanarMetrics; + + fn calculate_video_metric( + dec1: &mut D, + dec2: &mut D, + progress_callback: F, + ) -> Result> { + psnr::calculate_video_mse(dec1, dec2, None, progress_callback) + } +} + +struct Rmse; + +impl CliMetric for Rmse { + type VideoResult = PlanarMetrics; + + fn calculate_video_metric( + dec1: &mut D, + dec2: &mut D, + progress_callback: F, + ) -> Result> { + psnr::calculate_video_rmse(dec1, dec2, None, progress_callback) + } +} + struct APsnr; impl CliMetric for APsnr {