/* * Copyright (c) 2018, Alliance for Open Media. All rights reserved. * * This source code is subject to the terms of the BSD 2 Clause License and * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License * was not distributed with this source code in the LICENSE file, you can * obtain it at www.aomedia.org/license/software. If the Alliance for Open * Media Patent License 1.0 was not distributed with this source code in the * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
// Return normally distrbuted values with standard deviation of sigma. double randn(libaom_test::ACMRandom *random, double sigma) { while (true) { constdouble u = 2.0 * ((double)random->Rand31() /
testing::internal::Random::kMaxRange) -
1.0; constdouble v = 2.0 * ((double)random->Rand31() /
testing::internal::Random::kMaxRange) -
1.0; constdouble s = u * u + v * v; if (s > 0 && s < 1) { return sigma * (u * sqrt(-2.0 * log(s) / s));
}
}
}
// Synthesizes noise using the auto-regressive filter of the given lag, // with the provided n coefficients sampled at the given coords. void noise_synth(libaom_test::ACMRandom *random, int lag, int n, constint (*coords)[2], constdouble *coeffs, double *data, int w, int h) { constint pad_size = 3 * lag; constint padded_w = w + pad_size; constint padded_h = h + pad_size; int x = 0, y = 0;
std::vector<double> padded(padded_w * padded_h);
for (y = 0; y < padded_h; ++y) { for (x = 0; x < padded_w; ++x) {
padded[y * padded_w + x] = randn(random, 1.0);
}
} for (y = lag; y < padded_h; ++y) { for (x = lag; x < padded_w; ++x) { double sum = 0; int i = 0; for (i = 0; i < n; ++i) { constint dx = coords[i][0]; constint dy = coords[i][1];
sum += padded[(y + dy) * padded_w + (x + dx)] * coeffs[i];
}
padded[y * padded_w + x] += sum;
}
} // Copy over the padded rows to the output for (y = 0; y < h; ++y) {
memcpy(data + y * w, &padded[0] + y * padded_w, sizeof(*data) * w);
}
}
std::vector<float> get_noise_psd(double *noise, int width, int height, int block_size) { float *block =
(float *)aom_memalign(32, block_size * block_size * sizeof(block));
std::vector<float> psd(block_size * block_size); if (block == nullptr) {
EXPECT_NE(block, nullptr); return psd;
} int num_blocks = 0; struct aom_noise_tx_t *tx = aom_noise_tx_malloc(block_size); if (tx == nullptr) {
EXPECT_NE(tx, nullptr); return psd;
} for (int y = 0; y <= height - block_size; y += block_size / 2) { for (int x = 0; x <= width - block_size; x += block_size / 2) { for (int yy = 0; yy < block_size; ++yy) { for (int xx = 0; xx < block_size; ++xx) {
block[yy * block_size + xx] = (float)noise[(y + yy) * width + x + xx];
}
}
aom_noise_tx_forward(tx, &block[0]);
aom_noise_tx_add_energy(tx, &psd[0]);
num_blocks++;
}
} for (int yy = 0; yy < block_size; ++yy) { for (int xx = 0; xx <= block_size / 2; ++xx) {
psd[yy * block_size + xx] /= num_blocks;
}
} // Fill in the data that is missing due to symmetries for (int xx = 1; xx < block_size / 2; ++xx) {
psd[(block_size - xx)] = psd[xx];
} for (int yy = 1; yy < block_size; ++yy) { for (int xx = 1; xx < block_size / 2; ++xx) {
psd[(block_size - yy) * block_size + (block_size - xx)] =
psd[yy * block_size + xx];
}
}
aom_noise_tx_free(tx);
aom_free(block); return psd;
}
for (int i = 0; i < 256; ++i) {
EXPECT_NEAR(i, aom_noise_strength_solver_get_center(&solver, i), 1e-5);
}
aom_noise_strength_solver_free(&solver);
}
// Tests that the noise strength solver returns the identity transform when // given identity-like constraints.
TEST(NoiseStrengthSolver, ObserveIdentity) { constint num_bins = 256;
aom_noise_strength_solver_t solver;
ASSERT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8));
// We have to add a big more strength to constraints at the boundary to // overcome any regularization. for (int j = 0; j < 5; ++j) {
aom_noise_strength_solver_add_measurement(&solver, 0, 0);
aom_noise_strength_solver_add_measurement(&solver, 255, 255);
} for (int i = 0; i < 256; ++i) {
aom_noise_strength_solver_add_measurement(&solver, i, i);
}
EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver)); for (int i = 2; i < num_bins - 2; ++i) {
EXPECT_NEAR(i, solver.eqns.x[i], 0.1);
}
// Create a parabolic input for (int i = 0; i < 256; ++i) { constdouble x = (i - 127.5) / 63.5;
aom_noise_strength_solver_add_measurement(&solver, i, x * x);
}
EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver));
// First try to fit an unconstrained lut
aom_noise_strength_lut_t lut;
EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, -1, &lut));
ASSERT_LE(20, lut.num_points);
aom_noise_strength_lut_free(&lut);
// Now constrain the maximum number of points constint kMaxPoints = 9;
EXPECT_EQ(1,
aom_noise_strength_solver_fit_piecewise(&solver, kMaxPoints, &lut));
ASSERT_EQ(kMaxPoints, lut.num_points);
// Check that the input parabola is still well represented
EXPECT_NEAR(0.0, lut.points[0][0], 1e-5);
EXPECT_NEAR(4.0, lut.points[0][1], 0.1); for (int i = 1; i < lut.num_points - 1; ++i) { constdouble x = (lut.points[i][0] - 128.) / 64.;
EXPECT_NEAR(x * x, lut.points[i][1], 0.1);
}
EXPECT_NEAR(255.0, lut.points[kMaxPoints - 1][0], 1e-5);
// Test lower boundary
EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, -1));
EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, 0));
// Test first part that should be identity
EXPECT_NEAR(0.25, aom_noise_strength_lut_eval(&lut, 0.25), kEps);
EXPECT_NEAR(0.75, aom_noise_strength_lut_eval(&lut, 0.75), kEps);
// This is a constant section (should evaluate to 1)
EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.25), kEps);
EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.75), kEps);
// Test interpolation between to non-zero y coords.
EXPECT_NEAR(1, aom_noise_strength_lut_eval(&lut, 2), kEps);
EXPECT_NEAR(251, aom_noise_strength_lut_eval(&lut, 26.5), kEps);
EXPECT_NEAR(751, aom_noise_strength_lut_eval(&lut, 75.5), kEps);
// Test upper boundary
EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 100));
EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 101));
TEST(NoiseModel, InitFailsWithInvalidBitdepth) {
aom_noise_model_t model;
aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 }; for (int i = 0; i <= 32; ++i) {
params.bit_depth = i; if (i == 8 || i == 10 || i == 12) {
EXPECT_TRUE(aom_noise_model_init(&model, params)) << "bit_depth: " << i;
aom_noise_model_free(&model);
} else {
EXPECT_FALSE(aom_noise_model_init(&model, params)) << "bit_depth: " << i;
}
}
params.bit_depth = INT_MAX;
EXPECT_FALSE(aom_noise_model_init(&model, params));
}
// A container template class to hold a data type and extra arguments. // All of these args are bundled into one struct so that we can use // parameterized tests on combinations of supported data types // (uint8_t and uint16_t) and bit depths (8, 10, 12). template <typename T, int bit_depth, bool use_highbd> struct BitDepthParams { typedef T data_type_t; staticconstint kBitDepth = bit_depth; staticconstbool kUseHighBD = use_highbd;
};
template <typename T> class FlatBlockEstimatorTest : public ::testing::Test, public T { public: void SetUp() override { random_.Reset(171); } typedef std::vector<typename T::data_type_t> VecType;
VecType data_;
libaom_test::ACMRandom random_;
};
// Test with an image of more than one block. constint h = 2 * kBlockSize; constint w = 2 * kBlockSize; constint stride = 2 * kBlockSize;
this->data_.resize(h * stride, 128);
// Set up the (0,0) block to be a plane and the (0,1) block to be a // checkerboard constint shift = this->kBitDepth - 8; for (int y = 0; y < kBlockSize; ++y) { for (int x = 0; x < kBlockSize; ++x) {
this->data_[y * stride + x] = (-y + x + 128) << shift;
this->data_[y * stride + x + kBlockSize] =
((x % 2 + y % 2) % 2 ? 128 - 20 : 128 + 20) << shift;
}
}
std::vector<double> block(kBlockSize * kBlockSize, 1);
std::vector<double> plane(kBlockSize * kBlockSize, 1);
// The block data should be a constant (zero) and the rest of the plane // trend is covered in the plane data.
aom_flat_block_finder_extract_block(&flat_block_finder,
(uint8_t *)&this->data_[0], w, h, stride,
0, 0, &plane[0], &block[0]); for (int y = 0; y < kBlockSize; ++y) { for (int x = 0; x < kBlockSize; ++x) {
EXPECT_NEAR(0, block[y * kBlockSize + x], 1e-5);
EXPECT_NEAR((double)(this->data_[y * stride + x]) / normalization,
plane[y * kBlockSize + x], 1e-5);
}
}
// The plane trend is a constant, and the block is a zero mean checkerboard.
aom_flat_block_finder_extract_block(&flat_block_finder,
(uint8_t *)&this->data_[0], w, h, stride,
kBlockSize, 0, &plane[0], &block[0]); constint mid = 128 << shift; for (int y = 0; y < kBlockSize; ++y) { for (int x = 0; x < kBlockSize; ++x) {
EXPECT_NEAR(((double)this->data_[y * stride + x + kBlockSize] - mid) /
normalization,
block[y * kBlockSize + x], 1e-5);
EXPECT_NEAR(mid / normalization, plane[y * kBlockSize + x], 1e-5);
}
}
aom_flat_block_finder_free(&flat_block_finder);
}
constint shift = this->kBitDepth - 8; for (int y = 0; y < kBlockSize; ++y) { for (int x = 0; x < kBlockSize; ++x) { // Block 0 (not flat): constant doesn't have enough variance to qualify
this->data_[y * stride + x + 0 * kBlockSize] = 128 << shift;
// Block 1 (not flat): too high of variance is hard to validate as flat
this->data_[y * stride + x + 1 * kBlockSize] =
((uint8_t)(128 + randn(&this->random_, 5))) << shift;
// First two blocks are not flat
EXPECT_EQ(0, flat_blocks[0]);
EXPECT_EQ(0, flat_blocks[1]);
// Next 4 blocks are flat.
EXPECT_EQ(255, flat_blocks[2]);
EXPECT_EQ(255, flat_blocks[3]);
EXPECT_EQ(255, flat_blocks[4]);
EXPECT_EQ(255, flat_blocks[5]);
// Last 2 are not flat by threshold
EXPECT_EQ(0, flat_blocks[6]);
EXPECT_EQ(0, flat_blocks[7]);
// Add the noise from non-flat block 1 to every block. for (int y = 0; y < kBlockSize; ++y) { for (int x = 0; x < kBlockSize * num_blocks_w; ++x) {
this->data_[y * stride + x] +=
(this->data_[y * stride + x % kBlockSize + kBlockSize] -
(128 << shift));
}
} // Now the scored selection will pick the one that is most likely flat (block // 0)
EXPECT_EQ(1, aom_flat_block_finder_run(&flat_block_finder,
(uint8_t *)&this->data_[0], w, h,
stride, &flat_blocks[0]));
EXPECT_EQ(1, flat_blocks[0]);
EXPECT_EQ(0, flat_blocks[1]);
EXPECT_EQ(0, flat_blocks[2]);
EXPECT_EQ(0, flat_blocks[3]);
EXPECT_EQ(0, flat_blocks[4]);
EXPECT_EQ(0, flat_blocks[5]);
EXPECT_EQ(0, flat_blocks[6]);
EXPECT_EQ(0, flat_blocks[7]);
typedef ::testing::Types<BitDepthParams<uint8_t, 8, false>, // lowbd
BitDepthParams<uint16_t, 8, true>, // lowbd in 16-bit
BitDepthParams<uint16_t, 10, true>, // highbd data
BitDepthParams<uint16_t, 12, true> >
AllBitDepthParams; // Note the empty final argument can be removed if C++20 is made the minimum // requirement.
INSTANTIATE_TYPED_TEST_SUITE_P(FlatBlockInstatiation, FlatBlockEstimatorTest,
AllBitDepthParams, );
template <typename T> class NoiseModelUpdateTest : public ::testing::Test, public T { public: staticconstint kWidth = 128; staticconstint kHeight = 128; staticconstint kBlockSize = 16; staticconstint kNumBlocksX = kWidth / kBlockSize; staticconstint kNumBlocksY = kHeight / kBlockSize;
constint shift = this->kBitDepth - 8; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) {
this->data_ptr_[0][y * width + x] = int(64 + y + randn(&this->random_, 1))
<< shift;
this->denoised_ptr_[0][y * width + x] = (64 + y) << shift; // Make the chroma planes completely correlated with the Y plane for (int c = 1; c < 3; ++c) {
this->data_ptr_[c][y * width + x] = this->data_ptr_[0][y * width + x];
this->denoised_ptr_[c][y * width + x] =
this->denoised_ptr_[0][y * width + x];
}
}
}
this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
constdouble kCoeffEps = 0.075; constint n = model.n; for (int c = 0; c < 3; ++c) { for (int i = 0; i < n; ++i) {
EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps);
EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps);
} // The second and third channels are highly correlated with the first. if (c > 0) {
ASSERT_EQ(n + 1, model.latest_state[c].eqns.n);
ASSERT_EQ(n + 1, model.combined_state[c].eqns.n);
// The fitted noise strength should be close to the standard deviation // for all intensity bins. constdouble kStdEps = 0.1; constdouble normalize = 1 << shift;
for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) {
EXPECT_NEAR(1.0,
model.latest_state[0].strength_solver.eqns.x[i] / normalize,
kStdEps);
EXPECT_NEAR(1.0,
model.combined_state[0].strength_solver.eqns.x[i] / normalize,
kStdEps);
}
constdouble kCoeffEps = 0.055; constdouble kLowStd = 1; constdouble kHighStd = 4; constint shift = this->kBitDepth - 8; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { for (int c = 0; c < 3; ++c) { // The image data is bimodal: // Bottom half has low intensity and low noise strength // Top half has high intensity and high noise strength constint avg = (y < height / 2) ? 4 : 245; constdouble std = (y < height / 2) ? kLowStd : kHighStd;
this->data_ptr_[c][y * width + x] =
((uint8_t)std::min((int)255,
(int)(2 + avg + randn(&this->random_, std))))
<< shift;
this->denoised_ptr_[c][y * width + x] = (2 + avg) << shift;
}
}
} // Label all blocks as flat for the update
this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
constint n = model.n; // The noise is uncorrelated spatially and with the y channel. // All coefficients should be reasonably close to zero. for (int c = 0; c < 3; ++c) { for (int i = 0; i < n; ++i) {
EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps);
EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps);
} if (c > 0) {
ASSERT_EQ(n + 1, model.latest_state[c].eqns.n);
ASSERT_EQ(n + 1, model.combined_state[c].eqns.n);
// The correlation to the y channel should be low (near zero)
EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps);
EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps);
}
}
// Noise strength should vary between kLowStd and kHighStd. constdouble kStdEps = 0.15; // We have to normalize fitted standard deviation based on bit depth. constdouble normalize = (1 << shift);
ASSERT_EQ(20, model.latest_state[0].strength_solver.eqns.n); for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) { constdouble a = i / 19.0; constdouble expected = (kLowStd * (1.0 - a) + kHighStd * a);
EXPECT_NEAR(expected,
model.latest_state[0].strength_solver.eqns.x[i] / normalize,
kStdEps);
EXPECT_NEAR(expected,
model.combined_state[0].strength_solver.eqns.x[i] / normalize,
kStdEps);
}
// If we fit a piecewise linear model, there should be two points: // one near kLowStd at 0, and the other near kHighStd and 255.
aom_noise_strength_lut_t lut;
aom_noise_strength_solver_fit_piecewise(
&model.latest_state[0].strength_solver, 2, &lut);
ASSERT_EQ(2, lut.num_points);
EXPECT_NEAR(0, lut.points[0][0], 1e-4);
EXPECT_NEAR(kLowStd, lut.points[0][1] / normalize, kStdEps);
EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5);
EXPECT_NEAR(kHighStd, lut.points[1][1] / normalize, kStdEps);
aom_noise_strength_lut_free(&lut);
}
// Add different noise onto each plane constint shift = this->kBitDepth - 8; for (int c = 0; c < 3; ++c) {
noise_synth(&this->random_, model.params.lag, model.n, model.coords,
kCoeffs[c], this->noise_ptr_[c], width, height); constint x_shift = c > 0 ? this->chroma_sub_[0] : 0; constint y_shift = c > 0 ? this->chroma_sub_[1] : 0; for (int y = 0; y < (height >> y_shift); ++y) { for (int x = 0; x < (width >> x_shift); ++x) { const uint8_t value = 64 + x / 2 + y / 4;
this->data_ptr_[c][y * width + x] =
(uint8_t(value + this->noise_ptr_[c][y * width + x] * kStd))
<< shift;
this->denoised_ptr_[c][y * width + x] = value << shift;
}
}
}
EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
// For the Y plane, the solved coefficients should be close to the original constint n = model.n; for (int c = 0; c < 3; ++c) { for (int i = 0; i < n; ++i) {
EXPECT_NEAR(kCoeffs[c][i], model.latest_state[c].eqns.x[i], kCoeffEps);
EXPECT_NEAR(kCoeffs[c][i], model.combined_state[c].eqns.x[i], kCoeffEps);
} // The chroma planes should be uncorrelated with the luma plane if (c > 0) {
EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps);
EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps);
} // Correlation between the coefficient vector and the fitted coefficients // should be close to 1.
EXPECT_LT(0.98, aom_normalized_cross_correlation(
model.latest_state[c].eqns.x, kCoeffs[c], kNumCoeffs));
// Bump up the noise strength on half the image for one channel by a // significant amount. for (int i = 0; i < width * height; ++i) { const uint8_t val = (i % width) < width / 2 ? 64 : 128; if (i % width < width / 2) {
this->data_ptr_[0][i] =
((uint8_t)(randn(&this->random_, kStd + 0.5) + val)) << shift;
}
}
EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate());
// Since we didn't update the combined state, it should still be at 2 * // num_blocks
EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations);
EXPECT_EQ(2 * kNumBlocks,
model.combined_state[0].strength_solver.num_equations);
// In normal operation, the "latest" estimate can be saved to the "combined" // state for continued updates.
aom_noise_model_save_latest(&model); for (int c = 0; c < 3; ++c) {
EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations);
EXPECT_EQ(15250, model.latest_state[c].num_observations);
EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance);
noise_synth(&this->random_, model.params.lag, model.n, model.coords,
kCoeffs[0], this->noise_ptr_[0], width, height); for (int i = 0; i < width * height; ++i) {
this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]);
}
this->flat_blocks_.assign(this->flat_blocks_.size(), 1);
EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate());
// Now try with the second set of AR coefficients
noise_synth(&this->random_, model.params.lag, model.n, model.coords,
kCoeffs[1], this->noise_ptr_[0], width, height); for (int i = 0; i < width * height; ++i) {
this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]);
}
EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate());
}
REGISTER_TYPED_TEST_SUITE_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks,
UpdateSuccessForZeroNoiseAllFlat,
UpdateFailsBlockSizeTooSmall,
UpdateSuccessForWhiteRandomNoise,
UpdateSuccessForScaledWhiteNoise,
UpdateSuccessForCorrelatedNoise,
NoiseStrengthChangeSignalsDifferentNoiseType,
NoiseCoeffsSignalsDifferentNoiseType);
// Note the empty final argument can be removed if C++20 is made the minimum // requirement.
INSTANTIATE_TYPED_TEST_SUITE_P(NoiseModelUpdateTestInstatiation,
NoiseModelUpdateTest, AllBitDepthParams, );
// Setup the AR coeffs
memcpy(model.combined_state[0].eqns.x, kInputCoeffsY, sizeof(kInputCoeffsY));
memcpy(model.combined_state[1].eqns.x, kInputCoeffsCB, sizeof(kInputCoeffsCB));
memcpy(model.combined_state[2].eqns.x, kInputCoeffsCR, sizeof(kInputCoeffsCR)); for (int i = 0; i < model.combined_state[0].strength_solver.num_bins; ++i) { constdouble x =
((double)i) / (model.combined_state[0].strength_solver.num_bins - 1.0);
model.combined_state[0].strength_solver.eqns.x[i] = 6 * sqrt(x);
model.combined_state[1].strength_solver.eqns.x[i] = 3;
model.combined_state[2].strength_solver.eqns.x[i] = 2;
// Inject some observations into the strength solver, as during film grain // parameter extraction an estimate of the average strength will be used to // adjust correlation. constint n = model.combined_state[0].strength_solver.num_bins; for (int j = 0; j < model.combined_state[0].strength_solver.num_bins; ++j) {
model.combined_state[0].strength_solver.eqns.A[i * n + j] = 1;
model.combined_state[1].strength_solver.eqns.A[i * n + j] = 1;
model.combined_state[2].strength_solver.eqns.A[i * n + j] = 1;
}
}
constint kNumARCoeffs = 24; for (int i = 0; i < kNumARCoeffs; ++i) {
EXPECT_EQ(kExpectedARCoeffsY[i], film_grain.ar_coeffs_y[i]);
} for (int i = 0; i < kNumARCoeffs + 1; ++i) {
EXPECT_EQ(kExpectedARCoeffsCB[i], film_grain.ar_coeffs_cb[i]);
} for (int i = 0; i < kNumARCoeffs + 1; ++i) {
EXPECT_EQ(kExpectedARCoeffsCR[i], film_grain.ar_coeffs_cr[i]);
} for (int i = 0; i < kNumScalingPointsY; ++i) {
EXPECT_EQ(kExpectedScalingPointsY[i][0], film_grain.scaling_points_y[i][0]);
EXPECT_EQ(kExpectedScalingPointsY[i][1], film_grain.scaling_points_y[i][1]);
}
// CB strength should just be a piecewise segment
EXPECT_EQ(2, film_grain.num_cb_points);
EXPECT_EQ(0, film_grain.scaling_points_cb[0][0]);
EXPECT_EQ(255, film_grain.scaling_points_cb[1][0]);
EXPECT_EQ(96, film_grain.scaling_points_cb[0][1]);
EXPECT_EQ(96, film_grain.scaling_points_cb[1][1]);
// CR strength should just be a piecewise segment
EXPECT_EQ(2, film_grain.num_cr_points);
EXPECT_EQ(0, film_grain.scaling_points_cr[0][0]);
EXPECT_EQ(255, film_grain.scaling_points_cr[1][0]);
EXPECT_EQ(64, film_grain.scaling_points_cr[0][1]);
EXPECT_EQ(64, film_grain.scaling_points_cr[1][1]);
// Check the noise on the denoised image (from the analytical gradient) // and make sure that it is less than what we added. for (int c = 0; c < 3; ++c) {
std::vector<double> measured_noise(width * height);
double var = 0; constint shift = (c > 0); for (int x = 0; x < (width >> shift); ++x) { for (int y = 0; y < (height >> shift); ++y) { constdouble diff = this->denoised_[c][y * this->stride_[c] + x] -
x * this->kScaleNoise;
var += diff * diff;
measured_noise[y * width + x] = diff;
}
}
var /= (width * height); constdouble std = sqrt(std::max(0.0, var));
EXPECT_LE(std, 1.25f * this->kScaleNoise); if (c == 0) {
std::vector<float> measured_psd =
get_noise_psd(&measured_noise[0], width, height, block_size);
std::vector<double> measured_psd_d(block_size * block_size);
std::vector<double> noise_psd_d(block_size * block_size);
std::copy(measured_psd.begin(), measured_psd.end(),
measured_psd_d.begin());
std::copy(this->noise_psd_[0].begin(), this->noise_psd_[0].end(),
noise_psd_d.begin());
EXPECT_LT(
aom_normalized_cross_correlation(&measured_psd_d[0], &noise_psd_d[0],
(int)(noise_psd_d.size())),
0.35);
}
}
}
// Note the empty final argument can be removed if C++20 is made the minimum // requirement.
INSTANTIATE_TYPED_TEST_SUITE_P(WienerDenoiseTestInstatiation, WienerDenoiseTest,
AllBitDepthParams, );
Messung V0.5
¤ Dauer der Verarbeitung: 0.6 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.