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