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 "test/acm_random.h"
21 #include "third_party/googletest/src/googletest/include/gtest/gtest.h"
22
23 namespace {
24
25 // Return normally distrbuted values with standard deviation of sigma.
randn(libaom_test::ACMRandom * random,double 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.
noise_synth(libaom_test::ACMRandom * random,int lag,int n,const int (* coords)[2],const double * coeffs,double * data,int w,int h)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
get_noise_psd(double * noise,int width,int height,int block_size)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
TEST(NoiseStrengthSolver,GetCentersTwoBins)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
TEST(NoiseStrengthSolver,GetCentersTwoBins10bit)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
TEST(NoiseStrengthSolver,GetCenters256Bins)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.
TEST(NoiseStrengthSolver,ObserveIdentity)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
TEST(NoiseStrengthSolver,SimplifiesCurve)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
TEST(NoiseStrengthLut,LutInitNegativeOrZeroSize)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
TEST(NoiseStrengthLut,LutEvalSinglePoint)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
TEST(NoiseStrengthLut,LutEvalMultiPointInterp)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
TEST(NoiseModel,InitSuccessWithValidSquareShape)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
TEST(NoiseModel,InitSuccessWithValidDiamondShape)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
TEST(NoiseModel,InitFailsWithTooLargeLag)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
TEST(NoiseModel,InitFailsWithTooSmallLag)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
TEST(NoiseModel,InitFailsWithInvalidShape)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
TEST(NoiseModel,InitFailsWithInvalidBitdepth)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 typedef T data_type_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:
SetUp()370 void SetUp() override { random_.Reset(171); }
371 typedef std::vector<typename T::data_type_t> VecType;
372 VecType data_;
373 libaom_test::ACMRandom random_;
374 };
375
376 TYPED_TEST_SUITE_P(FlatBlockEstimatorTest);
377
TYPED_TEST_P(FlatBlockEstimatorTest,ExtractBlock)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
TYPED_TEST_P(FlatBlockEstimatorTest,FindFlatBlocks)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 typedef ::testing::Types<BitDepthParams<uint8_t, 8, false>, // lowbd
531 BitDepthParams<uint16_t, 8, true>, // lowbd in 16-bit
532 BitDepthParams<uint16_t, 10, true>, // highbd data
533 BitDepthParams<uint16_t, 12, true> >
534 AllBitDepthParams;
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
SetUp()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
NoiseModelUpdate(int block_size=kBlockSize)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
TearDown()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
TYPED_TEST_P(NoiseModelUpdateTest,UpdateFailsNoFlatBlocks)605 TYPED_TEST_P(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks) {
606 EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS,
607 this->NoiseModelUpdate());
608 }
609
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForZeroNoiseAllFlat)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
TYPED_TEST_P(NoiseModelUpdateTest,UpdateFailsBlockSizeTooSmall)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
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForWhiteRandomNoise)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
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForScaledWhiteNoise)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
TYPED_TEST_P(NoiseModelUpdateTest,UpdateSuccessForCorrelatedNoise)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
TYPED_TEST_P(NoiseModelUpdateTest,NoiseStrengthChangeSignalsDifferentNoiseType)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
TYPED_TEST_P(NoiseModelUpdateTest,NoiseCoeffsSignalsDifferentNoiseType)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 INSTANTIATE_TYPED_TEST_SUITE_P(NoiseModelUpdateTestInstatiation,
972 NoiseModelUpdateTest, AllBitDepthParams);
973
TEST(NoiseModelGetGrainParameters,TestLagSize)974 TEST(NoiseModelGetGrainParameters, TestLagSize) {
975 aom_film_grain_t film_grain;
976 for (int lag = 1; lag <= 3; ++lag) {
977 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
978 aom_noise_model_t model;
979 EXPECT_TRUE(aom_noise_model_init(&model, params));
980 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
981 EXPECT_EQ(lag, film_grain.ar_coeff_lag);
982 aom_noise_model_free(&model);
983 }
984
985 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 4, 8, 0 };
986 aom_noise_model_t model;
987 EXPECT_TRUE(aom_noise_model_init(&model, params));
988 EXPECT_FALSE(aom_noise_model_get_grain_parameters(&model, &film_grain));
989 aom_noise_model_free(&model);
990 }
991
TEST(NoiseModelGetGrainParameters,TestARCoeffShiftBounds)992 TEST(NoiseModelGetGrainParameters, TestARCoeffShiftBounds) {
993 struct TestCase {
994 double max_input_value;
995 int expected_ar_coeff_shift;
996 int expected_value;
997 };
998 const int lag = 1;
999 const int kNumTestCases = 19;
1000 const TestCase test_cases[] = {
1001 // Test cases for ar_coeff_shift = 9
1002 { 0, 9, 0 },
1003 { 0.125, 9, 64 },
1004 { -0.125, 9, -64 },
1005 { 0.2499, 9, 127 },
1006 { -0.25, 9, -128 },
1007 // Test cases for ar_coeff_shift = 8
1008 { 0.25, 8, 64 },
1009 { -0.2501, 8, -64 },
1010 { 0.499, 8, 127 },
1011 { -0.5, 8, -128 },
1012 // Test cases for ar_coeff_shift = 7
1013 { 0.5, 7, 64 },
1014 { -0.5001, 7, -64 },
1015 { 0.999, 7, 127 },
1016 { -1, 7, -128 },
1017 // Test cases for ar_coeff_shift = 6
1018 { 1.0, 6, 64 },
1019 { -1.0001, 6, -64 },
1020 { 2.0, 6, 127 },
1021 { -2.0, 6, -128 },
1022 { 4, 6, 127 },
1023 { -4, 6, -128 },
1024 };
1025 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
1026 aom_noise_model_t model;
1027 EXPECT_TRUE(aom_noise_model_init(&model, params));
1028
1029 for (int i = 0; i < kNumTestCases; ++i) {
1030 const TestCase &test_case = test_cases[i];
1031 model.combined_state[0].eqns.x[0] = test_case.max_input_value;
1032
1033 aom_film_grain_t film_grain;
1034 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
1035 EXPECT_EQ(1, film_grain.ar_coeff_lag);
1036 EXPECT_EQ(test_case.expected_ar_coeff_shift, film_grain.ar_coeff_shift);
1037 EXPECT_EQ(test_case.expected_value, film_grain.ar_coeffs_y[0]);
1038 }
1039 aom_noise_model_free(&model);
1040 }
1041
TEST(NoiseModelGetGrainParameters,TestNoiseStrengthShiftBounds)1042 TEST(NoiseModelGetGrainParameters, TestNoiseStrengthShiftBounds) {
1043 struct TestCase {
1044 double max_input_value;
1045 int expected_scaling_shift;
1046 int expected_value;
1047 };
1048 const int kNumTestCases = 10;
1049 const TestCase test_cases[] = {
1050 { 0, 11, 0 }, { 1, 11, 64 }, { 2, 11, 128 }, { 3.99, 11, 255 },
1051 { 4, 10, 128 }, { 7.99, 10, 255 }, { 8, 9, 128 }, { 16, 8, 128 },
1052 { 31.99, 8, 255 }, { 64, 8, 255 }, // clipped
1053 };
1054 const int lag = 1;
1055 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
1056 aom_noise_model_t model;
1057 EXPECT_TRUE(aom_noise_model_init(&model, params));
1058
1059 for (int i = 0; i < kNumTestCases; ++i) {
1060 const TestCase &test_case = test_cases[i];
1061 aom_equation_system_t &eqns = model.combined_state[0].strength_solver.eqns;
1062 // Set the fitted scale parameters to be a constant value.
1063 for (int j = 0; j < eqns.n; ++j) {
1064 eqns.x[j] = test_case.max_input_value;
1065 }
1066 aom_film_grain_t film_grain;
1067 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
1068 // We expect a single constant segemnt
1069 EXPECT_EQ(test_case.expected_scaling_shift, film_grain.scaling_shift);
1070 EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[0][1]);
1071 EXPECT_EQ(test_case.expected_value, film_grain.scaling_points_y[1][1]);
1072 }
1073 aom_noise_model_free(&model);
1074 }
1075
1076 // The AR coefficients are the same inputs used to generate "Test 2" in the test
1077 // vectors
TEST(NoiseModelGetGrainParameters,GetGrainParametersReal)1078 TEST(NoiseModelGetGrainParameters, GetGrainParametersReal) {
1079 const double kInputCoeffsY[] = { 0.0315, 0.0073, 0.0218, 0.00235, 0.00511,
1080 -0.0222, 0.0627, -0.022, 0.05575, -0.1816,
1081 0.0107, -0.1966, 0.00065, -0.0809, 0.04934,
1082 -0.1349, -0.0352, 0.41772, 0.27973, 0.04207,
1083 -0.0429, -0.1372, 0.06193, 0.52032 };
1084 const double kInputCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1085 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5 };
1086 const double kInputCoeffsCR[] = { 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 int kExpectedARCoeffsY[] = { 4, 1, 3, 0, 1, -3, 8, -3,
1089 7, -23, 1, -25, 0, -10, 6, -17,
1090 -5, 53, 36, 5, -5, -18, 8, 67 };
1091 const int kExpectedARCoeffsCB[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1092 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 };
1093 const int kExpectedARCoeffsCR[] = { 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, -126 };
1095 // Scaling function is initialized analytically with a sqrt function.
1096 const int kNumScalingPointsY = 12;
1097 const int kExpectedScalingPointsY[][2] = {
1098 { 0, 0 }, { 13, 44 }, { 27, 62 }, { 40, 76 },
1099 { 54, 88 }, { 67, 98 }, { 94, 117 }, { 121, 132 },
1100 { 148, 146 }, { 174, 159 }, { 201, 171 }, { 255, 192 },
1101 };
1102
1103 const int lag = 3;
1104 aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, lag, 8, 0 };
1105 aom_noise_model_t model;
1106 EXPECT_TRUE(aom_noise_model_init(&model, params));
1107
1108 // Setup the AR coeffs
1109 memcpy(model.combined_state[0].eqns.x, kInputCoeffsY, sizeof(kInputCoeffsY));
1110 memcpy(model.combined_state[1].eqns.x, kInputCoeffsCB,
1111 sizeof(kInputCoeffsCB));
1112 memcpy(model.combined_state[2].eqns.x, kInputCoeffsCR,
1113 sizeof(kInputCoeffsCR));
1114 for (int i = 0; i < model.combined_state[0].strength_solver.num_bins; ++i) {
1115 const double x =
1116 ((double)i) / (model.combined_state[0].strength_solver.num_bins - 1.0);
1117 model.combined_state[0].strength_solver.eqns.x[i] = 6 * sqrt(x);
1118 model.combined_state[1].strength_solver.eqns.x[i] = 3;
1119 model.combined_state[2].strength_solver.eqns.x[i] = 2;
1120
1121 // Inject some observations into the strength solver, as during film grain
1122 // parameter extraction an estimate of the average strength will be used to
1123 // adjust correlation.
1124 const int n = model.combined_state[0].strength_solver.num_bins;
1125 for (int j = 0; j < model.combined_state[0].strength_solver.num_bins; ++j) {
1126 model.combined_state[0].strength_solver.eqns.A[i * n + j] = 1;
1127 model.combined_state[1].strength_solver.eqns.A[i * n + j] = 1;
1128 model.combined_state[2].strength_solver.eqns.A[i * n + j] = 1;
1129 }
1130 }
1131
1132 aom_film_grain_t film_grain;
1133 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model, &film_grain));
1134 EXPECT_EQ(lag, film_grain.ar_coeff_lag);
1135 EXPECT_EQ(3, film_grain.ar_coeff_lag);
1136 EXPECT_EQ(7, film_grain.ar_coeff_shift);
1137 EXPECT_EQ(10, film_grain.scaling_shift);
1138 EXPECT_EQ(kNumScalingPointsY, film_grain.num_y_points);
1139 EXPECT_EQ(1, film_grain.update_parameters);
1140 EXPECT_EQ(1, film_grain.apply_grain);
1141
1142 const int kNumARCoeffs = 24;
1143 for (int i = 0; i < kNumARCoeffs; ++i) {
1144 EXPECT_EQ(kExpectedARCoeffsY[i], film_grain.ar_coeffs_y[i]);
1145 }
1146 for (int i = 0; i < kNumARCoeffs + 1; ++i) {
1147 EXPECT_EQ(kExpectedARCoeffsCB[i], film_grain.ar_coeffs_cb[i]);
1148 }
1149 for (int i = 0; i < kNumARCoeffs + 1; ++i) {
1150 EXPECT_EQ(kExpectedARCoeffsCR[i], film_grain.ar_coeffs_cr[i]);
1151 }
1152 for (int i = 0; i < kNumScalingPointsY; ++i) {
1153 EXPECT_EQ(kExpectedScalingPointsY[i][0], film_grain.scaling_points_y[i][0]);
1154 EXPECT_EQ(kExpectedScalingPointsY[i][1], film_grain.scaling_points_y[i][1]);
1155 }
1156
1157 // CB strength should just be a piecewise segment
1158 EXPECT_EQ(2, film_grain.num_cb_points);
1159 EXPECT_EQ(0, film_grain.scaling_points_cb[0][0]);
1160 EXPECT_EQ(255, film_grain.scaling_points_cb[1][0]);
1161 EXPECT_EQ(96, film_grain.scaling_points_cb[0][1]);
1162 EXPECT_EQ(96, film_grain.scaling_points_cb[1][1]);
1163
1164 // CR strength should just be a piecewise segment
1165 EXPECT_EQ(2, film_grain.num_cr_points);
1166 EXPECT_EQ(0, film_grain.scaling_points_cr[0][0]);
1167 EXPECT_EQ(255, film_grain.scaling_points_cr[1][0]);
1168 EXPECT_EQ(64, film_grain.scaling_points_cr[0][1]);
1169 EXPECT_EQ(64, film_grain.scaling_points_cr[1][1]);
1170
1171 EXPECT_EQ(128, film_grain.cb_mult);
1172 EXPECT_EQ(192, film_grain.cb_luma_mult);
1173 EXPECT_EQ(256, film_grain.cb_offset);
1174 EXPECT_EQ(128, film_grain.cr_mult);
1175 EXPECT_EQ(192, film_grain.cr_luma_mult);
1176 EXPECT_EQ(256, film_grain.cr_offset);
1177 EXPECT_EQ(0, film_grain.chroma_scaling_from_luma);
1178 EXPECT_EQ(0, film_grain.grain_scale_shift);
1179
1180 aom_noise_model_free(&model);
1181 }
1182
1183 template <typename T>
1184 class WienerDenoiseTest : public ::testing::Test, public T {
1185 public:
SetUpTestSuite()1186 static void SetUpTestSuite() { aom_dsp_rtcd(); }
1187
1188 protected:
SetUp()1189 void SetUp() override {
1190 static const float kNoiseLevel = 5.f;
1191 static const float kStd = 4.0;
1192 static const double kMaxValue = (1 << T::kBitDepth) - 1;
1193
1194 chroma_sub_[0] = 1;
1195 chroma_sub_[1] = 1;
1196 stride_[0] = kWidth;
1197 stride_[1] = kWidth / 2;
1198 stride_[2] = kWidth / 2;
1199 for (int k = 0; k < 3; ++k) {
1200 data_[k].resize(kWidth * kHeight);
1201 denoised_[k].resize(kWidth * kHeight);
1202 noise_psd_[k].resize(kBlockSize * kBlockSize);
1203 }
1204
1205 const double kCoeffsY[] = { 0.0406, -0.116, -0.078, -0.152, 0.0033, -0.093,
1206 0.048, 0.404, 0.2353, -0.035, -0.093, 0.441 };
1207 const int kCoords[12][2] = {
1208 { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 },
1209 { -1, -1 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 }
1210 };
1211 const int kLag = 2;
1212 const int kLength = 12;
1213 libaom_test::ACMRandom random;
1214 std::vector<double> noise(kWidth * kHeight);
1215 noise_synth(&random, kLag, kLength, kCoords, kCoeffsY, &noise[0], kWidth,
1216 kHeight);
1217 noise_psd_[0] = get_noise_psd(&noise[0], kWidth, kHeight, kBlockSize);
1218 for (int i = 0; i < kBlockSize * kBlockSize; ++i) {
1219 noise_psd_[0][i] = (float)(noise_psd_[0][i] * kStd * kStd * kScaleNoise *
1220 kScaleNoise / (kMaxValue * kMaxValue));
1221 }
1222
1223 float psd_value =
1224 aom_noise_psd_get_default_value(kBlockSizeChroma, kNoiseLevel);
1225 for (int i = 0; i < kBlockSizeChroma * kBlockSizeChroma; ++i) {
1226 noise_psd_[1][i] = psd_value;
1227 noise_psd_[2][i] = psd_value;
1228 }
1229 for (int y = 0; y < kHeight; ++y) {
1230 for (int x = 0; x < kWidth; ++x) {
1231 data_[0][y * stride_[0] + x] = (typename T::data_type_t)fclamp(
1232 (x + noise[y * stride_[0] + x] * kStd) * kScaleNoise, 0, kMaxValue);
1233 }
1234 }
1235
1236 for (int c = 1; c < 3; ++c) {
1237 for (int y = 0; y < (kHeight >> 1); ++y) {
1238 for (int x = 0; x < (kWidth >> 1); ++x) {
1239 data_[c][y * stride_[c] + x] = (typename T::data_type_t)fclamp(
1240 (x + randn(&random, kStd)) * kScaleNoise, 0, kMaxValue);
1241 }
1242 }
1243 }
1244 for (int k = 0; k < 3; ++k) {
1245 noise_psd_ptrs_[k] = &noise_psd_[k][0];
1246 }
1247 }
1248 static const int kBlockSize = 32;
1249 static const int kBlockSizeChroma = 16;
1250 static const int kWidth = 256;
1251 static const int kHeight = 256;
1252 static const int kScaleNoise = 1 << (T::kBitDepth - 8);
1253
1254 std::vector<typename T::data_type_t> data_[3];
1255 std::vector<typename T::data_type_t> denoised_[3];
1256 std::vector<float> noise_psd_[3];
1257 int chroma_sub_[2];
1258 float *noise_psd_ptrs_[3];
1259 int stride_[3];
1260 };
1261
1262 TYPED_TEST_SUITE_P(WienerDenoiseTest);
1263
TYPED_TEST_P(WienerDenoiseTest,InvalidBlockSize)1264 TYPED_TEST_P(WienerDenoiseTest, InvalidBlockSize) {
1265 const uint8_t *const data_ptrs[3] = {
1266 reinterpret_cast<uint8_t *>(&this->data_[0][0]),
1267 reinterpret_cast<uint8_t *>(&this->data_[1][0]),
1268 reinterpret_cast<uint8_t *>(&this->data_[2][0]),
1269 };
1270 uint8_t *denoised_ptrs[3] = {
1271 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]),
1272 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]),
1273 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]),
1274 };
1275 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1276 this->kHeight, this->stride_,
1277 this->chroma_sub_, this->noise_psd_ptrs_,
1278 18, this->kBitDepth, this->kUseHighBD));
1279 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1280 this->kHeight, this->stride_,
1281 this->chroma_sub_, this->noise_psd_ptrs_,
1282 48, this->kBitDepth, this->kUseHighBD));
1283 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1284 this->kHeight, this->stride_,
1285 this->chroma_sub_, this->noise_psd_ptrs_,
1286 64, this->kBitDepth, this->kUseHighBD));
1287 }
1288
TYPED_TEST_P(WienerDenoiseTest,InvalidChromaSubsampling)1289 TYPED_TEST_P(WienerDenoiseTest, InvalidChromaSubsampling) {
1290 const uint8_t *const data_ptrs[3] = {
1291 reinterpret_cast<uint8_t *>(&this->data_[0][0]),
1292 reinterpret_cast<uint8_t *>(&this->data_[1][0]),
1293 reinterpret_cast<uint8_t *>(&this->data_[2][0]),
1294 };
1295 uint8_t *denoised_ptrs[3] = {
1296 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]),
1297 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]),
1298 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]),
1299 };
1300 int chroma_sub[2] = { 1, 0 };
1301 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1302 this->kHeight, this->stride_, chroma_sub,
1303 this->noise_psd_ptrs_, 32, this->kBitDepth,
1304 this->kUseHighBD));
1305
1306 chroma_sub[0] = 0;
1307 chroma_sub[1] = 1;
1308 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs, denoised_ptrs, this->kWidth,
1309 this->kHeight, this->stride_, chroma_sub,
1310 this->noise_psd_ptrs_, 32, this->kBitDepth,
1311 this->kUseHighBD));
1312 }
1313
TYPED_TEST_P(WienerDenoiseTest,GradientTest)1314 TYPED_TEST_P(WienerDenoiseTest, GradientTest) {
1315 const int width = this->kWidth;
1316 const int height = this->kHeight;
1317 const int block_size = this->kBlockSize;
1318 const uint8_t *const data_ptrs[3] = {
1319 reinterpret_cast<uint8_t *>(&this->data_[0][0]),
1320 reinterpret_cast<uint8_t *>(&this->data_[1][0]),
1321 reinterpret_cast<uint8_t *>(&this->data_[2][0]),
1322 };
1323 uint8_t *denoised_ptrs[3] = {
1324 reinterpret_cast<uint8_t *>(&this->denoised_[0][0]),
1325 reinterpret_cast<uint8_t *>(&this->denoised_[1][0]),
1326 reinterpret_cast<uint8_t *>(&this->denoised_[2][0]),
1327 };
1328 const int ret = aom_wiener_denoise_2d(
1329 data_ptrs, denoised_ptrs, width, height, this->stride_, this->chroma_sub_,
1330 this->noise_psd_ptrs_, block_size, this->kBitDepth, this->kUseHighBD);
1331 EXPECT_EQ(1, ret);
1332
1333 // Check the noise on the denoised image (from the analytical gradient)
1334 // and make sure that it is less than what we added.
1335 for (int c = 0; c < 3; ++c) {
1336 std::vector<double> measured_noise(width * height);
1337
1338 double var = 0;
1339 const int shift = (c > 0);
1340 for (int x = 0; x < (width >> shift); ++x) {
1341 for (int y = 0; y < (height >> shift); ++y) {
1342 const double diff = this->denoised_[c][y * this->stride_[c] + x] -
1343 x * this->kScaleNoise;
1344 var += diff * diff;
1345 measured_noise[y * width + x] = diff;
1346 }
1347 }
1348 var /= (width * height);
1349 const double std = sqrt(std::max(0.0, var));
1350 EXPECT_LE(std, 1.25f * this->kScaleNoise);
1351 if (c == 0) {
1352 std::vector<float> measured_psd =
1353 get_noise_psd(&measured_noise[0], width, height, block_size);
1354 std::vector<double> measured_psd_d(block_size * block_size);
1355 std::vector<double> noise_psd_d(block_size * block_size);
1356 std::copy(measured_psd.begin(), measured_psd.end(),
1357 measured_psd_d.begin());
1358 std::copy(this->noise_psd_[0].begin(), this->noise_psd_[0].end(),
1359 noise_psd_d.begin());
1360 EXPECT_LT(
1361 aom_normalized_cross_correlation(&measured_psd_d[0], &noise_psd_d[0],
1362 (int)(noise_psd_d.size())),
1363 0.35);
1364 }
1365 }
1366 }
1367
1368 REGISTER_TYPED_TEST_SUITE_P(WienerDenoiseTest, InvalidBlockSize,
1369 InvalidChromaSubsampling, GradientTest);
1370
1371 INSTANTIATE_TYPED_TEST_SUITE_P(WienerDenoiseTestInstatiation, WienerDenoiseTest,
1372 AllBitDepthParams);
1373