Add weighted PSNR-YUV from Ohm2012 to PC test framework.

This metric weights the PSNRs of luma and chroma planes in
a slightly smarter way than our current PSNR metric.

> J. Ohm, G. J. Sullivan, H. Schwarz, T. K. Tan and T. Wiegand,
> "Comparison of the Coding Efficiency of Video Coding Standards—Including
> High Efficiency Video Coding (HEVC)," in IEEE Transactions on Circuits and
> Systems for Video Technology, vol. 22, no. 12, pp. 1669-1684, Dec. 2012
> doi: 10.1109/TCSVT.2012.2221192.

Bug: None
Change-Id: Iec105e0b491628fc0ad4be9155b991203846ad1b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/256463
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Artem Titov <titovartem@webrtc.org>
Commit-Queue: Rasmus Brandt <brandtr@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36311}
This commit is contained in:
Rasmus Brandt
2022-03-23 19:56:56 +01:00
committed by WebRTC LUCI CQ
parent 6c61b90458
commit b6b34fc213
5 changed files with 92 additions and 13 deletions

View File

@ -87,11 +87,24 @@ double I420SSE(const I420BufferInterface& ref_buffer,
const I420BufferInterface& test_buffer);
// Compute PSNR for an I420 frame (all planes).
// Returns the PSNR in decibel, to a maximum of kInfinitePSNR.
// Returns the PSNR in decibel, to a maximum of kPerfectPSNR.
double I420PSNR(const VideoFrame* ref_frame, const VideoFrame* test_frame);
double I420PSNR(const I420BufferInterface& ref_buffer,
const I420BufferInterface& test_buffer);
// Computes the weighted PSNR-YUV for an I420 buffer.
//
// For the definition and motivation, see
// J. Ohm, G. J. Sullivan, H. Schwarz, T. K. Tan and T. Wiegand,
// "Comparison of the Coding Efficiency of Video Coding Standards—Including
// High Efficiency Video Coding (HEVC)," in IEEE Transactions on Circuits and
// Systems for Video Technology, vol. 22, no. 12, pp. 1669-1684, Dec. 2012
// doi: 10.1109/TCSVT.2012.2221192.
//
// Returns the PSNR-YUV in decibel, to a maximum of kPerfectPSNR.
double I420WeightedPSNR(const I420BufferInterface& ref_buffer,
const I420BufferInterface& test_buffer);
// Compute SSIM for an I420 frame (all planes).
double I420SSIM(const VideoFrame* ref_frame, const VideoFrame* test_frame);
double I420SSIM(const I420BufferInterface& ref_buffer,

View File

@ -114,10 +114,6 @@ void TestLibYuv::TearDown() {
source_file_ = NULL;
}
TEST_F(TestLibYuv, ConvertSanityTest) {
// TODO(mikhal)
}
TEST_F(TestLibYuv, ConvertTest) {
// Reading YUV frame - testing on the first frame of the foreman sequence
int j = 0;
@ -368,4 +364,23 @@ TEST_F(TestLibYuv, NV12Scale4x4to2x2) {
::testing::ElementsAre(Average(0, 2, 4, 6), Average(1, 3, 5, 7)));
}
TEST(I420WeightedPSNRTest, SmokeTest) {
uint8_t ref_y[] = {0, 0, 0, 0};
uint8_t ref_uv[] = {0};
rtc::scoped_refptr<I420Buffer> ref_buffer =
I420Buffer::Copy(/*width=*/2, /*height=*/2, ref_y, /*stride_y=*/2, ref_uv,
/*stride_u=*/1, ref_uv, /*stride_v=*/1);
uint8_t test_y[] = {1, 1, 1, 1};
uint8_t test_uv[] = {2};
rtc::scoped_refptr<I420Buffer> test_buffer = I420Buffer::Copy(
/*width=*/2, /*height=*/2, test_y, /*stride_y=*/2, test_uv,
/*stride_u=*/1, test_uv, /*stride_v=*/1);
auto psnr = [](double mse) { return 10.0 * log10(255.0 * 255.0 / mse); };
EXPECT_NEAR(I420WeightedPSNR(*ref_buffer, *test_buffer),
(6.0 * psnr(1.0) + psnr(4.0) + psnr(4.0)) / 8.0,
/*abs_error=*/0.001);
}
} // namespace webrtc

View File

@ -255,6 +255,45 @@ double I420PSNR(const VideoFrame* ref_frame, const VideoFrame* test_frame) {
*test_frame->video_frame_buffer()->ToI420());
}
double I420WeightedPSNR(const I420BufferInterface& ref_buffer,
const I420BufferInterface& test_buffer) {
RTC_DCHECK_GE(ref_buffer.width(), test_buffer.width());
RTC_DCHECK_GE(ref_buffer.height(), test_buffer.height());
if ((ref_buffer.width() != test_buffer.width()) ||
(ref_buffer.height() != test_buffer.height())) {
rtc::scoped_refptr<I420Buffer> scaled_ref_buffer =
I420Buffer::Create(test_buffer.width(), test_buffer.height());
scaled_ref_buffer->ScaleFrom(ref_buffer);
return I420WeightedPSNR(*scaled_ref_buffer, test_buffer);
}
// Luma.
int width_y = test_buffer.width();
int height_y = test_buffer.height();
uint64_t sse_y = libyuv::ComputeSumSquareErrorPlane(
ref_buffer.DataY(), ref_buffer.StrideY(), test_buffer.DataY(),
test_buffer.StrideY(), width_y, height_y);
uint64_t num_samples_y = (uint64_t)width_y * (uint64_t)height_y;
double psnr_y = libyuv::SumSquareErrorToPsnr(sse_y, num_samples_y);
// Chroma.
int width_uv = (width_y + 1) >> 1;
int height_uv = (height_y + 1) >> 1;
uint64_t sse_u = libyuv::ComputeSumSquareErrorPlane(
ref_buffer.DataU(), ref_buffer.StrideU(), test_buffer.DataU(),
test_buffer.StrideU(), width_uv, height_uv);
uint64_t num_samples_uv = (uint64_t)width_uv * (uint64_t)height_uv;
double psnr_u = libyuv::SumSquareErrorToPsnr(sse_u, num_samples_uv);
uint64_t sse_v = libyuv::ComputeSumSquareErrorPlane(
ref_buffer.DataV(), ref_buffer.StrideV(), test_buffer.DataV(),
test_buffer.StrideV(), width_uv, height_uv);
double psnr_v = libyuv::SumSquareErrorToPsnr(sse_v, num_samples_uv);
// Weights from Ohm et. al 2012.
double psnr_yuv = (6.0 * psnr_y + psnr_u + psnr_v) / 8.0;
return (psnr_yuv > kPerfectPSNR) ? kPerfectPSNR : psnr_yuv;
}
// Compute SSIM for an I420A frame (all planes). Can upscale test frame.
double I420ASSIM(const I420ABufferInterface& ref_buffer,
const I420ABufferInterface& test_buffer) {

View File

@ -363,20 +363,28 @@ void DefaultVideoQualityAnalyzerFramesComparator::ProcessComparison(
// Perform expensive psnr and ssim calculations while not holding lock.
double psnr = -1.0;
double ssim = -1.0;
if (options_.heavy_metrics_computation_enabled &&
// TODO(brandtr): Remove `heavy_metrics_computation_enabled` when downstream
// has been updated.
if ((options_.heavy_metrics_computation_enabled || options_.compute_psnr ||
options_.compute_ssim) &&
comparison.captured.has_value() && comparison.rendered.has_value()) {
rtc::scoped_refptr<I420BufferInterface> reference_buffer =
comparison.captured->video_frame_buffer()->ToI420();
rtc::scoped_refptr<I420BufferInterface> test_buffer =
comparison.rendered->video_frame_buffer()->ToI420();
if (options_.adjust_cropping_before_comparing_frames) {
test_buffer =
ScaleVideoFrameBuffer(*test_buffer.get(), reference_buffer->width(),
reference_buffer->height());
test_buffer = ScaleVideoFrameBuffer(
*test_buffer, reference_buffer->width(), reference_buffer->height());
reference_buffer = test::AdjustCropping(reference_buffer, test_buffer);
}
psnr = I420PSNR(*reference_buffer.get(), *test_buffer.get());
ssim = I420SSIM(*reference_buffer.get(), *test_buffer.get());
if (options_.compute_psnr) {
psnr = options_.use_weighted_psnr
? I420WeightedPSNR(*reference_buffer, *test_buffer)
: I420PSNR(*reference_buffer, *test_buffer);
}
if (options_.compute_ssim) {
ssim = I420SSIM(*reference_buffer, *test_buffer);
}
}
const FrameStats& frame_stats = comparison.frame_stats;

View File

@ -209,8 +209,12 @@ class VideoStreamsInfo {
};
struct DefaultVideoQualityAnalyzerOptions {
// Tells DefaultVideoQualityAnalyzer if heavy metrics like PSNR and SSIM have
// to be computed or not.
// Tells DefaultVideoQualityAnalyzer if heavy metrics have to be computed.
bool compute_psnr = true;
bool compute_ssim = true;
// If true, weights the luma plane more than the chroma planes in the PSNR.
bool use_weighted_psnr = false;
// DEPRECATED.
bool heavy_metrics_computation_enabled = true;
// If true DefaultVideoQualityAnalyzer will try to adjust frames before
// computing PSNR and SSIM for them. In some cases picture may be shifted by