noise_model_test.cc (53858B)
1 /* 2 * Copyright (c) 2018, Alliance for Open Media. All rights reserved. 3 * 4 * This source code is subject to the terms of the BSD 2 Clause License and 5 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License 6 * was not distributed with this source code in the LICENSE file, you can 7 * obtain it at www.aomedia.org/license/software. If the Alliance for Open 8 * Media Patent License 1.0 was not distributed with this source code in the 9 * PATENTS file, you can obtain it at www.aomedia.org/license/patent. 10 */ 11 12 #include <limits.h> 13 #include <math.h> 14 #include <algorithm> 15 #include <vector> 16 17 #include "aom_dsp/noise_model.h" 18 #include "aom_dsp/noise_util.h" 19 #include "config/aom_dsp_rtcd.h" 20 #include "gtest/gtest.h" 21 #include "test/acm_random.h" 22 23 namespace { 24 25 // Return normally distrbuted values with standard deviation of sigma. 26 double randn(libaom_test::ACMRandom *random, double sigma) { 27 while (true) { 28 const double u = 2.0 * ((double)random->Rand31() / 29 testing::internal::Random::kMaxRange) - 30 1.0; 31 const double v = 2.0 * ((double)random->Rand31() / 32 testing::internal::Random::kMaxRange) - 33 1.0; 34 const double s = u * u + v * v; 35 if (s > 0 && s < 1) { 36 return sigma * (u * sqrt(-2.0 * log(s) / s)); 37 } 38 } 39 } 40 41 // Synthesizes noise using the auto-regressive filter of the given lag, 42 // with the provided n coefficients sampled at the given coords. 43 void noise_synth(libaom_test::ACMRandom *random, int lag, int n, 44 const int (*coords)[2], const double *coeffs, double *data, 45 int w, int h) { 46 const int pad_size = 3 * lag; 47 const int padded_w = w + pad_size; 48 const int padded_h = h + pad_size; 49 int x = 0, y = 0; 50 std::vector<double> padded(padded_w * padded_h); 51 52 for (y = 0; y < padded_h; ++y) { 53 for (x = 0; x < padded_w; ++x) { 54 padded[y * padded_w + x] = randn(random, 1.0); 55 } 56 } 57 for (y = lag; y < padded_h; ++y) { 58 for (x = lag; x < padded_w; ++x) { 59 double sum = 0; 60 int i = 0; 61 for (i = 0; i < n; ++i) { 62 const int dx = coords[i][0]; 63 const int dy = coords[i][1]; 64 sum += padded[(y + dy) * padded_w + (x + dx)] * coeffs[i]; 65 } 66 padded[y * padded_w + x] += sum; 67 } 68 } 69 // Copy over the padded rows to the output 70 for (y = 0; y < h; ++y) { 71 memcpy(data + y * w, &padded[0] + y * padded_w, sizeof(*data) * w); 72 } 73 } 74 75 std::vector<float> get_noise_psd(double *noise, int width, int height, 76 int block_size) { 77 float *block = 78 (float *)aom_memalign(32, block_size * block_size * sizeof(block)); 79 std::vector<float> psd(block_size * block_size); 80 if (block == nullptr) { 81 EXPECT_NE(block, nullptr); 82 return psd; 83 } 84 int num_blocks = 0; 85 struct aom_noise_tx_t *tx = aom_noise_tx_malloc(block_size); 86 if (tx == nullptr) { 87 EXPECT_NE(tx, nullptr); 88 return psd; 89 } 90 for (int y = 0; y <= height - block_size; y += block_size / 2) { 91 for (int x = 0; x <= width - block_size; x += block_size / 2) { 92 for (int yy = 0; yy < block_size; ++yy) { 93 for (int xx = 0; xx < block_size; ++xx) { 94 block[yy * block_size + xx] = (float)noise[(y + yy) * width + x + xx]; 95 } 96 } 97 aom_noise_tx_forward(tx, &block[0]); 98 aom_noise_tx_add_energy(tx, &psd[0]); 99 num_blocks++; 100 } 101 } 102 for (int yy = 0; yy < block_size; ++yy) { 103 for (int xx = 0; xx <= block_size / 2; ++xx) { 104 psd[yy * block_size + xx] /= num_blocks; 105 } 106 } 107 // Fill in the data that is missing due to symmetries 108 for (int xx = 1; xx < block_size / 2; ++xx) { 109 psd[(block_size - xx)] = psd[xx]; 110 } 111 for (int yy = 1; yy < block_size; ++yy) { 112 for (int xx = 1; xx < block_size / 2; ++xx) { 113 psd[(block_size - yy) * block_size + (block_size - xx)] = 114 psd[yy * block_size + xx]; 115 } 116 } 117 aom_noise_tx_free(tx); 118 aom_free(block); 119 return psd; 120 } 121 122 } // namespace 123 124 TEST(NoiseStrengthSolver, GetCentersTwoBins) { 125 aom_noise_strength_solver_t solver; 126 aom_noise_strength_solver_init(&solver, 2, 8); 127 EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5); 128 EXPECT_NEAR(255, aom_noise_strength_solver_get_center(&solver, 1), 1e-5); 129 aom_noise_strength_solver_free(&solver); 130 } 131 132 TEST(NoiseStrengthSolver, GetCentersTwoBins10bit) { 133 aom_noise_strength_solver_t solver; 134 aom_noise_strength_solver_init(&solver, 2, 10); 135 EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5); 136 EXPECT_NEAR(1023, aom_noise_strength_solver_get_center(&solver, 1), 1e-5); 137 aom_noise_strength_solver_free(&solver); 138 } 139 140 TEST(NoiseStrengthSolver, GetCenters256Bins) { 141 const int num_bins = 256; 142 aom_noise_strength_solver_t solver; 143 aom_noise_strength_solver_init(&solver, num_bins, 8); 144 145 for (int i = 0; i < 256; ++i) { 146 EXPECT_NEAR(i, aom_noise_strength_solver_get_center(&solver, i), 1e-5); 147 } 148 aom_noise_strength_solver_free(&solver); 149 } 150 151 // Tests that the noise strength solver returns the identity transform when 152 // given identity-like constraints. 153 TEST(NoiseStrengthSolver, ObserveIdentity) { 154 const int num_bins = 256; 155 aom_noise_strength_solver_t solver; 156 ASSERT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8)); 157 158 // We have to add a big more strength to constraints at the boundary to 159 // overcome any regularization. 160 for (int j = 0; j < 5; ++j) { 161 aom_noise_strength_solver_add_measurement(&solver, 0, 0); 162 aom_noise_strength_solver_add_measurement(&solver, 255, 255); 163 } 164 for (int i = 0; i < 256; ++i) { 165 aom_noise_strength_solver_add_measurement(&solver, i, i); 166 } 167 EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver)); 168 for (int i = 2; i < num_bins - 2; ++i) { 169 EXPECT_NEAR(i, solver.eqns.x[i], 0.1); 170 } 171 172 aom_noise_strength_lut_t lut; 173 EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, 2, &lut)); 174 175 ASSERT_EQ(2, lut.num_points); 176 EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); 177 EXPECT_NEAR(0.0, lut.points[0][1], 0.5); 178 EXPECT_NEAR(255.0, lut.points[1][0], 1e-5); 179 EXPECT_NEAR(255.0, lut.points[1][1], 0.5); 180 181 aom_noise_strength_lut_free(&lut); 182 aom_noise_strength_solver_free(&solver); 183 } 184 185 TEST(NoiseStrengthSolver, SimplifiesCurve) { 186 const int num_bins = 256; 187 aom_noise_strength_solver_t solver; 188 EXPECT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins, 8)); 189 190 // Create a parabolic input 191 for (int i = 0; i < 256; ++i) { 192 const double x = (i - 127.5) / 63.5; 193 aom_noise_strength_solver_add_measurement(&solver, i, x * x); 194 } 195 EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver)); 196 197 // First try to fit an unconstrained lut 198 aom_noise_strength_lut_t lut; 199 EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, -1, &lut)); 200 ASSERT_LE(20, lut.num_points); 201 aom_noise_strength_lut_free(&lut); 202 203 // Now constrain the maximum number of points 204 const int kMaxPoints = 9; 205 EXPECT_EQ(1, 206 aom_noise_strength_solver_fit_piecewise(&solver, kMaxPoints, &lut)); 207 ASSERT_EQ(kMaxPoints, lut.num_points); 208 209 // Check that the input parabola is still well represented 210 EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); 211 EXPECT_NEAR(4.0, lut.points[0][1], 0.1); 212 for (int i = 1; i < lut.num_points - 1; ++i) { 213 const double x = (lut.points[i][0] - 128.) / 64.; 214 EXPECT_NEAR(x * x, lut.points[i][1], 0.1); 215 } 216 EXPECT_NEAR(255.0, lut.points[kMaxPoints - 1][0], 1e-5); 217 218 EXPECT_NEAR(4.0, lut.points[kMaxPoints - 1][1], 0.1); 219 aom_noise_strength_lut_free(&lut); 220 aom_noise_strength_solver_free(&solver); 221 } 222 223 TEST(NoiseStrengthLut, LutInitNegativeOrZeroSize) { 224 aom_noise_strength_lut_t lut; 225 ASSERT_FALSE(aom_noise_strength_lut_init(&lut, -1)); 226 ASSERT_FALSE(aom_noise_strength_lut_init(&lut, 0)); 227 } 228 229 TEST(NoiseStrengthLut, LutEvalSinglePoint) { 230 aom_noise_strength_lut_t lut; 231 ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 1)); 232 ASSERT_EQ(1, lut.num_points); 233 lut.points[0][0] = 0; 234 lut.points[0][1] = 1; 235 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, -1)); 236 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 0)); 237 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 1)); 238 aom_noise_strength_lut_free(&lut); 239 } 240 241 TEST(NoiseStrengthLut, LutEvalMultiPointInterp) { 242 const double kEps = 1e-5; 243 aom_noise_strength_lut_t lut; 244 ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 4)); 245 ASSERT_EQ(4, lut.num_points); 246 247 lut.points[0][0] = 0; 248 lut.points[0][1] = 0; 249 250 lut.points[1][0] = 1; 251 lut.points[1][1] = 1; 252 253 lut.points[2][0] = 2; 254 lut.points[2][1] = 1; 255 256 lut.points[3][0] = 100; 257 lut.points[3][1] = 1001; 258 259 // Test lower boundary 260 EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, -1)); 261 EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, 0)); 262 263 // Test first part that should be identity 264 EXPECT_NEAR(0.25, aom_noise_strength_lut_eval(&lut, 0.25), kEps); 265 EXPECT_NEAR(0.75, aom_noise_strength_lut_eval(&lut, 0.75), kEps); 266 267 // This is a constant section (should evaluate to 1) 268 EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.25), kEps); 269 EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.75), kEps); 270 271 // Test interpolation between to non-zero y coords. 272 EXPECT_NEAR(1, aom_noise_strength_lut_eval(&lut, 2), kEps); 273 EXPECT_NEAR(251, aom_noise_strength_lut_eval(&lut, 26.5), kEps); 274 EXPECT_NEAR(751, aom_noise_strength_lut_eval(&lut, 75.5), kEps); 275 276 // Test upper boundary 277 EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 100)); 278 EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 101)); 279 280 aom_noise_strength_lut_free(&lut); 281 } 282 283 TEST(NoiseModel, InitSuccessWithValidSquareShape) { 284 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 }; 285 aom_noise_model_t model; 286 287 EXPECT_TRUE(aom_noise_model_init(&model, params)); 288 289 const int kNumCoords = 12; 290 const int kCoords[][2] = { { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, 291 { 2, -2 }, { -2, -1 }, { -1, -1 }, { 0, -1 }, 292 { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 } }; 293 EXPECT_EQ(kNumCoords, model.n); 294 for (int i = 0; i < kNumCoords; ++i) { 295 const int *coord = kCoords[i]; 296 EXPECT_EQ(coord[0], model.coords[i][0]); 297 EXPECT_EQ(coord[1], model.coords[i][1]); 298 } 299 aom_noise_model_free(&model); 300 } 301 302 TEST(NoiseModel, InitSuccessWithValidDiamondShape) { 303 aom_noise_model_t model; 304 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_DIAMOND, 2, 8, 0 }; 305 EXPECT_TRUE(aom_noise_model_init(&model, params)); 306 EXPECT_EQ(6, model.n); 307 const int kNumCoords = 6; 308 const int kCoords[][2] = { { 0, -2 }, { -1, -1 }, { 0, -1 }, 309 { 1, -1 }, { -2, 0 }, { -1, 0 } }; 310 EXPECT_EQ(kNumCoords, model.n); 311 for (int i = 0; i < kNumCoords; ++i) { 312 const int *coord = kCoords[i]; 313 EXPECT_EQ(coord[0], model.coords[i][0]); 314 EXPECT_EQ(coord[1], model.coords[i][1]); 315 } 316 aom_noise_model_free(&model); 317 } 318 319 TEST(NoiseModel, InitFailsWithTooLargeLag) { 320 aom_noise_model_t model; 321 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 10, 8, 0 }; 322 EXPECT_FALSE(aom_noise_model_init(&model, params)); 323 aom_noise_model_free(&model); 324 } 325 326 TEST(NoiseModel, InitFailsWithTooSmallLag) { 327 aom_noise_model_t model; 328 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 0, 8, 0 }; 329 EXPECT_FALSE(aom_noise_model_init(&model, params)); 330 aom_noise_model_free(&model); 331 } 332 333 TEST(NoiseModel, InitFailsWithInvalidShape) { 334 aom_noise_model_t model; 335 aom_noise_model_params_t params = { aom_noise_shape(100), 3, 8, 0 }; 336 EXPECT_FALSE(aom_noise_model_init(&model, params)); 337 aom_noise_model_free(&model); 338 } 339 340 TEST(NoiseModel, InitFailsWithInvalidBitdepth) { 341 aom_noise_model_t model; 342 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2, 8, 0 }; 343 for (int i = 0; i <= 32; ++i) { 344 params.bit_depth = i; 345 if (i == 8 || i == 10 || i == 12) { 346 EXPECT_TRUE(aom_noise_model_init(&model, params)) << "bit_depth: " << i; 347 aom_noise_model_free(&model); 348 } else { 349 EXPECT_FALSE(aom_noise_model_init(&model, params)) << "bit_depth: " << i; 350 } 351 } 352 params.bit_depth = INT_MAX; 353 EXPECT_FALSE(aom_noise_model_init(&model, params)); 354 } 355 356 // A container template class to hold a data type and extra arguments. 357 // All of these args are bundled into one struct so that we can use 358 // parameterized tests on combinations of supported data types 359 // (uint8_t and uint16_t) and bit depths (8, 10, 12). 360 template <typename T, int bit_depth, bool use_highbd> 361 struct BitDepthParams { 362 using data_type_t = T; 363 static const int kBitDepth = bit_depth; 364 static const bool kUseHighBD = use_highbd; 365 }; 366 367 template <typename T> 368 class FlatBlockEstimatorTest : public ::testing::Test, public T { 369 public: 370 void SetUp() override { random_.Reset(171); } 371 using VecType = std::vector<typename T::data_type_t>; 372 VecType data_; 373 libaom_test::ACMRandom random_; 374 }; 375 376 TYPED_TEST_SUITE_P(FlatBlockEstimatorTest); 377 378 TYPED_TEST_P(FlatBlockEstimatorTest, ExtractBlock) { 379 const int kBlockSize = 16; 380 aom_flat_block_finder_t flat_block_finder; 381 ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize, 382 this->kBitDepth, this->kUseHighBD)); 383 const double normalization = flat_block_finder.normalization; 384 385 // Test with an image of more than one block. 386 const int h = 2 * kBlockSize; 387 const int w = 2 * kBlockSize; 388 const int stride = 2 * kBlockSize; 389 this->data_.resize(h * stride, 128); 390 391 // Set up the (0,0) block to be a plane and the (0,1) block to be a 392 // checkerboard 393 const int shift = this->kBitDepth - 8; 394 for (int y = 0; y < kBlockSize; ++y) { 395 for (int x = 0; x < kBlockSize; ++x) { 396 this->data_[y * stride + x] = (-y + x + 128) << shift; 397 this->data_[y * stride + x + kBlockSize] = 398 ((x % 2 + y % 2) % 2 ? 128 - 20 : 128 + 20) << shift; 399 } 400 } 401 std::vector<double> block(kBlockSize * kBlockSize, 1); 402 std::vector<double> plane(kBlockSize * kBlockSize, 1); 403 404 // The block data should be a constant (zero) and the rest of the plane 405 // trend is covered in the plane data. 406 aom_flat_block_finder_extract_block(&flat_block_finder, 407 (uint8_t *)&this->data_[0], w, h, stride, 408 0, 0, &plane[0], &block[0]); 409 for (int y = 0; y < kBlockSize; ++y) { 410 for (int x = 0; x < kBlockSize; ++x) { 411 EXPECT_NEAR(0, block[y * kBlockSize + x], 1e-5); 412 EXPECT_NEAR((double)(this->data_[y * stride + x]) / normalization, 413 plane[y * kBlockSize + x], 1e-5); 414 } 415 } 416 417 // The plane trend is a constant, and the block is a zero mean checkerboard. 418 aom_flat_block_finder_extract_block(&flat_block_finder, 419 (uint8_t *)&this->data_[0], w, h, stride, 420 kBlockSize, 0, &plane[0], &block[0]); 421 const int mid = 128 << shift; 422 for (int y = 0; y < kBlockSize; ++y) { 423 for (int x = 0; x < kBlockSize; ++x) { 424 EXPECT_NEAR(((double)this->data_[y * stride + x + kBlockSize] - mid) / 425 normalization, 426 block[y * kBlockSize + x], 1e-5); 427 EXPECT_NEAR(mid / normalization, plane[y * kBlockSize + x], 1e-5); 428 } 429 } 430 aom_flat_block_finder_free(&flat_block_finder); 431 } 432 433 TYPED_TEST_P(FlatBlockEstimatorTest, FindFlatBlocks) { 434 const int kBlockSize = 32; 435 aom_flat_block_finder_t flat_block_finder; 436 ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize, 437 this->kBitDepth, this->kUseHighBD)); 438 439 const int num_blocks_w = 8; 440 const int h = kBlockSize; 441 const int w = kBlockSize * num_blocks_w; 442 const int stride = w; 443 this->data_.resize(h * stride, 128); 444 std::vector<uint8_t> flat_blocks(num_blocks_w, 0); 445 446 const int shift = this->kBitDepth - 8; 447 for (int y = 0; y < kBlockSize; ++y) { 448 for (int x = 0; x < kBlockSize; ++x) { 449 // Block 0 (not flat): constant doesn't have enough variance to qualify 450 this->data_[y * stride + x + 0 * kBlockSize] = 128 << shift; 451 452 // Block 1 (not flat): too high of variance is hard to validate as flat 453 this->data_[y * stride + x + 1 * kBlockSize] = 454 ((uint8_t)(128 + randn(&this->random_, 5))) << shift; 455 456 // Block 2 (flat): slight checkerboard added to constant 457 const int check = (x % 2 + y % 2) % 2 ? -2 : 2; 458 this->data_[y * stride + x + 2 * kBlockSize] = (128 + check) << shift; 459 460 // Block 3 (flat): planar block with checkerboard pattern is also flat 461 this->data_[y * stride + x + 3 * kBlockSize] = 462 (y * 2 - x / 2 + 128 + check) << shift; 463 464 // Block 4 (flat): gaussian random with standard deviation 1. 465 this->data_[y * stride + x + 4 * kBlockSize] = 466 ((uint8_t)(randn(&this->random_, 1) + x + 128.0)) << shift; 467 468 // Block 5 (flat): gaussian random with standard deviation 2. 469 this->data_[y * stride + x + 5 * kBlockSize] = 470 ((uint8_t)(randn(&this->random_, 2) + y + 128.0)) << shift; 471 472 // Block 6 (not flat): too high of directional gradient. 473 const int strong_edge = x > kBlockSize / 2 ? 64 : 0; 474 this->data_[y * stride + x + 6 * kBlockSize] = 475 ((uint8_t)(randn(&this->random_, 1) + strong_edge + 128.0)) << shift; 476 477 // Block 7 (not flat): too high gradient. 478 const int big_check = ((x >> 2) % 2 + (y >> 2) % 2) % 2 ? -16 : 16; 479 this->data_[y * stride + x + 7 * kBlockSize] = 480 ((uint8_t)(randn(&this->random_, 1) + big_check + 128.0)) << shift; 481 } 482 } 483 484 EXPECT_EQ(4, aom_flat_block_finder_run(&flat_block_finder, 485 (uint8_t *)&this->data_[0], w, h, 486 stride, &flat_blocks[0])); 487 488 // First two blocks are not flat 489 EXPECT_EQ(0, flat_blocks[0]); 490 EXPECT_EQ(0, flat_blocks[1]); 491 492 // Next 4 blocks are flat. 493 EXPECT_EQ(255, flat_blocks[2]); 494 EXPECT_EQ(255, flat_blocks[3]); 495 EXPECT_EQ(255, flat_blocks[4]); 496 EXPECT_EQ(255, flat_blocks[5]); 497 498 // Last 2 are not flat by threshold 499 EXPECT_EQ(0, flat_blocks[6]); 500 EXPECT_EQ(0, flat_blocks[7]); 501 502 // Add the noise from non-flat block 1 to every block. 503 for (int y = 0; y < kBlockSize; ++y) { 504 for (int x = 0; x < kBlockSize * num_blocks_w; ++x) { 505 this->data_[y * stride + x] += 506 (this->data_[y * stride + x % kBlockSize + kBlockSize] - 507 (128 << shift)); 508 } 509 } 510 // Now the scored selection will pick the one that is most likely flat (block 511 // 0) 512 EXPECT_EQ(1, aom_flat_block_finder_run(&flat_block_finder, 513 (uint8_t *)&this->data_[0], w, h, 514 stride, &flat_blocks[0])); 515 EXPECT_EQ(1, flat_blocks[0]); 516 EXPECT_EQ(0, flat_blocks[1]); 517 EXPECT_EQ(0, flat_blocks[2]); 518 EXPECT_EQ(0, flat_blocks[3]); 519 EXPECT_EQ(0, flat_blocks[4]); 520 EXPECT_EQ(0, flat_blocks[5]); 521 EXPECT_EQ(0, flat_blocks[6]); 522 EXPECT_EQ(0, flat_blocks[7]); 523 524 aom_flat_block_finder_free(&flat_block_finder); 525 } 526 527 REGISTER_TYPED_TEST_SUITE_P(FlatBlockEstimatorTest, ExtractBlock, 528 FindFlatBlocks); 529 530 using AllBitDepthParams = ::testing::Types< 531 BitDepthParams<uint8_t, 8, false>, BitDepthParams<uint16_t, 8, true>, 532 BitDepthParams<uint16_t, 10, true>, BitDepthParams<uint16_t, 12, true>>; 533 // Note the empty final argument can be removed if C++20 is made the minimum 534 // requirement. 535 INSTANTIATE_TYPED_TEST_SUITE_P(FlatBlockInstatiation, FlatBlockEstimatorTest, 536 AllBitDepthParams, ); 537 538 template <typename T> 539 class NoiseModelUpdateTest : public ::testing::Test, public T { 540 public: 541 static const int kWidth = 128; 542 static const int kHeight = 128; 543 static const int kBlockSize = 16; 544 static const int kNumBlocksX = kWidth / kBlockSize; 545 static const int kNumBlocksY = kHeight / kBlockSize; 546 547 void SetUp() override { 548 const aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3, 549 T::kBitDepth, T::kUseHighBD }; 550 ASSERT_TRUE(aom_noise_model_init(&model_, params)); 551 552 random_.Reset(100171); 553 554 data_.resize(kWidth * kHeight * 3); 555 denoised_.resize(kWidth * kHeight * 3); 556 noise_.resize(kWidth * kHeight * 3); 557 renoise_.resize(kWidth * kHeight); 558 flat_blocks_.resize(kNumBlocksX * kNumBlocksY); 559 560 for (int c = 0, offset = 0; c < 3; ++c, offset += kWidth * kHeight) { 561 data_ptr_[c] = &data_[offset]; 562 noise_ptr_[c] = &noise_[offset]; 563 denoised_ptr_[c] = &denoised_[offset]; 564 strides_[c] = kWidth; 565 566 data_ptr_raw_[c] = (uint8_t *)&data_[offset]; 567 denoised_ptr_raw_[c] = (uint8_t *)&denoised_[offset]; 568 } 569 chroma_sub_[0] = 0; 570 chroma_sub_[1] = 0; 571 } 572 573 int NoiseModelUpdate(int block_size = kBlockSize) { 574 return aom_noise_model_update(&model_, data_ptr_raw_, denoised_ptr_raw_, 575 kWidth, kHeight, strides_, chroma_sub_, 576 &flat_blocks_[0], block_size); 577 } 578 579 void TearDown() override { aom_noise_model_free(&model_); } 580 581 protected: 582 aom_noise_model_t model_; 583 std::vector<typename T::data_type_t> data_; 584 std::vector<typename T::data_type_t> denoised_; 585 586 std::vector<double> noise_; 587 std::vector<double> renoise_; 588 std::vector<uint8_t> flat_blocks_; 589 590 typename T::data_type_t *data_ptr_[3]; 591 typename T::data_type_t *denoised_ptr_[3]; 592 593 double *noise_ptr_[3]; 594 int strides_[3]; 595 int chroma_sub_[2]; 596 libaom_test::ACMRandom random_; 597 598 private: 599 uint8_t *data_ptr_raw_[3]; 600 uint8_t *denoised_ptr_raw_[3]; 601 }; 602 603 TYPED_TEST_SUITE_P(NoiseModelUpdateTest); 604 605 TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks) { 606 EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS, 607 this->NoiseModelUpdate()); 608 } 609 610 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForZeroNoiseAllFlat) { 611 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 612 this->denoised_.assign(this->denoised_.size(), 128); 613 this->data_.assign(this->denoised_.size(), 128); 614 EXPECT_EQ(AOM_NOISE_STATUS_INTERNAL_ERROR, this->NoiseModelUpdate()); 615 } 616 617 TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsBlockSizeTooSmall) { 618 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 619 this->denoised_.assign(this->denoised_.size(), 128); 620 this->data_.assign(this->denoised_.size(), 128); 621 EXPECT_EQ(AOM_NOISE_STATUS_INVALID_ARGUMENT, 622 this->NoiseModelUpdate(6 /* block_size=6 is too small*/)); 623 } 624 625 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForWhiteRandomNoise) { 626 aom_noise_model_t &model = this->model_; 627 const int width = this->kWidth; 628 const int height = this->kHeight; 629 630 const int shift = this->kBitDepth - 8; 631 for (int y = 0; y < height; ++y) { 632 for (int x = 0; x < width; ++x) { 633 this->data_ptr_[0][y * width + x] = int(64 + y + randn(&this->random_, 1)) 634 << shift; 635 this->denoised_ptr_[0][y * width + x] = (64 + y) << shift; 636 // Make the chroma planes completely correlated with the Y plane 637 for (int c = 1; c < 3; ++c) { 638 this->data_ptr_[c][y * width + x] = this->data_ptr_[0][y * width + x]; 639 this->denoised_ptr_[c][y * width + x] = 640 this->denoised_ptr_[0][y * width + x]; 641 } 642 } 643 } 644 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 645 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); 646 647 const double kCoeffEps = 0.075; 648 const int n = model.n; 649 for (int c = 0; c < 3; ++c) { 650 for (int i = 0; i < n; ++i) { 651 EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps); 652 EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps); 653 } 654 // The second and third channels are highly correlated with the first. 655 if (c > 0) { 656 ASSERT_EQ(n + 1, model.latest_state[c].eqns.n); 657 ASSERT_EQ(n + 1, model.combined_state[c].eqns.n); 658 659 EXPECT_NEAR(1, model.latest_state[c].eqns.x[n], kCoeffEps); 660 EXPECT_NEAR(1, model.combined_state[c].eqns.x[n], kCoeffEps); 661 } 662 } 663 664 // The fitted noise strength should be close to the standard deviation 665 // for all intensity bins. 666 const double kStdEps = 0.1; 667 const double normalize = 1 << shift; 668 669 for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) { 670 EXPECT_NEAR(1.0, 671 model.latest_state[0].strength_solver.eqns.x[i] / normalize, 672 kStdEps); 673 EXPECT_NEAR(1.0, 674 model.combined_state[0].strength_solver.eqns.x[i] / normalize, 675 kStdEps); 676 } 677 678 aom_noise_strength_lut_t lut; 679 aom_noise_strength_solver_fit_piecewise( 680 &model.latest_state[0].strength_solver, -1, &lut); 681 ASSERT_EQ(2, lut.num_points); 682 EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); 683 EXPECT_NEAR(1.0, lut.points[0][1] / normalize, kStdEps); 684 EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5); 685 EXPECT_NEAR(1.0, lut.points[1][1] / normalize, kStdEps); 686 aom_noise_strength_lut_free(&lut); 687 } 688 689 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForScaledWhiteNoise) { 690 aom_noise_model_t &model = this->model_; 691 const int width = this->kWidth; 692 const int height = this->kHeight; 693 694 const double kCoeffEps = 0.055; 695 const double kLowStd = 1; 696 const double kHighStd = 4; 697 const int shift = this->kBitDepth - 8; 698 for (int y = 0; y < height; ++y) { 699 for (int x = 0; x < width; ++x) { 700 for (int c = 0; c < 3; ++c) { 701 // The image data is bimodal: 702 // Bottom half has low intensity and low noise strength 703 // Top half has high intensity and high noise strength 704 const int avg = (y < height / 2) ? 4 : 245; 705 const double std = (y < height / 2) ? kLowStd : kHighStd; 706 this->data_ptr_[c][y * width + x] = 707 ((uint8_t)std::min((int)255, 708 (int)(2 + avg + randn(&this->random_, std)))) 709 << shift; 710 this->denoised_ptr_[c][y * width + x] = (2 + avg) << shift; 711 } 712 } 713 } 714 // Label all blocks as flat for the update 715 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 716 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); 717 718 const int n = model.n; 719 // The noise is uncorrelated spatially and with the y channel. 720 // All coefficients should be reasonably close to zero. 721 for (int c = 0; c < 3; ++c) { 722 for (int i = 0; i < n; ++i) { 723 EXPECT_NEAR(0, model.latest_state[c].eqns.x[i], kCoeffEps); 724 EXPECT_NEAR(0, model.combined_state[c].eqns.x[i], kCoeffEps); 725 } 726 if (c > 0) { 727 ASSERT_EQ(n + 1, model.latest_state[c].eqns.n); 728 ASSERT_EQ(n + 1, model.combined_state[c].eqns.n); 729 730 // The correlation to the y channel should be low (near zero) 731 EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps); 732 EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps); 733 } 734 } 735 736 // Noise strength should vary between kLowStd and kHighStd. 737 const double kStdEps = 0.15; 738 // We have to normalize fitted standard deviation based on bit depth. 739 const double normalize = (1 << shift); 740 741 ASSERT_EQ(20, model.latest_state[0].strength_solver.eqns.n); 742 for (int i = 0; i < model.latest_state[0].strength_solver.eqns.n; ++i) { 743 const double a = i / 19.0; 744 const double expected = (kLowStd * (1.0 - a) + kHighStd * a); 745 EXPECT_NEAR(expected, 746 model.latest_state[0].strength_solver.eqns.x[i] / normalize, 747 kStdEps); 748 EXPECT_NEAR(expected, 749 model.combined_state[0].strength_solver.eqns.x[i] / normalize, 750 kStdEps); 751 } 752 753 // If we fit a piecewise linear model, there should be two points: 754 // one near kLowStd at 0, and the other near kHighStd and 255. 755 aom_noise_strength_lut_t lut; 756 aom_noise_strength_solver_fit_piecewise( 757 &model.latest_state[0].strength_solver, 2, &lut); 758 ASSERT_EQ(2, lut.num_points); 759 EXPECT_NEAR(0, lut.points[0][0], 1e-4); 760 EXPECT_NEAR(kLowStd, lut.points[0][1] / normalize, kStdEps); 761 EXPECT_NEAR((1 << this->kBitDepth) - 1, lut.points[1][0], 1e-5); 762 EXPECT_NEAR(kHighStd, lut.points[1][1] / normalize, kStdEps); 763 aom_noise_strength_lut_free(&lut); 764 } 765 766 TYPED_TEST_P(NoiseModelUpdateTest, UpdateSuccessForCorrelatedNoise) { 767 aom_noise_model_t &model = this->model_; 768 const int width = this->kWidth; 769 const int height = this->kHeight; 770 const int kNumCoeffs = 24; 771 const double kStd = 4; 772 const double kStdEps = 0.3; 773 const double kCoeffEps = 0.065; 774 // Use different coefficients for each channel 775 const double kCoeffs[3][24] = { 776 { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620, 777 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571, 778 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968, 779 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 }, 780 { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477, 781 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336, 782 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903, 783 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 }, 784 { -0.00643, -0.01080, -0.01466, 0.06951, 0.03707, -0.00482, 785 0.00817, -0.00909, 0.02949, 0.12181, -0.25210, -0.07886, 786 0.06083, -0.01210, -0.03108, 0.08944, -0.35875, 0.49150, 787 0.00415, -0.12905, 0.02870, 0.09740, -0.34610, 0.58824 }, 788 }; 789 790 ASSERT_EQ(model.n, kNumCoeffs); 791 this->chroma_sub_[0] = this->chroma_sub_[1] = 1; 792 793 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 794 795 // Add different noise onto each plane 796 const int shift = this->kBitDepth - 8; 797 for (int c = 0; c < 3; ++c) { 798 noise_synth(&this->random_, model.params.lag, model.n, model.coords, 799 kCoeffs[c], this->noise_ptr_[c], width, height); 800 const int x_shift = c > 0 ? this->chroma_sub_[0] : 0; 801 const int y_shift = c > 0 ? this->chroma_sub_[1] : 0; 802 for (int y = 0; y < (height >> y_shift); ++y) { 803 for (int x = 0; x < (width >> x_shift); ++x) { 804 const uint8_t value = 64 + x / 2 + y / 4; 805 this->data_ptr_[c][y * width + x] = 806 (uint8_t(value + this->noise_ptr_[c][y * width + x] * kStd)) 807 << shift; 808 this->denoised_ptr_[c][y * width + x] = value << shift; 809 } 810 } 811 } 812 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); 813 814 // For the Y plane, the solved coefficients should be close to the original 815 const int n = model.n; 816 for (int c = 0; c < 3; ++c) { 817 for (int i = 0; i < n; ++i) { 818 EXPECT_NEAR(kCoeffs[c][i], model.latest_state[c].eqns.x[i], kCoeffEps); 819 EXPECT_NEAR(kCoeffs[c][i], model.combined_state[c].eqns.x[i], kCoeffEps); 820 } 821 // The chroma planes should be uncorrelated with the luma plane 822 if (c > 0) { 823 EXPECT_NEAR(0, model.latest_state[c].eqns.x[n], kCoeffEps); 824 EXPECT_NEAR(0, model.combined_state[c].eqns.x[n], kCoeffEps); 825 } 826 // Correlation between the coefficient vector and the fitted coefficients 827 // should be close to 1. 828 EXPECT_LT(0.98, aom_normalized_cross_correlation( 829 model.latest_state[c].eqns.x, kCoeffs[c], kNumCoeffs)); 830 831 noise_synth(&this->random_, model.params.lag, model.n, model.coords, 832 model.latest_state[c].eqns.x, &this->renoise_[0], width, 833 height); 834 835 EXPECT_TRUE(aom_noise_data_validate(&this->renoise_[0], width, height)); 836 } 837 838 // Check fitted noise strength 839 const double normalize = 1 << shift; 840 for (int c = 0; c < 3; ++c) { 841 for (int i = 0; i < model.latest_state[c].strength_solver.eqns.n; ++i) { 842 EXPECT_NEAR(kStd, 843 model.latest_state[c].strength_solver.eqns.x[i] / normalize, 844 kStdEps); 845 } 846 } 847 } 848 849 TYPED_TEST_P(NoiseModelUpdateTest, 850 NoiseStrengthChangeSignalsDifferentNoiseType) { 851 aom_noise_model_t &model = this->model_; 852 const int width = this->kWidth; 853 const int height = this->kHeight; 854 const int block_size = this->kBlockSize; 855 // Create a gradient image with std = 2 uncorrelated noise 856 const double kStd = 2; 857 const int shift = this->kBitDepth - 8; 858 859 for (int i = 0; i < width * height; ++i) { 860 const uint8_t val = (i % width) < width / 2 ? 64 : 192; 861 for (int c = 0; c < 3; ++c) { 862 this->noise_ptr_[c][i] = randn(&this->random_, 1); 863 this->data_ptr_[c][i] = ((uint8_t)(this->noise_ptr_[c][i] * kStd + val)) 864 << shift; 865 this->denoised_ptr_[c][i] = val << shift; 866 } 867 } 868 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 869 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); 870 871 const int kNumBlocks = width * height / block_size / block_size; 872 EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations); 873 EXPECT_EQ(kNumBlocks, model.latest_state[1].strength_solver.num_equations); 874 EXPECT_EQ(kNumBlocks, model.latest_state[2].strength_solver.num_equations); 875 EXPECT_EQ(kNumBlocks, model.combined_state[0].strength_solver.num_equations); 876 EXPECT_EQ(kNumBlocks, model.combined_state[1].strength_solver.num_equations); 877 EXPECT_EQ(kNumBlocks, model.combined_state[2].strength_solver.num_equations); 878 879 // Bump up noise by an insignificant amount 880 for (int i = 0; i < width * height; ++i) { 881 const uint8_t val = (i % width) < width / 2 ? 64 : 192; 882 this->data_ptr_[0][i] = 883 ((uint8_t)(this->noise_ptr_[0][i] * (kStd + 0.085) + val)) << shift; 884 } 885 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); 886 887 const double kARGainTolerance = 0.02; 888 for (int c = 0; c < 3; ++c) { 889 EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations); 890 EXPECT_EQ(15250, model.latest_state[c].num_observations); 891 EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance); 892 893 EXPECT_EQ(2 * kNumBlocks, 894 model.combined_state[c].strength_solver.num_equations); 895 EXPECT_EQ(2 * 15250, model.combined_state[c].num_observations); 896 EXPECT_NEAR(1, model.combined_state[c].ar_gain, kARGainTolerance); 897 } 898 899 // Bump up the noise strength on half the image for one channel by a 900 // significant amount. 901 for (int i = 0; i < width * height; ++i) { 902 const uint8_t val = (i % width) < width / 2 ? 64 : 128; 903 if (i % width < width / 2) { 904 this->data_ptr_[0][i] = 905 ((uint8_t)(randn(&this->random_, kStd + 0.5) + val)) << shift; 906 } 907 } 908 EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate()); 909 910 // Since we didn't update the combined state, it should still be at 2 * 911 // num_blocks 912 EXPECT_EQ(kNumBlocks, model.latest_state[0].strength_solver.num_equations); 913 EXPECT_EQ(2 * kNumBlocks, 914 model.combined_state[0].strength_solver.num_equations); 915 916 // In normal operation, the "latest" estimate can be saved to the "combined" 917 // state for continued updates. 918 aom_noise_model_save_latest(&model); 919 for (int c = 0; c < 3; ++c) { 920 EXPECT_EQ(kNumBlocks, model.latest_state[c].strength_solver.num_equations); 921 EXPECT_EQ(15250, model.latest_state[c].num_observations); 922 EXPECT_NEAR(1, model.latest_state[c].ar_gain, kARGainTolerance); 923 924 EXPECT_EQ(kNumBlocks, 925 model.combined_state[c].strength_solver.num_equations); 926 EXPECT_EQ(15250, model.combined_state[c].num_observations); 927 EXPECT_NEAR(1, model.combined_state[c].ar_gain, kARGainTolerance); 928 } 929 } 930 931 TYPED_TEST_P(NoiseModelUpdateTest, NoiseCoeffsSignalsDifferentNoiseType) { 932 aom_noise_model_t &model = this->model_; 933 const int width = this->kWidth; 934 const int height = this->kHeight; 935 const double kCoeffs[2][24] = { 936 { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620, 937 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571, 938 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968, 939 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 }, 940 { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477, 941 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336, 942 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903, 943 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 } 944 }; 945 946 noise_synth(&this->random_, model.params.lag, model.n, model.coords, 947 kCoeffs[0], this->noise_ptr_[0], width, height); 948 for (int i = 0; i < width * height; ++i) { 949 this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]); 950 } 951 this->flat_blocks_.assign(this->flat_blocks_.size(), 1); 952 EXPECT_EQ(AOM_NOISE_STATUS_OK, this->NoiseModelUpdate()); 953 954 // Now try with the second set of AR coefficients 955 noise_synth(&this->random_, model.params.lag, model.n, model.coords, 956 kCoeffs[1], this->noise_ptr_[0], width, height); 957 for (int i = 0; i < width * height; ++i) { 958 this->data_ptr_[0][i] = (uint8_t)(128 + this->noise_ptr_[0][i]); 959 } 960 EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE, this->NoiseModelUpdate()); 961 } 962 REGISTER_TYPED_TEST_SUITE_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks, 963 UpdateSuccessForZeroNoiseAllFlat, 964 UpdateFailsBlockSizeTooSmall, 965 UpdateSuccessForWhiteRandomNoise, 966 UpdateSuccessForScaledWhiteNoise, 967 UpdateSuccessForCorrelatedNoise, 968 NoiseStrengthChangeSignalsDifferentNoiseType, 969 NoiseCoeffsSignalsDifferentNoiseType); 970 971 // Note the empty final argument can be removed if C++20 is made the minimum 972 // requirement. 973 INSTANTIATE_TYPED_TEST_SUITE_P(NoiseModelUpdateTestInstatiation, 974 NoiseModelUpdateTest, AllBitDepthParams, ); 975 976 TEST(NoiseModelGetGrainParameters, TestLagSize) { 977 aom_film_grain_t film_grain; 978 for (int lag = 1; lag <= 3; ++lag) { 979 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; 980 aom_noise_model_t model; 981 EXPECT_TRUE(aom_noise_model_init(&model, params)); 982 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); 983 EXPECT_EQ(lag, film_grain.ar_coeff_lag); 984 aom_noise_model_free(&model); 985 } 986 987 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 4, 8, 0 }; 988 aom_noise_model_t model; 989 EXPECT_TRUE(aom_noise_model_init(&model, params)); 990 EXPECT_FALSE(aom_noise_model_get_grain_parameters(&model, &film_grain)); 991 aom_noise_model_free(&model); 992 } 993 994 TEST(NoiseModelGetGrainParameters, TestARCoeffShiftBounds) { 995 struct TestCase { 996 double max_input_value; 997 int expected_ar_coeff_shift; 998 int expected_value; 999 }; 1000 const int lag = 1; 1001 const int kNumTestCases = 19; 1002 const TestCase test_cases[] = { 1003 // Test cases for ar_coeff_shift = 9 1004 { 0, 9, 0 }, 1005 { 0.125, 9, 64 }, 1006 { -0.125, 9, -64 }, 1007 { 0.2499, 9, 127 }, 1008 { -0.25, 9, -128 }, 1009 // Test cases for ar_coeff_shift = 8 1010 { 0.25, 8, 64 }, 1011 { -0.2501, 8, -64 }, 1012 { 0.499, 8, 127 }, 1013 { -0.5, 8, -128 }, 1014 // Test cases for ar_coeff_shift = 7 1015 { 0.5, 7, 64 }, 1016 { -0.5001, 7, -64 }, 1017 { 0.999, 7, 127 }, 1018 { -1, 7, -128 }, 1019 // Test cases for ar_coeff_shift = 6 1020 { 1.0, 6, 64 }, 1021 { -1.0001, 6, -64 }, 1022 { 2.0, 6, 127 }, 1023 { -2.0, 6, -128 }, 1024 { 4, 6, 127 }, 1025 { -4, 6, -128 }, 1026 }; 1027 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; 1028 aom_noise_model_t model; 1029 EXPECT_TRUE(aom_noise_model_init(&model, params)); 1030 1031 for (int i = 0; i < kNumTestCases; ++i) { 1032 const TestCase &test_case = test_cases[i]; 1033 model.combined_state[0].eqns.x[0] = test_case.max_input_value; 1034 1035 aom_film_grain_t film_grain; 1036 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); 1037 EXPECT_EQ(1, film_grain.ar_coeff_lag); 1038 EXPECT_EQ(test_case.expected_ar_coeff_shift, film_grain.ar_coeff_shift); 1039 EXPECT_EQ(test_case.expected_value, film_grain.ar_coeffs_y[0]); 1040 } 1041 aom_noise_model_free(&model); 1042 } 1043 1044 TEST(NoiseModelGetGrainParameters, TestNoiseStrengthShiftBounds) { 1045 struct TestCase { 1046 double max_input_value; 1047 int expected_scaling_shift; 1048 int expected_value; 1049 }; 1050 const int kNumTestCases = 10; 1051 const TestCase test_cases[] = { 1052 { 0, 11, 0 }, { 1, 11, 64 }, { 2, 11, 128 }, { 3.99, 11, 255 }, 1053 { 4, 10, 128 }, { 7.99, 10, 255 }, { 8, 9, 128 }, { 16, 8, 128 }, 1054 { 31.99, 8, 255 }, { 64, 8, 255 }, // clipped 1055 }; 1056 const int lag = 1; 1057 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; 1058 aom_noise_model_t model; 1059 EXPECT_TRUE(aom_noise_model_init(&model, params)); 1060 1061 for (int i = 0; i < kNumTestCases; ++i) { 1062 const TestCase &test_case = test_cases[i]; 1063 aom_equation_system_t &eqns = model.combined_state[0].strength_solver.eqns; 1064 // Set the fitted scale parameters to be a constant value. 1065 for (int j = 0; j < eqns.n; ++j) { 1066 eqns.x[j] = test_case.max_input_value; 1067 } 1068 aom_film_grain_t film_grain; 1069 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); 1070 // We expect a single constant segemnt 1071 EXPECT_EQ(test_case.expected_scaling_shift, film_grain.scaling_shift); 1072 EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[0][1]); 1073 EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[1][1]); 1074 } 1075 aom_noise_model_free(&model); 1076 } 1077 1078 // The AR coefficients are the same inputs used to generate "Test 2" in the test 1079 // vectors 1080 TEST(NoiseModelGetGrainParameters, GetGrainParametersReal) { 1081 const double kInputCoeffsY[] = { 0.0315, 0.0073, 0.0218, 0.00235, 0.00511, 1082 -0.0222, 0.0627, -0.022, 0.05575, -0.1816, 1083 0.0107, -0.1966, 0.00065, -0.0809, 0.04934, 1084 -0.1349, -0.0352, 0.41772, 0.27973, 0.04207, 1085 -0.0429, -0.1372, 0.06193, 0.52032 }; 1086 const double kInputCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1087 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5 }; 1088 const double kInputCoeffsCR[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1089 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.5 }; 1090 const int kExpectedARCoeffsY[] = { 4, 1, 3, 0, 1, -3, 8, -3, 1091 7, -23, 1, -25, 0, -10, 6, -17, 1092 -5, 53, 36, 5, -5, -18, 8, 67 }; 1093 const int kExpectedARCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1094 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 }; 1095 const int kExpectedARCoeffsCR[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1096 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -126 }; 1097 // Scaling function is initialized analytically with a sqrt function. 1098 const int kNumScalingPointsY = 12; 1099 const int kExpectedScalingPointsY[][2] = { 1100 { 0, 0 }, { 13, 44 }, { 27, 62 }, { 40, 76 }, 1101 { 54, 88 }, { 67, 98 }, { 94, 117 }, { 121, 132 }, 1102 { 148, 146 }, { 174, 159 }, { 201, 171 }, { 255, 192 }, 1103 }; 1104 1105 const int lag = 3; 1106 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 }; 1107 aom_noise_model_t model; 1108 EXPECT_TRUE(aom_noise_model_init(&model, params)); 1109 1110 // Setup the AR coeffs 1111 memcpy(model.combined_state[0].eqns.x, kInputCoeffsY, sizeof(kInputCoeffsY)); 1112 memcpy(model.combined_state[1].eqns.x, kInputCoeffsCB, 1113 sizeof(kInputCoeffsCB)); 1114 memcpy(model.combined_state[2].eqns.x, kInputCoeffsCR, 1115 sizeof(kInputCoeffsCR)); 1116 for (int i = 0; i < model.combined_state[0].strength_solver.num_bins; ++i) { 1117 const double x = 1118 ((double)i) / (model.combined_state[0].strength_solver.num_bins - 1.0); 1119 model.combined_state[0].strength_solver.eqns.x[i] = 6 * sqrt(x); 1120 model.combined_state[1].strength_solver.eqns.x[i] = 3; 1121 model.combined_state[2].strength_solver.eqns.x[i] = 2; 1122 1123 // Inject some observations into the strength solver, as during film grain 1124 // parameter extraction an estimate of the average strength will be used to 1125 // adjust correlation. 1126 const int n = model.combined_state[0].strength_solver.num_bins; 1127 for (int j = 0; j < model.combined_state[0].strength_solver.num_bins; ++j) { 1128 model.combined_state[0].strength_solver.eqns.A[i * n + j] = 1; 1129 model.combined_state[1].strength_solver.eqns.A[i * n + j] = 1; 1130 model.combined_state[2].strength_solver.eqns.A[i * n + j] = 1; 1131 } 1132 } 1133 1134 aom_film_grain_t film_grain; 1135 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain)); 1136 EXPECT_EQ(lag, film_grain.ar_coeff_lag); 1137 EXPECT_EQ(3, film_grain.ar_coeff_lag); 1138 EXPECT_EQ(7, film_grain.ar_coeff_shift); 1139 EXPECT_EQ(10, film_grain.scaling_shift); 1140 EXPECT_EQ(kNumScalingPointsY, film_grain.num_y_points); 1141 EXPECT_EQ(1, film_grain.update_parameters); 1142 EXPECT_EQ(1, film_grain.apply_grain); 1143 1144 const int kNumARCoeffs = 24; 1145 for (int i = 0; i < kNumARCoeffs; ++i) { 1146 EXPECT_EQ(kExpectedARCoeffsY[i], film_grain.ar_coeffs_y[i]); 1147 } 1148 for (int i = 0; i < kNumARCoeffs + 1; ++i) { 1149 EXPECT_EQ(kExpectedARCoeffsCB[i], film_grain.ar_coeffs_cb[i]); 1150 } 1151 for (int i = 0; i < kNumARCoeffs + 1; ++i) { 1152 EXPECT_EQ(kExpectedARCoeffsCR[i], film_grain.ar_coeffs_cr[i]); 1153 } 1154 for (int i = 0; i < kNumScalingPointsY; ++i) { 1155 EXPECT_EQ(kExpectedScalingPointsY[i][0], film_grain.scaling_points_y[i][0]); 1156 EXPECT_EQ(kExpectedScalingPointsY[i][1], film_grain.scaling_points_y[i][1]); 1157 } 1158 1159 // CB strength should just be a piecewise segment 1160 EXPECT_EQ(2, film_grain.num_cb_points); 1161 EXPECT_EQ(0, film_grain.scaling_points_cb[0][0]); 1162 EXPECT_EQ(255, film_grain.scaling_points_cb[1][0]); 1163 EXPECT_EQ(96, film_grain.scaling_points_cb[0][1]); 1164 EXPECT_EQ(96, film_grain.scaling_points_cb[1][1]); 1165 1166 // CR strength should just be a piecewise segment 1167 EXPECT_EQ(2, film_grain.num_cr_points); 1168 EXPECT_EQ(0, film_grain.scaling_points_cr[0][0]); 1169 EXPECT_EQ(255, film_grain.scaling_points_cr[1][0]); 1170 EXPECT_EQ(64, film_grain.scaling_points_cr[0][1]); 1171 EXPECT_EQ(64, film_grain.scaling_points_cr[1][1]); 1172 1173 EXPECT_EQ(128, film_grain.cb_mult); 1174 EXPECT_EQ(192, film_grain.cb_luma_mult); 1175 EXPECT_EQ(256, film_grain.cb_offset); 1176 EXPECT_EQ(128, film_grain.cr_mult); 1177 EXPECT_EQ(192, film_grain.cr_luma_mult); 1178 EXPECT_EQ(256, film_grain.cr_offset); 1179 EXPECT_EQ(0, film_grain.chroma_scaling_from_luma); 1180 EXPECT_EQ(0, film_grain.grain_scale_shift); 1181 1182 aom_noise_model_free(&model); 1183 } 1184 1185 template <typename T> 1186 class WienerDenoiseTest : public ::testing::Test, public T { 1187 public: 1188 static void SetUpTestSuite() { aom_dsp_rtcd(); } 1189 1190 protected: 1191 void SetUp() override { 1192 static const float kNoiseLevel = 5.f; 1193 static const float kStd = 4.0; 1194 static const double kMaxValue = (1 << T::kBitDepth) - 1; 1195 1196 chroma_sub_[0] = 1; 1197 chroma_sub_[1] = 1; 1198 stride_[0] = kWidth; 1199 stride_[1] = kWidth / 2; 1200 stride_[2] = kWidth / 2; 1201 for (int k = 0; k < 3; ++k) { 1202 data_[k].resize(kWidth * kHeight); 1203 denoised_[k].resize(kWidth * kHeight); 1204 noise_psd_[k].resize(kBlockSize * kBlockSize); 1205 } 1206 1207 const double kCoeffsY[] = { 0.0406, -0.116, -0.078, -0.152, 0.0033, -0.093, 1208 0.048, 0.404, 0.2353, -0.035, -0.093, 0.441 }; 1209 const int kCoords[12][2] = { 1210 { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 }, 1211 { -1, -1 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 } 1212 }; 1213 const int kLag = 2; 1214 const int kLength = 12; 1215 libaom_test::ACMRandom random; 1216 std::vector<double> noise(kWidth * kHeight); 1217 noise_synth(&random, kLag, kLength, kCoords, kCoeffsY, &noise[0], kWidth, 1218 kHeight); 1219 noise_psd_[0] = get_noise_psd(&noise[0], kWidth, kHeight, kBlockSize); 1220 for (int i = 0; i < kBlockSize * kBlockSize; ++i) { 1221 noise_psd_[0][i] = (float)(noise_psd_[0][i] * kStd * kStd * kScaleNoise * 1222 kScaleNoise / (kMaxValue * kMaxValue)); 1223 } 1224 1225 float psd_value = 1226 aom_noise_psd_get_default_value(kBlockSizeChroma, kNoiseLevel); 1227 for (int i = 0; i < kBlockSizeChroma * kBlockSizeChroma; ++i) { 1228 noise_psd_[1][i] = psd_value; 1229 noise_psd_[2][i] = psd_value; 1230 } 1231 for (int y = 0; y < kHeight; ++y) { 1232 for (int x = 0; x < kWidth; ++x) { 1233 data_[0][y * stride_[0] + x] = (typename T::data_type_t)fclamp( 1234 (x + noise[y * stride_[0] + x] * kStd) * kScaleNoise, 0, kMaxValue); 1235 } 1236 } 1237 1238 for (int c = 1; c < 3; ++c) { 1239 for (int y = 0; y < (kHeight >> 1); ++y) { 1240 for (int x = 0; x < (kWidth >> 1); ++x) { 1241 data_[c][y * stride_[c] + x] = (typename T::data_type_t)fclamp( 1242 (x + randn(&random, kStd)) * kScaleNoise, 0, kMaxValue); 1243 } 1244 } 1245 } 1246 for (int k = 0; k < 3; ++k) { 1247 noise_psd_ptrs_[k] = &noise_psd_[k][0]; 1248 } 1249 } 1250 static const int kBlockSize = 32; 1251 static const int kBlockSizeChroma = 16; 1252 static const int kWidth = 256; 1253 static const int kHeight = 256; 1254 static const int kScaleNoise = 1 << (T::kBitDepth - 8); 1255 1256 std::vector<typename T::data_type_t> data_[3]; 1257 std::vector<typename T::data_type_t> denoised_[3]; 1258 std::vector<float> noise_psd_[3]; 1259 int chroma_sub_[2]; 1260 float *noise_psd_ptrs_[3]; 1261 int stride_[3]; 1262 }; 1263 1264 TYPED_TEST_SUITE_P(WienerDenoiseTest); 1265 1266 TYPED_TEST_P(WienerDenoiseTest, InvalidBlockSize) { 1267 const uint8_t *const data_ptrs[3] = { 1268 reinterpret_cast<uint8_t *>(&this->data_[0][0]), 1269 reinterpret_cast<uint8_t *>(&this->data_[1][0]), 1270 reinterpret_cast<uint8_t *>(&this->data_[2][0]), 1271 }; 1272 uint8_t *denoised_ptrs[3] = { 1273 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]), 1274 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]), 1275 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]), 1276 }; 1277 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, 1278 this->kHeight, this->stride_, 1279 this->chroma_sub_, this->noise_psd_ptrs_, 1280 18, this->kBitDepth, this->kUseHighBD)); 1281 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, 1282 this->kHeight, this->stride_, 1283 this->chroma_sub_, this->noise_psd_ptrs_, 1284 48, this->kBitDepth, this->kUseHighBD)); 1285 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, 1286 this->kHeight, this->stride_, 1287 this->chroma_sub_, this->noise_psd_ptrs_, 1288 64, this->kBitDepth, this->kUseHighBD)); 1289 } 1290 1291 TYPED_TEST_P(WienerDenoiseTest, InvalidChromaSubsampling) { 1292 const uint8_t *const data_ptrs[3] = { 1293 reinterpret_cast<uint8_t *>(&this->data_[0][0]), 1294 reinterpret_cast<uint8_t *>(&this->data_[1][0]), 1295 reinterpret_cast<uint8_t *>(&this->data_[2][0]), 1296 }; 1297 uint8_t *denoised_ptrs[3] = { 1298 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]), 1299 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]), 1300 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]), 1301 }; 1302 int chroma_sub[2] = { 1, 0 }; 1303 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, 1304 this->kHeight, this->stride_, chroma_sub, 1305 this->noise_psd_ptrs_, 32, this->kBitDepth, 1306 this->kUseHighBD)); 1307 1308 chroma_sub[0] = 0; 1309 chroma_sub[1] = 1; 1310 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth, 1311 this->kHeight, this->stride_, chroma_sub, 1312 this->noise_psd_ptrs_, 32, this->kBitDepth, 1313 this->kUseHighBD)); 1314 } 1315 1316 TYPED_TEST_P(WienerDenoiseTest, GradientTest) { 1317 const int width = this->kWidth; 1318 const int height = this->kHeight; 1319 const int block_size = this->kBlockSize; 1320 const uint8_t *const data_ptrs[3] = { 1321 reinterpret_cast<uint8_t *>(&this->data_[0][0]), 1322 reinterpret_cast<uint8_t *>(&this->data_[1][0]), 1323 reinterpret_cast<uint8_t *>(&this->data_[2][0]), 1324 }; 1325 uint8_t *denoised_ptrs[3] = { 1326 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]), 1327 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]), 1328 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]), 1329 }; 1330 const int ret = aom_wiener_denoise_2d( 1331 data_ptrs, denoised_ptrs, width, height, this->stride_, this->chroma_sub_, 1332 this->noise_psd_ptrs_, block_size, this->kBitDepth, this->kUseHighBD); 1333 EXPECT_EQ(1, ret); 1334 1335 // Check the noise on the denoised image (from the analytical gradient) 1336 // and make sure that it is less than what we added. 1337 for (int c = 0; c < 3; ++c) { 1338 std::vector<double> measured_noise(width * height); 1339 1340 double var = 0; 1341 const int shift = (c > 0); 1342 for (int x = 0; x < (width >> shift); ++x) { 1343 for (int y = 0; y < (height >> shift); ++y) { 1344 const double diff = this->denoised_[c][y * this->stride_[c] + x] - 1345 x * this->kScaleNoise; 1346 var += diff * diff; 1347 measured_noise[y * width + x] = diff; 1348 } 1349 } 1350 var /= (width * height); 1351 const double std = sqrt(std::max(0.0, var)); 1352 EXPECT_LE(std, 1.25f * this->kScaleNoise); 1353 if (c == 0) { 1354 std::vector<float> measured_psd = 1355 get_noise_psd(&measured_noise[0], width, height, block_size); 1356 std::vector<double> measured_psd_d(block_size * block_size); 1357 std::vector<double> noise_psd_d(block_size * block_size); 1358 std::copy(measured_psd.begin(), measured_psd.end(), 1359 measured_psd_d.begin()); 1360 std::copy(this->noise_psd_[0].begin(), this->noise_psd_[0].end(), 1361 noise_psd_d.begin()); 1362 EXPECT_LT( 1363 aom_normalized_cross_correlation(&measured_psd_d[0], &noise_psd_d[0], 1364 (int)(noise_psd_d.size())), 1365 0.35); 1366 } 1367 } 1368 } 1369 1370 REGISTER_TYPED_TEST_SUITE_P(WienerDenoiseTest, InvalidBlockSize, 1371 InvalidChromaSubsampling, GradientTest); 1372 1373 // Note the empty final argument can be removed if C++20 is made the minimum 1374 // requirement. 1375 INSTANTIATE_TYPED_TEST_SUITE_P(WienerDenoiseTestInstatiation, WienerDenoiseTest, 1376 AllBitDepthParams, );