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:

committed by
WebRTC LUCI CQ

parent
6c61b90458
commit
b6b34fc213
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user