Coverage Report

Created: 2026-05-24 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/aom/aom_dsp/noise_model.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2017, 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 <math.h>
13
#include <stdio.h>
14
#include <stdlib.h>
15
#include <string.h>
16
17
#include "aom_dsp/aom_dsp_common.h"
18
#include "aom_dsp/mathutils.h"
19
#include "aom_dsp/noise_model.h"
20
#include "aom_dsp/noise_util.h"
21
#include "aom_mem/aom_mem.h"
22
#include "aom_ports/mem.h"
23
#include "aom_scale/yv12config.h"
24
25
0
#define kLowPolyNumParams 3
26
27
static const int kMaxLag = 4;
28
29
// Defines a function that can be used to obtain the mean of a block for the
30
// provided data type (uint8_t, or uint16_t)
31
#define GET_BLOCK_MEAN(INT_TYPE, suffix)                                    \
32
  static double get_block_mean_##suffix(const INT_TYPE *data, int w, int h, \
33
                                        int stride, int x_o, int y_o,       \
34
0
                                        int block_size) {                   \
35
0
    const int max_h = AOMMIN(h - y_o, block_size);                          \
36
0
    const int max_w = AOMMIN(w - x_o, block_size);                          \
37
0
    double block_mean = 0;                                                  \
38
0
    for (int y = 0; y < max_h; ++y) {                                       \
39
0
      for (int x = 0; x < max_w; ++x) {                                     \
40
0
        block_mean += data[(y_o + y) * stride + x_o + x];                   \
41
0
      }                                                                     \
42
0
    }                                                                       \
43
0
    return block_mean / (max_w * max_h);                                    \
44
0
  }
Unexecuted instantiation: noise_model.c:get_block_mean_highbd
Unexecuted instantiation: noise_model.c:get_block_mean_lowbd
45
46
GET_BLOCK_MEAN(uint8_t, lowbd)
47
GET_BLOCK_MEAN(uint16_t, highbd)
48
49
static inline double get_block_mean(const uint8_t *data, int w, int h,
50
                                    int stride, int x_o, int y_o,
51
0
                                    int block_size, int use_highbd) {
52
0
  if (use_highbd)
53
0
    return get_block_mean_highbd((const uint16_t *)data, w, h, stride, x_o, y_o,
54
0
                                 block_size);
55
0
  return get_block_mean_lowbd(data, w, h, stride, x_o, y_o, block_size);
56
0
}
57
58
// Defines a function that can be used to obtain the variance of a block
59
// for the provided data type (uint8_t, or uint16_t)
60
#define GET_NOISE_VAR(INT_TYPE, suffix)                                  \
61
  static double get_noise_var_##suffix(                                  \
62
      const INT_TYPE *data, const INT_TYPE *denoised, int stride, int w, \
63
0
      int h, int x_o, int y_o, int block_size_x, int block_size_y) {     \
64
0
    const int max_h = AOMMIN(h - y_o, block_size_y);                     \
65
0
    const int max_w = AOMMIN(w - x_o, block_size_x);                     \
66
0
    double noise_var = 0;                                                \
67
0
    double noise_mean = 0;                                               \
68
0
    for (int y = 0; y < max_h; ++y) {                                    \
69
0
      for (int x = 0; x < max_w; ++x) {                                  \
70
0
        double noise = (double)data[(y_o + y) * stride + x_o + x] -      \
71
0
                       denoised[(y_o + y) * stride + x_o + x];           \
72
0
        noise_mean += noise;                                             \
73
0
        noise_var += noise * noise;                                      \
74
0
      }                                                                  \
75
0
    }                                                                    \
76
0
    noise_mean /= (max_w * max_h);                                       \
77
0
    return noise_var / (max_w * max_h) - noise_mean * noise_mean;        \
78
0
  }
Unexecuted instantiation: noise_model.c:get_noise_var_highbd
Unexecuted instantiation: noise_model.c:get_noise_var_lowbd
79
80
GET_NOISE_VAR(uint8_t, lowbd)
81
GET_NOISE_VAR(uint16_t, highbd)
82
83
static inline double get_noise_var(const uint8_t *data, const uint8_t *denoised,
84
                                   int w, int h, int stride, int x_o, int y_o,
85
                                   int block_size_x, int block_size_y,
86
0
                                   int use_highbd) {
87
0
  if (use_highbd)
88
0
    return get_noise_var_highbd((const uint16_t *)data,
89
0
                                (const uint16_t *)denoised, w, h, stride, x_o,
90
0
                                y_o, block_size_x, block_size_y);
91
0
  return get_noise_var_lowbd(data, denoised, w, h, stride, x_o, y_o,
92
0
                             block_size_x, block_size_y);
93
0
}
94
95
0
static void equation_system_clear(aom_equation_system_t *eqns) {
96
0
  const int n = eqns->n;
97
0
  memset(eqns->A, 0, sizeof(*eqns->A) * n * n);
98
0
  memset(eqns->x, 0, sizeof(*eqns->x) * n);
99
0
  memset(eqns->b, 0, sizeof(*eqns->b) * n);
100
0
}
101
102
static void equation_system_copy(aom_equation_system_t *dst,
103
0
                                 const aom_equation_system_t *src) {
104
0
  const int n = dst->n;
105
0
  memcpy(dst->A, src->A, sizeof(*dst->A) * n * n);
106
0
  memcpy(dst->x, src->x, sizeof(*dst->x) * n);
107
0
  memcpy(dst->b, src->b, sizeof(*dst->b) * n);
108
0
}
109
110
0
static int equation_system_init(aom_equation_system_t *eqns, int n) {
111
0
  eqns->A = (double *)aom_malloc(sizeof(*eqns->A) * n * n);
112
0
  eqns->b = (double *)aom_malloc(sizeof(*eqns->b) * n);
113
0
  eqns->x = (double *)aom_malloc(sizeof(*eqns->x) * n);
114
0
  eqns->n = n;
115
0
  if (!eqns->A || !eqns->b || !eqns->x) {
116
0
    fprintf(stderr, "Failed to allocate system of equations of size %d\n", n);
117
0
    aom_free(eqns->A);
118
0
    aom_free(eqns->b);
119
0
    aom_free(eqns->x);
120
0
    memset(eqns, 0, sizeof(*eqns));
121
0
    return 0;
122
0
  }
123
0
  equation_system_clear(eqns);
124
0
  return 1;
125
0
}
126
127
0
static int equation_system_solve(aom_equation_system_t *eqns) {
128
0
  const int n = eqns->n;
129
0
  double *b = (double *)aom_malloc(sizeof(*b) * n);
130
0
  double *A = (double *)aom_malloc(sizeof(*A) * n * n);
131
0
  int ret = 0;
132
0
  if (A == NULL || b == NULL) {
133
0
    fprintf(stderr, "Unable to allocate temp values of size %dx%d\n", n, n);
134
0
    aom_free(b);
135
0
    aom_free(A);
136
0
    return 0;
137
0
  }
138
0
  memcpy(A, eqns->A, sizeof(*eqns->A) * n * n);
139
0
  memcpy(b, eqns->b, sizeof(*eqns->b) * n);
140
0
  ret = linsolve(n, A, eqns->n, b, eqns->x);
141
0
  aom_free(b);
142
0
  aom_free(A);
143
144
0
  if (ret == 0) {
145
0
    return 0;
146
0
  }
147
0
  for (int i = 0; i < n; ++i) {
148
0
    if (isnan((float)eqns->x[i])) {
149
0
      return 0;
150
0
    }
151
0
  }
152
0
  return 1;
153
0
}
154
155
static void equation_system_add(aom_equation_system_t *dest,
156
0
                                aom_equation_system_t *src) {
157
0
  const int n = dest->n;
158
0
  int i, j;
159
0
  for (i = 0; i < n; ++i) {
160
0
    for (j = 0; j < n; ++j) {
161
0
      dest->A[i * n + j] += src->A[i * n + j];
162
0
    }
163
0
    dest->b[i] += src->b[i];
164
0
  }
165
0
}
166
167
0
static void equation_system_free(aom_equation_system_t *eqns) {
168
0
  if (!eqns) return;
169
0
  aom_free(eqns->A);
170
0
  aom_free(eqns->b);
171
0
  aom_free(eqns->x);
172
0
  memset(eqns, 0, sizeof(*eqns));
173
0
}
174
175
0
static void noise_strength_solver_clear(aom_noise_strength_solver_t *solver) {
176
0
  equation_system_clear(&solver->eqns);
177
0
  solver->num_equations = 0;
178
0
  solver->total = 0;
179
0
}
180
181
static void noise_strength_solver_add(aom_noise_strength_solver_t *dest,
182
0
                                      aom_noise_strength_solver_t *src) {
183
0
  equation_system_add(&dest->eqns, &src->eqns);
184
0
  dest->num_equations += src->num_equations;
185
0
  dest->total += src->total;
186
0
}
187
188
// Return the number of coefficients required for the given parameters
189
0
static int num_coeffs(const aom_noise_model_params_t params) {
190
0
  const int n = 2 * params.lag + 1;
191
0
  switch (params.shape) {
192
0
    case AOM_NOISE_SHAPE_DIAMOND: return params.lag * (params.lag + 1);
193
0
    case AOM_NOISE_SHAPE_SQUARE: return (n * n) / 2;
194
0
  }
195
0
  return 0;
196
0
}
197
198
0
static int noise_state_init(aom_noise_state_t *state, int n, int bit_depth) {
199
0
  const int kNumBins = 20;
200
0
  if (!equation_system_init(&state->eqns, n)) {
201
0
    fprintf(stderr, "Failed initialization noise state with size %d\n", n);
202
0
    return 0;
203
0
  }
204
0
  state->ar_gain = 1.0;
205
0
  state->num_observations = 0;
206
0
  return aom_noise_strength_solver_init(&state->strength_solver, kNumBins,
207
0
                                        bit_depth);
208
0
}
209
210
0
static void set_chroma_coefficient_fallback_soln(aom_equation_system_t *eqns) {
211
0
  const double kTolerance = 1e-6;
212
0
  const int last = eqns->n - 1;
213
  // Set all of the AR coefficients to zero, but try to solve for correlation
214
  // with the luma channel
215
0
  memset(eqns->x, 0, sizeof(*eqns->x) * eqns->n);
216
0
  if (fabs(eqns->A[last * eqns->n + last]) > kTolerance) {
217
0
    eqns->x[last] = eqns->b[last] / eqns->A[last * eqns->n + last];
218
0
  }
219
0
}
220
221
0
int aom_noise_strength_lut_init(aom_noise_strength_lut_t *lut, int num_points) {
222
0
  if (!lut) return 0;
223
0
  if (num_points <= 0) return 0;
224
0
  lut->num_points = 0;
225
0
  lut->points = (double(*)[2])aom_malloc(num_points * sizeof(*lut->points));
226
0
  if (!lut->points) return 0;
227
0
  lut->num_points = num_points;
228
0
  memset(lut->points, 0, sizeof(*lut->points) * num_points);
229
0
  return 1;
230
0
}
231
232
0
void aom_noise_strength_lut_free(aom_noise_strength_lut_t *lut) {
233
0
  if (!lut) return;
234
0
  aom_free(lut->points);
235
0
  memset(lut, 0, sizeof(*lut));
236
0
}
237
238
double aom_noise_strength_lut_eval(const aom_noise_strength_lut_t *lut,
239
0
                                   double x) {
240
0
  int i = 0;
241
  // Constant extrapolation for x <  x_0.
242
0
  if (x < lut->points[0][0]) return lut->points[0][1];
243
0
  for (i = 0; i < lut->num_points - 1; ++i) {
244
0
    if (x >= lut->points[i][0] && x <= lut->points[i + 1][0]) {
245
0
      const double a =
246
0
          (x - lut->points[i][0]) / (lut->points[i + 1][0] - lut->points[i][0]);
247
0
      return lut->points[i + 1][1] * a + lut->points[i][1] * (1.0 - a);
248
0
    }
249
0
  }
250
  // Constant extrapolation for x > x_{n-1}
251
0
  return lut->points[lut->num_points - 1][1];
252
0
}
253
254
static double noise_strength_solver_get_bin_index(
255
0
    const aom_noise_strength_solver_t *solver, double value) {
256
0
  const double val =
257
0
      fclamp(value, solver->min_intensity, solver->max_intensity);
258
0
  const double range = solver->max_intensity - solver->min_intensity;
259
0
  return (solver->num_bins - 1) * (val - solver->min_intensity) / range;
260
0
}
261
262
static double noise_strength_solver_get_value(
263
0
    const aom_noise_strength_solver_t *solver, double x) {
264
0
  const double bin = noise_strength_solver_get_bin_index(solver, x);
265
0
  const int bin_i0 = (int)floor(bin);
266
0
  const int bin_i1 = AOMMIN(solver->num_bins - 1, bin_i0 + 1);
267
0
  const double a = bin - bin_i0;
268
0
  return (1.0 - a) * solver->eqns.x[bin_i0] + a * solver->eqns.x[bin_i1];
269
0
}
270
271
void aom_noise_strength_solver_add_measurement(
272
0
    aom_noise_strength_solver_t *solver, double block_mean, double noise_std) {
273
0
  const double bin = noise_strength_solver_get_bin_index(solver, block_mean);
274
0
  const int bin_i0 = (int)floor(bin);
275
0
  const int bin_i1 = AOMMIN(solver->num_bins - 1, bin_i0 + 1);
276
0
  const double a = bin - bin_i0;
277
0
  const int n = solver->num_bins;
278
0
  solver->eqns.A[bin_i0 * n + bin_i0] += (1.0 - a) * (1.0 - a);
279
0
  solver->eqns.A[bin_i1 * n + bin_i0] += a * (1.0 - a);
280
0
  solver->eqns.A[bin_i1 * n + bin_i1] += a * a;
281
0
  solver->eqns.A[bin_i0 * n + bin_i1] += a * (1.0 - a);
282
0
  solver->eqns.b[bin_i0] += (1.0 - a) * noise_std;
283
0
  solver->eqns.b[bin_i1] += a * noise_std;
284
0
  solver->total += noise_std;
285
0
  solver->num_equations++;
286
0
}
287
288
0
int aom_noise_strength_solver_solve(aom_noise_strength_solver_t *solver) {
289
  // Add regularization proportional to the number of constraints
290
0
  const int n = solver->num_bins;
291
0
  const double kAlpha = 2.0 * (double)(solver->num_equations) / n;
292
0
  int result = 0;
293
0
  double mean = 0;
294
295
  // Do this in a non-destructive manner so it is not confusing to the caller
296
0
  double *old_A = solver->eqns.A;
297
0
  double *A = (double *)aom_malloc(sizeof(*A) * n * n);
298
0
  if (!A) {
299
0
    fprintf(stderr, "Unable to allocate copy of A\n");
300
0
    return 0;
301
0
  }
302
0
  memcpy(A, old_A, sizeof(*A) * n * n);
303
304
0
  for (int i = 0; i < n; ++i) {
305
0
    const int i_lo = AOMMAX(0, i - 1);
306
0
    const int i_hi = AOMMIN(n - 1, i + 1);
307
0
    A[i * n + i_lo] -= kAlpha;
308
0
    A[i * n + i] += 2 * kAlpha;
309
0
    A[i * n + i_hi] -= kAlpha;
310
0
  }
311
312
  // Small regularization to give average noise strength
313
0
  mean = solver->total / solver->num_equations;
314
0
  for (int i = 0; i < n; ++i) {
315
0
    A[i * n + i] += 1.0 / 8192.;
316
0
    solver->eqns.b[i] += mean / 8192.;
317
0
  }
318
0
  solver->eqns.A = A;
319
0
  result = equation_system_solve(&solver->eqns);
320
0
  solver->eqns.A = old_A;
321
322
0
  aom_free(A);
323
0
  return result;
324
0
}
325
326
int aom_noise_strength_solver_init(aom_noise_strength_solver_t *solver,
327
0
                                   int num_bins, int bit_depth) {
328
0
  if (!solver) return 0;
329
0
  memset(solver, 0, sizeof(*solver));
330
0
  solver->num_bins = num_bins;
331
0
  solver->min_intensity = 0;
332
0
  solver->max_intensity = (1 << bit_depth) - 1;
333
0
  solver->total = 0;
334
0
  solver->num_equations = 0;
335
0
  return equation_system_init(&solver->eqns, num_bins);
336
0
}
337
338
0
void aom_noise_strength_solver_free(aom_noise_strength_solver_t *solver) {
339
0
  if (!solver) return;
340
0
  equation_system_free(&solver->eqns);
341
0
}
342
343
double aom_noise_strength_solver_get_center(
344
0
    const aom_noise_strength_solver_t *solver, int i) {
345
0
  const double range = solver->max_intensity - solver->min_intensity;
346
0
  const int n = solver->num_bins;
347
0
  return ((double)i) / (n - 1) * range + solver->min_intensity;
348
0
}
349
350
// Computes the residual if a point were to be removed from the lut. This is
351
// calculated as the area between the output of the solver and the line segment
352
// that would be formed between [x_{i - 1}, x_{i + 1}).
353
static void update_piecewise_linear_residual(
354
    const aom_noise_strength_solver_t *solver,
355
0
    const aom_noise_strength_lut_t *lut, double *residual, int start, int end) {
356
0
  const double dx = 255. / solver->num_bins;
357
0
  for (int i = AOMMAX(start, 1); i < AOMMIN(end, lut->num_points - 1); ++i) {
358
0
    const int lower = AOMMAX(0, (int)floor(noise_strength_solver_get_bin_index(
359
0
                                    solver, lut->points[i - 1][0])));
360
0
    const int upper = AOMMIN(solver->num_bins - 1,
361
0
                             (int)ceil(noise_strength_solver_get_bin_index(
362
0
                                 solver, lut->points[i + 1][0])));
363
0
    double r = 0;
364
0
    for (int j = lower; j <= upper; ++j) {
365
0
      const double x = aom_noise_strength_solver_get_center(solver, j);
366
0
      if (x < lut->points[i - 1][0]) continue;
367
0
      if (x >= lut->points[i + 1][0]) continue;
368
0
      const double y = solver->eqns.x[j];
369
0
      const double a = (x - lut->points[i - 1][0]) /
370
0
                       (lut->points[i + 1][0] - lut->points[i - 1][0]);
371
0
      const double estimate_y =
372
0
          lut->points[i - 1][1] * (1.0 - a) + lut->points[i + 1][1] * a;
373
0
      r += fabs(y - estimate_y);
374
0
    }
375
0
    residual[i] = r * dx;
376
0
  }
377
0
}
378
379
int aom_noise_strength_solver_fit_piecewise(
380
    const aom_noise_strength_solver_t *solver, int max_output_points,
381
0
    aom_noise_strength_lut_t *lut) {
382
  // The tolerance is normalized to be give consistent results between
383
  // different bit-depths.
384
0
  const double kTolerance = solver->max_intensity * 0.00625 / 255.0;
385
0
  if (!aom_noise_strength_lut_init(lut, solver->num_bins)) {
386
0
    fprintf(stderr, "Failed to init lut\n");
387
0
    return 0;
388
0
  }
389
0
  for (int i = 0; i < solver->num_bins; ++i) {
390
0
    lut->points[i][0] = aom_noise_strength_solver_get_center(solver, i);
391
0
    lut->points[i][1] = solver->eqns.x[i];
392
0
  }
393
0
  if (max_output_points < 0) {
394
0
    max_output_points = solver->num_bins;
395
0
  }
396
397
0
  double *residual = (double *)aom_malloc(solver->num_bins * sizeof(*residual));
398
0
  if (!residual) {
399
0
    aom_noise_strength_lut_free(lut);
400
0
    return 0;
401
0
  }
402
0
  memset(residual, 0, sizeof(*residual) * solver->num_bins);
403
404
0
  update_piecewise_linear_residual(solver, lut, residual, 0, solver->num_bins);
405
406
  // Greedily remove points if there are too many or if it doesn't hurt local
407
  // approximation (never remove the end points)
408
0
  while (lut->num_points > 2) {
409
0
    int min_index = 1;
410
0
    for (int j = 1; j < lut->num_points - 1; ++j) {
411
0
      if (residual[j] < residual[min_index]) {
412
0
        min_index = j;
413
0
      }
414
0
    }
415
0
    const double dx =
416
0
        lut->points[min_index + 1][0] - lut->points[min_index - 1][0];
417
0
    const double avg_residual = residual[min_index] / dx;
418
0
    if (lut->num_points <= max_output_points && avg_residual > kTolerance) {
419
0
      break;
420
0
    }
421
422
0
    const int num_remaining = lut->num_points - min_index - 1;
423
0
    memmove(lut->points + min_index, lut->points + min_index + 1,
424
0
            sizeof(lut->points[0]) * num_remaining);
425
0
    lut->num_points--;
426
427
0
    update_piecewise_linear_residual(solver, lut, residual, min_index - 1,
428
0
                                     min_index + 1);
429
0
  }
430
0
  aom_free(residual);
431
0
  return 1;
432
0
}
433
434
int aom_flat_block_finder_init(aom_flat_block_finder_t *block_finder,
435
0
                               int block_size, int bit_depth, int use_highbd) {
436
0
  const int n = block_size * block_size;
437
0
  aom_equation_system_t eqns;
438
0
  double *AtA_inv = 0;
439
0
  double *A = 0;
440
0
  int x = 0, y = 0, i = 0, j = 0;
441
0
  block_finder->A = NULL;
442
0
  block_finder->AtA_inv = NULL;
443
444
0
  if (!equation_system_init(&eqns, kLowPolyNumParams)) {
445
0
    fprintf(stderr, "Failed to init equation system for block_size=%d\n",
446
0
            block_size);
447
0
    return 0;
448
0
  }
449
450
0
  AtA_inv = (double *)aom_malloc(kLowPolyNumParams * kLowPolyNumParams *
451
0
                                 sizeof(*AtA_inv));
452
0
  A = (double *)aom_malloc(kLowPolyNumParams * n * sizeof(*A));
453
0
  if (AtA_inv == NULL || A == NULL) {
454
0
    fprintf(stderr, "Failed to alloc A or AtA_inv for block_size=%d\n",
455
0
            block_size);
456
0
    aom_free(AtA_inv);
457
0
    aom_free(A);
458
0
    equation_system_free(&eqns);
459
0
    return 0;
460
0
  }
461
462
0
  block_finder->A = A;
463
0
  block_finder->AtA_inv = AtA_inv;
464
0
  block_finder->block_size = block_size;
465
0
  block_finder->normalization = (1 << bit_depth) - 1;
466
0
  block_finder->use_highbd = use_highbd;
467
468
0
  for (y = 0; y < block_size; ++y) {
469
0
    const double yd = ((double)y - block_size / 2.) / (block_size / 2.);
470
0
    for (x = 0; x < block_size; ++x) {
471
0
      const double xd = ((double)x - block_size / 2.) / (block_size / 2.);
472
0
      const double coords[3] = { yd, xd, 1 };
473
0
      const int row = y * block_size + x;
474
0
      A[kLowPolyNumParams * row + 0] = yd;
475
0
      A[kLowPolyNumParams * row + 1] = xd;
476
0
      A[kLowPolyNumParams * row + 2] = 1;
477
478
0
      for (i = 0; i < kLowPolyNumParams; ++i) {
479
0
        for (j = 0; j < kLowPolyNumParams; ++j) {
480
0
          eqns.A[kLowPolyNumParams * i + j] += coords[i] * coords[j];
481
0
        }
482
0
      }
483
0
    }
484
0
  }
485
486
  // Lazy inverse using existing equation solver.
487
0
  for (i = 0; i < kLowPolyNumParams; ++i) {
488
0
    memset(eqns.b, 0, sizeof(*eqns.b) * kLowPolyNumParams);
489
0
    eqns.b[i] = 1;
490
0
    const int ret = equation_system_solve(&eqns);
491
0
    if (!ret) return ret;
492
493
0
    for (j = 0; j < kLowPolyNumParams; ++j) {
494
0
      AtA_inv[j * kLowPolyNumParams + i] = eqns.x[j];
495
0
    }
496
0
  }
497
0
  equation_system_free(&eqns);
498
0
  return 1;
499
0
}
500
501
0
void aom_flat_block_finder_free(aom_flat_block_finder_t *block_finder) {
502
0
  if (!block_finder) return;
503
0
  aom_free(block_finder->A);
504
0
  aom_free(block_finder->AtA_inv);
505
0
  memset(block_finder, 0, sizeof(*block_finder));
506
0
}
507
508
void aom_flat_block_finder_extract_block(
509
    const aom_flat_block_finder_t *block_finder, const uint8_t *const data,
510
    int w, int h, int stride, int offsx, int offsy, double *plane,
511
0
    double *block) {
512
0
  const int block_size = block_finder->block_size;
513
0
  const int n = block_size * block_size;
514
0
  const double *A = block_finder->A;
515
0
  const double *AtA_inv = block_finder->AtA_inv;
516
0
  double plane_coords[kLowPolyNumParams];
517
0
  double AtA_inv_b[kLowPolyNumParams];
518
0
  int xi, yi, i;
519
520
0
  if (block_finder->use_highbd) {
521
0
    const uint16_t *const data16 = (const uint16_t *const)data;
522
0
    for (yi = 0; yi < block_size; ++yi) {
523
0
      const int y = clamp(offsy + yi, 0, h - 1);
524
0
      for (xi = 0; xi < block_size; ++xi) {
525
0
        const int x = clamp(offsx + xi, 0, w - 1);
526
0
        block[yi * block_size + xi] =
527
0
            ((double)data16[y * stride + x]) / block_finder->normalization;
528
0
      }
529
0
    }
530
0
  } else {
531
0
    for (yi = 0; yi < block_size; ++yi) {
532
0
      const int y = clamp(offsy + yi, 0, h - 1);
533
0
      for (xi = 0; xi < block_size; ++xi) {
534
0
        const int x = clamp(offsx + xi, 0, w - 1);
535
0
        block[yi * block_size + xi] =
536
0
            ((double)data[y * stride + x]) / block_finder->normalization;
537
0
      }
538
0
    }
539
0
  }
540
0
  multiply_mat(block, A, AtA_inv_b, 1, n, kLowPolyNumParams);
541
0
  multiply_mat(AtA_inv, AtA_inv_b, plane_coords, kLowPolyNumParams,
542
0
               kLowPolyNumParams, 1);
543
0
  multiply_mat(A, plane_coords, plane, n, kLowPolyNumParams, 1);
544
545
0
  for (i = 0; i < n; ++i) {
546
0
    block[i] -= plane[i];
547
0
  }
548
0
}
549
550
typedef struct {
551
  int index;
552
  float score;
553
} index_and_score_t;
554
555
0
static int compare_scores(const void *a, const void *b) {
556
0
  const float diff =
557
0
      ((index_and_score_t *)a)->score - ((index_and_score_t *)b)->score;
558
0
  if (diff < 0)
559
0
    return -1;
560
0
  else if (diff > 0)
561
0
    return 1;
562
0
  return 0;
563
0
}
564
565
int aom_flat_block_finder_run(const aom_flat_block_finder_t *block_finder,
566
                              const uint8_t *const data, int w, int h,
567
0
                              int stride, uint8_t *flat_blocks) {
568
  // The gradient-based features used in this code are based on:
569
  //  A. Kokaram, D. Kelly, H. Denman and A. Crawford, "Measuring noise
570
  //  correlation for improved video denoising," 2012 19th, ICIP.
571
  // The thresholds are more lenient to allow for correct grain modeling
572
  // if extreme cases.
573
0
  const int block_size = block_finder->block_size;
574
0
  const int n = block_size * block_size;
575
0
  const double kTraceThreshold = 0.15 / (32 * 32);
576
0
  const double kRatioThreshold = 1.25;
577
0
  const double kNormThreshold = 0.08 / (32 * 32);
578
0
  const double kVarThreshold = 0.005 / (double)n;
579
0
  const int num_blocks_w = (w + block_size - 1) / block_size;
580
0
  const int num_blocks_h = (h + block_size - 1) / block_size;
581
0
  int num_flat = 0;
582
0
  double *plane = (double *)aom_malloc(n * sizeof(*plane));
583
0
  double *block = (double *)aom_malloc(n * sizeof(*block));
584
0
  index_and_score_t *scores = (index_and_score_t *)aom_malloc(
585
0
      num_blocks_w * num_blocks_h * sizeof(*scores));
586
0
  if (plane == NULL || block == NULL || scores == NULL) {
587
0
    fprintf(stderr, "Failed to allocate memory for block of size %d\n", n);
588
0
    aom_free(plane);
589
0
    aom_free(block);
590
0
    aom_free(scores);
591
0
    return -1;
592
0
  }
593
594
#ifdef NOISE_MODEL_LOG_SCORE
595
  fprintf(stderr, "score = [");
596
#endif
597
0
  for (int by = 0; by < num_blocks_h; ++by) {
598
0
    for (int bx = 0; bx < num_blocks_w; ++bx) {
599
      // Compute gradient covariance matrix.
600
0
      aom_flat_block_finder_extract_block(block_finder, data, w, h, stride,
601
0
                                          bx * block_size, by * block_size,
602
0
                                          plane, block);
603
0
      double Gxx = 0, Gxy = 0, Gyy = 0;
604
0
      double mean = 0;
605
0
      double var = 0;
606
607
0
      for (int yi = 1; yi < block_size - 1; ++yi) {
608
0
        for (int xi = 1; xi < block_size - 1; ++xi) {
609
0
          const double gx = (block[yi * block_size + xi + 1] -
610
0
                             block[yi * block_size + xi - 1]) /
611
0
                            2;
612
0
          const double gy = (block[yi * block_size + xi + block_size] -
613
0
                             block[yi * block_size + xi - block_size]) /
614
0
                            2;
615
0
          Gxx += gx * gx;
616
0
          Gxy += gx * gy;
617
0
          Gyy += gy * gy;
618
619
0
          const double value = block[yi * block_size + xi];
620
0
          mean += value;
621
0
          var += value * value;
622
0
        }
623
0
      }
624
0
      mean /= (block_size - 2) * (block_size - 2);
625
626
      // Normalize gradients by block_size.
627
0
      Gxx /= ((block_size - 2) * (block_size - 2));
628
0
      Gxy /= ((block_size - 2) * (block_size - 2));
629
0
      Gyy /= ((block_size - 2) * (block_size - 2));
630
0
      var = var / ((block_size - 2) * (block_size - 2)) - mean * mean;
631
632
0
      {
633
0
        const double trace = Gxx + Gyy;
634
0
        const double det = Gxx * Gyy - Gxy * Gxy;
635
0
        const double e1 = (trace + sqrt(trace * trace - 4 * det)) / 2.;
636
0
        const double e2 = (trace - sqrt(trace * trace - 4 * det)) / 2.;
637
0
        const double norm = e1;  // Spectral norm
638
0
        const double ratio = (e1 / AOMMAX(e2, 1e-6));
639
0
        const int is_flat = (trace < kTraceThreshold) &&
640
0
                            (ratio < kRatioThreshold) &&
641
0
                            (norm < kNormThreshold) && (var > kVarThreshold);
642
        // The following weights are used to combine the above features to give
643
        // a sigmoid score for flatness. If the input was normalized to [0,100]
644
        // the magnitude of these values would be close to 1 (e.g., weights
645
        // corresponding to variance would be a factor of 10000x smaller).
646
        // The weights are given in the following order:
647
        //    [{var}, {ratio}, {trace}, {norm}, offset]
648
        // with one of the most discriminative being simply the variance.
649
0
        const double weights[5] = { -6682, -0.2056, 13087, -12434, 2.5694 };
650
0
        double sum_weights = weights[0] * var + weights[1] * ratio +
651
0
                             weights[2] * trace + weights[3] * norm +
652
0
                             weights[4];
653
        // clamp the value to [-25.0, 100.0] to prevent overflow
654
0
        sum_weights = fclamp(sum_weights, -25.0, 100.0);
655
0
        const float score = (float)(1.0 / (1 + exp(-sum_weights)));
656
0
        flat_blocks[by * num_blocks_w + bx] = is_flat ? 255 : 0;
657
0
        scores[by * num_blocks_w + bx].score = var > kVarThreshold ? score : 0;
658
0
        scores[by * num_blocks_w + bx].index = by * num_blocks_w + bx;
659
#ifdef NOISE_MODEL_LOG_SCORE
660
        fprintf(stderr, "%g %g %g %g %g %d ", score, var, ratio, trace, norm,
661
                is_flat);
662
#endif
663
0
        num_flat += is_flat;
664
0
      }
665
0
    }
666
#ifdef NOISE_MODEL_LOG_SCORE
667
    fprintf(stderr, "\n");
668
#endif
669
0
  }
670
#ifdef NOISE_MODEL_LOG_SCORE
671
  fprintf(stderr, "];\n");
672
#endif
673
  // Find the top-scored blocks (most likely to be flat) and set the flat blocks
674
  // be the union of the thresholded results and the top 10th percentile of the
675
  // scored results.
676
0
  qsort(scores, num_blocks_w * num_blocks_h, sizeof(*scores), &compare_scores);
677
0
  const int top_nth_percentile = num_blocks_w * num_blocks_h * 90 / 100;
678
0
  const float score_threshold = scores[top_nth_percentile].score;
679
0
  for (int i = 0; i < num_blocks_w * num_blocks_h; ++i) {
680
0
    if (scores[i].score >= score_threshold) {
681
0
      num_flat += flat_blocks[scores[i].index] == 0;
682
0
      flat_blocks[scores[i].index] |= 1;
683
0
    }
684
0
  }
685
0
  aom_free(block);
686
0
  aom_free(plane);
687
0
  aom_free(scores);
688
0
  return num_flat;
689
0
}
690
691
int aom_noise_model_init(aom_noise_model_t *model,
692
0
                         const aom_noise_model_params_t params) {
693
0
  const int n = num_coeffs(params);
694
0
  const int lag = params.lag;
695
0
  const int bit_depth = params.bit_depth;
696
0
  int x = 0, y = 0, i = 0, c = 0;
697
698
0
  memset(model, 0, sizeof(*model));
699
0
  if (params.lag < 1) {
700
0
    fprintf(stderr, "Invalid noise param: lag = %d must be >= 1\n", params.lag);
701
0
    return 0;
702
0
  }
703
0
  if (params.lag > kMaxLag) {
704
0
    fprintf(stderr, "Invalid noise param: lag = %d must be <= %d\n", params.lag,
705
0
            kMaxLag);
706
0
    return 0;
707
0
  }
708
0
  if (!(params.bit_depth == 8 || params.bit_depth == 10 ||
709
0
        params.bit_depth == 12)) {
710
0
    return 0;
711
0
  }
712
713
0
  model->params = params;
714
0
  for (c = 0; c < 3; ++c) {
715
0
    if (!noise_state_init(&model->combined_state[c], n + (c > 0), bit_depth)) {
716
0
      fprintf(stderr, "Failed to allocate noise state for channel %d\n", c);
717
0
      aom_noise_model_free(model);
718
0
      return 0;
719
0
    }
720
0
    if (!noise_state_init(&model->latest_state[c], n + (c > 0), bit_depth)) {
721
0
      fprintf(stderr, "Failed to allocate noise state for channel %d\n", c);
722
0
      aom_noise_model_free(model);
723
0
      return 0;
724
0
    }
725
0
  }
726
0
  model->n = n;
727
0
  model->coords = (int(*)[2])aom_malloc(sizeof(*model->coords) * n);
728
0
  if (!model->coords) {
729
0
    aom_noise_model_free(model);
730
0
    return 0;
731
0
  }
732
733
0
  for (y = -lag; y <= 0; ++y) {
734
0
    const int max_x = y == 0 ? -1 : lag;
735
0
    for (x = -lag; x <= max_x; ++x) {
736
0
      switch (params.shape) {
737
0
        case AOM_NOISE_SHAPE_DIAMOND:
738
0
          if (abs(x) <= y + lag) {
739
0
            model->coords[i][0] = x;
740
0
            model->coords[i][1] = y;
741
0
            ++i;
742
0
          }
743
0
          break;
744
0
        case AOM_NOISE_SHAPE_SQUARE:
745
0
          model->coords[i][0] = x;
746
0
          model->coords[i][1] = y;
747
0
          ++i;
748
0
          break;
749
0
        default:
750
0
          fprintf(stderr, "Invalid shape\n");
751
0
          aom_noise_model_free(model);
752
0
          return 0;
753
0
      }
754
0
    }
755
0
  }
756
0
  assert(i == n);
757
0
  return 1;
758
0
}
759
760
0
void aom_noise_model_free(aom_noise_model_t *model) {
761
0
  int c = 0;
762
0
  if (!model) return;
763
764
0
  aom_free(model->coords);
765
0
  for (c = 0; c < 3; ++c) {
766
0
    equation_system_free(&model->latest_state[c].eqns);
767
0
    equation_system_free(&model->combined_state[c].eqns);
768
769
0
    equation_system_free(&model->latest_state[c].strength_solver.eqns);
770
0
    equation_system_free(&model->combined_state[c].strength_solver.eqns);
771
0
  }
772
0
  memset(model, 0, sizeof(*model));
773
0
}
774
775
// Extracts the neighborhood defined by coords around point (x, y) from
776
// the difference between the data and denoised images. Also extracts the
777
// entry (possibly downsampled) for (x, y) in the alt_data (e.g., luma).
778
#define EXTRACT_AR_ROW(INT_TYPE, suffix)                                   \
779
  static double extract_ar_row_##suffix(                                   \
780
      int(*coords)[2], int num_coords, const INT_TYPE *const data,         \
781
      const INT_TYPE *const denoised, int stride, int sub_log2[2],         \
782
      const INT_TYPE *const alt_data, const INT_TYPE *const alt_denoised,  \
783
0
      int alt_stride, int x, int y, double *buffer) {                      \
784
0
    for (int i = 0; i < num_coords; ++i) {                                 \
785
0
      const int x_i = x + coords[i][0], y_i = y + coords[i][1];            \
786
0
      buffer[i] =                                                          \
787
0
          (double)data[y_i * stride + x_i] - denoised[y_i * stride + x_i]; \
788
0
    }                                                                      \
789
0
    const double val =                                                     \
790
0
        (double)data[y * stride + x] - denoised[y * stride + x];           \
791
0
                                                                           \
792
0
    if (alt_data && alt_denoised) {                                        \
793
0
      double avg_data = 0, avg_denoised = 0;                               \
794
0
      int num_samples = 0;                                                 \
795
0
      for (int dy_i = 0; dy_i < (1 << sub_log2[1]); dy_i++) {              \
796
0
        const int y_up = (y << sub_log2[1]) + dy_i;                        \
797
0
        for (int dx_i = 0; dx_i < (1 << sub_log2[0]); dx_i++) {            \
798
0
          const int x_up = (x << sub_log2[0]) + dx_i;                      \
799
0
          avg_data += alt_data[y_up * alt_stride + x_up];                  \
800
0
          avg_denoised += alt_denoised[y_up * alt_stride + x_up];          \
801
0
          num_samples++;                                                   \
802
0
        }                                                                  \
803
0
      }                                                                    \
804
0
      buffer[num_coords] = (avg_data - avg_denoised) / num_samples;        \
805
0
    }                                                                      \
806
0
    return val;                                                            \
807
0
  }
Unexecuted instantiation: noise_model.c:extract_ar_row_highbd
Unexecuted instantiation: noise_model.c:extract_ar_row_lowbd
808
809
EXTRACT_AR_ROW(uint8_t, lowbd)
810
EXTRACT_AR_ROW(uint16_t, highbd)
811
812
static int add_block_observations(
813
    aom_noise_model_t *noise_model, int c, const uint8_t *const data,
814
    const uint8_t *const denoised, int w, int h, int stride, int sub_log2[2],
815
    const uint8_t *const alt_data, const uint8_t *const alt_denoised,
816
    int alt_stride, const uint8_t *const flat_blocks, int block_size,
817
0
    int num_blocks_w, int num_blocks_h) {
818
0
  const int lag = noise_model->params.lag;
819
0
  const int num_coords = noise_model->n;
820
0
  const double normalization = (1 << noise_model->params.bit_depth) - 1;
821
0
  double *A = noise_model->latest_state[c].eqns.A;
822
0
  double *b = noise_model->latest_state[c].eqns.b;
823
0
  double *buffer = (double *)aom_malloc(sizeof(*buffer) * (num_coords + 1));
824
0
  const int n = noise_model->latest_state[c].eqns.n;
825
826
0
  if (!buffer) {
827
0
    fprintf(stderr, "Unable to allocate buffer of size %d\n", num_coords + 1);
828
0
    return 0;
829
0
  }
830
0
  for (int by = 0; by < num_blocks_h; ++by) {
831
0
    const int y_o = by * (block_size >> sub_log2[1]);
832
0
    for (int bx = 0; bx < num_blocks_w; ++bx) {
833
0
      const int x_o = bx * (block_size >> sub_log2[0]);
834
0
      if (!flat_blocks[by * num_blocks_w + bx]) {
835
0
        continue;
836
0
      }
837
0
      int y_start =
838
0
          (by > 0 && flat_blocks[(by - 1) * num_blocks_w + bx]) ? 0 : lag;
839
0
      int x_start =
840
0
          (bx > 0 && flat_blocks[by * num_blocks_w + bx - 1]) ? 0 : lag;
841
0
      int y_end = AOMMIN((h >> sub_log2[1]) - by * (block_size >> sub_log2[1]),
842
0
                         block_size >> sub_log2[1]);
843
0
      int x_end = AOMMIN(
844
0
          (w >> sub_log2[0]) - bx * (block_size >> sub_log2[0]) - lag,
845
0
          (bx + 1 < num_blocks_w && flat_blocks[by * num_blocks_w + bx + 1])
846
0
              ? (block_size >> sub_log2[0])
847
0
              : ((block_size >> sub_log2[0]) - lag));
848
0
      for (int y = y_start; y < y_end; ++y) {
849
0
        for (int x = x_start; x < x_end; ++x) {
850
0
          const double val =
851
0
              noise_model->params.use_highbd
852
0
                  ? extract_ar_row_highbd(noise_model->coords, num_coords,
853
0
                                          (const uint16_t *const)data,
854
0
                                          (const uint16_t *const)denoised,
855
0
                                          stride, sub_log2,
856
0
                                          (const uint16_t *const)alt_data,
857
0
                                          (const uint16_t *const)alt_denoised,
858
0
                                          alt_stride, x + x_o, y + y_o, buffer)
859
0
                  : extract_ar_row_lowbd(noise_model->coords, num_coords, data,
860
0
                                         denoised, stride, sub_log2, alt_data,
861
0
                                         alt_denoised, alt_stride, x + x_o,
862
0
                                         y + y_o, buffer);
863
0
          for (int i = 0; i < n; ++i) {
864
0
            for (int j = 0; j < n; ++j) {
865
0
              A[i * n + j] +=
866
0
                  (buffer[i] * buffer[j]) / (normalization * normalization);
867
0
            }
868
0
            b[i] += (buffer[i] * val) / (normalization * normalization);
869
0
          }
870
0
          noise_model->latest_state[c].num_observations++;
871
0
        }
872
0
      }
873
0
    }
874
0
  }
875
0
  aom_free(buffer);
876
0
  return 1;
877
0
}
878
879
static void add_noise_std_observations(
880
    aom_noise_model_t *noise_model, int c, const double *coeffs,
881
    const uint8_t *const data, const uint8_t *const denoised, int w, int h,
882
    int stride, int sub_log2[2], const uint8_t *const alt_data, int alt_stride,
883
    const uint8_t *const flat_blocks, int block_size, int num_blocks_w,
884
0
    int num_blocks_h) {
885
0
  const int num_coords = noise_model->n;
886
0
  aom_noise_strength_solver_t *noise_strength_solver =
887
0
      &noise_model->latest_state[c].strength_solver;
888
889
0
  const aom_noise_strength_solver_t *noise_strength_luma =
890
0
      &noise_model->latest_state[0].strength_solver;
891
0
  const double luma_gain = noise_model->latest_state[0].ar_gain;
892
0
  const double noise_gain = noise_model->latest_state[c].ar_gain;
893
0
  for (int by = 0; by < num_blocks_h; ++by) {
894
0
    const int y_o = by * (block_size >> sub_log2[1]);
895
0
    for (int bx = 0; bx < num_blocks_w; ++bx) {
896
0
      const int x_o = bx * (block_size >> sub_log2[0]);
897
0
      if (!flat_blocks[by * num_blocks_w + bx]) {
898
0
        continue;
899
0
      }
900
0
      const int num_samples_h =
901
0
          AOMMIN((h >> sub_log2[1]) - by * (block_size >> sub_log2[1]),
902
0
                 block_size >> sub_log2[1]);
903
0
      const int num_samples_w =
904
0
          AOMMIN((w >> sub_log2[0]) - bx * (block_size >> sub_log2[0]),
905
0
                 (block_size >> sub_log2[0]));
906
      // Make sure that we have a reasonable amount of samples to consider the
907
      // block
908
0
      if (num_samples_w * num_samples_h > block_size) {
909
0
        const double block_mean = get_block_mean(
910
0
            alt_data ? alt_data : data, w, h, alt_data ? alt_stride : stride,
911
0
            x_o << sub_log2[0], y_o << sub_log2[1], block_size,
912
0
            noise_model->params.use_highbd);
913
0
        const double noise_var = get_noise_var(
914
0
            data, denoised, stride, w >> sub_log2[0], h >> sub_log2[1], x_o,
915
0
            y_o, block_size >> sub_log2[0], block_size >> sub_log2[1],
916
0
            noise_model->params.use_highbd);
917
        // We want to remove the part of the noise that came from being
918
        // correlated with luma. Note that the noise solver for luma must
919
        // have already been run.
920
0
        const double luma_strength =
921
0
            c > 0 ? luma_gain * noise_strength_solver_get_value(
922
0
                                    noise_strength_luma, block_mean)
923
0
                  : 0;
924
0
        const double corr = c > 0 ? coeffs[num_coords] : 0;
925
        // Chroma noise:
926
        //    N(0, noise_var) = N(0, uncorr_var) + corr * N(0, luma_strength^2)
927
        // The uncorrelated component:
928
        //   uncorr_var = noise_var - (corr * luma_strength)^2
929
        // But don't allow fully correlated noise (hence the max), since the
930
        // synthesis cannot model it.
931
0
        const double uncorr_std = sqrt(
932
0
            AOMMAX(noise_var / 16, noise_var - pow(corr * luma_strength, 2)));
933
        // After we've removed correlation with luma, undo the gain that will
934
        // come from running the IIR filter.
935
0
        const double adjusted_strength = uncorr_std / noise_gain;
936
0
        aom_noise_strength_solver_add_measurement(
937
0
            noise_strength_solver, block_mean, adjusted_strength);
938
0
      }
939
0
    }
940
0
  }
941
0
}
942
943
// Return true if the noise estimate appears to be different from the combined
944
// (multi-frame) estimate. The difference is measured by checking whether the
945
// AR coefficients have diverged (using a threshold on normalized cross
946
// correlation), or whether the noise strength has changed.
947
0
static int is_noise_model_different(aom_noise_model_t *const noise_model) {
948
  // These thresholds are kind of arbitrary and will likely need further tuning
949
  // (or exported as parameters). The threshold on noise strength is a weighted
950
  // difference between the noise strength histograms
951
0
  const double kCoeffThreshold = 0.9;
952
0
  const double kStrengthThreshold =
953
0
      0.005 * (1 << (noise_model->params.bit_depth - 8));
954
0
  for (int c = 0; c < 1; ++c) {
955
0
    const double corr =
956
0
        aom_normalized_cross_correlation(noise_model->latest_state[c].eqns.x,
957
0
                                         noise_model->combined_state[c].eqns.x,
958
0
                                         noise_model->combined_state[c].eqns.n);
959
0
    if (corr < kCoeffThreshold) return 1;
960
961
0
    const double dx =
962
0
        1.0 / noise_model->latest_state[c].strength_solver.num_bins;
963
964
0
    const aom_equation_system_t *latest_eqns =
965
0
        &noise_model->latest_state[c].strength_solver.eqns;
966
0
    const aom_equation_system_t *combined_eqns =
967
0
        &noise_model->combined_state[c].strength_solver.eqns;
968
0
    double diff = 0;
969
0
    double total_weight = 0;
970
0
    for (int j = 0; j < latest_eqns->n; ++j) {
971
0
      double weight = 0;
972
0
      for (int i = 0; i < latest_eqns->n; ++i) {
973
0
        weight += latest_eqns->A[i * latest_eqns->n + j];
974
0
      }
975
0
      weight = sqrt(weight);
976
0
      diff += weight * fabs(latest_eqns->x[j] - combined_eqns->x[j]);
977
0
      total_weight += weight;
978
0
    }
979
0
    if (diff * dx / total_weight > kStrengthThreshold) return 1;
980
0
  }
981
0
  return 0;
982
0
}
983
984
0
static int ar_equation_system_solve(aom_noise_state_t *state, int is_chroma) {
985
0
  const int ret = equation_system_solve(&state->eqns);
986
0
  state->ar_gain = 1.0;
987
0
  if (!ret) return ret;
988
989
  // Update the AR gain from the equation system as it will be used to fit
990
  // the noise strength as a function of intensity.  In the Yule-Walker
991
  // equations, the diagonal should be the variance of the correlated noise.
992
  // In the case of the least squares estimate, there will be some variability
993
  // in the diagonal. So use the mean of the diagonal as the estimate of
994
  // overall variance (this works for least squares or Yule-Walker formulation).
995
0
  double var = 0;
996
0
  const int n = state->eqns.n;
997
0
  for (int i = 0; i < (state->eqns.n - is_chroma); ++i) {
998
0
    var += state->eqns.A[i * n + i] / state->num_observations;
999
0
  }
1000
0
  var /= (n - is_chroma);
1001
1002
  // Keep track of E(Y^2) = <b, x> + E(X^2)
1003
  // In the case that we are using chroma and have an estimate of correlation
1004
  // with luma we adjust that estimate slightly to remove the correlated bits by
1005
  // subtracting out the last column of a scaled by our correlation estimate
1006
  // from b. E(y^2) = <b - A(:, end)*x(end), x>
1007
0
  double sum_covar = 0;
1008
0
  for (int i = 0; i < state->eqns.n - is_chroma; ++i) {
1009
0
    double bi = state->eqns.b[i];
1010
0
    if (is_chroma) {
1011
0
      bi -= state->eqns.A[i * n + (n - 1)] * state->eqns.x[n - 1];
1012
0
    }
1013
0
    sum_covar += (bi * state->eqns.x[i]) / state->num_observations;
1014
0
  }
1015
  // Now, get an estimate of the variance of uncorrelated noise signal and use
1016
  // it to determine the gain of the AR filter.
1017
0
  const double noise_var = AOMMAX(var - sum_covar, 1e-6);
1018
0
  state->ar_gain = AOMMAX(1, sqrt(AOMMAX(var / noise_var, 1e-6)));
1019
0
  return ret;
1020
0
}
1021
1022
aom_noise_status_t aom_noise_model_update(
1023
    aom_noise_model_t *const noise_model, const uint8_t *const data[3],
1024
    const uint8_t *const denoised[3], int w, int h, int stride[3],
1025
0
    int chroma_sub_log2[2], const uint8_t *const flat_blocks, int block_size) {
1026
0
  const int num_blocks_w = (w + block_size - 1) / block_size;
1027
0
  const int num_blocks_h = (h + block_size - 1) / block_size;
1028
0
  int y_model_different = 0;
1029
0
  int num_blocks = 0;
1030
0
  int i = 0, channel = 0;
1031
1032
0
  if (block_size <= 1) {
1033
0
    fprintf(stderr, "block_size = %d must be > 1\n", block_size);
1034
0
    return AOM_NOISE_STATUS_INVALID_ARGUMENT;
1035
0
  }
1036
1037
0
  if (block_size < noise_model->params.lag * 2 + 1) {
1038
0
    fprintf(stderr, "block_size = %d must be >= %d\n", block_size,
1039
0
            noise_model->params.lag * 2 + 1);
1040
0
    return AOM_NOISE_STATUS_INVALID_ARGUMENT;
1041
0
  }
1042
1043
  // Clear the latest equation system
1044
0
  for (i = 0; i < 3; ++i) {
1045
0
    equation_system_clear(&noise_model->latest_state[i].eqns);
1046
0
    noise_model->latest_state[i].num_observations = 0;
1047
0
    noise_strength_solver_clear(&noise_model->latest_state[i].strength_solver);
1048
0
  }
1049
1050
  // Check that we have enough flat blocks
1051
0
  for (i = 0; i < num_blocks_h * num_blocks_w; ++i) {
1052
0
    if (flat_blocks[i]) {
1053
0
      num_blocks++;
1054
0
    }
1055
0
  }
1056
1057
0
  if (num_blocks <= 1) {
1058
0
    fprintf(stderr, "Not enough flat blocks to update noise estimate\n");
1059
0
    return AOM_NOISE_STATUS_INSUFFICIENT_FLAT_BLOCKS;
1060
0
  }
1061
1062
0
  for (channel = 0; channel < 3; ++channel) {
1063
0
    int no_subsampling[2] = { 0, 0 };
1064
0
    const uint8_t *alt_data = channel > 0 ? data[0] : 0;
1065
0
    const uint8_t *alt_denoised = channel > 0 ? denoised[0] : 0;
1066
0
    int *sub = channel > 0 ? chroma_sub_log2 : no_subsampling;
1067
0
    const int is_chroma = channel != 0;
1068
0
    if (!data[channel] || !denoised[channel]) break;
1069
0
    if (!add_block_observations(noise_model, channel, data[channel],
1070
0
                                denoised[channel], w, h, stride[channel], sub,
1071
0
                                alt_data, alt_denoised, stride[0], flat_blocks,
1072
0
                                block_size, num_blocks_w, num_blocks_h)) {
1073
0
      fprintf(stderr, "Adding block observation failed\n");
1074
0
      return AOM_NOISE_STATUS_INTERNAL_ERROR;
1075
0
    }
1076
1077
0
    if (!ar_equation_system_solve(&noise_model->latest_state[channel],
1078
0
                                  is_chroma)) {
1079
0
      if (is_chroma) {
1080
0
        set_chroma_coefficient_fallback_soln(
1081
0
            &noise_model->latest_state[channel].eqns);
1082
0
      } else {
1083
0
        fprintf(stderr, "Solving latest noise equation system failed %d!\n",
1084
0
                channel);
1085
0
        return AOM_NOISE_STATUS_INTERNAL_ERROR;
1086
0
      }
1087
0
    }
1088
1089
0
    add_noise_std_observations(
1090
0
        noise_model, channel, noise_model->latest_state[channel].eqns.x,
1091
0
        data[channel], denoised[channel], w, h, stride[channel], sub, alt_data,
1092
0
        stride[0], flat_blocks, block_size, num_blocks_w, num_blocks_h);
1093
1094
0
    if (!aom_noise_strength_solver_solve(
1095
0
            &noise_model->latest_state[channel].strength_solver)) {
1096
0
      fprintf(stderr, "Solving latest noise strength failed!\n");
1097
0
      return AOM_NOISE_STATUS_INTERNAL_ERROR;
1098
0
    }
1099
1100
    // Check noise characteristics and return if error.
1101
0
    if (channel == 0 &&
1102
0
        noise_model->combined_state[channel].strength_solver.num_equations >
1103
0
            0 &&
1104
0
        is_noise_model_different(noise_model)) {
1105
0
      y_model_different = 1;
1106
0
    }
1107
1108
    // Don't update the combined stats if the y model is different.
1109
0
    if (y_model_different) continue;
1110
1111
0
    noise_model->combined_state[channel].num_observations +=
1112
0
        noise_model->latest_state[channel].num_observations;
1113
0
    equation_system_add(&noise_model->combined_state[channel].eqns,
1114
0
                        &noise_model->latest_state[channel].eqns);
1115
0
    if (!ar_equation_system_solve(&noise_model->combined_state[channel],
1116
0
                                  is_chroma)) {
1117
0
      if (is_chroma) {
1118
0
        set_chroma_coefficient_fallback_soln(
1119
0
            &noise_model->combined_state[channel].eqns);
1120
0
      } else {
1121
0
        fprintf(stderr, "Solving combined noise equation system failed %d!\n",
1122
0
                channel);
1123
0
        return AOM_NOISE_STATUS_INTERNAL_ERROR;
1124
0
      }
1125
0
    }
1126
1127
0
    noise_strength_solver_add(
1128
0
        &noise_model->combined_state[channel].strength_solver,
1129
0
        &noise_model->latest_state[channel].strength_solver);
1130
1131
0
    if (!aom_noise_strength_solver_solve(
1132
0
            &noise_model->combined_state[channel].strength_solver)) {
1133
0
      fprintf(stderr, "Solving combined noise strength failed!\n");
1134
0
      return AOM_NOISE_STATUS_INTERNAL_ERROR;
1135
0
    }
1136
0
  }
1137
1138
0
  return y_model_different ? AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE
1139
0
                           : AOM_NOISE_STATUS_OK;
1140
0
}
1141
1142
0
void aom_noise_model_save_latest(aom_noise_model_t *noise_model) {
1143
0
  for (int c = 0; c < 3; c++) {
1144
0
    equation_system_copy(&noise_model->combined_state[c].eqns,
1145
0
                         &noise_model->latest_state[c].eqns);
1146
0
    equation_system_copy(&noise_model->combined_state[c].strength_solver.eqns,
1147
0
                         &noise_model->latest_state[c].strength_solver.eqns);
1148
0
    noise_model->combined_state[c].strength_solver.num_equations =
1149
0
        noise_model->latest_state[c].strength_solver.num_equations;
1150
0
    noise_model->combined_state[c].num_observations =
1151
0
        noise_model->latest_state[c].num_observations;
1152
0
    noise_model->combined_state[c].ar_gain =
1153
0
        noise_model->latest_state[c].ar_gain;
1154
0
  }
1155
0
}
1156
1157
int aom_noise_model_get_grain_parameters(aom_noise_model_t *const noise_model,
1158
0
                                         aom_film_grain_t *film_grain) {
1159
0
  if (noise_model->params.lag > 3) {
1160
0
    fprintf(stderr, "params.lag = %d > 3\n", noise_model->params.lag);
1161
0
    return 0;
1162
0
  }
1163
0
  uint16_t random_seed = film_grain->random_seed;
1164
0
  memset(film_grain, 0, sizeof(*film_grain));
1165
0
  film_grain->random_seed = random_seed;
1166
1167
0
  film_grain->apply_grain = 1;
1168
0
  film_grain->update_parameters = 1;
1169
1170
0
  film_grain->ar_coeff_lag = noise_model->params.lag;
1171
1172
  // Convert the scaling functions to 8 bit values
1173
0
  aom_noise_strength_lut_t scaling_points[3];
1174
0
  if (!aom_noise_strength_solver_fit_piecewise(
1175
0
          &noise_model->combined_state[0].strength_solver, 14,
1176
0
          scaling_points + 0)) {
1177
0
    return 0;
1178
0
  }
1179
0
  if (!aom_noise_strength_solver_fit_piecewise(
1180
0
          &noise_model->combined_state[1].strength_solver, 10,
1181
0
          scaling_points + 1)) {
1182
0
    aom_noise_strength_lut_free(scaling_points + 0);
1183
0
    return 0;
1184
0
  }
1185
0
  if (!aom_noise_strength_solver_fit_piecewise(
1186
0
          &noise_model->combined_state[2].strength_solver, 10,
1187
0
          scaling_points + 2)) {
1188
0
    aom_noise_strength_lut_free(scaling_points + 0);
1189
0
    aom_noise_strength_lut_free(scaling_points + 1);
1190
0
    return 0;
1191
0
  }
1192
1193
  // Both the domain and the range of the scaling functions in the film_grain
1194
  // are normalized to 8-bit (e.g., they are implicitly scaled during grain
1195
  // synthesis).
1196
0
  const double strength_divisor = 1 << (noise_model->params.bit_depth - 8);
1197
0
  double max_scaling_value = 1e-4;
1198
0
  for (int c = 0; c < 3; ++c) {
1199
0
    for (int i = 0; i < scaling_points[c].num_points; ++i) {
1200
0
      scaling_points[c].points[i][0] =
1201
0
          AOMMIN(255, scaling_points[c].points[i][0] / strength_divisor);
1202
0
      scaling_points[c].points[i][1] =
1203
0
          AOMMIN(255, scaling_points[c].points[i][1] / strength_divisor);
1204
0
      max_scaling_value =
1205
0
          AOMMAX(scaling_points[c].points[i][1], max_scaling_value);
1206
0
    }
1207
0
  }
1208
1209
  // Scaling_shift values are in the range [8,11]
1210
0
  const int max_scaling_value_log2 =
1211
0
      clamp((int)floor(log2(max_scaling_value) + 1), 2, 5);
1212
0
  film_grain->scaling_shift = 5 + (8 - max_scaling_value_log2);
1213
1214
0
  const double scale_factor = 1 << (8 - max_scaling_value_log2);
1215
0
  film_grain->num_y_points = scaling_points[0].num_points;
1216
0
  film_grain->num_cb_points = scaling_points[1].num_points;
1217
0
  film_grain->num_cr_points = scaling_points[2].num_points;
1218
1219
0
  int(*film_grain_scaling[3])[2] = {
1220
0
    film_grain->scaling_points_y,
1221
0
    film_grain->scaling_points_cb,
1222
0
    film_grain->scaling_points_cr,
1223
0
  };
1224
0
  for (int c = 0; c < 3; c++) {
1225
0
    for (int i = 0; i < scaling_points[c].num_points; ++i) {
1226
0
      film_grain_scaling[c][i][0] = (int)(scaling_points[c].points[i][0] + 0.5);
1227
0
      film_grain_scaling[c][i][1] = clamp(
1228
0
          (int)(scale_factor * scaling_points[c].points[i][1] + 0.5), 0, 255);
1229
0
    }
1230
0
  }
1231
0
  aom_noise_strength_lut_free(scaling_points + 0);
1232
0
  aom_noise_strength_lut_free(scaling_points + 1);
1233
0
  aom_noise_strength_lut_free(scaling_points + 2);
1234
1235
  // Convert the ar_coeffs into 8-bit values
1236
0
  const int n_coeff = noise_model->combined_state[0].eqns.n;
1237
0
  double max_coeff = 1e-4, min_coeff = -1e-4;
1238
0
  double y_corr[2] = { 0, 0 };
1239
0
  double avg_luma_strength = 0;
1240
0
  for (int c = 0; c < 3; c++) {
1241
0
    aom_equation_system_t *eqns = &noise_model->combined_state[c].eqns;
1242
0
    for (int i = 0; i < n_coeff; ++i) {
1243
0
      max_coeff = AOMMAX(max_coeff, eqns->x[i]);
1244
0
      min_coeff = AOMMIN(min_coeff, eqns->x[i]);
1245
0
    }
1246
    // Since the correlation between luma/chroma was computed in an already
1247
    // scaled space, we adjust it in the un-scaled space.
1248
0
    aom_noise_strength_solver_t *solver =
1249
0
        &noise_model->combined_state[c].strength_solver;
1250
    // Compute a weighted average of the strength for the channel.
1251
0
    double average_strength = 0, total_weight = 0;
1252
0
    for (int i = 0; i < solver->eqns.n; ++i) {
1253
0
      double w = 0;
1254
0
      for (int j = 0; j < solver->eqns.n; ++j) {
1255
0
        w += solver->eqns.A[i * solver->eqns.n + j];
1256
0
      }
1257
0
      w = sqrt(w);
1258
0
      average_strength += solver->eqns.x[i] * w;
1259
0
      total_weight += w;
1260
0
    }
1261
0
    if (total_weight == 0)
1262
0
      average_strength = 1;
1263
0
    else
1264
0
      average_strength /= total_weight;
1265
0
    if (c == 0) {
1266
0
      avg_luma_strength = average_strength;
1267
0
    } else {
1268
0
      y_corr[c - 1] =
1269
0
          average_strength > 1e-6
1270
0
              ? avg_luma_strength * eqns->x[n_coeff] / average_strength
1271
0
              : 0;
1272
0
      max_coeff = AOMMAX(max_coeff, y_corr[c - 1]);
1273
0
      min_coeff = AOMMIN(min_coeff, y_corr[c - 1]);
1274
0
    }
1275
0
  }
1276
  // Shift value: AR coeffs range (values 6-9)
1277
  // 6: [-2, 2),  7: [-1, 1), 8: [-0.5, 0.5), 9: [-0.25, 0.25)
1278
0
  film_grain->ar_coeff_shift =
1279
0
      clamp(7 - (int)AOMMAX(1 + floor(log2(max_coeff)), ceil(log2(-min_coeff))),
1280
0
            6, 9);
1281
0
  double scale_ar_coeff = 1 << film_grain->ar_coeff_shift;
1282
0
  int *ar_coeffs[3] = {
1283
0
    film_grain->ar_coeffs_y,
1284
0
    film_grain->ar_coeffs_cb,
1285
0
    film_grain->ar_coeffs_cr,
1286
0
  };
1287
0
  for (int c = 0; c < 3; ++c) {
1288
0
    aom_equation_system_t *eqns = &noise_model->combined_state[c].eqns;
1289
0
    for (int i = 0; i < n_coeff; ++i) {
1290
0
      ar_coeffs[c][i] =
1291
0
          clamp((int)round(scale_ar_coeff * eqns->x[i]), -128, 127);
1292
0
    }
1293
0
    if (c > 0) {
1294
0
      ar_coeffs[c][n_coeff] =
1295
0
          clamp((int)round(scale_ar_coeff * y_corr[c - 1]), -128, 127);
1296
0
    }
1297
0
  }
1298
1299
  // At the moment, the noise modeling code assumes that the chroma scaling
1300
  // functions are a function of luma.
1301
0
  film_grain->cb_mult = 128;       // 8 bits
1302
0
  film_grain->cb_luma_mult = 192;  // 8 bits
1303
0
  film_grain->cb_offset = 256;     // 9 bits
1304
1305
0
  film_grain->cr_mult = 128;       // 8 bits
1306
0
  film_grain->cr_luma_mult = 192;  // 8 bits
1307
0
  film_grain->cr_offset = 256;     // 9 bits
1308
1309
0
  film_grain->chroma_scaling_from_luma = 0;
1310
0
  film_grain->grain_scale_shift = 0;
1311
0
  film_grain->overlap_flag = 1;
1312
0
  return 1;
1313
0
}
1314
1315
0
static void pointwise_multiply(const float *a, float *b, int n) {
1316
0
  for (int i = 0; i < n; ++i) {
1317
0
    b[i] *= a[i];
1318
0
  }
1319
0
}
1320
1321
0
static float *get_half_cos_window(int block_size) {
1322
0
  float *window_function =
1323
0
      (float *)aom_malloc(block_size * block_size * sizeof(*window_function));
1324
0
  if (!window_function) return NULL;
1325
0
  for (int y = 0; y < block_size; ++y) {
1326
0
    const double cos_yd = cos((.5 + y) * PI / block_size - PI / 2);
1327
0
    for (int x = 0; x < block_size; ++x) {
1328
0
      const double cos_xd = cos((.5 + x) * PI / block_size - PI / 2);
1329
0
      window_function[y * block_size + x] = (float)(cos_yd * cos_xd);
1330
0
    }
1331
0
  }
1332
0
  return window_function;
1333
0
}
1334
1335
#define DITHER_AND_QUANTIZE(INT_TYPE, suffix)                               \
1336
  static void dither_and_quantize_##suffix(                                 \
1337
      float *result, int result_stride, INT_TYPE *denoised, int w, int h,   \
1338
      int stride, int chroma_sub_w, int chroma_sub_h, int block_size,       \
1339
0
      float block_normalization) {                                          \
1340
0
    for (int y = 0; y < (h >> chroma_sub_h); ++y) {                         \
1341
0
      for (int x = 0; x < (w >> chroma_sub_w); ++x) {                       \
1342
0
        const int result_idx =                                              \
1343
0
            (y + (block_size >> chroma_sub_h)) * result_stride + x +        \
1344
0
            (block_size >> chroma_sub_w);                                   \
1345
0
        INT_TYPE new_val = (INT_TYPE)AOMMIN(                                \
1346
0
            AOMMAX(result[result_idx] * block_normalization + 0.5f, 0),     \
1347
0
            block_normalization);                                           \
1348
0
        float err =                                                         \
1349
0
            -(((float)new_val) / block_normalization - result[result_idx]); \
1350
0
        if (fabsf(err) < 1e-6f) {                                           \
1351
0
          err = 0.0f;                                                       \
1352
0
        }                                                                   \
1353
0
        denoised[y * stride + x] = new_val;                                 \
1354
0
        if (x + 1 < (w >> chroma_sub_w)) {                                  \
1355
0
          result[result_idx + 1] += err * 7.0f / 16.0f;                     \
1356
0
        }                                                                   \
1357
0
        if (y + 1 < (h >> chroma_sub_h)) {                                  \
1358
0
          if (x > 0) {                                                      \
1359
0
            result[result_idx + result_stride - 1] += err * 3.0f / 16.0f;   \
1360
0
          }                                                                 \
1361
0
          result[result_idx + result_stride] += err * 5.0f / 16.0f;         \
1362
0
          if (x + 1 < (w >> chroma_sub_w)) {                                \
1363
0
            result[result_idx + result_stride + 1] += err * 1.0f / 16.0f;   \
1364
0
          }                                                                 \
1365
0
        }                                                                   \
1366
0
      }                                                                     \
1367
0
    }                                                                       \
1368
0
  }
Unexecuted instantiation: noise_model.c:dither_and_quantize_highbd
Unexecuted instantiation: noise_model.c:dither_and_quantize_lowbd
1369
1370
DITHER_AND_QUANTIZE(uint8_t, lowbd)
1371
DITHER_AND_QUANTIZE(uint16_t, highbd)
1372
1373
int aom_wiener_denoise_2d(const uint8_t *const data[3], uint8_t *denoised[3],
1374
                          int w, int h, int stride[3], int chroma_sub[2],
1375
                          float *noise_psd[3], int block_size, int bit_depth,
1376
0
                          int use_highbd) {
1377
0
  float *plane = NULL, *block = NULL, *window_full = NULL,
1378
0
        *window_chroma = NULL;
1379
0
  double *block_d = NULL, *plane_d = NULL;
1380
0
  struct aom_noise_tx_t *tx_full = NULL;
1381
0
  struct aom_noise_tx_t *tx_chroma = NULL;
1382
0
  const int num_blocks_w = (w + block_size - 1) / block_size;
1383
0
  const int num_blocks_h = (h + block_size - 1) / block_size;
1384
0
  const int result_stride = (num_blocks_w + 2) * block_size;
1385
0
  const int result_height = (num_blocks_h + 2) * block_size;
1386
0
  float *result = NULL;
1387
0
  int init_success = 1;
1388
0
  aom_flat_block_finder_t block_finder_full;
1389
0
  aom_flat_block_finder_t block_finder_chroma;
1390
0
  const float kBlockNormalization = (float)((1 << bit_depth) - 1);
1391
0
  if (chroma_sub[0] != chroma_sub[1]) {
1392
0
    fprintf(stderr,
1393
0
            "aom_wiener_denoise_2d doesn't handle different chroma "
1394
0
            "subsampling\n");
1395
0
    return 0;
1396
0
  }
1397
0
  init_success &= aom_flat_block_finder_init(&block_finder_full, block_size,
1398
0
                                             bit_depth, use_highbd);
1399
0
  result = (float *)aom_malloc((num_blocks_h + 2) * block_size * result_stride *
1400
0
                               sizeof(*result));
1401
0
  plane = (float *)aom_malloc(block_size * block_size * sizeof(*plane));
1402
0
  block =
1403
0
      (float *)aom_memalign(32, 2 * block_size * block_size * sizeof(*block));
1404
0
  block_d = (double *)aom_malloc(block_size * block_size * sizeof(*block_d));
1405
0
  plane_d = (double *)aom_malloc(block_size * block_size * sizeof(*plane_d));
1406
0
  window_full = get_half_cos_window(block_size);
1407
0
  tx_full = aom_noise_tx_malloc(block_size);
1408
1409
0
  if (chroma_sub[0] != 0) {
1410
0
    init_success &= aom_flat_block_finder_init(&block_finder_chroma,
1411
0
                                               block_size >> chroma_sub[0],
1412
0
                                               bit_depth, use_highbd);
1413
0
    window_chroma = get_half_cos_window(block_size >> chroma_sub[0]);
1414
0
    tx_chroma = aom_noise_tx_malloc(block_size >> chroma_sub[0]);
1415
0
  } else {
1416
0
    window_chroma = window_full;
1417
0
    tx_chroma = tx_full;
1418
0
  }
1419
1420
0
  init_success &= (tx_full != NULL) && (tx_chroma != NULL) && (plane != NULL) &&
1421
0
                  (plane_d != NULL) && (block != NULL) && (block_d != NULL) &&
1422
0
                  (window_full != NULL) && (window_chroma != NULL) &&
1423
0
                  (result != NULL);
1424
0
  for (int c = init_success ? 0 : 3; c < 3; ++c) {
1425
0
    float *window_function = c == 0 ? window_full : window_chroma;
1426
0
    aom_flat_block_finder_t *block_finder = &block_finder_full;
1427
0
    const int chroma_sub_h = c > 0 ? chroma_sub[1] : 0;
1428
0
    const int chroma_sub_w = c > 0 ? chroma_sub[0] : 0;
1429
0
    struct aom_noise_tx_t *tx =
1430
0
        (c > 0 && chroma_sub[0] > 0) ? tx_chroma : tx_full;
1431
0
    if (!data[c] || !denoised[c]) continue;
1432
0
    if (c > 0 && chroma_sub[0] != 0) {
1433
0
      block_finder = &block_finder_chroma;
1434
0
    }
1435
0
    memset(result, 0, sizeof(*result) * result_stride * result_height);
1436
    // Do overlapped block processing (half overlapped). The block rows can
1437
    // easily be done in parallel
1438
0
    for (int offsy = 0; offsy < (block_size >> chroma_sub_h);
1439
0
         offsy += (block_size >> chroma_sub_h) / 2) {
1440
0
      for (int offsx = 0; offsx < (block_size >> chroma_sub_w);
1441
0
           offsx += (block_size >> chroma_sub_w) / 2) {
1442
        // Pad the boundary when processing each block-set.
1443
0
        for (int by = -1; by < num_blocks_h; ++by) {
1444
0
          for (int bx = -1; bx < num_blocks_w; ++bx) {
1445
0
            const int pixels_per_block =
1446
0
                (block_size >> chroma_sub_w) * (block_size >> chroma_sub_h);
1447
0
            aom_flat_block_finder_extract_block(
1448
0
                block_finder, data[c], w >> chroma_sub_w, h >> chroma_sub_h,
1449
0
                stride[c], bx * (block_size >> chroma_sub_w) + offsx,
1450
0
                by * (block_size >> chroma_sub_h) + offsy, plane_d, block_d);
1451
0
            for (int j = 0; j < pixels_per_block; ++j) {
1452
0
              block[j] = (float)block_d[j];
1453
0
              plane[j] = (float)plane_d[j];
1454
0
            }
1455
0
            pointwise_multiply(window_function, block, pixels_per_block);
1456
0
            aom_noise_tx_forward(tx, block);
1457
0
            aom_noise_tx_filter(tx, noise_psd[c]);
1458
0
            aom_noise_tx_inverse(tx, block);
1459
1460
            // Apply window function to the plane approximation (we will apply
1461
            // it to the sum of plane + block when composing the results).
1462
0
            pointwise_multiply(window_function, plane, pixels_per_block);
1463
1464
0
            for (int y = 0; y < (block_size >> chroma_sub_h); ++y) {
1465
0
              const int y_result =
1466
0
                  y + (by + 1) * (block_size >> chroma_sub_h) + offsy;
1467
0
              for (int x = 0; x < (block_size >> chroma_sub_w); ++x) {
1468
0
                const int x_result =
1469
0
                    x + (bx + 1) * (block_size >> chroma_sub_w) + offsx;
1470
0
                result[y_result * result_stride + x_result] +=
1471
0
                    (block[y * (block_size >> chroma_sub_w) + x] +
1472
0
                     plane[y * (block_size >> chroma_sub_w) + x]) *
1473
0
                    window_function[y * (block_size >> chroma_sub_w) + x];
1474
0
              }
1475
0
            }
1476
0
          }
1477
0
        }
1478
0
      }
1479
0
    }
1480
0
    if (use_highbd) {
1481
0
      dither_and_quantize_highbd(result, result_stride, (uint16_t *)denoised[c],
1482
0
                                 w, h, stride[c], chroma_sub_w, chroma_sub_h,
1483
0
                                 block_size, kBlockNormalization);
1484
0
    } else {
1485
0
      dither_and_quantize_lowbd(result, result_stride, denoised[c], w, h,
1486
0
                                stride[c], chroma_sub_w, chroma_sub_h,
1487
0
                                block_size, kBlockNormalization);
1488
0
    }
1489
0
  }
1490
0
  aom_free(result);
1491
0
  aom_free(plane);
1492
0
  aom_free(block);
1493
0
  aom_free(plane_d);
1494
0
  aom_free(block_d);
1495
0
  aom_free(window_full);
1496
1497
0
  aom_noise_tx_free(tx_full);
1498
1499
0
  aom_flat_block_finder_free(&block_finder_full);
1500
0
  if (chroma_sub[0] != 0) {
1501
0
    aom_flat_block_finder_free(&block_finder_chroma);
1502
0
    aom_free(window_chroma);
1503
0
    aom_noise_tx_free(tx_chroma);
1504
0
  }
1505
0
  return init_success;
1506
0
}
1507
1508
struct aom_denoise_and_model_t {
1509
  int block_size;
1510
  int bit_depth;
1511
  float noise_level;
1512
1513
  // Size of current denoised buffer and flat_block buffer
1514
  int width;
1515
  int height;
1516
  int y_stride;
1517
  int uv_stride;
1518
  int num_blocks_w;
1519
  int num_blocks_h;
1520
1521
  // Buffers for image and noise_psd allocated on the fly
1522
  float *noise_psd[3];
1523
  uint8_t *denoised[3];
1524
  uint8_t *flat_blocks;
1525
1526
  aom_flat_block_finder_t flat_block_finder;
1527
  aom_noise_model_t noise_model;
1528
};
1529
1530
struct aom_denoise_and_model_t *aom_denoise_and_model_alloc(int bit_depth,
1531
                                                            int block_size,
1532
0
                                                            float noise_level) {
1533
0
  struct aom_denoise_and_model_t *ctx =
1534
0
      (struct aom_denoise_and_model_t *)aom_malloc(
1535
0
          sizeof(struct aom_denoise_and_model_t));
1536
0
  if (!ctx) {
1537
0
    fprintf(stderr, "Unable to allocate denoise_and_model struct\n");
1538
0
    return NULL;
1539
0
  }
1540
0
  memset(ctx, 0, sizeof(*ctx));
1541
1542
0
  ctx->block_size = block_size;
1543
0
  ctx->noise_level = noise_level;
1544
0
  ctx->bit_depth = bit_depth;
1545
1546
0
  ctx->noise_psd[0] =
1547
0
      (float *)aom_malloc(sizeof(*ctx->noise_psd[0]) * block_size * block_size);
1548
0
  ctx->noise_psd[1] =
1549
0
      (float *)aom_malloc(sizeof(*ctx->noise_psd[1]) * block_size * block_size);
1550
0
  ctx->noise_psd[2] =
1551
0
      (float *)aom_malloc(sizeof(*ctx->noise_psd[2]) * block_size * block_size);
1552
0
  if (!ctx->noise_psd[0] || !ctx->noise_psd[1] || !ctx->noise_psd[2]) {
1553
0
    fprintf(stderr, "Unable to allocate noise PSD buffers\n");
1554
0
    aom_denoise_and_model_free(ctx);
1555
0
    return NULL;
1556
0
  }
1557
0
  return ctx;
1558
0
}
1559
1560
0
void aom_denoise_and_model_free(struct aom_denoise_and_model_t *ctx) {
1561
0
  aom_free(ctx->flat_blocks);
1562
0
  for (int i = 0; i < 3; ++i) {
1563
0
    aom_free(ctx->denoised[i]);
1564
0
    aom_free(ctx->noise_psd[i]);
1565
0
  }
1566
0
  aom_noise_model_free(&ctx->noise_model);
1567
0
  aom_flat_block_finder_free(&ctx->flat_block_finder);
1568
0
  aom_free(ctx);
1569
0
}
1570
1571
static int denoise_and_model_realloc_if_necessary(
1572
0
    struct aom_denoise_and_model_t *ctx, const YV12_BUFFER_CONFIG *sd) {
1573
0
  if (ctx->width == sd->y_width && ctx->height == sd->y_height &&
1574
0
      ctx->y_stride == sd->y_stride && ctx->uv_stride == sd->uv_stride)
1575
0
    return 1;
1576
0
  const int use_highbd = (sd->flags & YV12_FLAG_HIGHBITDEPTH) != 0;
1577
0
  const int block_size = ctx->block_size;
1578
1579
0
  ctx->width = sd->y_width;
1580
0
  ctx->height = sd->y_height;
1581
0
  ctx->y_stride = sd->y_stride;
1582
0
  ctx->uv_stride = sd->uv_stride;
1583
1584
0
  for (int i = 0; i < 3; ++i) {
1585
0
    aom_free(ctx->denoised[i]);
1586
0
    ctx->denoised[i] = NULL;
1587
0
  }
1588
0
  aom_free(ctx->flat_blocks);
1589
0
  ctx->flat_blocks = NULL;
1590
1591
0
  ctx->denoised[0] =
1592
0
      (uint8_t *)aom_malloc((sd->y_stride * sd->y_height) << use_highbd);
1593
0
  ctx->denoised[1] =
1594
0
      (uint8_t *)aom_malloc((sd->uv_stride * sd->uv_height) << use_highbd);
1595
0
  ctx->denoised[2] =
1596
0
      (uint8_t *)aom_malloc((sd->uv_stride * sd->uv_height) << use_highbd);
1597
0
  if (!ctx->denoised[0] || !ctx->denoised[1] || !ctx->denoised[2]) {
1598
0
    fprintf(stderr, "Unable to allocate denoise buffers\n");
1599
0
    return 0;
1600
0
  }
1601
0
  ctx->num_blocks_w = (sd->y_width + ctx->block_size - 1) / ctx->block_size;
1602
0
  ctx->num_blocks_h = (sd->y_height + ctx->block_size - 1) / ctx->block_size;
1603
0
  ctx->flat_blocks =
1604
0
      (uint8_t *)aom_malloc(ctx->num_blocks_w * ctx->num_blocks_h);
1605
0
  if (!ctx->flat_blocks) {
1606
0
    fprintf(stderr, "Unable to allocate flat_blocks buffer\n");
1607
0
    return 0;
1608
0
  }
1609
1610
0
  aom_flat_block_finder_free(&ctx->flat_block_finder);
1611
0
  if (!aom_flat_block_finder_init(&ctx->flat_block_finder, ctx->block_size,
1612
0
                                  ctx->bit_depth, use_highbd)) {
1613
0
    fprintf(stderr, "Unable to init flat block finder\n");
1614
0
    return 0;
1615
0
  }
1616
1617
0
  const aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3,
1618
0
                                            ctx->bit_depth, use_highbd };
1619
0
  aom_noise_model_free(&ctx->noise_model);
1620
0
  if (!aom_noise_model_init(&ctx->noise_model, params)) {
1621
0
    fprintf(stderr, "Unable to init noise model\n");
1622
0
    return 0;
1623
0
  }
1624
1625
  // Simply use a flat PSD (although we could use the flat blocks to estimate
1626
  // PSD) those to estimate an actual noise PSD)
1627
0
  const float y_noise_level =
1628
0
      aom_noise_psd_get_default_value(ctx->block_size, ctx->noise_level);
1629
0
  const float uv_noise_level = aom_noise_psd_get_default_value(
1630
0
      ctx->block_size >> sd->subsampling_x, ctx->noise_level);
1631
0
  for (int i = 0; i < block_size * block_size; ++i) {
1632
0
    ctx->noise_psd[0][i] = y_noise_level;
1633
0
    ctx->noise_psd[1][i] = ctx->noise_psd[2][i] = uv_noise_level;
1634
0
  }
1635
0
  return 1;
1636
0
}
1637
1638
// TODO(aomedia:3151): Handle a monochrome image (sd->u_buffer and sd->v_buffer
1639
// are null pointers) correctly.
1640
int aom_denoise_and_model_run(struct aom_denoise_and_model_t *ctx,
1641
                              const YV12_BUFFER_CONFIG *sd,
1642
0
                              aom_film_grain_t *film_grain, int apply_denoise) {
1643
0
  const int block_size = ctx->block_size;
1644
0
  const int use_highbd = (sd->flags & YV12_FLAG_HIGHBITDEPTH) != 0;
1645
0
  uint8_t *raw_data[3] = {
1646
0
    use_highbd ? (uint8_t *)CONVERT_TO_SHORTPTR(sd->y_buffer) : sd->y_buffer,
1647
0
    use_highbd ? (uint8_t *)CONVERT_TO_SHORTPTR(sd->u_buffer) : sd->u_buffer,
1648
0
    use_highbd ? (uint8_t *)CONVERT_TO_SHORTPTR(sd->v_buffer) : sd->v_buffer,
1649
0
  };
1650
0
  const uint8_t *const data[3] = { raw_data[0], raw_data[1], raw_data[2] };
1651
0
  int strides[3] = { sd->y_stride, sd->uv_stride, sd->uv_stride };
1652
0
  int chroma_sub_log2[2] = { sd->subsampling_x, sd->subsampling_y };
1653
1654
0
  if (!denoise_and_model_realloc_if_necessary(ctx, sd)) {
1655
0
    fprintf(stderr, "Unable to realloc buffers\n");
1656
0
    return 0;
1657
0
  }
1658
1659
0
  aom_flat_block_finder_run(&ctx->flat_block_finder, data[0], sd->y_width,
1660
0
                            sd->y_height, strides[0], ctx->flat_blocks);
1661
1662
0
  if (!aom_wiener_denoise_2d(data, ctx->denoised, sd->y_width, sd->y_height,
1663
0
                             strides, chroma_sub_log2, ctx->noise_psd,
1664
0
                             block_size, ctx->bit_depth, use_highbd)) {
1665
0
    fprintf(stderr, "Unable to denoise image\n");
1666
0
    return 0;
1667
0
  }
1668
1669
0
  const aom_noise_status_t status = aom_noise_model_update(
1670
0
      &ctx->noise_model, data, (const uint8_t *const *)ctx->denoised,
1671
0
      sd->y_width, sd->y_height, strides, chroma_sub_log2, ctx->flat_blocks,
1672
0
      block_size);
1673
0
  int have_noise_estimate = 0;
1674
0
  if (status == AOM_NOISE_STATUS_OK) {
1675
0
    have_noise_estimate = 1;
1676
0
  } else if (status == AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE) {
1677
0
    aom_noise_model_save_latest(&ctx->noise_model);
1678
0
    have_noise_estimate = 1;
1679
0
  } else {
1680
    // Unable to update noise model; proceed if we have a previous estimate.
1681
0
    have_noise_estimate =
1682
0
        (ctx->noise_model.combined_state[0].strength_solver.num_equations > 0);
1683
0
  }
1684
1685
0
  film_grain->apply_grain = 0;
1686
0
  if (have_noise_estimate) {
1687
0
    if (!aom_noise_model_get_grain_parameters(&ctx->noise_model, film_grain)) {
1688
0
      fprintf(stderr, "Unable to get grain parameters.\n");
1689
0
      return 0;
1690
0
    }
1691
0
    if (!film_grain->random_seed) {
1692
0
      film_grain->random_seed = 7391;
1693
0
    }
1694
0
    if (apply_denoise) {
1695
0
      memcpy(raw_data[0], ctx->denoised[0],
1696
0
             (strides[0] * sd->y_height) << use_highbd);
1697
0
      if (!sd->monochrome) {
1698
0
        memcpy(raw_data[1], ctx->denoised[1],
1699
0
               (strides[1] * sd->uv_height) << use_highbd);
1700
0
        memcpy(raw_data[2], ctx->denoised[2],
1701
0
               (strides[2] * sd->uv_height) << use_highbd);
1702
0
      }
1703
0
    }
1704
0
  }
1705
0
  return 1;
1706
0
}