Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 1 | #include <algorithm> |
| 2 | #include <vector> |
| 3 | |
| 4 | #include "./aom_dsp/noise_model.h" |
| 5 | #include "./aom_dsp/noise_util.h" |
| 6 | #include "third_party/googletest/src/googletest/include/gtest/gtest.h" |
| 7 | |
| 8 | extern "C" double aom_randn(double sigma); |
| 9 | |
| 10 | TEST(NoiseStrengthSolver, GetCentersTwoBins) { |
| 11 | aom_noise_strength_solver_t solver; |
| 12 | aom_noise_strength_solver_init(&solver, 2); |
| 13 | EXPECT_NEAR(0, aom_noise_strength_solver_get_center(&solver, 0), 1e-5); |
| 14 | EXPECT_NEAR(255, aom_noise_strength_solver_get_center(&solver, 1), 1e-5); |
| 15 | aom_noise_strength_solver_free(&solver); |
| 16 | } |
| 17 | |
| 18 | TEST(NoiseStrengthSolver, GetCenters256Bins) { |
| 19 | const int num_bins = 256; |
| 20 | aom_noise_strength_solver_t solver; |
| 21 | aom_noise_strength_solver_init(&solver, num_bins); |
| 22 | |
| 23 | for (int i = 0; i < 256; ++i) { |
| 24 | EXPECT_NEAR(i, aom_noise_strength_solver_get_center(&solver, i), 1e-5); |
| 25 | } |
| 26 | aom_noise_strength_solver_free(&solver); |
| 27 | } |
| 28 | |
| 29 | // Tests that the noise strength solver returns the identity transform when |
| 30 | // given identity-like constraints. |
| 31 | TEST(NoiseStrengthSolver, ObserveIdentity) { |
| 32 | const int num_bins = 256; |
| 33 | aom_noise_strength_solver_t solver; |
| 34 | EXPECT_EQ(1, aom_noise_strength_solver_init(&solver, num_bins)); |
| 35 | |
| 36 | // We have to add a big more strength to constraints at the boundary to |
| 37 | // overcome any regularization. |
| 38 | for (int j = 0; j < 5; ++j) { |
| 39 | aom_noise_strength_solver_add_measurement(&solver, 0, 0); |
| 40 | aom_noise_strength_solver_add_measurement(&solver, 255, 255); |
| 41 | } |
| 42 | for (int i = 0; i < 256; ++i) { |
| 43 | aom_noise_strength_solver_add_measurement(&solver, i, i); |
| 44 | } |
| 45 | EXPECT_EQ(1, aom_noise_strength_solver_solve(&solver)); |
| 46 | for (int i = 2; i < num_bins - 2; ++i) { |
| 47 | EXPECT_NEAR(i, solver.eqns.x[i], 0.1); |
| 48 | } |
| 49 | |
| 50 | aom_noise_strength_lut_t lut; |
| 51 | EXPECT_EQ(1, aom_noise_strength_solver_fit_piecewise(&solver, &lut)); |
| 52 | |
| 53 | ASSERT_EQ(2, lut.num_points); |
| 54 | EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); |
| 55 | EXPECT_NEAR(0.0, lut.points[0][1], 0.5); |
| 56 | EXPECT_NEAR(255.0, lut.points[1][0], 1e-5); |
| 57 | EXPECT_NEAR(255.0, lut.points[1][1], 0.5); |
| 58 | |
| 59 | aom_noise_strength_lut_free(&lut); |
| 60 | aom_noise_strength_solver_free(&solver); |
| 61 | } |
| 62 | |
| 63 | TEST(NoiseStrengthLut, LutEvalSinglePoint) { |
| 64 | aom_noise_strength_lut_t lut; |
| 65 | ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 1)); |
| 66 | ASSERT_EQ(1, lut.num_points); |
| 67 | lut.points[0][0] = 0; |
| 68 | lut.points[0][1] = 1; |
| 69 | EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, -1)); |
| 70 | EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 0)); |
| 71 | EXPECT_EQ(1, aom_noise_strength_lut_eval(&lut, 1)); |
| 72 | aom_noise_strength_lut_free(&lut); |
| 73 | } |
| 74 | |
| 75 | TEST(NoiseStrengthLut, LutEvalMultiPointInterp) { |
| 76 | const double kEps = 1e-5; |
| 77 | aom_noise_strength_lut_t lut; |
| 78 | ASSERT_TRUE(aom_noise_strength_lut_init(&lut, 4)); |
| 79 | ASSERT_EQ(4, lut.num_points); |
| 80 | |
| 81 | lut.points[0][0] = 0; |
| 82 | lut.points[0][1] = 0; |
| 83 | |
| 84 | lut.points[1][0] = 1; |
| 85 | lut.points[1][1] = 1; |
| 86 | |
| 87 | lut.points[2][0] = 2; |
| 88 | lut.points[2][1] = 1; |
| 89 | |
| 90 | lut.points[3][0] = 100; |
| 91 | lut.points[3][1] = 1001; |
| 92 | |
| 93 | // Test lower boundary |
| 94 | EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, -1)); |
| 95 | EXPECT_EQ(0, aom_noise_strength_lut_eval(&lut, 0)); |
| 96 | |
| 97 | // Test first part that should be identity |
| 98 | EXPECT_NEAR(0.25, aom_noise_strength_lut_eval(&lut, 0.25), kEps); |
| 99 | EXPECT_NEAR(0.75, aom_noise_strength_lut_eval(&lut, 0.75), kEps); |
| 100 | |
| 101 | // This is a constant section (should evaluate to 1) |
| 102 | EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.25), kEps); |
| 103 | EXPECT_NEAR(1.0, aom_noise_strength_lut_eval(&lut, 1.75), kEps); |
| 104 | |
| 105 | // Test interpolation between to non-zero y coords. |
| 106 | EXPECT_NEAR(1, aom_noise_strength_lut_eval(&lut, 2), kEps); |
| 107 | EXPECT_NEAR(251, aom_noise_strength_lut_eval(&lut, 26.5), kEps); |
| 108 | EXPECT_NEAR(751, aom_noise_strength_lut_eval(&lut, 75.5), kEps); |
| 109 | |
| 110 | // Test upper boundary |
| 111 | EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 100)); |
| 112 | EXPECT_EQ(1001, aom_noise_strength_lut_eval(&lut, 101)); |
| 113 | |
| 114 | aom_noise_strength_lut_free(&lut); |
| 115 | } |
| 116 | |
| 117 | TEST(NoiseModel, InitSuccessWithValidSquareShape) { |
Yaowu Xu | 8ddc7ae | 2018-01-17 17:09:16 -0800 | [diff] [blame] | 118 | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 2 }; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 119 | aom_noise_model_t model; |
| 120 | |
| 121 | EXPECT_TRUE(aom_noise_model_init(&model, params)); |
| 122 | |
| 123 | const int kNumCoords = 12; |
| 124 | const int kCoords[][2] = { { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, |
| 125 | { 2, -2 }, { -2, -1 }, { -1, -1 }, { 0, -1 }, |
| 126 | { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 } }; |
| 127 | EXPECT_EQ(kNumCoords, model.n); |
| 128 | for (int i = 0; i < kNumCoords; ++i) { |
| 129 | const int *coord = kCoords[i]; |
| 130 | EXPECT_EQ(coord[0], model.coords[i][0]); |
| 131 | EXPECT_EQ(coord[1], model.coords[i][1]); |
| 132 | } |
| 133 | aom_noise_model_free(&model); |
| 134 | } |
| 135 | |
| 136 | TEST(NoiseModel, InitSuccessWithValidDiamondShape) { |
| 137 | aom_noise_model_t model; |
Yaowu Xu | 8ddc7ae | 2018-01-17 17:09:16 -0800 | [diff] [blame] | 138 | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_DIAMOND, 2 }; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 139 | EXPECT_TRUE(aom_noise_model_init(&model, params)); |
| 140 | EXPECT_EQ(6, model.n); |
| 141 | const int kNumCoords = 6; |
| 142 | const int kCoords[][2] = { { 0, -2 }, { -1, -1 }, { 0, -1 }, |
| 143 | { 1, -1 }, { -2, 0 }, { -1, 0 } }; |
| 144 | EXPECT_EQ(kNumCoords, model.n); |
| 145 | for (int i = 0; i < kNumCoords; ++i) { |
| 146 | const int *coord = kCoords[i]; |
| 147 | EXPECT_EQ(coord[0], model.coords[i][0]); |
| 148 | EXPECT_EQ(coord[1], model.coords[i][1]); |
| 149 | } |
| 150 | aom_noise_model_free(&model); |
| 151 | } |
| 152 | |
| 153 | TEST(NoiseModel, InitFailsWithTooLargeLag) { |
| 154 | aom_noise_model_t model; |
Yaowu Xu | 8ddc7ae | 2018-01-17 17:09:16 -0800 | [diff] [blame] | 155 | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 10 }; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 156 | EXPECT_FALSE(aom_noise_model_init(&model, params)); |
| 157 | aom_noise_model_free(&model); |
| 158 | } |
| 159 | |
| 160 | TEST(NoiseModel, InitFailsWithTooSmallLag) { |
| 161 | aom_noise_model_t model; |
Yaowu Xu | 8ddc7ae | 2018-01-17 17:09:16 -0800 | [diff] [blame] | 162 | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 0 }; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 163 | EXPECT_FALSE(aom_noise_model_init(&model, params)); |
| 164 | aom_noise_model_free(&model); |
| 165 | } |
| 166 | |
| 167 | TEST(NoiseModel, InitFailsWithInvalidShape) { |
| 168 | aom_noise_model_t model; |
Yaowu Xu | 8ddc7ae | 2018-01-17 17:09:16 -0800 | [diff] [blame] | 169 | aom_noise_model_params_t params = { aom_noise_shape(100), 3 }; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 170 | EXPECT_FALSE(aom_noise_model_init(&model, params)); |
| 171 | aom_noise_model_free(&model); |
| 172 | } |
| 173 | |
| 174 | TEST(FlatBlockEstimator, ExtractBlock) { |
| 175 | const int kBlockSize = 16; |
| 176 | aom_flat_block_finder_t flat_block_finder; |
| 177 | ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize)); |
| 178 | |
| 179 | // Test with an image of more than one block. |
| 180 | const int h = 2 * kBlockSize; |
| 181 | const int w = 2 * kBlockSize; |
| 182 | const int stride = 2 * kBlockSize; |
| 183 | std::vector<uint8_t> data(h * stride, 128); |
| 184 | |
| 185 | // Set up the (0,0) block to be a plane and the (0,1) block to be a |
| 186 | // checkerboard |
| 187 | for (int y = 0; y < kBlockSize; ++y) { |
| 188 | for (int x = 0; x < kBlockSize; ++x) { |
| 189 | data[y * stride + x] = -y + x + 128; |
| 190 | data[y * stride + x + kBlockSize] = |
| 191 | (x % 2 + y % 2) % 2 ? 128 - 20 : 128 + 20; |
| 192 | } |
| 193 | } |
| 194 | std::vector<double> block(kBlockSize * kBlockSize, 1); |
| 195 | std::vector<double> plane(kBlockSize * kBlockSize, 1); |
| 196 | |
| 197 | // The block data should be a constant (zero) and the rest of the plane |
| 198 | // trend is covered in the plane data. |
| 199 | aom_flat_block_finder_extract_block(&flat_block_finder, &data[0], w, h, |
| 200 | stride, 0, 0, &plane[0], &block[0]); |
| 201 | for (int y = 0; y < kBlockSize; ++y) { |
| 202 | for (int x = 0; x < kBlockSize; ++x) { |
| 203 | EXPECT_NEAR(0, block[y * kBlockSize + x], 1e-5); |
| 204 | EXPECT_NEAR((double)(data[y * stride + x]) / 255, |
| 205 | plane[y * kBlockSize + x], 1e-5); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | // The plane trend is a constant, and the block is a zero mean checkerboard. |
| 210 | aom_flat_block_finder_extract_block(&flat_block_finder, &data[0], w, h, |
| 211 | stride, kBlockSize, 0, &plane[0], |
| 212 | &block[0]); |
| 213 | for (int y = 0; y < kBlockSize; ++y) { |
| 214 | for (int x = 0; x < kBlockSize; ++x) { |
| 215 | EXPECT_NEAR(((double)data[y * stride + x + kBlockSize] - 128.0) / 255, |
| 216 | block[y * kBlockSize + x], 1e-5); |
| 217 | EXPECT_NEAR(128.0 / 255.0, plane[y * kBlockSize + x], 1e-5); |
| 218 | } |
| 219 | } |
| 220 | aom_flat_block_finder_free(&flat_block_finder); |
| 221 | } |
| 222 | |
| 223 | TEST(FlatBlockEstimator, FindFlatBlocks) { |
| 224 | const int kBlockSize = 32; |
| 225 | aom_flat_block_finder_t flat_block_finder; |
| 226 | ASSERT_EQ(1, aom_flat_block_finder_init(&flat_block_finder, kBlockSize)); |
| 227 | |
| 228 | const int num_blocks_w = 8; |
| 229 | const int h = kBlockSize; |
| 230 | const int w = kBlockSize * num_blocks_w; |
| 231 | const int stride = w; |
| 232 | std::vector<uint8_t> data(h * stride, 128); |
| 233 | std::vector<uint8_t> flat_blocks(num_blocks_w, 0); |
| 234 | |
| 235 | for (int y = 0; y < kBlockSize; ++y) { |
| 236 | for (int x = 0; x < kBlockSize; ++x) { |
| 237 | // Block 0 (not flat): constant doesn't have enough variance to qualify |
| 238 | data[y * stride + x + 0 * kBlockSize] = 128; |
| 239 | |
| 240 | // Block 1 (not flat): too high of variance is hard to validate as flat |
| 241 | data[y * stride + x + 1 * kBlockSize] = (uint8_t)(128 + aom_randn(5)); |
| 242 | |
| 243 | // Block 2 (flat): slight checkerboard added to constant |
| 244 | const int check = (x % 2 + y % 2) % 2 ? -2 : 2; |
| 245 | data[y * stride + x + 2 * kBlockSize] = 128 + check; |
| 246 | |
| 247 | // Block 3 (flat): planar block with checkerboard pattern is also flat |
| 248 | data[y * stride + x + 3 * kBlockSize] = y * 2 - x / 2 + 128 + check; |
| 249 | |
| 250 | // Block 4 (flat): gaussian random with standard deviation 1. |
| 251 | data[y * stride + x + 4 * kBlockSize] = |
| 252 | (uint8_t)(aom_randn(1) + x + 128.0); |
| 253 | |
| 254 | // Block 5 (flat): gaussian random with standard deviation 2. |
| 255 | data[y * stride + x + 5 * kBlockSize] = |
| 256 | (uint8_t)(aom_randn(2) + y + 128.0); |
| 257 | |
| 258 | // Block 6 (not flat): too high of directional gradient. |
| 259 | const int strong_edge = x > kBlockSize / 2 ? 64 : 0; |
| 260 | data[y * stride + x + 6 * kBlockSize] = |
| 261 | (uint8_t)(aom_randn(1) + strong_edge + 128.0); |
| 262 | |
| 263 | // Block 7 (not flat): too high gradient. |
| 264 | const int big_check = ((x >> 2) % 2 + (y >> 2) % 2) % 2 ? -16 : 16; |
| 265 | data[y * stride + x + 7 * kBlockSize] = |
| 266 | (uint8_t)(aom_randn(1) + big_check + 128.0); |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | EXPECT_EQ(4, aom_flat_block_finder_run(&flat_block_finder, &data[0], w, h, |
| 271 | stride, &flat_blocks[0])); |
| 272 | |
| 273 | // First two blocks are not flat |
| 274 | EXPECT_EQ(0, flat_blocks[0]); |
| 275 | EXPECT_EQ(0, flat_blocks[1]); |
| 276 | |
| 277 | // Next 4 blocks are flat. |
| 278 | EXPECT_NE(0, flat_blocks[2]); |
| 279 | EXPECT_NE(0, flat_blocks[3]); |
| 280 | EXPECT_NE(0, flat_blocks[4]); |
| 281 | EXPECT_NE(0, flat_blocks[5]); |
| 282 | |
| 283 | // Last 2 are not. |
| 284 | EXPECT_EQ(0, flat_blocks[6]); |
| 285 | EXPECT_EQ(0, flat_blocks[7]); |
| 286 | |
| 287 | aom_flat_block_finder_free(&flat_block_finder); |
| 288 | } |
| 289 | |
| 290 | class NoiseModelUpdateTest : public ::testing::Test { |
| 291 | public: |
| 292 | static const int kWidth = 128; |
| 293 | static const int kHeight = 128; |
| 294 | static const int kBlockSize = 16; |
| 295 | static const int kNumBlocksX = kWidth / kBlockSize; |
| 296 | static const int kNumBlocksY = kHeight / kBlockSize; |
| 297 | |
| 298 | void SetUp() { |
Yaowu Xu | 8ddc7ae | 2018-01-17 17:09:16 -0800 | [diff] [blame] | 299 | const aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3 }; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 300 | ASSERT_TRUE(aom_noise_model_init(&model_, params)); |
| 301 | |
| 302 | data_.resize(kWidth * kHeight * 3); |
| 303 | denoised_.resize(kWidth * kHeight * 3); |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 304 | noise_.resize(kWidth * kHeight * 3); |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 305 | renoise_.resize(kWidth * kHeight); |
| 306 | flat_blocks_.resize(kNumBlocksX * kNumBlocksY); |
| 307 | |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 308 | for (int c = 0, offset = 0; c < 3; ++c, offset += kWidth * kHeight) { |
| 309 | data_ptr_[c] = &data_[offset]; |
| 310 | noise_ptr_[c] = &noise_[offset]; |
| 311 | denoised_ptr_[c] = &denoised_[offset]; |
| 312 | strides_[c] = kWidth; |
| 313 | } |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 314 | chroma_sub_[0] = 0; |
| 315 | chroma_sub_[1] = 0; |
| 316 | } |
| 317 | |
| 318 | void TearDown() { aom_noise_model_free(&model_); } |
| 319 | |
| 320 | protected: |
| 321 | aom_noise_model_t model_; |
| 322 | std::vector<uint8_t> data_; |
| 323 | std::vector<uint8_t> denoised_; |
| 324 | |
| 325 | std::vector<double> noise_; |
| 326 | std::vector<double> renoise_; |
| 327 | std::vector<uint8_t> flat_blocks_; |
| 328 | |
| 329 | uint8_t *data_ptr_[3]; |
| 330 | uint8_t *denoised_ptr_[3]; |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 331 | double *noise_ptr_[3]; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 332 | int strides_[3]; |
| 333 | int chroma_sub_[2]; |
| 334 | }; |
| 335 | |
| 336 | TEST_F(NoiseModelUpdateTest, UpdateFailsNoFlatBlocks) { |
| 337 | EXPECT_EQ(AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS, |
| 338 | aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth, |
| 339 | kHeight, strides_, chroma_sub_, |
| 340 | &flat_blocks_[0], kBlockSize)); |
| 341 | } |
| 342 | |
| 343 | TEST_F(NoiseModelUpdateTest, UpdateSuccessForZeroNoiseAllFlat) { |
| 344 | flat_blocks_.assign(flat_blocks_.size(), 1); |
| 345 | denoised_.assign(denoised_.size(), 128); |
| 346 | data_.assign(denoised_.size(), 128); |
| 347 | EXPECT_EQ(AOM_NOISE_STATUS_INTERNAL_ERROR, |
| 348 | aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth, |
| 349 | kHeight, strides_, chroma_sub_, |
| 350 | &flat_blocks_[0], kBlockSize)); |
| 351 | } |
| 352 | |
| 353 | TEST_F(NoiseModelUpdateTest, UpdateFailsBlockSizeTooSmall) { |
| 354 | flat_blocks_.assign(flat_blocks_.size(), 1); |
| 355 | denoised_.assign(denoised_.size(), 128); |
| 356 | data_.assign(denoised_.size(), 128); |
| 357 | EXPECT_EQ( |
| 358 | AOM_NOISE_STATUS_INVALID_ARGUMENT, |
| 359 | aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth, kHeight, |
| 360 | strides_, chroma_sub_, &flat_blocks_[0], |
| 361 | 6 /* block_size=2 is too small*/)); |
| 362 | } |
| 363 | |
| 364 | TEST_F(NoiseModelUpdateTest, UpdateSuccessForWhiteRandomNoise) { |
| 365 | for (int y = 0; y < kHeight; ++y) { |
| 366 | for (int x = 0; x < kWidth; ++x) { |
| 367 | data_ptr_[0][y * kWidth + x] = int(64 + y + aom_randn(1)); |
| 368 | denoised_ptr_[0][y * kWidth + x] = 64 + y; |
| 369 | // Make the chroma planes completely correlated with the Y plane |
| 370 | for (int c = 1; c < 3; ++c) { |
| 371 | data_ptr_[c][y * kWidth + x] = data_ptr_[0][y * kWidth + x]; |
| 372 | denoised_ptr_[c][y * kWidth + x] = denoised_ptr_[0][y * kWidth + x]; |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | flat_blocks_.assign(flat_blocks_.size(), 1); |
| 377 | EXPECT_EQ(AOM_NOISE_STATUS_OK, |
| 378 | aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth, |
| 379 | kHeight, strides_, chroma_sub_, |
| 380 | &flat_blocks_[0], kBlockSize)); |
| 381 | |
| 382 | const double kCoeffEps = 0.075; |
| 383 | const int n = model_.n; |
| 384 | for (int c = 0; c < 3; ++c) { |
| 385 | for (int i = 0; i < n; ++i) { |
| 386 | EXPECT_NEAR(0, model_.latest_state[c].eqns.x[i], kCoeffEps); |
| 387 | EXPECT_NEAR(0, model_.combined_state[c].eqns.x[i], kCoeffEps); |
| 388 | } |
| 389 | // The second and third channels are highly correlated with the first. |
| 390 | if (c > 0) { |
| 391 | ASSERT_EQ(n + 1, model_.latest_state[c].eqns.n); |
| 392 | ASSERT_EQ(n + 1, model_.combined_state[c].eqns.n); |
| 393 | |
| 394 | EXPECT_NEAR(1, model_.latest_state[c].eqns.x[n], kCoeffEps); |
| 395 | EXPECT_NEAR(1, model_.combined_state[c].eqns.x[n], kCoeffEps); |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | // The fitted noise strength should be close to the standard deviation |
| 400 | // for all intensity bins. |
| 401 | const double kStdEps = 0.1; |
| 402 | for (int i = 0; i < model_.latest_state[0].strength_solver.eqns.n; ++i) { |
| 403 | EXPECT_NEAR(1.0, model_.latest_state[0].strength_solver.eqns.x[i], kStdEps); |
| 404 | EXPECT_NEAR(1.0, model_.combined_state[0].strength_solver.eqns.x[i], |
| 405 | kStdEps); |
| 406 | } |
| 407 | |
| 408 | aom_noise_strength_lut_t lut; |
| 409 | aom_noise_strength_solver_fit_piecewise( |
| 410 | &model_.latest_state[0].strength_solver, &lut); |
| 411 | ASSERT_EQ(2, lut.num_points); |
| 412 | EXPECT_NEAR(0.0, lut.points[0][0], 1e-5); |
| 413 | EXPECT_NEAR(1.0, lut.points[0][1], kStdEps); |
| 414 | EXPECT_NEAR(255.0, lut.points[1][0], 1e-5); |
| 415 | EXPECT_NEAR(1.0, lut.points[1][1], kStdEps); |
| 416 | aom_noise_strength_lut_free(&lut); |
| 417 | } |
| 418 | |
| 419 | TEST_F(NoiseModelUpdateTest, UpdateSuccessForScaledWhiteNoise) { |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 420 | const double kCoeffEps = 0.055; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 421 | const double kLowStd = 1; |
| 422 | const double kHighStd = 4; |
| 423 | for (int y = 0; y < kHeight; ++y) { |
| 424 | for (int x = 0; x < kWidth; ++x) { |
| 425 | for (int c = 0; c < 3; ++c) { |
| 426 | // The image data is bimodal: |
| 427 | // Bottom half has low intensity and low noise strength |
| 428 | // Top half has high intensity and high noise strength |
| 429 | const int avg = (y < kHeight / 2) ? 4 : 245; |
| 430 | const double std = (y < kHeight / 2) ? kLowStd : kHighStd; |
| 431 | data_ptr_[c][y * kWidth + x] = |
| 432 | (uint8_t)std::min((int)255, (int)(2 + avg + aom_randn(std))); |
| 433 | denoised_ptr_[c][y * kWidth + x] = 2 + avg; |
| 434 | } |
| 435 | } |
| 436 | } |
| 437 | // Label all blocks as flat for the update |
| 438 | flat_blocks_.assign(flat_blocks_.size(), 1); |
| 439 | EXPECT_EQ(AOM_NOISE_STATUS_OK, |
| 440 | aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth, |
| 441 | kHeight, strides_, chroma_sub_, |
| 442 | &flat_blocks_[0], kBlockSize)); |
| 443 | |
| 444 | const int n = model_.n; |
| 445 | // The noise is uncorrelated spatially and with the y channel. |
| 446 | // All coefficients should be reasonably close to zero. |
| 447 | for (int c = 0; c < 3; ++c) { |
| 448 | for (int i = 0; i < n; ++i) { |
| 449 | EXPECT_NEAR(0, model_.latest_state[c].eqns.x[i], kCoeffEps); |
| 450 | EXPECT_NEAR(0, model_.combined_state[c].eqns.x[i], kCoeffEps); |
| 451 | } |
| 452 | if (c > 0) { |
| 453 | ASSERT_EQ(n + 1, model_.latest_state[c].eqns.n); |
| 454 | ASSERT_EQ(n + 1, model_.combined_state[c].eqns.n); |
| 455 | |
| 456 | // The correlation to the y channel should be low (near zero) |
| 457 | EXPECT_NEAR(0, model_.latest_state[c].eqns.x[n], kCoeffEps); |
| 458 | EXPECT_NEAR(0, model_.combined_state[c].eqns.x[n], kCoeffEps); |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | // Noise strength should vary between kLowStd and kHighStd. |
| 463 | const double kStdEps = 0.15; |
| 464 | ASSERT_EQ(20, model_.latest_state[0].strength_solver.eqns.n); |
| 465 | for (int i = 0; i < model_.latest_state[0].strength_solver.eqns.n; ++i) { |
| 466 | const double a = i / 19.0; |
| 467 | const double expected = (kLowStd * (1.0 - a) + kHighStd * a); |
| 468 | EXPECT_NEAR(expected, model_.latest_state[0].strength_solver.eqns.x[i], |
| 469 | kStdEps); |
| 470 | EXPECT_NEAR(expected, model_.combined_state[0].strength_solver.eqns.x[i], |
| 471 | kStdEps); |
| 472 | } |
| 473 | |
| 474 | // If we fit a piecewise linear model, there should be two points: |
| 475 | // one near kLowStd at 0, and the other near kHighStd and 255. |
| 476 | aom_noise_strength_lut_t lut; |
| 477 | aom_noise_strength_solver_fit_piecewise( |
| 478 | &model_.latest_state[0].strength_solver, &lut); |
| 479 | ASSERT_EQ(2, lut.num_points); |
| 480 | EXPECT_NEAR(0, lut.points[0][0], 1e-4); |
| 481 | EXPECT_NEAR(kLowStd, lut.points[0][1], kStdEps); |
| 482 | EXPECT_NEAR(255.0, lut.points[1][0], 1e-5); |
| 483 | EXPECT_NEAR(kHighStd, lut.points[1][1], kStdEps); |
| 484 | aom_noise_strength_lut_free(&lut); |
| 485 | } |
| 486 | |
| 487 | TEST_F(NoiseModelUpdateTest, UpdateSuccessForCorrelatedNoise) { |
| 488 | const int kNumCoeffs = 24; |
| 489 | const double kStd = 4; |
| 490 | const double kStdEps = 0.3; |
| 491 | const int kBlockSize = 16; |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 492 | const double kCoeffEps = 0.06; |
| 493 | // Use different coefficients for each channel |
| 494 | const double kCoeffs[3][24] = { |
| 495 | { 0.02884, -0.03356, 0.00633, 0.01757, 0.02849, -0.04620, |
| 496 | 0.02833, -0.07178, 0.07076, -0.11603, -0.10413, -0.16571, |
| 497 | 0.05158, -0.07969, 0.02640, -0.07191, 0.02530, 0.41968, |
| 498 | 0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 }, |
| 499 | { 0.00269, -0.01291, -0.01513, 0.07234, 0.03208, 0.00477, |
| 500 | 0.00226, -0.00254, 0.03533, 0.12841, -0.25970, -0.06336, |
| 501 | 0.05238, -0.00845, -0.03118, 0.09043, -0.36558, 0.48903, |
| 502 | 0.00595, -0.11938, 0.02106, 0.095956, -0.350139, 0.59305 }, |
| 503 | { -0.00643, -0.01080, -0.01466, 0.06951, 0.03707, -0.00482, |
| 504 | 0.00817, -0.00909, 0.02949, 0.12181, -0.25210, -0.07886, |
| 505 | 0.06083, -0.01210, -0.03108, 0.08944, -0.35875, 0.49150, |
| 506 | 0.00415, -0.12905, 0.02870, 0.09740, -0.34610, 0.58824 }, |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 507 | }; |
| 508 | ASSERT_EQ(model_.n, kNumCoeffs); |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 509 | chroma_sub_[0] = chroma_sub_[1] = 1; |
| 510 | |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 511 | flat_blocks_.assign(flat_blocks_.size(), 1); |
| 512 | |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 513 | // Add different noise onto each plane |
| 514 | for (int c = 0; c < 3; ++c) { |
| 515 | aom_noise_synth(model_.params.lag, model_.n, model_.coords, kCoeffs[c], |
| 516 | noise_ptr_[c], kWidth, kHeight); |
| 517 | const int x_shift = c > 0 ? chroma_sub_[0] : 0; |
| 518 | const int y_shift = c > 0 ? chroma_sub_[1] : 0; |
| 519 | for (int y = 0; y < (kHeight >> y_shift); ++y) { |
| 520 | for (int x = 0; x < (kWidth >> x_shift); ++x) { |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 521 | const uint8_t value = 64 + x / 2 + y / 4; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 522 | data_ptr_[c][y * kWidth + x] = |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 523 | uint8_t(value + noise_ptr_[c][y * strides_[c] + x] * kStd); |
| 524 | denoised_ptr_[c][y * strides_[c] + x] = value; |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 525 | } |
| 526 | } |
| 527 | } |
| 528 | EXPECT_EQ(AOM_NOISE_STATUS_OK, |
| 529 | aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth, |
| 530 | kHeight, strides_, chroma_sub_, |
| 531 | &flat_blocks_[0], kBlockSize)); |
| 532 | |
| 533 | // For the Y plane, the solved coefficients should be close to the original |
| 534 | const int n = model_.n; |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 535 | for (int c = 0; c < 3; ++c) { |
| 536 | for (int i = 0; i < n; ++i) { |
| 537 | EXPECT_NEAR(kCoeffs[c][i], model_.latest_state[c].eqns.x[i], kCoeffEps); |
| 538 | EXPECT_NEAR(kCoeffs[c][i], model_.combined_state[c].eqns.x[i], kCoeffEps); |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 539 | } |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 540 | // The chroma planes should be uncorrelated with the luma plane |
| 541 | if (c > 0) { |
| 542 | EXPECT_NEAR(0, model_.latest_state[c].eqns.x[n], kCoeffEps); |
| 543 | EXPECT_NEAR(0, model_.combined_state[c].eqns.x[n], kCoeffEps); |
| 544 | } |
| 545 | // Correlation between the coefficient vector and the fitted coefficients |
| 546 | // should be close to 1. |
| 547 | EXPECT_LT(0.98, aom_normalized_cross_correlation( |
| 548 | model_.latest_state[c].eqns.x, kCoeffs[c], kNumCoeffs)); |
| 549 | |
| 550 | aom_noise_synth(model_.params.lag, model_.n, model_.coords, |
| 551 | model_.latest_state[c].eqns.x, &renoise_[0], kWidth, |
| 552 | kHeight); |
| 553 | |
| 554 | EXPECT_TRUE(aom_noise_data_validate(&renoise_[0], kWidth, kHeight)); |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 555 | } |
| 556 | |
Neil Birkbeck | 7029948 | 2018-02-01 13:55:35 -0800 | [diff] [blame] | 557 | // Check fitted noise strength |
| 558 | for (int c = 0; c < 3; ++c) { |
| 559 | for (int i = 0; i < model_.latest_state[c].strength_solver.eqns.n; ++i) { |
| 560 | EXPECT_NEAR(kStd, model_.latest_state[c].strength_solver.eqns.x[i], |
| 561 | kStdEps); |
| 562 | } |
Neil Birkbeck | ed25a61 | 2017-12-21 13:18:32 -0800 | [diff] [blame] | 563 | } |
| 564 | } |