2 * Copyright (c) 2018, Alliance for Open Media. All rights reserved
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.
16 #include "aom_dsp/noise_model.h"
17 #include "aom_dsp/noise_util.h"
18 #include "config/aom_dsp_rtcd.h"
19 #include "test/acm_random.h"
20 #include "third_party/googletest/src/googletest/include/gtest/gtest.h"
24 // Return normally distrbuted values with standard deviation of sigma.
25 double randn(libaom_test::ACMRandom
*random
, double sigma
) {
27 const double u
= 2.0 * ((double)random
->Rand31() /
28 testing::internal::Random::kMaxRange
) -
30 const double v
= 2.0 * ((double)random
->Rand31() /
31 testing::internal::Random::kMaxRange
) -
33 const double s
= u
* u
+ v
* v
;
35 return sigma
* (u
* sqrt(-2.0 * log(s
) / s
));
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
,
46 const int pad_size
= 3 * lag
;
47 const int padded_w
= w
+ pad_size
;
48 const int padded_h
= h
+ pad_size
;
50 std::vector
<double> padded(padded_w
* padded_h
);
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);
57 for (y
= lag
; y
< padded_h
; ++y
) {
58 for (x
= lag
; x
< padded_w
; ++x
) {
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
];
66 padded
[y
* padded_w
+ x
] += sum
;
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
);
75 std::vector
<float> get_noise_psd(double *noise
, int width
, int height
,
78 (float *)aom_memalign(32, block_size
* block_size
* sizeof(block
));
79 std::vector
<float> psd(block_size
* block_size
);
81 struct aom_noise_tx_t
*tx
= aom_noise_tx_malloc(block_size
);
82 for (int y
= 0; y
<= height
- block_size
; y
+= block_size
/ 2) {
83 for (int x
= 0; x
<= width
- block_size
; x
+= block_size
/ 2) {
84 for (int yy
= 0; yy
< block_size
; ++yy
) {
85 for (int xx
= 0; xx
< block_size
; ++xx
) {
86 block
[yy
* block_size
+ xx
] = (float)noise
[(y
+ yy
) * width
+ x
+ xx
];
89 aom_noise_tx_forward(tx
, &block
[0]);
90 aom_noise_tx_add_energy(tx
, &psd
[0]);
94 for (int yy
= 0; yy
< block_size
; ++yy
) {
95 for (int xx
= 0; xx
<= block_size
/ 2; ++xx
) {
96 psd
[yy
* block_size
+ xx
] /= num_blocks
;
99 // Fill in the data that is missing due to symmetries
100 for (int xx
= 1; xx
< block_size
/ 2; ++xx
) {
101 psd
[(block_size
- xx
)] = psd
[xx
];
103 for (int yy
= 1; yy
< block_size
; ++yy
) {
104 for (int xx
= 1; xx
< block_size
/ 2; ++xx
) {
105 psd
[(block_size
- yy
) * block_size
+ (block_size
- xx
)] =
106 psd
[yy
* block_size
+ xx
];
109 aom_noise_tx_free(tx
);
116 TEST(NoiseStrengthSolver
, GetCentersTwoBins
) {
117 aom_noise_strength_solver_t solver
;
118 aom_noise_strength_solver_init(&solver
, 2, 8);
119 EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver
, 0), 1e-5);
120 EXPECT_NEAR(255, aom_noise_strength_solver_get_center(&solver
, 1), 1e-5);
121 aom_noise_strength_solver_free(&solver
);
124 TEST(NoiseStrengthSolver
, GetCentersTwoBins10bit
) {
125 aom_noise_strength_solver_t solver
;
126 aom_noise_strength_solver_init(&solver
, 2, 10);
127 EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver
, 0), 1e-5);
128 EXPECT_NEAR(1023, aom_noise_strength_solver_get_center(&solver
, 1), 1e-5);
129 aom_noise_strength_solver_free(&solver
);
132 TEST(NoiseStrengthSolver
, GetCenters256Bins
) {
133 const int num_bins
= 256;
134 aom_noise_strength_solver_t solver
;
135 aom_noise_strength_solver_init(&solver
, num_bins
, 8);
137 for (int i
= 0; i
< 256; ++i
) {
138 EXPECT_NEAR(i
, aom_noise_strength_solver_get_center(&solver
, i
), 1e-5);
140 aom_noise_strength_solver_free(&solver
);
143 // Tests that the noise strength solver returns the identity transform when
144 // given identity-like constraints.
145 TEST(NoiseStrengthSolver
, ObserveIdentity
) {
146 const int num_bins
= 256;
147 aom_noise_strength_solver_t solver
;
148 EXPECT_EQ(1, aom_noise_strength_solver_init(&solver
, num_bins
, 8));
150 // We have to add a big more strength to constraints at the boundary to
151 // overcome any regularization.
152 for (int j
= 0; j
< 5; ++j
) {
153 aom_noise_strength_solver_add_measurement(&solver
, 0, 0);
154 aom_noise_strength_solver_add_measurement(&solver
, 255, 255);
156 for (int i
= 0; i
< 256; ++i
) {
157 aom_noise_strength_solver_add_measurement(&solver
, i
, i
);
159 EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver
));
160 for (int i
= 2; i
< num_bins
- 2; ++i
) {
161 EXPECT_NEAR(i
, solver
.eqns
.x
[i
], 0.1);
164 aom_noise_strength_lut_t lut
;
165 EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver
, 2, &lut
));
167 ASSERT_EQ(2, lut
.num_points
);
168 EXPECT_NEAR(0.0, lut
.points
[0][0], 1e-5);
169 EXPECT_NEAR(0.0, lut
.points
[0][1], 0.5);
170 EXPECT_NEAR(255.0, lut
.points
[1][0], 1e-5);
171 EXPECT_NEAR(255.0, lut
.points
[1][1], 0.5);
173 aom_noise_strength_lut_free(&lut
);
174 aom_noise_strength_solver_free(&solver
);
177 TEST(NoiseStrengthSolver
, SimplifiesCurve
) {
178 const int num_bins
= 256;
179 aom_noise_strength_solver_t solver
;
180 EXPECT_EQ(1, aom_noise_strength_solver_init(&solver
, num_bins
, 8));
182 // Create a parabolic input
183 for (int i
= 0; i
< 256; ++i
) {
184 const double x
= (i
- 127.5) / 63.5;
185 aom_noise_strength_solver_add_measurement(&solver
, i
, x
* x
);
187 EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver
));
189 // First try to fit an unconstrained lut
190 aom_noise_strength_lut_t lut
;
191 EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver
, -1, &lut
));
192 ASSERT_LE(20, lut
.num_points
);
193 aom_noise_strength_lut_free(&lut
);
195 // Now constrain the maximum number of points
196 const int kMaxPoints
= 9;
198 aom_noise_strength_solver_fit_piecewise(&solver
, kMaxPoints
, &lut
));
199 ASSERT_EQ(kMaxPoints
, lut
.num_points
);
201 // Check that the input parabola is still well represented
202 EXPECT_NEAR(0.0, lut
.points
[0][0], 1e-5);
203 EXPECT_NEAR(4.0, lut
.points
[0][1], 0.1);
204 for (int i
= 1; i
< lut
.num_points
- 1; ++i
) {
205 const double x
= (lut
.points
[i
][0] - 128.) / 64.;
206 EXPECT_NEAR(x
* x
, lut
.points
[i
][1], 0.1);
208 EXPECT_NEAR(255.0, lut
.points
[kMaxPoints
- 1][0], 1e-5);
210 EXPECT_NEAR(4.0, lut
.points
[kMaxPoints
- 1][1], 0.1);
211 aom_noise_strength_lut_free(&lut
);
212 aom_noise_strength_solver_free(&solver
);
215 TEST(NoiseStrengthLut
, LutEvalSinglePoint
) {
216 aom_noise_strength_lut_t lut
;
217 ASSERT_TRUE(aom_noise_strength_lut_init(&lut
, 1));
218 ASSERT_EQ(1, lut
.num_points
);
219 lut
.points
[0][0] = 0;
220 lut
.points
[0][1] = 1;
221 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut
, -1));
222 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut
, 0));
223 EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut
, 1));
224 aom_noise_strength_lut_free(&lut
);
227 TEST(NoiseStrengthLut
, LutEvalMultiPointInterp
) {
228 const double kEps
= 1e-5;
229 aom_noise_strength_lut_t lut
;
230 ASSERT_TRUE(aom_noise_strength_lut_init(&lut
, 4));
231 ASSERT_EQ(4, lut
.num_points
);
233 lut
.points
[0][0] = 0;
234 lut
.points
[0][1] = 0;
236 lut
.points
[1][0] = 1;
237 lut
.points
[1][1] = 1;
239 lut
.points
[2][0] = 2;
240 lut
.points
[2][1] = 1;
242 lut
.points
[3][0] = 100;
243 lut
.points
[3][1] = 1001;
245 // Test lower boundary
246 EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut
, -1));
247 EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut
, 0));
249 // Test first part that should be identity
250 EXPECT_NEAR(0.25, aom_noise_strength_lut_eval(&lut
, 0.25), kEps
);
251 EXPECT_NEAR(0.75, aom_noise_strength_lut_eval(&lut
, 0.75), kEps
);
253 // This is a constant section (should evaluate to 1)
254 EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut
, 1.25), kEps
);
255 EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut
, 1.75), kEps
);
257 // Test interpolation between to non-zero y coords.
258 EXPECT_NEAR(1, aom_noise_strength_lut_eval(&lut
, 2), kEps
);
259 EXPECT_NEAR(251, aom_noise_strength_lut_eval(&lut
, 26.5), kEps
);
260 EXPECT_NEAR(751, aom_noise_strength_lut_eval(&lut
, 75.5), kEps
);
262 // Test upper boundary
263 EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut
, 100));
264 EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut
, 101));
266 aom_noise_strength_lut_free(&lut
);
269 TEST(NoiseModel
, InitSuccessWithValidSquareShape
) {
270 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, 2, 8, 0 };
271 aom_noise_model_t model
;
273 EXPECT_TRUE(aom_noise_model_init(&model
, params
));
275 const int kNumCoords
= 12;
276 const int kCoords
[][2] = { { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 },
277 { 2, -2 }, { -2, -1 }, { -1, -1 }, { 0, -1 },
278 { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 } };
279 EXPECT_EQ(kNumCoords
, model
.n
);
280 for (int i
= 0; i
< kNumCoords
; ++i
) {
281 const int *coord
= kCoords
[i
];
282 EXPECT_EQ(coord
[0], model
.coords
[i
][0]);
283 EXPECT_EQ(coord
[1], model
.coords
[i
][1]);
285 aom_noise_model_free(&model
);
288 TEST(NoiseModel
, InitSuccessWithValidDiamondShape
) {
289 aom_noise_model_t model
;
290 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_DIAMOND
, 2, 8, 0 };
291 EXPECT_TRUE(aom_noise_model_init(&model
, params
));
292 EXPECT_EQ(6, model
.n
);
293 const int kNumCoords
= 6;
294 const int kCoords
[][2] = { { 0, -2 }, { -1, -1 }, { 0, -1 },
295 { 1, -1 }, { -2, 0 }, { -1, 0 } };
296 EXPECT_EQ(kNumCoords
, model
.n
);
297 for (int i
= 0; i
< kNumCoords
; ++i
) {
298 const int *coord
= kCoords
[i
];
299 EXPECT_EQ(coord
[0], model
.coords
[i
][0]);
300 EXPECT_EQ(coord
[1], model
.coords
[i
][1]);
302 aom_noise_model_free(&model
);
305 TEST(NoiseModel
, InitFailsWithTooLargeLag
) {
306 aom_noise_model_t model
;
307 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, 10, 8, 0 };
308 EXPECT_FALSE(aom_noise_model_init(&model
, params
));
309 aom_noise_model_free(&model
);
312 TEST(NoiseModel
, InitFailsWithTooSmallLag
) {
313 aom_noise_model_t model
;
314 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, 0, 8, 0 };
315 EXPECT_FALSE(aom_noise_model_init(&model
, params
));
316 aom_noise_model_free(&model
);
319 TEST(NoiseModel
, InitFailsWithInvalidShape
) {
320 aom_noise_model_t model
;
321 aom_noise_model_params_t params
= { aom_noise_shape(100), 3, 8, 0 };
322 EXPECT_FALSE(aom_noise_model_init(&model
, params
));
323 aom_noise_model_free(&model
);
326 // A container template class to hold a data type and extra arguments.
327 // All of these args are bundled into one struct so that we can use
328 // parameterized tests on combinations of supported data types
329 // (uint8_t and uint16_t) and bit depths (8, 10, 12).
330 template <typename T
, int bit_depth
, bool use_highbd
>
331 struct BitDepthParams
{
332 typedef T data_type_t
;
333 static const int kBitDepth
= bit_depth
;
334 static const bool kUseHighBD
= use_highbd
;
337 template <typename T
>
338 class FlatBlockEstimatorTest
: public ::testing::Test
, public T
{
340 virtual void SetUp() { random_
.Reset(171); }
341 typedef std::vector
<typename
T::data_type_t
> VecType
;
343 libaom_test::ACMRandom random_
;
346 TYPED_TEST_CASE_P(FlatBlockEstimatorTest
);
348 TYPED_TEST_P(FlatBlockEstimatorTest
, ExtractBlock
) {
349 const int kBlockSize
= 16;
350 aom_flat_block_finder_t flat_block_finder
;
351 ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder
, kBlockSize
,
352 this->kBitDepth
, this->kUseHighBD
));
353 const double normalization
= flat_block_finder
.normalization
;
355 // Test with an image of more than one block.
356 const int h
= 2 * kBlockSize
;
357 const int w
= 2 * kBlockSize
;
358 const int stride
= 2 * kBlockSize
;
359 this->data_
.resize(h
* stride
, 128);
361 // Set up the (0,0) block to be a plane and the (0,1) block to be a
363 const int shift
= this->kBitDepth
- 8;
364 for (int y
= 0; y
< kBlockSize
; ++y
) {
365 for (int x
= 0; x
< kBlockSize
; ++x
) {
366 this->data_
[y
* stride
+ x
] = (-y
+ x
+ 128) << shift
;
367 this->data_
[y
* stride
+ x
+ kBlockSize
] =
368 ((x
% 2 + y
% 2) % 2 ? 128 - 20 : 128 + 20) << shift
;
371 std::vector
<double> block(kBlockSize
* kBlockSize
, 1);
372 std::vector
<double> plane(kBlockSize
* kBlockSize
, 1);
374 // The block data should be a constant (zero) and the rest of the plane
375 // trend is covered in the plane data.
376 aom_flat_block_finder_extract_block(&flat_block_finder
,
377 (uint8_t *)&this->data_
[0], w
, h
, stride
,
378 0, 0, &plane
[0], &block
[0]);
379 for (int y
= 0; y
< kBlockSize
; ++y
) {
380 for (int x
= 0; x
< kBlockSize
; ++x
) {
381 EXPECT_NEAR(0, block
[y
* kBlockSize
+ x
], 1e-5);
382 EXPECT_NEAR((double)(this->data_
[y
* stride
+ x
]) / normalization
,
383 plane
[y
* kBlockSize
+ x
], 1e-5);
387 // The plane trend is a constant, and the block is a zero mean checkerboard.
388 aom_flat_block_finder_extract_block(&flat_block_finder
,
389 (uint8_t *)&this->data_
[0], w
, h
, stride
,
390 kBlockSize
, 0, &plane
[0], &block
[0]);
391 const int mid
= 128 << shift
;
392 for (int y
= 0; y
< kBlockSize
; ++y
) {
393 for (int x
= 0; x
< kBlockSize
; ++x
) {
394 EXPECT_NEAR(((double)this->data_
[y
* stride
+ x
+ kBlockSize
] - mid
) /
396 block
[y
* kBlockSize
+ x
], 1e-5);
397 EXPECT_NEAR(mid
/ normalization
, plane
[y
* kBlockSize
+ x
], 1e-5);
400 aom_flat_block_finder_free(&flat_block_finder
);
403 TYPED_TEST_P(FlatBlockEstimatorTest
, FindFlatBlocks
) {
404 const int kBlockSize
= 32;
405 aom_flat_block_finder_t flat_block_finder
;
406 ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder
, kBlockSize
,
407 this->kBitDepth
, this->kUseHighBD
));
409 const int num_blocks_w
= 8;
410 const int h
= kBlockSize
;
411 const int w
= kBlockSize
* num_blocks_w
;
412 const int stride
= w
;
413 this->data_
.resize(h
* stride
, 128);
414 std::vector
<uint8_t> flat_blocks(num_blocks_w
, 0);
416 const int shift
= this->kBitDepth
- 8;
417 for (int y
= 0; y
< kBlockSize
; ++y
) {
418 for (int x
= 0; x
< kBlockSize
; ++x
) {
419 // Block 0 (not flat): constant doesn't have enough variance to qualify
420 this->data_
[y
* stride
+ x
+ 0 * kBlockSize
] = 128 << shift
;
422 // Block 1 (not flat): too high of variance is hard to validate as flat
423 this->data_
[y
* stride
+ x
+ 1 * kBlockSize
] =
424 ((uint8_t)(128 + randn(&this->random_
, 5))) << shift
;
426 // Block 2 (flat): slight checkerboard added to constant
427 const int check
= (x
% 2 + y
% 2) % 2 ? -2 : 2;
428 this->data_
[y
* stride
+ x
+ 2 * kBlockSize
] = (128 + check
) << shift
;
430 // Block 3 (flat): planar block with checkerboard pattern is also flat
431 this->data_
[y
* stride
+ x
+ 3 * kBlockSize
] =
432 (y
* 2 - x
/ 2 + 128 + check
) << shift
;
434 // Block 4 (flat): gaussian random with standard deviation 1.
435 this->data_
[y
* stride
+ x
+ 4 * kBlockSize
] =
436 ((uint8_t)(randn(&this->random_
, 1) + x
+ 128.0)) << shift
;
438 // Block 5 (flat): gaussian random with standard deviation 2.
439 this->data_
[y
* stride
+ x
+ 5 * kBlockSize
] =
440 ((uint8_t)(randn(&this->random_
, 2) + y
+ 128.0)) << shift
;
442 // Block 6 (not flat): too high of directional gradient.
443 const int strong_edge
= x
> kBlockSize
/ 2 ? 64 : 0;
444 this->data_
[y
* stride
+ x
+ 6 * kBlockSize
] =
445 ((uint8_t)(randn(&this->random_
, 1) + strong_edge
+ 128.0)) << shift
;
447 // Block 7 (not flat): too high gradient.
448 const int big_check
= ((x
>> 2) % 2 + (y
>> 2) % 2) % 2 ? -16 : 16;
449 this->data_
[y
* stride
+ x
+ 7 * kBlockSize
] =
450 ((uint8_t)(randn(&this->random_
, 1) + big_check
+ 128.0)) << shift
;
454 EXPECT_EQ(4, aom_flat_block_finder_run(&flat_block_finder
,
455 (uint8_t *)&this->data_
[0], w
, h
,
456 stride
, &flat_blocks
[0]));
458 // First two blocks are not flat
459 EXPECT_EQ(0, flat_blocks
[0]);
460 EXPECT_EQ(0, flat_blocks
[1]);
462 // Next 4 blocks are flat.
463 EXPECT_EQ(255, flat_blocks
[2]);
464 EXPECT_EQ(255, flat_blocks
[3]);
465 EXPECT_EQ(255, flat_blocks
[4]);
466 EXPECT_EQ(255, flat_blocks
[5]);
468 // Last 2 are not flat by threshold
469 EXPECT_EQ(0, flat_blocks
[6]);
470 EXPECT_EQ(0, flat_blocks
[7]);
472 // Add the noise from non-flat block 1 to every block.
473 for (int y
= 0; y
< kBlockSize
; ++y
) {
474 for (int x
= 0; x
< kBlockSize
* num_blocks_w
; ++x
) {
475 this->data_
[y
* stride
+ x
] +=
476 (this->data_
[y
* stride
+ x
% kBlockSize
+ kBlockSize
] -
480 // Now the scored selection will pick the one that is most likely flat (block
482 EXPECT_EQ(1, aom_flat_block_finder_run(&flat_block_finder
,
483 (uint8_t *)&this->data_
[0], w
, h
,
484 stride
, &flat_blocks
[0]));
485 EXPECT_EQ(1, flat_blocks
[0]);
486 EXPECT_EQ(0, flat_blocks
[1]);
487 EXPECT_EQ(0, flat_blocks
[2]);
488 EXPECT_EQ(0, flat_blocks
[3]);
489 EXPECT_EQ(0, flat_blocks
[4]);
490 EXPECT_EQ(0, flat_blocks
[5]);
491 EXPECT_EQ(0, flat_blocks
[6]);
492 EXPECT_EQ(0, flat_blocks
[7]);
494 aom_flat_block_finder_free(&flat_block_finder
);
497 REGISTER_TYPED_TEST_CASE_P(FlatBlockEstimatorTest
, ExtractBlock
,
500 typedef ::testing::Types
<BitDepthParams
<uint8_t, 8, false>, // lowbd
501 BitDepthParams
<uint16_t, 8, true>, // lowbd in 16-bit
502 BitDepthParams
<uint16_t, 10, true>, // highbd data
503 BitDepthParams
<uint16_t, 12, true> >
505 INSTANTIATE_TYPED_TEST_CASE_P(FlatBlockInstatiation
, FlatBlockEstimatorTest
,
508 template <typename T
>
509 class NoiseModelUpdateTest
: public ::testing::Test
, public T
{
511 static const int kWidth
= 128;
512 static const int kHeight
= 128;
513 static const int kBlockSize
= 16;
514 static const int kNumBlocksX
= kWidth
/ kBlockSize
;
515 static const int kNumBlocksY
= kHeight
/ kBlockSize
;
517 virtual void SetUp() {
518 const aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, 3,
519 T::kBitDepth
, T::kUseHighBD
};
520 ASSERT_TRUE(aom_noise_model_init(&model_
, params
));
522 random_
.Reset(100171);
524 data_
.resize(kWidth
* kHeight
* 3);
525 denoised_
.resize(kWidth
* kHeight
* 3);
526 noise_
.resize(kWidth
* kHeight
* 3);
527 renoise_
.resize(kWidth
* kHeight
);
528 flat_blocks_
.resize(kNumBlocksX
* kNumBlocksY
);
530 for (int c
= 0, offset
= 0; c
< 3; ++c
, offset
+= kWidth
* kHeight
) {
531 data_ptr_
[c
] = &data_
[offset
];
532 noise_ptr_
[c
] = &noise_
[offset
];
533 denoised_ptr_
[c
] = &denoised_
[offset
];
534 strides_
[c
] = kWidth
;
536 data_ptr_raw_
[c
] = (uint8_t *)&data_
[offset
];
537 denoised_ptr_raw_
[c
] = (uint8_t *)&denoised_
[offset
];
543 int NoiseModelUpdate(int block_size
= kBlockSize
) {
544 return aom_noise_model_update(&model_
, data_ptr_raw_
, denoised_ptr_raw_
,
545 kWidth
, kHeight
, strides_
, chroma_sub_
,
546 &flat_blocks_
[0], block_size
);
549 void TearDown() { aom_noise_model_free(&model_
); }
552 aom_noise_model_t model_
;
553 std::vector
<typename
T::data_type_t
> data_
;
554 std::vector
<typename
T::data_type_t
> denoised_
;
556 std::vector
<double> noise_
;
557 std::vector
<double> renoise_
;
558 std::vector
<uint8_t> flat_blocks_
;
560 typename
T::data_type_t
*data_ptr_
[3];
561 typename
T::data_type_t
*denoised_ptr_
[3];
563 double *noise_ptr_
[3];
566 libaom_test::ACMRandom random_
;
569 uint8_t *data_ptr_raw_
[3];
570 uint8_t *denoised_ptr_raw_
[3];
573 TYPED_TEST_CASE_P(NoiseModelUpdateTest
);
575 TYPED_TEST_P(NoiseModelUpdateTest
, UpdateFailsNoFlatBlocks
) {
576 EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS
,
577 this->NoiseModelUpdate());
580 TYPED_TEST_P(NoiseModelUpdateTest
, UpdateSuccessForZeroNoiseAllFlat
) {
581 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
582 this->denoised_
.assign(this->denoised_
.size(), 128);
583 this->data_
.assign(this->denoised_
.size(), 128);
584 EXPECT_EQ(AOM_NOISE_STATUS_INTERNAL_ERROR
, this->NoiseModelUpdate());
587 TYPED_TEST_P(NoiseModelUpdateTest
, UpdateFailsBlockSizeTooSmall
) {
588 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
589 this->denoised_
.assign(this->denoised_
.size(), 128);
590 this->data_
.assign(this->denoised_
.size(), 128);
591 EXPECT_EQ(AOM_NOISE_STATUS_INVALID_ARGUMENT
,
592 this->NoiseModelUpdate(6 /* block_size=6 is too small*/));
595 TYPED_TEST_P(NoiseModelUpdateTest
, UpdateSuccessForWhiteRandomNoise
) {
596 aom_noise_model_t
&model
= this->model_
;
597 const int kWidth
= this->kWidth
;
598 const int kHeight
= this->kHeight
;
600 const int shift
= this->kBitDepth
- 8;
601 for (int y
= 0; y
< kHeight
; ++y
) {
602 for (int x
= 0; x
< kWidth
; ++x
) {
603 this->data_ptr_
[0][y
* kWidth
+ x
] =
604 int(64 + y
+ randn(&this->random_
, 1)) << shift
;
605 this->denoised_ptr_
[0][y
* kWidth
+ x
] = (64 + y
) << shift
;
606 // Make the chroma planes completely correlated with the Y plane
607 for (int c
= 1; c
< 3; ++c
) {
608 this->data_ptr_
[c
][y
* kWidth
+ x
] = this->data_ptr_
[0][y
* kWidth
+ x
];
609 this->denoised_ptr_
[c
][y
* kWidth
+ x
] =
610 this->denoised_ptr_
[0][y
* kWidth
+ x
];
614 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
615 EXPECT_EQ(AOM_NOISE_STATUS_OK
, this->NoiseModelUpdate());
617 const double kCoeffEps
= 0.075;
618 const int n
= model
.n
;
619 for (int c
= 0; c
< 3; ++c
) {
620 for (int i
= 0; i
< n
; ++i
) {
621 EXPECT_NEAR(0, model
.latest_state
[c
].eqns
.x
[i
], kCoeffEps
);
622 EXPECT_NEAR(0, model
.combined_state
[c
].eqns
.x
[i
], kCoeffEps
);
624 // The second and third channels are highly correlated with the first.
626 ASSERT_EQ(n
+ 1, model
.latest_state
[c
].eqns
.n
);
627 ASSERT_EQ(n
+ 1, model
.combined_state
[c
].eqns
.n
);
629 EXPECT_NEAR(1, model
.latest_state
[c
].eqns
.x
[n
], kCoeffEps
);
630 EXPECT_NEAR(1, model
.combined_state
[c
].eqns
.x
[n
], kCoeffEps
);
634 // The fitted noise strength should be close to the standard deviation
635 // for all intensity bins.
636 const double kStdEps
= 0.1;
637 const double normalize
= 1 << shift
;
639 for (int i
= 0; i
< model
.latest_state
[0].strength_solver
.eqns
.n
; ++i
) {
641 model
.latest_state
[0].strength_solver
.eqns
.x
[i
] / normalize
,
644 model
.combined_state
[0].strength_solver
.eqns
.x
[i
] / normalize
,
648 aom_noise_strength_lut_t lut
;
649 aom_noise_strength_solver_fit_piecewise(
650 &model
.latest_state
[0].strength_solver
, -1, &lut
);
651 ASSERT_EQ(2, lut
.num_points
);
652 EXPECT_NEAR(0.0, lut
.points
[0][0], 1e-5);
653 EXPECT_NEAR(1.0, lut
.points
[0][1] / normalize
, kStdEps
);
654 EXPECT_NEAR((1 << this->kBitDepth
) - 1, lut
.points
[1][0], 1e-5);
655 EXPECT_NEAR(1.0, lut
.points
[1][1] / normalize
, kStdEps
);
656 aom_noise_strength_lut_free(&lut
);
659 TYPED_TEST_P(NoiseModelUpdateTest
, UpdateSuccessForScaledWhiteNoise
) {
660 aom_noise_model_t
&model
= this->model_
;
661 const int kWidth
= this->kWidth
;
662 const int kHeight
= this->kHeight
;
664 const double kCoeffEps
= 0.055;
665 const double kLowStd
= 1;
666 const double kHighStd
= 4;
667 const int shift
= this->kBitDepth
- 8;
668 for (int y
= 0; y
< kHeight
; ++y
) {
669 for (int x
= 0; x
< kWidth
; ++x
) {
670 for (int c
= 0; c
< 3; ++c
) {
671 // The image data is bimodal:
672 // Bottom half has low intensity and low noise strength
673 // Top half has high intensity and high noise strength
674 const int avg
= (y
< kHeight
/ 2) ? 4 : 245;
675 const double std
= (y
< kHeight
/ 2) ? kLowStd
: kHighStd
;
676 this->data_ptr_
[c
][y
* kWidth
+ x
] =
677 ((uint8_t)std::min((int)255,
678 (int)(2 + avg
+ randn(&this->random_
, std
))))
680 this->denoised_ptr_
[c
][y
* kWidth
+ x
] = (2 + avg
) << shift
;
684 // Label all blocks as flat for the update
685 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
686 EXPECT_EQ(AOM_NOISE_STATUS_OK
, this->NoiseModelUpdate());
688 const int n
= model
.n
;
689 // The noise is uncorrelated spatially and with the y channel.
690 // All coefficients should be reasonably close to zero.
691 for (int c
= 0; c
< 3; ++c
) {
692 for (int i
= 0; i
< n
; ++i
) {
693 EXPECT_NEAR(0, model
.latest_state
[c
].eqns
.x
[i
], kCoeffEps
);
694 EXPECT_NEAR(0, model
.combined_state
[c
].eqns
.x
[i
], kCoeffEps
);
697 ASSERT_EQ(n
+ 1, model
.latest_state
[c
].eqns
.n
);
698 ASSERT_EQ(n
+ 1, model
.combined_state
[c
].eqns
.n
);
700 // The correlation to the y channel should be low (near zero)
701 EXPECT_NEAR(0, model
.latest_state
[c
].eqns
.x
[n
], kCoeffEps
);
702 EXPECT_NEAR(0, model
.combined_state
[c
].eqns
.x
[n
], kCoeffEps
);
706 // Noise strength should vary between kLowStd and kHighStd.
707 const double kStdEps
= 0.15;
708 // We have to normalize fitted standard deviation based on bit depth.
709 const double normalize
= (1 << shift
);
711 ASSERT_EQ(20, model
.latest_state
[0].strength_solver
.eqns
.n
);
712 for (int i
= 0; i
< model
.latest_state
[0].strength_solver
.eqns
.n
; ++i
) {
713 const double a
= i
/ 19.0;
714 const double expected
= (kLowStd
* (1.0 - a
) + kHighStd
* a
);
715 EXPECT_NEAR(expected
,
716 model
.latest_state
[0].strength_solver
.eqns
.x
[i
] / normalize
,
718 EXPECT_NEAR(expected
,
719 model
.combined_state
[0].strength_solver
.eqns
.x
[i
] / normalize
,
723 // If we fit a piecewise linear model, there should be two points:
724 // one near kLowStd at 0, and the other near kHighStd and 255.
725 aom_noise_strength_lut_t lut
;
726 aom_noise_strength_solver_fit_piecewise(
727 &model
.latest_state
[0].strength_solver
, 2, &lut
);
728 ASSERT_EQ(2, lut
.num_points
);
729 EXPECT_NEAR(0, lut
.points
[0][0], 1e-4);
730 EXPECT_NEAR(kLowStd
, lut
.points
[0][1] / normalize
, kStdEps
);
731 EXPECT_NEAR((1 << this->kBitDepth
) - 1, lut
.points
[1][0], 1e-5);
732 EXPECT_NEAR(kHighStd
, lut
.points
[1][1] / normalize
, kStdEps
);
733 aom_noise_strength_lut_free(&lut
);
736 TYPED_TEST_P(NoiseModelUpdateTest
, UpdateSuccessForCorrelatedNoise
) {
737 aom_noise_model_t
&model
= this->model_
;
738 const int kWidth
= this->kWidth
;
739 const int kHeight
= this->kHeight
;
740 const int kNumCoeffs
= 24;
741 const double kStd
= 4;
742 const double kStdEps
= 0.3;
743 const double kCoeffEps
= 0.065;
744 // Use different coefficients for each channel
745 const double kCoeffs
[3][24] = {
746 { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620,
747 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571,
748 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968,
749 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 },
750 { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477,
751 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336,
752 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903,
753 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 },
754 { -0.00643, -0.01080, -0.01466, 0.06951, 0.03707, -0.00482,
755 0.00817, -0.00909, 0.02949, 0.12181, -0.25210, -0.07886,
756 0.06083, -0.01210, -0.03108, 0.08944, -0.35875, 0.49150,
757 0.00415, -0.12905, 0.02870, 0.09740, -0.34610, 0.58824 },
760 ASSERT_EQ(model
.n
, kNumCoeffs
);
761 this->chroma_sub_
[0] = this->chroma_sub_
[1] = 1;
763 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
765 // Add different noise onto each plane
766 const int shift
= this->kBitDepth
- 8;
767 for (int c
= 0; c
< 3; ++c
) {
768 noise_synth(&this->random_
, model
.params
.lag
, model
.n
, model
.coords
,
769 kCoeffs
[c
], this->noise_ptr_
[c
], kWidth
, kHeight
);
770 const int x_shift
= c
> 0 ? this->chroma_sub_
[0] : 0;
771 const int y_shift
= c
> 0 ? this->chroma_sub_
[1] : 0;
772 for (int y
= 0; y
< (kHeight
>> y_shift
); ++y
) {
773 for (int x
= 0; x
< (kWidth
>> x_shift
); ++x
) {
774 const uint8_t value
= 64 + x
/ 2 + y
/ 4;
775 this->data_ptr_
[c
][y
* kWidth
+ x
] =
776 (uint8_t(value
+ this->noise_ptr_
[c
][y
* kWidth
+ x
] * kStd
))
778 this->denoised_ptr_
[c
][y
* kWidth
+ x
] = value
<< shift
;
782 EXPECT_EQ(AOM_NOISE_STATUS_OK
, this->NoiseModelUpdate());
784 // For the Y plane, the solved coefficients should be close to the original
785 const int n
= model
.n
;
786 for (int c
= 0; c
< 3; ++c
) {
787 for (int i
= 0; i
< n
; ++i
) {
788 EXPECT_NEAR(kCoeffs
[c
][i
], model
.latest_state
[c
].eqns
.x
[i
], kCoeffEps
);
789 EXPECT_NEAR(kCoeffs
[c
][i
], model
.combined_state
[c
].eqns
.x
[i
], kCoeffEps
);
791 // The chroma planes should be uncorrelated with the luma plane
793 EXPECT_NEAR(0, model
.latest_state
[c
].eqns
.x
[n
], kCoeffEps
);
794 EXPECT_NEAR(0, model
.combined_state
[c
].eqns
.x
[n
], kCoeffEps
);
796 // Correlation between the coefficient vector and the fitted coefficients
797 // should be close to 1.
798 EXPECT_LT(0.98, aom_normalized_cross_correlation(
799 model
.latest_state
[c
].eqns
.x
, kCoeffs
[c
], kNumCoeffs
));
801 noise_synth(&this->random_
, model
.params
.lag
, model
.n
, model
.coords
,
802 model
.latest_state
[c
].eqns
.x
, &this->renoise_
[0], kWidth
,
805 EXPECT_TRUE(aom_noise_data_validate(&this->renoise_
[0], kWidth
, kHeight
));
808 // Check fitted noise strength
809 const double normalize
= 1 << shift
;
810 for (int c
= 0; c
< 3; ++c
) {
811 for (int i
= 0; i
< model
.latest_state
[c
].strength_solver
.eqns
.n
; ++i
) {
813 model
.latest_state
[c
].strength_solver
.eqns
.x
[i
] / normalize
,
819 TYPED_TEST_P(NoiseModelUpdateTest
,
820 NoiseStrengthChangeSignalsDifferentNoiseType
) {
821 aom_noise_model_t
&model
= this->model_
;
822 const int kWidth
= this->kWidth
;
823 const int kHeight
= this->kHeight
;
824 const int kBlockSize
= this->kBlockSize
;
825 // Create a gradient image with std = 2 uncorrelated noise
826 const double kStd
= 2;
827 const int shift
= this->kBitDepth
- 8;
829 for (int i
= 0; i
< kWidth
* kHeight
; ++i
) {
830 const uint8_t val
= (i
% kWidth
) < kWidth
/ 2 ? 64 : 192;
831 for (int c
= 0; c
< 3; ++c
) {
832 this->noise_ptr_
[c
][i
] = randn(&this->random_
, 1);
833 this->data_ptr_
[c
][i
] = ((uint8_t)(this->noise_ptr_
[c
][i
] * kStd
+ val
))
835 this->denoised_ptr_
[c
][i
] = val
<< shift
;
838 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
839 EXPECT_EQ(AOM_NOISE_STATUS_OK
, this->NoiseModelUpdate());
841 const int kNumBlocks
= kWidth
* kHeight
/ kBlockSize
/ kBlockSize
;
842 EXPECT_EQ(kNumBlocks
, model
.latest_state
[0].strength_solver
.num_equations
);
843 EXPECT_EQ(kNumBlocks
, model
.latest_state
[1].strength_solver
.num_equations
);
844 EXPECT_EQ(kNumBlocks
, model
.latest_state
[2].strength_solver
.num_equations
);
845 EXPECT_EQ(kNumBlocks
, model
.combined_state
[0].strength_solver
.num_equations
);
846 EXPECT_EQ(kNumBlocks
, model
.combined_state
[1].strength_solver
.num_equations
);
847 EXPECT_EQ(kNumBlocks
, model
.combined_state
[2].strength_solver
.num_equations
);
849 // Bump up noise by an insignificant amount
850 for (int i
= 0; i
< kWidth
* kHeight
; ++i
) {
851 const uint8_t val
= (i
% kWidth
) < kWidth
/ 2 ? 64 : 192;
852 this->data_ptr_
[0][i
] =
853 ((uint8_t)(this->noise_ptr_
[0][i
] * (kStd
+ 0.085) + val
)) << shift
;
855 EXPECT_EQ(AOM_NOISE_STATUS_OK
, this->NoiseModelUpdate());
857 const double kARGainTolerance
= 0.02;
858 for (int c
= 0; c
< 3; ++c
) {
859 EXPECT_EQ(kNumBlocks
, model
.latest_state
[c
].strength_solver
.num_equations
);
860 EXPECT_EQ(15250, model
.latest_state
[c
].num_observations
);
861 EXPECT_NEAR(1, model
.latest_state
[c
].ar_gain
, kARGainTolerance
);
863 EXPECT_EQ(2 * kNumBlocks
,
864 model
.combined_state
[c
].strength_solver
.num_equations
);
865 EXPECT_EQ(2 * 15250, model
.combined_state
[c
].num_observations
);
866 EXPECT_NEAR(1, model
.combined_state
[c
].ar_gain
, kARGainTolerance
);
869 // Bump up the noise strength on half the image for one channel by a
870 // significant amount.
871 for (int i
= 0; i
< kWidth
* kHeight
; ++i
) {
872 const uint8_t val
= (i
% kWidth
) < kWidth
/ 2 ? 64 : 128;
873 if (i
% kWidth
< kWidth
/ 2) {
874 this->data_ptr_
[0][i
] =
875 ((uint8_t)(randn(&this->random_
, kStd
+ 0.5) + val
)) << shift
;
878 EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE
, this->NoiseModelUpdate());
880 // Since we didn't update the combined state, it should still be at 2 *
882 EXPECT_EQ(kNumBlocks
, model
.latest_state
[0].strength_solver
.num_equations
);
883 EXPECT_EQ(2 * kNumBlocks
,
884 model
.combined_state
[0].strength_solver
.num_equations
);
886 // In normal operation, the "latest" estimate can be saved to the "combined"
887 // state for continued updates.
888 aom_noise_model_save_latest(&model
);
889 for (int c
= 0; c
< 3; ++c
) {
890 EXPECT_EQ(kNumBlocks
, model
.latest_state
[c
].strength_solver
.num_equations
);
891 EXPECT_EQ(15250, model
.latest_state
[c
].num_observations
);
892 EXPECT_NEAR(1, model
.latest_state
[c
].ar_gain
, kARGainTolerance
);
894 EXPECT_EQ(kNumBlocks
,
895 model
.combined_state
[c
].strength_solver
.num_equations
);
896 EXPECT_EQ(15250, model
.combined_state
[c
].num_observations
);
897 EXPECT_NEAR(1, model
.combined_state
[c
].ar_gain
, kARGainTolerance
);
901 TYPED_TEST_P(NoiseModelUpdateTest
, NoiseCoeffsSignalsDifferentNoiseType
) {
902 aom_noise_model_t
&model
= this->model_
;
903 const int kWidth
= this->kWidth
;
904 const int kHeight
= this->kHeight
;
905 const double kCoeffs
[2][24] = {
906 { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620,
907 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571,
908 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968,
909 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 },
910 { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477,
911 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336,
912 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903,
913 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 }
916 noise_synth(&this->random_
, model
.params
.lag
, model
.n
, model
.coords
,
917 kCoeffs
[0], this->noise_ptr_
[0], kWidth
, kHeight
);
918 for (int i
= 0; i
< kWidth
* kHeight
; ++i
) {
919 this->data_ptr_
[0][i
] = (uint8_t)(128 + this->noise_ptr_
[0][i
]);
921 this->flat_blocks_
.assign(this->flat_blocks_
.size(), 1);
922 EXPECT_EQ(AOM_NOISE_STATUS_OK
, this->NoiseModelUpdate());
924 // Now try with the second set of AR coefficients
925 noise_synth(&this->random_
, model
.params
.lag
, model
.n
, model
.coords
,
926 kCoeffs
[1], this->noise_ptr_
[0], kWidth
, kHeight
);
927 for (int i
= 0; i
< kWidth
* kHeight
; ++i
) {
928 this->data_ptr_
[0][i
] = (uint8_t)(128 + this->noise_ptr_
[0][i
]);
930 EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE
, this->NoiseModelUpdate());
932 REGISTER_TYPED_TEST_CASE_P(NoiseModelUpdateTest
, UpdateFailsNoFlatBlocks
,
933 UpdateSuccessForZeroNoiseAllFlat
,
934 UpdateFailsBlockSizeTooSmall
,
935 UpdateSuccessForWhiteRandomNoise
,
936 UpdateSuccessForScaledWhiteNoise
,
937 UpdateSuccessForCorrelatedNoise
,
938 NoiseStrengthChangeSignalsDifferentNoiseType
,
939 NoiseCoeffsSignalsDifferentNoiseType
);
941 INSTANTIATE_TYPED_TEST_CASE_P(NoiseModelUpdateTestInstatiation
,
942 NoiseModelUpdateTest
, AllBitDepthParams
);
944 TEST(NoiseModelGetGrainParameters
, TestLagSize
) {
945 aom_film_grain_t film_grain
;
946 for (int lag
= 1; lag
<= 3; ++lag
) {
947 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, lag
, 8, 0 };
948 aom_noise_model_t model
;
949 EXPECT_TRUE(aom_noise_model_init(&model
, params
));
950 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model
, &film_grain
));
951 EXPECT_EQ(lag
, film_grain
.ar_coeff_lag
);
952 aom_noise_model_free(&model
);
955 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, 4, 8, 0 };
956 aom_noise_model_t model
;
957 EXPECT_TRUE(aom_noise_model_init(&model
, params
));
958 EXPECT_FALSE(aom_noise_model_get_grain_parameters(&model
, &film_grain
));
959 aom_noise_model_free(&model
);
962 TEST(NoiseModelGetGrainParameters
, TestARCoeffShiftBounds
) {
964 double max_input_value
;
965 int expected_ar_coeff_shift
;
969 const int kNumTestCases
= 19;
970 const TestCase test_cases
[] = {
971 // Test cases for ar_coeff_shift = 9
977 // Test cases for ar_coeff_shift = 8
982 // Test cases for ar_coeff_shift = 7
987 // Test cases for ar_coeff_shift = 6
995 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, lag
, 8, 0 };
996 aom_noise_model_t model
;
997 EXPECT_TRUE(aom_noise_model_init(&model
, params
));
999 for (int i
= 0; i
< kNumTestCases
; ++i
) {
1000 const TestCase
&test_case
= test_cases
[i
];
1001 model
.combined_state
[0].eqns
.x
[0] = test_case
.max_input_value
;
1003 aom_film_grain_t film_grain
;
1004 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model
, &film_grain
));
1005 EXPECT_EQ(1, film_grain
.ar_coeff_lag
);
1006 EXPECT_EQ(test_case
.expected_ar_coeff_shift
, film_grain
.ar_coeff_shift
);
1007 EXPECT_EQ(test_case
.expected_value
, film_grain
.ar_coeffs_y
[0]);
1009 aom_noise_model_free(&model
);
1012 TEST(NoiseModelGetGrainParameters
, TestNoiseStrengthShiftBounds
) {
1014 double max_input_value
;
1015 int expected_scaling_shift
;
1018 const int kNumTestCases
= 10;
1019 const TestCase test_cases
[] = {
1020 { 0, 11, 0 }, { 1, 11, 64 }, { 2, 11, 128 }, { 3.99, 11, 255 },
1021 { 4, 10, 128 }, { 7.99, 10, 255 }, { 8, 9, 128 }, { 16, 8, 128 },
1022 { 31.99, 8, 255 }, { 64, 8, 255 }, // clipped
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
));
1029 for (int i
= 0; i
< kNumTestCases
; ++i
) {
1030 const TestCase
&test_case
= test_cases
[i
];
1031 aom_equation_system_t
&eqns
= model
.combined_state
[0].strength_solver
.eqns
;
1032 // Set the fitted scale parameters to be a constant value.
1033 for (int j
= 0; j
< eqns
.n
; ++j
) {
1034 eqns
.x
[j
] = test_case
.max_input_value
;
1036 aom_film_grain_t film_grain
;
1037 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model
, &film_grain
));
1038 // We expect a single constant segemnt
1039 EXPECT_EQ(test_case
.expected_scaling_shift
, film_grain
.scaling_shift
);
1040 EXPECT_EQ(test_case
.expected_value
, film_grain
.scaling_points_y
[0][1]);
1041 EXPECT_EQ(test_case
.expected_value
, film_grain
.scaling_points_y
[1][1]);
1043 aom_noise_model_free(&model
);
1046 // The AR coefficients are the same inputs used to generate "Test 2" in the test
1048 TEST(NoiseModelGetGrainParameters
, GetGrainParametersReal
) {
1049 const double kInputCoeffsY
[] = { 0.0315, 0.0073, 0.0218, 0.00235, 0.00511,
1050 -0.0222, 0.0627, -0.022, 0.05575, -0.1816,
1051 0.0107, -0.1966, 0.00065, -0.0809, 0.04934,
1052 -0.1349, -0.0352, 0.41772, 0.27973, 0.04207,
1053 -0.0429, -0.1372, 0.06193, 0.52032 };
1054 const double kInputCoeffsCB
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1055 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5 };
1056 const double kInputCoeffsCR
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1057 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.5 };
1058 const int kExpectedARCoeffsY
[] = { 4, 1, 3, 0, 1, -3, 8, -3,
1059 7, -23, 1, -25, 0, -10, 6, -17,
1060 -5, 53, 36, 5, -5, -18, 8, 67 };
1061 const int kExpectedARCoeffsCB
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1062 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 };
1063 const int kExpectedARCoeffsCR
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1064 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -126 };
1065 // Scaling function is initialized analytically with a sqrt function.
1066 const int kNumScalingPointsY
= 12;
1067 const int kExpectedScalingPointsY
[][2] = {
1068 { 0, 0 }, { 13, 44 }, { 27, 62 }, { 40, 76 },
1069 { 54, 88 }, { 67, 98 }, { 94, 117 }, { 121, 132 },
1070 { 148, 146 }, { 174, 159 }, { 201, 171 }, { 255, 192 },
1074 aom_noise_model_params_t params
= { AOM_NOISE_SHAPE_SQUARE
, lag
, 8, 0 };
1075 aom_noise_model_t model
;
1076 EXPECT_TRUE(aom_noise_model_init(&model
, params
));
1078 // Setup the AR coeffs
1079 memcpy(model
.combined_state
[0].eqns
.x
, kInputCoeffsY
, sizeof(kInputCoeffsY
));
1080 memcpy(model
.combined_state
[1].eqns
.x
, kInputCoeffsCB
,
1081 sizeof(kInputCoeffsCB
));
1082 memcpy(model
.combined_state
[2].eqns
.x
, kInputCoeffsCR
,
1083 sizeof(kInputCoeffsCR
));
1084 for (int i
= 0; i
< model
.combined_state
[0].strength_solver
.num_bins
; ++i
) {
1086 ((double)i
) / (model
.combined_state
[0].strength_solver
.num_bins
- 1.0);
1087 model
.combined_state
[0].strength_solver
.eqns
.x
[i
] = 6 * sqrt(x
);
1088 model
.combined_state
[1].strength_solver
.eqns
.x
[i
] = 3;
1089 model
.combined_state
[2].strength_solver
.eqns
.x
[i
] = 2;
1091 // Inject some observations into the strength solver, as during film grain
1092 // parameter extraction an estimate of the average strength will be used to
1093 // adjust correlation.
1094 const int n
= model
.combined_state
[0].strength_solver
.num_bins
;
1095 for (int j
= 0; j
< model
.combined_state
[0].strength_solver
.num_bins
; ++j
) {
1096 model
.combined_state
[0].strength_solver
.eqns
.A
[i
* n
+ j
] = 1;
1097 model
.combined_state
[1].strength_solver
.eqns
.A
[i
* n
+ j
] = 1;
1098 model
.combined_state
[2].strength_solver
.eqns
.A
[i
* n
+ j
] = 1;
1102 aom_film_grain_t film_grain
;
1103 EXPECT_TRUE(aom_noise_model_get_grain_parameters(&model
, &film_grain
));
1104 EXPECT_EQ(lag
, film_grain
.ar_coeff_lag
);
1105 EXPECT_EQ(3, film_grain
.ar_coeff_lag
);
1106 EXPECT_EQ(7, film_grain
.ar_coeff_shift
);
1107 EXPECT_EQ(10, film_grain
.scaling_shift
);
1108 EXPECT_EQ(kNumScalingPointsY
, film_grain
.num_y_points
);
1109 EXPECT_EQ(1, film_grain
.update_parameters
);
1110 EXPECT_EQ(1, film_grain
.apply_grain
);
1112 const int kNumARCoeffs
= 24;
1113 for (int i
= 0; i
< kNumARCoeffs
; ++i
) {
1114 EXPECT_EQ(kExpectedARCoeffsY
[i
], film_grain
.ar_coeffs_y
[i
]);
1116 for (int i
= 0; i
< kNumARCoeffs
+ 1; ++i
) {
1117 EXPECT_EQ(kExpectedARCoeffsCB
[i
], film_grain
.ar_coeffs_cb
[i
]);
1119 for (int i
= 0; i
< kNumARCoeffs
+ 1; ++i
) {
1120 EXPECT_EQ(kExpectedARCoeffsCR
[i
], film_grain
.ar_coeffs_cr
[i
]);
1122 for (int i
= 0; i
< kNumScalingPointsY
; ++i
) {
1123 EXPECT_EQ(kExpectedScalingPointsY
[i
][0], film_grain
.scaling_points_y
[i
][0]);
1124 EXPECT_EQ(kExpectedScalingPointsY
[i
][1], film_grain
.scaling_points_y
[i
][1]);
1127 // CB strength should just be a piecewise segment
1128 EXPECT_EQ(2, film_grain
.num_cb_points
);
1129 EXPECT_EQ(0, film_grain
.scaling_points_cb
[0][0]);
1130 EXPECT_EQ(255, film_grain
.scaling_points_cb
[1][0]);
1131 EXPECT_EQ(96, film_grain
.scaling_points_cb
[0][1]);
1132 EXPECT_EQ(96, film_grain
.scaling_points_cb
[1][1]);
1134 // CR strength should just be a piecewise segment
1135 EXPECT_EQ(2, film_grain
.num_cr_points
);
1136 EXPECT_EQ(0, film_grain
.scaling_points_cr
[0][0]);
1137 EXPECT_EQ(255, film_grain
.scaling_points_cr
[1][0]);
1138 EXPECT_EQ(64, film_grain
.scaling_points_cr
[0][1]);
1139 EXPECT_EQ(64, film_grain
.scaling_points_cr
[1][1]);
1141 EXPECT_EQ(128, film_grain
.cb_mult
);
1142 EXPECT_EQ(192, film_grain
.cb_luma_mult
);
1143 EXPECT_EQ(256, film_grain
.cb_offset
);
1144 EXPECT_EQ(128, film_grain
.cr_mult
);
1145 EXPECT_EQ(192, film_grain
.cr_luma_mult
);
1146 EXPECT_EQ(256, film_grain
.cr_offset
);
1147 EXPECT_EQ(0, film_grain
.chroma_scaling_from_luma
);
1148 EXPECT_EQ(0, film_grain
.grain_scale_shift
);
1150 aom_noise_model_free(&model
);
1153 template <typename T
>
1154 class WienerDenoiseTest
: public ::testing::Test
, public T
{
1156 static void SetUpTestCase() { aom_dsp_rtcd(); }
1160 static const float kNoiseLevel
= 5.f
;
1161 static const float kStd
= 4.0;
1162 static const double kMaxValue
= (1 << T::kBitDepth
) - 1;
1166 stride_
[0] = kWidth
;
1167 stride_
[1] = kWidth
/ 2;
1168 stride_
[2] = kWidth
/ 2;
1169 for (int k
= 0; k
< 3; ++k
) {
1170 data_
[k
].resize(kWidth
* kHeight
);
1171 denoised_
[k
].resize(kWidth
* kHeight
);
1172 noise_psd_
[k
].resize(kBlockSize
* kBlockSize
);
1175 const double kCoeffsY
[] = { 0.0406, -0.116, -0.078, -0.152, 0.0033, -0.093,
1176 0.048, 0.404, 0.2353, -0.035, -0.093, 0.441 };
1177 const int kCoords
[12][2] = {
1178 { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 },
1179 { -1, -1 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 }
1182 const int kLength
= 12;
1183 libaom_test::ACMRandom random
;
1184 std::vector
<double> noise(kWidth
* kHeight
);
1185 noise_synth(&random
, kLag
, kLength
, kCoords
, kCoeffsY
, &noise
[0], kWidth
,
1187 noise_psd_
[0] = get_noise_psd(&noise
[0], kWidth
, kHeight
, kBlockSize
);
1188 for (int i
= 0; i
< kBlockSize
* kBlockSize
; ++i
) {
1189 noise_psd_
[0][i
] = (float)(noise_psd_
[0][i
] * kStd
* kStd
* kScaleNoise
*
1190 kScaleNoise
/ (kMaxValue
* kMaxValue
));
1194 aom_noise_psd_get_default_value(kBlockSizeChroma
, kNoiseLevel
);
1195 for (int i
= 0; i
< kBlockSizeChroma
* kBlockSizeChroma
; ++i
) {
1196 noise_psd_
[1][i
] = psd_value
;
1197 noise_psd_
[2][i
] = psd_value
;
1199 for (int y
= 0; y
< kHeight
; ++y
) {
1200 for (int x
= 0; x
< kWidth
; ++x
) {
1201 data_
[0][y
* stride_
[0] + x
] = (typename
T::data_type_t
)fclamp(
1202 (x
+ noise
[y
* stride_
[0] + x
] * kStd
) * kScaleNoise
, 0, kMaxValue
);
1206 for (int c
= 1; c
< 3; ++c
) {
1207 for (int y
= 0; y
< (kHeight
>> 1); ++y
) {
1208 for (int x
= 0; x
< (kWidth
>> 1); ++x
) {
1209 data_
[c
][y
* stride_
[c
] + x
] = (typename
T::data_type_t
)fclamp(
1210 (x
+ randn(&random
, kStd
)) * kScaleNoise
, 0, kMaxValue
);
1214 for (int k
= 0; k
< 3; ++k
) {
1215 noise_psd_ptrs_
[k
] = &noise_psd_
[k
][0];
1218 static const int kBlockSize
= 32;
1219 static const int kBlockSizeChroma
= 16;
1220 static const int kWidth
= 256;
1221 static const int kHeight
= 256;
1222 static const int kScaleNoise
= 1 << (T::kBitDepth
- 8);
1224 std::vector
<typename
T::data_type_t
> data_
[3];
1225 std::vector
<typename
T::data_type_t
> denoised_
[3];
1226 std::vector
<float> noise_psd_
[3];
1228 float *noise_psd_ptrs_
[3];
1232 TYPED_TEST_CASE_P(WienerDenoiseTest
);
1234 TYPED_TEST_P(WienerDenoiseTest
, InvalidBlockSize
) {
1235 const uint8_t *const data_ptrs
[3] = {
1236 reinterpret_cast<uint8_t *>(&this->data_
[0][0]),
1237 reinterpret_cast<uint8_t *>(&this->data_
[1][0]),
1238 reinterpret_cast<uint8_t *>(&this->data_
[2][0]),
1240 uint8_t *denoised_ptrs
[3] = {
1241 reinterpret_cast<uint8_t *>(&this->denoised_
[0][0]),
1242 reinterpret_cast<uint8_t *>(&this->denoised_
[1][0]),
1243 reinterpret_cast<uint8_t *>(&this->denoised_
[2][0]),
1245 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs
, denoised_ptrs
, this->kWidth
,
1246 this->kHeight
, this->stride_
,
1247 this->chroma_sub_
, this->noise_psd_ptrs_
,
1248 18, this->kBitDepth
, this->kUseHighBD
));
1249 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs
, denoised_ptrs
, this->kWidth
,
1250 this->kHeight
, this->stride_
,
1251 this->chroma_sub_
, this->noise_psd_ptrs_
,
1252 48, this->kBitDepth
, this->kUseHighBD
));
1253 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs
, denoised_ptrs
, this->kWidth
,
1254 this->kHeight
, this->stride_
,
1255 this->chroma_sub_
, this->noise_psd_ptrs_
,
1256 64, this->kBitDepth
, this->kUseHighBD
));
1259 TYPED_TEST_P(WienerDenoiseTest
, InvalidChromaSubsampling
) {
1260 const uint8_t *const data_ptrs
[3] = {
1261 reinterpret_cast<uint8_t *>(&this->data_
[0][0]),
1262 reinterpret_cast<uint8_t *>(&this->data_
[1][0]),
1263 reinterpret_cast<uint8_t *>(&this->data_
[2][0]),
1265 uint8_t *denoised_ptrs
[3] = {
1266 reinterpret_cast<uint8_t *>(&this->denoised_
[0][0]),
1267 reinterpret_cast<uint8_t *>(&this->denoised_
[1][0]),
1268 reinterpret_cast<uint8_t *>(&this->denoised_
[2][0]),
1270 int chroma_sub
[2] = { 1, 0 };
1271 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs
, denoised_ptrs
, this->kWidth
,
1272 this->kHeight
, this->stride_
, chroma_sub
,
1273 this->noise_psd_ptrs_
, 32, this->kBitDepth
,
1278 EXPECT_EQ(0, aom_wiener_denoise_2d(data_ptrs
, denoised_ptrs
, this->kWidth
,
1279 this->kHeight
, this->stride_
, chroma_sub
,
1280 this->noise_psd_ptrs_
, 32, this->kBitDepth
,
1284 TYPED_TEST_P(WienerDenoiseTest
, GradientTest
) {
1285 const int kWidth
= this->kWidth
;
1286 const int kHeight
= this->kHeight
;
1287 const int kBlockSize
= this->kBlockSize
;
1288 const uint8_t *const data_ptrs
[3] = {
1289 reinterpret_cast<uint8_t *>(&this->data_
[0][0]),
1290 reinterpret_cast<uint8_t *>(&this->data_
[1][0]),
1291 reinterpret_cast<uint8_t *>(&this->data_
[2][0]),
1293 uint8_t *denoised_ptrs
[3] = {
1294 reinterpret_cast<uint8_t *>(&this->denoised_
[0][0]),
1295 reinterpret_cast<uint8_t *>(&this->denoised_
[1][0]),
1296 reinterpret_cast<uint8_t *>(&this->denoised_
[2][0]),
1298 const int ret
= aom_wiener_denoise_2d(
1299 data_ptrs
, denoised_ptrs
, kWidth
, kHeight
, this->stride_
,
1300 this->chroma_sub_
, this->noise_psd_ptrs_
, this->kBlockSize
,
1301 this->kBitDepth
, this->kUseHighBD
);
1304 // Check the noise on the denoised image (from the analytical gradient)
1305 // and make sure that it is less than what we added.
1306 for (int c
= 0; c
< 3; ++c
) {
1307 std::vector
<double> measured_noise(kWidth
* kHeight
);
1310 const int shift
= (c
> 0);
1311 for (int x
= 0; x
< (kWidth
>> shift
); ++x
) {
1312 for (int y
= 0; y
< (kHeight
>> shift
); ++y
) {
1313 const double diff
= this->denoised_
[c
][y
* this->stride_
[c
] + x
] -
1314 x
* this->kScaleNoise
;
1316 measured_noise
[y
* kWidth
+ x
] = diff
;
1319 var
/= (kWidth
* kHeight
);
1320 const double std
= sqrt(std::max(0.0, var
));
1321 EXPECT_LE(std
, 1.25f
* this->kScaleNoise
);
1323 std::vector
<float> measured_psd
=
1324 get_noise_psd(&measured_noise
[0], kWidth
, kHeight
, kBlockSize
);
1325 std::vector
<double> measured_psd_d(kBlockSize
* kBlockSize
);
1326 std::vector
<double> noise_psd_d(kBlockSize
* kBlockSize
);
1327 std::copy(measured_psd
.begin(), measured_psd
.end(),
1328 measured_psd_d
.begin());
1329 std::copy(this->noise_psd_
[0].begin(), this->noise_psd_
[0].end(),
1330 noise_psd_d
.begin());
1332 aom_normalized_cross_correlation(&measured_psd_d
[0], &noise_psd_d
[0],
1333 (int)(noise_psd_d
.size())),
1339 REGISTER_TYPED_TEST_CASE_P(WienerDenoiseTest
, InvalidBlockSize
,
1340 InvalidChromaSubsampling
, GradientTest
);
1342 INSTANTIATE_TYPED_TEST_CASE_P(WienerDenoiseTestInstatiation
, WienerDenoiseTest
,