From: Jim Bankoski Date: Fri, 17 Apr 2015 18:13:18 +0000 (-0700) Subject: Adds a blockiness metric to internal stats. X-Git-Tag: v1.5.0~754^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1777413a2a160f3d2585e54ff155dc876c5bca1b;p=libvpx Adds a blockiness metric to internal stats. Change-Id: Iedceeb020492050063acf3fd2326f96c29db9ae5 --- diff --git a/test/blockiness_test.cc b/test/blockiness_test.cc new file mode 100644 index 000000000..92cce6a85 --- /dev/null +++ b/test/blockiness_test.cc @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2012 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#include +#include +#include + +#include "./vpx_config.h" +#if CONFIG_VP9_ENCODER +#include "./vp9_rtcd.h" +#endif + +#include "test/acm_random.h" +#include "test/clear_system_state.h" +#include "test/register_state_check.h" +#include "test/util.h" +#include "third_party/googletest/src/include/gtest/gtest.h" + +#include "vpx_mem/vpx_mem.h" + + +extern "C" +double vp9_get_blockiness(const unsigned char *img1, int img1_pitch, + const unsigned char *img2, int img2_pitch, + int width, int height); + +using libvpx_test::ACMRandom; + +namespace { +class BlockinessTestBase : public ::testing::Test { + public: + BlockinessTestBase(int width, int height) : width_(width), height_(height) {} + + static void SetUpTestCase() { + source_data_ = reinterpret_cast( + vpx_memalign(kDataAlignment, kDataBufferSize)); + reference_data_ = reinterpret_cast( + vpx_memalign(kDataAlignment, kDataBufferSize)); + } + + static void TearDownTestCase() { + vpx_free(source_data_); + source_data_ = NULL; + vpx_free(reference_data_); + reference_data_ = NULL; + } + + virtual void TearDown() { + libvpx_test::ClearSystemState(); + } + + protected: + // Handle frames up to 640x480 + static const int kDataAlignment = 16; + static const int kDataBufferSize = 640*480; + + virtual void SetUp() { + source_stride_ = (width_ + 31) & ~31; + reference_stride_ = width_ * 2; + rnd_.Reset(ACMRandom::DeterministicSeed()); + } + + void FillConstant(uint8_t *data, int stride, uint8_t fill_constant, + int width, int height) { + for (int h = 0; h < height; ++h) { + for (int w = 0; w < width; ++w) { + data[h * stride + w] = fill_constant; + } + } + } + + void FillConstant(uint8_t *data, int stride, uint8_t fill_constant) { + FillConstant(data, stride, fill_constant, width_, height_); + } + + void FillRandom(uint8_t *data, int stride, int width, int height) { + for (int h = 0; h < height; ++h) { + for (int w = 0; w < width; ++w) { + data[h * stride + w] = rnd_.Rand8(); + } + } + } + + void FillRandom(uint8_t *data, int stride) { + FillRandom(data, stride, width_, height_); + } + + void FillRandomBlocky(uint8_t *data, int stride) { + for (int h = 0; h < height_; h += 4) { + for (int w = 0; w < width_; w += 4) { + FillRandom(data + h * stride + w, stride, 4, 4); + } + } + } + + void FillCheckerboard(uint8_t *data, int stride) { + for (int h = 0; h < height_; h += 4) { + for (int w = 0; w < width_; w += 4) { + if (((h/4) ^ (w/4)) & 1) + FillConstant(data + h * stride + w, stride, 255, 4, 4); + else + FillConstant(data + h * stride + w, stride, 0, 4, 4); + } + } + } + + void Blur(uint8_t *data, int stride, int taps) { + int sum = 0; + int half_taps = taps / 2; + for (int h = 0; h < height_; ++h) { + for (int w = 0; w < taps; ++w) { + sum += data[w + h * stride]; + } + for (int w = taps; w < width_; ++w) { + sum += data[w + h * stride] - data[w - taps + h * stride]; + data[w - half_taps + h * stride] = (sum + half_taps) / taps; + } + } + for (int w = 0; w < width_; ++w) { + for (int h = 0; h < taps; ++h) { + sum += data[h + w * stride]; + } + for (int h = taps; h < height_; ++h) { + sum += data[w + h * stride] - data[(h - taps) * stride + w]; + data[(h - half_taps) * stride + w] = (sum + half_taps) / taps; + } + } + } + int width_, height_; + static uint8_t* source_data_; + int source_stride_; + static uint8_t* reference_data_; + int reference_stride_; + + ACMRandom rnd_; +}; + +#if CONFIG_VP9_ENCODER +typedef std::tr1::tuple BlockinessParam; +class BlockinessVP9Test + : public BlockinessTestBase, + public ::testing::WithParamInterface { + public: + BlockinessVP9Test() : BlockinessTestBase(GET_PARAM(0), GET_PARAM(1)) {} + + protected: + int CheckBlockiness() { + return vp9_get_blockiness(source_data_, source_stride_, + reference_data_, reference_stride_, + width_, height_); + } +}; +#endif // CONFIG_VP9_ENCODER + +uint8_t* BlockinessTestBase::source_data_ = NULL; +uint8_t* BlockinessTestBase::reference_data_ = NULL; + +#if CONFIG_VP9_ENCODER +TEST_P(BlockinessVP9Test, SourceBlockierThanReference) { + // Source is blockier than reference. + FillRandomBlocky(source_data_, source_stride_); + FillConstant(reference_data_, reference_stride_, 128); + int super_blocky = CheckBlockiness(); + + EXPECT_EQ(0, super_blocky) << "Blocky source should produce 0 blockiness."; +} + +TEST_P(BlockinessVP9Test, ReferenceBlockierThanSource) { + // Source is blockier than reference. + FillConstant(source_data_, source_stride_, 128); + FillRandomBlocky(reference_data_, reference_stride_); + int super_blocky = CheckBlockiness(); + + EXPECT_GT(super_blocky, 0.0) + << "Blocky reference should score high for blockiness."; +} + +TEST_P(BlockinessVP9Test, BlurringDecreasesBlockiness) { + // Source is blockier than reference. + FillConstant(source_data_, source_stride_, 128); + FillRandomBlocky(reference_data_, reference_stride_); + int super_blocky = CheckBlockiness(); + + Blur(reference_data_, reference_stride_, 4); + int less_blocky = CheckBlockiness(); + + EXPECT_GT(super_blocky, less_blocky) + << "A straight blur should decrease blockiness."; +} + +TEST_P(BlockinessVP9Test, WorstCaseBlockiness) { + // Source is blockier than reference. + FillConstant(source_data_, source_stride_, 128); + FillCheckerboard(reference_data_, reference_stride_); + + int super_blocky = CheckBlockiness(); + + Blur(reference_data_, reference_stride_, 4); + int less_blocky = CheckBlockiness(); + + EXPECT_GT(super_blocky, less_blocky) + << "A straight blur should decrease blockiness."; +} +#endif // CONFIG_VP9_ENCODER + + +using std::tr1::make_tuple; + +//------------------------------------------------------------------------------ +// C functions + +#if CONFIG_VP9_ENCODER +const BlockinessParam c_vp9_tests[] = { + make_tuple(320, 240), + make_tuple(318, 242), + make_tuple(318, 238), +}; +INSTANTIATE_TEST_CASE_P(C, BlockinessVP9Test, ::testing::ValuesIn(c_vp9_tests)); +#endif + +} // namespace diff --git a/test/test.mk b/test/test.mk index 91dd3354e..5baf23470 100644 --- a/test/test.mk +++ b/test/test.mk @@ -150,6 +150,7 @@ LIBVPX_TEST_SRCS-$(CONFIG_VP9) += vp9_intrapred_test.cc ifeq ($(CONFIG_VP9_ENCODER),yes) LIBVPX_TEST_SRCS-$(CONFIG_SPATIAL_SVC) += svc_test.cc +LIBVPX_TEST_SRCS-$(CONFIG_INTERNAL_STATS) += blockiness_test.cc endif ifeq ($(CONFIG_VP9_ENCODER)$(CONFIG_VP9_TEMPORAL_DENOISING),yesyes) diff --git a/vp9/encoder/vp9_blockiness.c b/vp9/encoder/vp9_blockiness.c new file mode 100644 index 000000000..b8629bd3b --- /dev/null +++ b/vp9/encoder/vp9_blockiness.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2014 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "./vpx_config.h" +#include "./vp9_rtcd.h" +#include "vp9/common/vp9_common.h" +#include "vp9/common/vp9_convolve.h" +#include "vp9/common/vp9_filter.h" +#include "vpx/vpx_integer.h" +#include "vpx_ports/mem.h" + +static int horizontal_filter(const uint8_t *s) { + return (s[1] - s[-2]) * 2 + (s[-1] - s[0]) * 6; +} + +static int vertical_filter(const uint8_t *s, int p) { + return (s[p] - s[-2 * p]) * 2 + (s[-p] - s[0]) * 6; +} + +static int variance(int sum, int sum_squared, int size) { + return sum_squared / size - (sum / size) * (sum / size); +} +// Calculate a blockiness level for a vertical block edge. +// This function returns a new blockiness metric that's defined as + +// p0 p1 p2 p3 +// q0 q1 q2 q3 +// block edge -> +// r0 r1 r2 r3 +// s0 s1 s2 s3 + +// blockiness = p0*-2+q0*6+r0*-6+s0*2 + +// p1*-2+q1*6+r1*-6+s1*2 + +// p2*-2+q2*6+r2*-6+s2*2 + +// p3*-2+q3*6+r3*-6+s3*2 ; + +// reconstructed_blockiness = abs(blockiness from reconstructed buffer - +// blockiness from source buffer,0) +// +// I make the assumption that flat blocks are much more visible than high +// contrast blocks. As such, I scale the result of the blockiness calc +// by dividing the blockiness by the variance of the pixels on either side +// of the edge as follows: +// var_0 = (q0^2+q1^2+q2^2+q3^2) - ((q0 + q1 + q2 + q3) / 4 )^2 +// var_1 = (r0^2+r1^2+r2^2+r3^2) - ((r0 + r1 + r2 + r3) / 4 )^2 +// The returned blockiness is the scaled value +// Reconstructed blockiness / ( 1 + var_0 + var_1 ) ; +int blockiness_vertical(const uint8_t *s, int sp, const uint8_t *r, int rp, + int size) { + int s_blockiness = 0; + int r_blockiness = 0; + int sum_0 = 0; + int sum_sq_0 = 0; + int sum_1 = 0; + int sum_sq_1 = 0; + int i; + int var_0; + int var_1; + for (i = 0; i < size; ++i, s += sp, r += rp) { + s_blockiness += horizontal_filter(s); + r_blockiness += horizontal_filter(r); + sum_0 += s[0]; + sum_sq_0 += s[0]*s[0]; + sum_1 += s[-1]; + sum_sq_1 += s[-1]*s[-1]; + } + var_0 = variance(sum_0, sum_sq_0, size); + var_1 = variance(sum_1, sum_sq_1, size); + r_blockiness = abs(r_blockiness); + s_blockiness = abs(s_blockiness); + + if (r_blockiness > s_blockiness) + return (r_blockiness - s_blockiness) / (1 + var_0 + var_1); + else + return 0; +} + +// Calculate a blockiness level for a horizontal block edge +// same as above. +int blockiness_horizontal(const uint8_t *s, int sp, const uint8_t *r, int rp, + int size) { + int s_blockiness = 0; + int r_blockiness = 0; + int sum_0 = 0; + int sum_sq_0 = 0; + int sum_1 = 0; + int sum_sq_1 = 0; + int i; + int var_0; + int var_1; + for (i = 0; i < size; ++i, ++s, ++r) { + s_blockiness += vertical_filter(s, sp); + r_blockiness += vertical_filter(r, rp); + sum_0 += s[0]; + sum_sq_0 += s[0] * s[0]; + sum_1 += s[-sp]; + sum_sq_1 += s[-sp] * s[-sp]; + } + var_0 = variance(sum_0, sum_sq_0, size); + var_1 = variance(sum_1, sum_sq_1, size); + r_blockiness = abs(r_blockiness); + s_blockiness = abs(s_blockiness); + + if (r_blockiness > s_blockiness) + return (r_blockiness - s_blockiness) / (1 + var_0 + var_1); + else + return 0; +} + +// This function returns the blockiness for the entire frame currently by +// looking at all borders in steps of 4. +double vp9_get_blockiness(const unsigned char *img1, int img1_pitch, + const unsigned char *img2, int img2_pitch, + int width, int height ) { + double blockiness = 0; + int i, j; + vp9_clear_system_state(); + for (i = 0; i < height; i += 4, img1 += img1_pitch * 4, + img2 += img2_pitch * 4) { + for (j = 0; j < width; j += 4) { + if (i > 0 && i < height && j > 0 && j < width) { + blockiness += blockiness_vertical(img1 + j, img1_pitch, + img2 + j, img2_pitch, 4); + blockiness += blockiness_horizontal(img1 + j, img1_pitch, + img2 + j, img2_pitch, 4); + } + } + } + blockiness /= width * height / 16; + return blockiness; +} diff --git a/vp9/encoder/vp9_encoder.c b/vp9/encoder/vp9_encoder.c index e41c13c8f..fc3f7d4e8 100644 --- a/vp9/encoder/vp9_encoder.c +++ b/vp9/encoder/vp9_encoder.c @@ -1583,6 +1583,8 @@ VP9_COMP *vp9_create_compressor(VP9EncoderConfig *oxcf, cpi->b_calculate_psnr = CONFIG_INTERNAL_STATS; #if CONFIG_INTERNAL_STATS cpi->b_calculate_ssimg = 0; + cpi->b_calculate_blockiness = 1; + cpi->count = 0; cpi->bytes = 0; @@ -1624,6 +1626,15 @@ VP9_COMP *vp9_create_compressor(VP9EncoderConfig *oxcf, cpi->total_psnrhvs_u = 0; cpi->total_psnrhvs_v = 0; cpi->total_psnrhvs_all = 0; + + if (cpi->b_calculate_blockiness) { + cpi->total_blockiness = 0; + } + + if (cpi->b_calculate_blockiness) { + cpi->total_blockiness = 0; + } + #endif cpi->first_time_stamp_ever = INT64_MAX; @@ -1831,7 +1842,6 @@ void vp9_remove_compressor(VP9_COMP *cpi) { if (cpi && (cm->current_video_frame > 0)) { #if CONFIG_INTERNAL_STATS - vp9_clear_system_state(); // printf("\n8x8-4x4:%d-%d\n", cpi->t8x8_count, cpi->t4x4_count); @@ -1854,19 +1864,28 @@ void vp9_remove_compressor(VP9_COMP *cpi) { (double)cpi->totalp_sq_error); const double total_ssim = 100 * pow(cpi->summed_quality / cpi->summed_weights, 8.0); - const double totalp_ssim = 100 * pow(cpi->summedp_quality / - cpi->summedp_weights, 8.0); - - - fprintf(f, "Bitrate\tAVGPsnr\tGLBPsnr\tAVPsnrP\tGLPsnrP\t" + if (cpi->b_calculate_blockiness) { + fprintf(f, "Bitrate\tAVGPsnr\tGLBPsnr\tAVPsnrP\tGLPsnrP\t" "VPXSSIM\tVPSSIMP\tFASTSSIM\tPSNRHVS\tTime(ms)\n"); - fprintf(f, "%7.2f\t%7.3f\t%7.3f\t%7.3f\t%7.3f\t%7.3f\t" - "%7.3f\t%7.3f\t%7.3f\t%8.0f\n", - dr, cpi->total / cpi->count, total_psnr, - cpi->totalp / cpi->count, totalp_psnr, total_ssim, totalp_ssim, + fprintf(f, "%7.2f\t%7.3f\t%7.3f\t%7.3f\t%7.3f\t%7.3f\t" + "%7.3f\t%7.3f\t%8.0f\n", + dr, cpi->total / cpi->count, total_psnr, + cpi->totalp / cpi->count, totalp_psnr, total_ssim, cpi->total_fastssim_all / cpi->count, cpi->total_psnrhvs_all / cpi->count, total_encode_time); + } else { + fprintf(f, "Bitrate\tAVGPsnr\tGLBPsnr\tAVPsnrP\tGLPsnrP\t" + "VPXSSIM\tVPSSIMP\tBlockiness\tFASTSSIM\tPSNRHVS\tTime(ms)\n"); + fprintf(f, "%7.2f\t%7.3f\t%7.3f\t%7.3f\t%7.3f\t%7.3f\t" + "%7.3f\t%7.3f\t%7.3f\t%8.0f\n", + dr, cpi->total / cpi->count, total_psnr, + cpi->totalp / cpi->count, totalp_psnr, total_ssim, + cpi->total_blockiness / cpi->count, + cpi->total_fastssim_all / cpi->count, + cpi->total_psnrhvs_all / cpi->count, + total_encode_time); + } } @@ -3786,6 +3805,12 @@ static void check_src_altref(VP9_COMP *cpi, } } +#if CONFIG_INTERNAL_STATS +extern double vp9_get_blockiness(const unsigned char *img1, int img1_pitch, + const unsigned char *img2, int img2_pitch, + int width, int height); +#endif + int vp9_get_compressed_data(VP9_COMP *cpi, unsigned int *frame_flags, size_t *size, uint8_t *dest, int64_t *time_stamp, int64_t *time_end, int flush) { @@ -4135,7 +4160,12 @@ int vp9_get_compressed_data(VP9_COMP *cpi, unsigned int *frame_flags, #endif } } - + if (cpi->b_calculate_blockiness) + cpi->total_blockiness += + vp9_get_blockiness(cpi->Source->y_buffer, cpi->Source->y_stride, + cm->frame_to_show->y_buffer, + cm->frame_to_show->y_stride, + cpi->Source->y_width, cpi->Source->y_height); if (cpi->b_calculate_ssimg) { double y, u, v, frame_all; diff --git a/vp9/encoder/vp9_encoder.h b/vp9/encoder/vp9_encoder.h index 6b1efafad..ed8e0d86a 100644 --- a/vp9/encoder/vp9_encoder.h +++ b/vp9/encoder/vp9_encoder.h @@ -402,6 +402,8 @@ typedef struct VP9_COMP { uint64_t totalp_sq_error; uint64_t totalp_samples; + double total_blockiness; + int bytes; double summed_quality; double summed_weights; @@ -426,6 +428,7 @@ typedef struct VP9_COMP { double total_psnrhvs_all; int b_calculate_ssimg; + int b_calculate_blockiness; #endif int b_calculate_psnr; diff --git a/vp9/vp9cx.mk b/vp9/vp9cx.mk index 39570b953..fe5d6c457 100644 --- a/vp9/vp9cx.mk +++ b/vp9/vp9cx.mk @@ -81,6 +81,8 @@ VP9_CX_SRCS-yes += encoder/vp9_resize.c VP9_CX_SRCS-yes += encoder/vp9_resize.h VP9_CX_SRCS-$(CONFIG_INTERNAL_STATS) += encoder/vp9_ssim.c VP9_CX_SRCS-$(CONFIG_INTERNAL_STATS) += encoder/vp9_ssim.h +VP9_CX_SRCS-$(CONFIG_INTERNAL_STATS) += encoder/vp9_blockiness.c + VP9_CX_SRCS-yes += encoder/vp9_tokenize.c VP9_CX_SRCS-yes += encoder/vp9_treewriter.c VP9_CX_SRCS-yes += encoder/vp9_variance.c