/work/vvenc/source/Lib/EncoderLib/SEIFilmGrainAnalyzer.cpp
Line | Count | Source |
1 | | /* ----------------------------------------------------------------------------- |
2 | | The copyright in this software is being made available under the Clear BSD |
3 | | License, included below. No patent rights, trademark rights and/or |
4 | | other Intellectual Property Rights other than the copyrights concerning |
5 | | the Software are granted under this license. |
6 | | |
7 | | The Clear BSD License |
8 | | |
9 | | Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors. |
10 | | All rights reserved. |
11 | | |
12 | | Redistribution and use in source and binary forms, with or without modification, |
13 | | are permitted (subject to the limitations in the disclaimer below) provided that |
14 | | the following conditions are met: |
15 | | |
16 | | * Redistributions of source code must retain the above copyright notice, |
17 | | this list of conditions and the following disclaimer. |
18 | | |
19 | | * Redistributions in binary form must reproduce the above copyright |
20 | | notice, this list of conditions and the following disclaimer in the |
21 | | documentation and/or other materials provided with the distribution. |
22 | | |
23 | | * Neither the name of the copyright holder nor the names of its |
24 | | contributors may be used to endorse or promote products derived from this |
25 | | software without specific prior written permission. |
26 | | |
27 | | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY |
28 | | THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
29 | | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
30 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
31 | | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
32 | | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
33 | | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
34 | | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
35 | | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
36 | | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
37 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
38 | | POSSIBILITY OF SUCH DAMAGE. |
39 | | |
40 | | |
41 | | ------------------------------------------------------------------------------------------- */ |
42 | | |
43 | | #include "SEIFilmGrainAnalyzer.h" |
44 | | #include "SEIFilmGrainSpectra.h" |
45 | | |
46 | | #include "CommonLib/Rom.h" |
47 | | |
48 | | #include <algorithm> |
49 | | #include <cmath> |
50 | | #include <limits> |
51 | | #include <numeric> |
52 | | |
53 | | namespace vvenc { |
54 | | |
55 | | namespace { |
56 | | |
57 | | // Constants of the VTM analysis. |
58 | | constexpr int MAXPAIRS = 256; |
59 | | constexpr int MAXORDER = 8; // maximum order of the polynomial fit |
60 | | constexpr int MAX_REAL_SCALE = 16; |
61 | | constexpr int ORDER = 4; // order of the polynomial function |
62 | | constexpr int QUANT_LEVELS = 4; // quantization levels of the Lloyd-Max quantizer |
63 | | constexpr int INTERVAL_SIZE = 8; |
64 | | constexpr int MIN_ELEMENTS_PER_INTERVAL = 8; |
65 | | constexpr int POINT_STEP = 16; // step size of the point extension |
66 | | constexpr int MAX_POINTS_TO_EXTEND = 4; |
67 | | constexpr double POINT_SCALE = 1.25; // scaling of the point extension (VTM; the improved analysis keeps the level) |
68 | | constexpr double VAR_SCALE_DOWN = 1.2; // filter out points far below the fitted function |
69 | | constexpr double VAR_SCALE_UP = 0.6; // filter out points far above the fitted function |
70 | | constexpr int NUM_PASSES = 2; // the second pass of the fit filters the data points |
71 | | constexpr int NBRS = 1; // neighbours a point needs within WINDOW to be kept |
72 | | constexpr int WINDOW = 1; |
73 | | constexpr int MIN_MEASURED_POINTS = 2; // measured points the improved fit needs, before the point extension |
74 | | constexpr int MIN_INTENSITY = 40; |
75 | | constexpr int MAX_INTENSITY = 950; |
76 | | constexpr int MAX_STANDARD_DEVIATION = 255; // at 8 bits |
77 | | // With the improvements, the grain strength is kept in units of a quarter of twice the standard deviation (VTM: twice |
78 | | // the standard deviation), so that the scaling factors calibrated from it keep their precision for weak grain. |
79 | | constexpr int IMPROVED_STRENGTH_SCALE = 4; |
80 | | constexpr int BIT_DEPTH_8 = 8; |
81 | | constexpr int MAX_ALLOWED_MODEL_VALUES = 3; |
82 | | constexpr int MAX_NUM_INTENSITIES = 256; |
83 | | constexpr int MAX_CUTOFF_PAIRS = 10; // different cut-off pairs the synthesis supports per picture |
84 | | constexpr double MIN_BLOCK_WEIGHT = 1.0; // accumulated block weight an interval needs for cut-off estimation |
85 | | |
86 | | // Calibration of the scaling factors, per component type (luma, 4:2:0 chroma) and cut-off pair, measured with the |
87 | | // vvdec film grain synthesis on flat grey 1920x1080 10-bit pictures: |
88 | | // - PATTERN_STDDEV: standard deviation of the synthesized grain for scaling factor 64 at log2_scale_factor 2, so that |
89 | | // a scaling factor s at log2 scale factor l gives grain of standard deviation |
90 | | // s * PATTERN_STDDEV / 2^( l + 14 - bitDepth ) (the synthesis of VTM gives the same); |
91 | | // - BLOCK_VARIANCE_SHARE: the share of that grain's variance that remains after removing the mean of every 16x16 |
92 | | // block, as the analysis measures it. |
93 | | constexpr int NUM_CUTOFFS = 13; // cut-off frequencies 2 to 14 |
94 | | // Largest scaling factor signalled. The syntax allows 255 and so does the scalar synthesis of vvdec, but its x86 SIMD |
95 | | // synthesis sign-extends the 8-bit scaling factors, so 128 and above would make weaker grain of the wrong sign. |
96 | | constexpr int MAX_SCALING_FACTOR = 127; |
97 | | constexpr double PATTERN_STDDEV[ 2 ][ NUM_CUTOFFS ][ NUM_CUTOFFS ] = |
98 | | { |
99 | | { // luma: [ horizontal - 2 ][ vertical - 2 ] |
100 | | { 11.3471, 13.1953, 14.4713, 16.0922, 17.5548, 18.7151, 20.0700, 20.9957, 21.8035, 22.8604, 23.8162, 24.4313, 25.4741 }, |
101 | | { 12.7110, 14.8901, 16.3847, 18.3015, 19.9240, 21.3337, 22.8999, 24.0157, 25.0655, 26.3700, 27.3986, 28.3434, 29.3805 }, |
102 | | { 14.1172, 16.5524, 18.2611, 20.5952, 22.4983, 24.0530, 25.9973, 27.3273, 28.5572, 30.0424, 31.1997, 32.1487, 33.2355 }, |
103 | | { 15.5021, 18.0297, 20.0899, 22.4546, 24.4364, 26.2005, 28.2301, 29.8060, 31.3123, 32.9112, 34.1466, 35.3053, 36.4918 }, |
104 | | { 16.4720, 19.2467, 21.3980, 23.9320, 26.0343, 28.0799, 30.3483, 32.0788, 33.6981, 35.4062, 36.7268, 37.9420, 39.4344 }, |
105 | | { 17.4095, 20.4055, 22.9677, 25.5371, 27.8608, 30.1310, 32.3993, 34.2167, 35.9951, 37.9442, 39.4321, 40.6859, 42.2433 }, |
106 | | { 18.1305, 21.4469, 24.0610, 26.8472, 29.2790, 31.7692, 34.0672, 36.0396, 37.8454, 39.9101, 41.3417, 42.7916, 44.3514 }, |
107 | | { 19.2211, 22.7276, 25.4476, 28.2473, 30.8452, 33.3751, 35.7479, 37.6866, 39.5467, 41.7627, 43.3952, 44.7700, 46.4703 }, |
108 | | { 20.4457, 24.1150, 27.0148, 29.9963, 32.7611, 35.2320, 37.6624, 39.8350, 41.6697, 43.8564, 45.5852, 46.9930, 48.9238 }, |
109 | | { 21.6430, 25.4942, 28.5270, 31.8942, 34.6005, 37.0724, 39.4975, 41.7995, 43.5896, 45.7560, 47.5483, 49.0574, 50.9609 }, |
110 | | { 22.5390, 26.4786, 29.5484, 32.9731, 35.8114, 38.4931, 41.0125, 43.2637, 45.0366, 47.2199, 49.1412, 50.7580, 52.6695 }, |
111 | | { 23.4841, 27.5594, 30.6919, 34.2237, 37.1031, 39.7887, 42.3244, 44.6668, 46.6079, 48.6607, 50.7871, 52.3455, 54.2592 }, |
112 | | { 24.0446, 28.2479, 31.5268, 35.1441, 38.0267, 40.7036, 43.3165, 45.7409, 47.7803, 49.8510, 51.9981, 53.5674, 55.5688 }, |
113 | | }, |
114 | | { // 4:2:0 chroma: [ horizontal - 2 ][ vertical - 2 ] |
115 | | { 9.8329, 11.6354, 12.8245, 13.6625, 15.3470, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060, 16.4060 }, |
116 | | { 11.7467, 14.0110, 15.6190, 16.6803, 18.4311, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934, 19.4934 }, |
117 | | { 12.8707, 15.3081, 17.2100, 18.5999, 20.4333, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392, 21.5392 }, |
118 | | { 14.0068, 16.3715, 18.4529, 19.9943, 21.8958, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361, 23.3361 }, |
119 | | { 15.2300, 17.7825, 20.0028, 21.6539, 23.5309, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114, 25.4114 }, |
120 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
121 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
122 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
123 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
124 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
125 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
126 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
127 | | { 16.5225, 19.3558, 21.5353, 23.2748, 25.1079, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849, 27.1849 }, |
128 | | }, |
129 | | }; |
130 | | |
131 | | constexpr double BLOCK_VARIANCE_SHARE[ 2 ][ NUM_CUTOFFS ][ NUM_CUTOFFS ] = |
132 | | { |
133 | | { // luma: [ horizontal - 2 ][ vertical - 2 ] |
134 | | { 0.9137, 0.9350, 0.9456, 0.9547, 0.9619, 0.9646, 0.9688, 0.9716, 0.9733, 0.9758, 0.9777, 0.9789, 0.9805 }, |
135 | | { 0.9299, 0.9481, 0.9568, 0.9642, 0.9699, 0.9724, 0.9756, 0.9778, 0.9795, 0.9813, 0.9828, 0.9839, 0.9850 }, |
136 | | { 0.9429, 0.9577, 0.9650, 0.9715, 0.9762, 0.9780, 0.9809, 0.9828, 0.9840, 0.9855, 0.9867, 0.9875, 0.9883 }, |
137 | | { 0.9520, 0.9641, 0.9708, 0.9757, 0.9796, 0.9815, 0.9837, 0.9854, 0.9867, 0.9879, 0.9888, 0.9895, 0.9902 }, |
138 | | { 0.9568, 0.9680, 0.9740, 0.9784, 0.9818, 0.9836, 0.9858, 0.9873, 0.9884, 0.9894, 0.9902, 0.9908, 0.9915 }, |
139 | | { 0.9609, 0.9709, 0.9770, 0.9807, 0.9839, 0.9856, 0.9874, 0.9886, 0.9896, 0.9907, 0.9914, 0.9919, 0.9925 }, |
140 | | { 0.9634, 0.9733, 0.9786, 0.9822, 0.9851, 0.9868, 0.9883, 0.9896, 0.9904, 0.9914, 0.9920, 0.9926, 0.9931 }, |
141 | | { 0.9674, 0.9761, 0.9809, 0.9839, 0.9866, 0.9881, 0.9894, 0.9905, 0.9913, 0.9921, 0.9928, 0.9933, 0.9938 }, |
142 | | { 0.9711, 0.9788, 0.9830, 0.9857, 0.9881, 0.9893, 0.9904, 0.9914, 0.9921, 0.9929, 0.9934, 0.9939, 0.9945 }, |
143 | | { 0.9744, 0.9811, 0.9848, 0.9875, 0.9894, 0.9904, 0.9914, 0.9923, 0.9929, 0.9936, 0.9941, 0.9945, 0.9950 }, |
144 | | { 0.9762, 0.9824, 0.9859, 0.9883, 0.9901, 0.9911, 0.9920, 0.9929, 0.9934, 0.9940, 0.9945, 0.9948, 0.9953 }, |
145 | | { 0.9782, 0.9838, 0.9869, 0.9891, 0.9907, 0.9916, 0.9925, 0.9933, 0.9938, 0.9943, 0.9948, 0.9951, 0.9956 }, |
146 | | { 0.9792, 0.9846, 0.9876, 0.9897, 0.9911, 0.9920, 0.9928, 0.9936, 0.9941, 0.9946, 0.9951, 0.9954, 0.9959 }, |
147 | | }, |
148 | | { // 4:2:0 chroma: [ horizontal - 2 ][ vertical - 2 ] |
149 | | { 0.9488, 0.9625, 0.9686, 0.9723, 0.9781, 0.9805, 0.9805, 0.9805, 0.9805, 0.9805, 0.9805, 0.9805, 0.9805 }, |
150 | | { 0.9621, 0.9727, 0.9776, 0.9805, 0.9840, 0.9855, 0.9855, 0.9855, 0.9855, 0.9855, 0.9855, 0.9855, 0.9855 }, |
151 | | { 0.9684, 0.9770, 0.9817, 0.9842, 0.9870, 0.9881, 0.9881, 0.9881, 0.9881, 0.9881, 0.9881, 0.9881, 0.9881 }, |
152 | | { 0.9733, 0.9798, 0.9839, 0.9862, 0.9886, 0.9897, 0.9897, 0.9897, 0.9897, 0.9897, 0.9897, 0.9897, 0.9897 }, |
153 | | { 0.9772, 0.9829, 0.9862, 0.9881, 0.9901, 0.9914, 0.9914, 0.9914, 0.9914, 0.9914, 0.9914, 0.9914, 0.9914 }, |
154 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
155 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
156 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
157 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
158 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
159 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
160 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
161 | | { 0.9807, 0.9855, 0.9882, 0.9899, 0.9914, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925, 0.9925 }, |
162 | | }, |
163 | | }; |
164 | | |
165 | | // After this many analyses in a row whose scaling function fit failed, a component's grain is no longer signalled. |
166 | | constexpr int MAX_FAILED_FITS = 3; |
167 | | // A picture whose mean grain strength differs from that of the accumulated data by more than this factor starts the |
168 | | // statistics anew (a new scene with different grain); it needs this many blocks for a reliable mean. |
169 | | constexpr double GRAIN_CHANGE_RATIO = 2.0; |
170 | | constexpr int GRAIN_CHANGE_MIN_BLOCKS = 64; |
171 | | |
172 | | // The auto-regressive model (--fga 3): vvdec's synthesis makes grain of standard deviation |
173 | | // scale * AR_PATTERN_STDDEV * sqrt( energy ) / 2^( log2ScaleFactor + bitDepth - 10 ) for the AR filter whose impulse |
174 | | // response has the given energy (its quantized coefficients, as the synthesis computes them). Measured with vvdec on |
175 | | // flat grey 1920x1088 10-bit pictures over 24 filters (left coefficient 0 to 0.375) and log2 scale factors 3 to 7: |
176 | | // the ratio stays within 3% of these values. |
177 | | constexpr double AR_PATTERN_STDDEV[ 2 ] = { 3.83, 3.60 }; // luma, chroma |
178 | | constexpr int AR_SPECTRUM_BINS = 64; // frequency bins per direction of the AR spectrum computation |
179 | | constexpr int AR_FIT_ITERATIONS = 200; // largest number of Nelder-Mead iterations of the AR filter fit |
180 | | constexpr double AR_FIT_TOLERANCE = 1e-5; // the fit stops when its simplex spans less than this L1 error |
181 | | |
182 | | // Outliers of the temporal residual: per intensity bin (16 bins), blocks above this ratio of the median grain variance |
183 | | // are motion compensation failures; bins need this many blocks for the median. |
184 | | constexpr int NUM_OUTLIER_BINS = 16; |
185 | | constexpr double OUTLIER_VARIANCE_RATIO = 4.0; |
186 | | constexpr int MIN_OUTLIER_BIN_BLOCKS = 8; |
187 | | |
188 | | // Temporal residual: motion search range in samples of the 4x downsampled pictures (12 or 20 samples at full |
189 | | // resolution), and the share of the zero motion SAD a vector must stay below, as 17 / 20 = 0.85. |
190 | | constexpr int RESIDUAL_RANGE = 3; |
191 | | constexpr int RESIDUAL_RANGE_WIDE = 5; // pictures wider than 2048 |
192 | | constexpr int ZERO_BIAS_NUM = 17; |
193 | | constexpr int ZERO_BIAS_DEN = 20; |
194 | | // A neighbour whose mean absolute difference is above twice that of the other neighbour, plus this many sample |
195 | | // values, is left out of the residual. |
196 | | constexpr double NEIGHBOUR_SAD_MARGIN = 1.0; |
197 | | |
198 | | constexpr double LOW_THRESHOLD_RATIO = 0.1; // Canny: low threshold relative to the strongest gradient |
199 | | constexpr int HIGH_THRESHOLD_RATIO = 3; // Canny: high threshold relative to the low threshold |
200 | | constexpr double LOW_INTENSITY_RATIO = 0.1; // samples below this share of the range are not analysed |
201 | | |
202 | | constexpr double TAP_FILTER[ 3 ] = { 1, 2, 1 }; |
203 | | constexpr double NORM_TAP = 4.0; |
204 | | |
205 | | typedef FGAnalyzer::Plane Plane; |
206 | | |
207 | | // ==================================================================================================================== |
208 | | // Flat area detection: Canny edge detector and morphological operations |
209 | | // ==================================================================================================================== |
210 | | |
211 | | // Classifies the magnitudes as strong, weak or no edge, with thresholds relative to the strongest gradient. |
212 | | void doubleThreshold( const FgaRowKernels& k, Plane& mag, int bitDepth ) |
213 | 0 | { |
214 | 0 | const Pel strongPel = (Pel) ( ( 1 << bitDepth ) - 1 ); |
215 | 0 | const Pel weakPel = (Pel) ( ( 1 << ( bitDepth - 1 ) ) - 1 ); |
216 | 0 | const int n = mag.width * mag.height; // the storage can be larger than the plane |
217 | |
|
218 | 0 | const Pel highest = k.rowMax( mag.samples.data(), n ); |
219 | 0 | const Pel lowThreshold = (Pel) ( LOW_THRESHOLD_RATIO * highest ); |
220 | 0 | const Pel highThreshold = (Pel) Clip3( 0, ( 1 << bitDepth ) - 1, HIGH_THRESHOLD_RATIO * lowThreshold ); |
221 | 0 | k.classify( mag.samples.data(), n, lowThreshold, highThreshold, weakPel, strongPel ); |
222 | 0 | } |
223 | | |
224 | | // Hysteresis: a weak edge becomes strong when a strong edge is in its 3x3 neighbourhood, otherwise it is removed. |
225 | | // As in VTM, the picture is updated in place, column by column, so a weak edge turned strong can promote weak edges |
226 | | // processed after it. Neighbours outside the picture repeat the border as it was before the tracking started. |
227 | | template<class ScratchT> |
228 | | void edgeTracking( const FgaRowKernels& k, Plane& edges, int bitDepth, ScratchT& s ) |
229 | 0 | { |
230 | 0 | const Pel strongPel = (Pel) ( ( 1 << bitDepth ) - 1 ); |
231 | 0 | const Pel weakPel = (Pel) ( ( 1 << ( bitDepth - 1 ) ) - 1 ); |
232 | 0 | const int w = edges.width, h = edges.height; |
233 | | |
234 | | // The weak edges in column-major order: found row by row, then sorted by column (counting sort, stable, so the |
235 | | // rows of every column stay in increasing order). |
236 | 0 | s.positions.resize( w ); |
237 | 0 | s.weakX.clear(); |
238 | 0 | s.weakY.clear(); |
239 | 0 | for( int y = 0; y < h; y++ ) |
240 | 0 | { |
241 | 0 | const int n = k.findEqual( edges.row( y ), w, weakPel, s.positions.data() ); |
242 | 0 | for( int i = 0; i < n; i++ ) |
243 | 0 | { |
244 | 0 | s.weakX.push_back( s.positions[ i ] ); |
245 | 0 | s.weakY.push_back( y ); |
246 | 0 | } |
247 | 0 | } |
248 | 0 | if( s.weakX.empty() ) |
249 | 0 | { |
250 | 0 | return; |
251 | 0 | } |
252 | 0 | std::vector<int>& columnStart = s.columnStart; |
253 | 0 | columnStart.assign( w + 1, 0 ); |
254 | 0 | for( int px : s.weakX ) |
255 | 0 | { |
256 | 0 | columnStart[ px + 1 ]++; |
257 | 0 | } |
258 | 0 | for( int px = 0; px < w; px++ ) |
259 | 0 | { |
260 | 0 | columnStart[ px + 1 ] += columnStart[ px ]; |
261 | 0 | } |
262 | 0 | std::vector<int>& weakRows = s.positions; |
263 | 0 | weakRows.resize( s.weakX.size() ); |
264 | 0 | { |
265 | 0 | std::vector<int> fill( columnStart.begin(), columnStart.end() - 1 ); |
266 | 0 | for( size_t i = 0; i < s.weakX.size(); i++ ) |
267 | 0 | { |
268 | 0 | weakRows[ fill[ s.weakX[ i ] ]++ ] = s.weakY[ i ]; |
269 | 0 | } |
270 | 0 | } |
271 | | |
272 | | // Neighbours outside the picture are border samples as they were before the tracking. |
273 | 0 | s.top.assign( edges.row( 0 ), edges.row( 0 ) + w ); |
274 | 0 | s.bottom.assign( edges.row( h - 1 ), edges.row( h - 1 ) + w ); |
275 | 0 | s.left.resize( h ); |
276 | 0 | s.right.resize( h ); |
277 | 0 | for( int y = 0; y < h; y++ ) |
278 | 0 | { |
279 | 0 | s.left[ y ] = edges.row( y )[ 0 ]; |
280 | 0 | s.right[ y ] = edges.row( y )[ w - 1 ]; |
281 | 0 | } |
282 | 0 | auto borderAt = [ & ]( int bx, int by ) |
283 | 0 | { |
284 | 0 | const int cx = Clip3( 0, w - 1, bx ), cy = Clip3( 0, h - 1, by ); |
285 | 0 | return cy == 0 ? s.top[ cx ] : cy == h - 1 ? s.bottom[ cx ] : cx == 0 ? s.left[ cy ] : s.right[ cy ]; |
286 | 0 | }; |
287 | |
|
288 | 0 | for( int x = 0; x < w; x++ ) |
289 | 0 | { |
290 | 0 | for( int i = columnStart[ x ]; i < columnStart[ x + 1 ]; i++ ) |
291 | 0 | { |
292 | 0 | const int y = weakRows[ i ]; |
293 | 0 | bool strong = false; |
294 | 0 | for( int ny = y - 1; ny <= y + 1 && !strong; ny++ ) |
295 | 0 | { |
296 | 0 | for( int nx = x - 1; nx <= x + 1 && !strong; nx++ ) |
297 | 0 | { |
298 | 0 | const bool inside = nx >= 0 && nx < w && ny >= 0 && ny < h; |
299 | 0 | strong = ( inside ? edges.row( ny )[ nx ] : borderAt( nx, ny ) ) == strongPel; |
300 | 0 | } |
301 | 0 | } |
302 | 0 | edges.row( y )[ x ] = strong ? strongPel : 0; |
303 | 0 | } |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | | // Sets the binary mask to "edge" where the picture is too dark to be analysed. |
308 | | void suppressLowIntensity( const Plane& p, Plane& mask, int bitDepth ) |
309 | 0 | { |
310 | 0 | const Pel maxIntensity = (Pel) ( ( 1 << bitDepth ) - 1 ); |
311 | 0 | const Pel threshold = (Pel) ( LOW_INTENSITY_RATIO * maxIntensity ); |
312 | 0 | const size_t n = (size_t) mask.width * mask.height; |
313 | 0 | const Pel* src = p.samples.data(); |
314 | 0 | Pel* dst = mask.samples.data(); |
315 | 0 | for( size_t i = 0; i < n; i++ ) |
316 | 0 | { |
317 | 0 | dst[ i ] = src[ i ] < threshold ? maxIntensity : dst[ i ]; // a select, so that the loop vectorizes |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | // Subsampling as in VTM: the average of the top-left 2x2 samples of every factor x factor block. |
322 | | void subsample( const Plane& in, Plane& out, int factor ) |
323 | 0 | { |
324 | 0 | out.resize( in.width / factor, in.height / factor ); |
325 | 0 | for( int y = 0; y < out.height; y++ ) |
326 | 0 | { |
327 | 0 | const Pel* r0 = in.row( y * factor ); |
328 | 0 | const Pel* r1 = in.row( y * factor + 1 ); |
329 | 0 | Pel* dst = out.row( y ); |
330 | 0 | for( int x = 0; x < out.width; x++ ) |
331 | 0 | { |
332 | 0 | const int sx = x * factor; |
333 | 0 | dst[ x ] = (Pel) ( ( r0[ sx ] + r1[ sx ] + r0[ sx + 1 ] + r1[ sx + 1 ] + 2 ) >> 2 ); |
334 | 0 | } |
335 | 0 | } |
336 | 0 | } |
337 | | |
338 | | // Adds a mask found at a lower resolution to the full-resolution mask (sample replication, then bitwise or). |
339 | | void combineUpsampled( Plane& mask, const Plane& low, int factor, std::vector<Pel>& wide ) |
340 | 0 | { |
341 | 0 | const int w = low.width * factor; |
342 | 0 | wide.resize( w ); |
343 | 0 | for( int ly = 0; ly < low.height; ly++ ) |
344 | 0 | { |
345 | 0 | const Pel* src = low.row( ly ); |
346 | 0 | for( int lx = 0; lx < low.width; lx++ ) |
347 | 0 | { |
348 | 0 | for( int f = 0; f < factor; f++ ) |
349 | 0 | { |
350 | 0 | wide[ lx * factor + f ] = src[ lx ]; |
351 | 0 | } |
352 | 0 | } |
353 | 0 | for( int y = ly * factor; y < ( ly + 1 ) * factor; y++ ) |
354 | 0 | { |
355 | 0 | Pel* dst = mask.row( y ); |
356 | 0 | for( int x = 0; x < w; x++ ) |
357 | 0 | { |
358 | 0 | dst[ x ] |= wide[ x ]; |
359 | 0 | } |
360 | 0 | } |
361 | 0 | } |
362 | 0 | } |
363 | | |
364 | | // ==================================================================================================================== |
365 | | // Block statistics |
366 | | // ==================================================================================================================== |
367 | | |
368 | | struct BlockStats |
369 | | { |
370 | | int mean; // truncated, as in VTM (it selects the intensity) |
371 | | double stddev; // exact; VTM truncates it to an integer, which loses up to a third of the grain strength |
372 | | }; |
373 | | |
374 | | // Mean and standard deviation of a 16x16 block. |
375 | | BlockStats meanStdDev( const FgaRowKernels& k, const Plane& p, int x0, int y0 ) |
376 | 0 | { |
377 | 0 | int64_t sum = 0, sumSq = 0; |
378 | 0 | k.blockSums( p.row( y0 ) + x0, p.width, sum, sumSq ); |
379 | 0 | const int n = 16 * 16; |
380 | 0 | const double variance = ( (double) sumSq - (double) sum * sum / n ) / n; |
381 | 0 | return { (int) ( sum / n ), std::sqrt( std::max( variance, 0.0 ) ) }; |
382 | 0 | } |
383 | | |
384 | | // Expected power of the coefficients of the orthonormal 16x16 DCT-II of grain made by the AR filter of vvdec's SEI AR |
385 | | // mode (see FGAnalyzer::ArFilter), as spec[ vertical * 16 + horizontal ]: its power spectrum 1 / |1 - A(w)|^2, |
386 | | // integrated against the squared spectra of the DCT basis vectors on AR_SPECTRUM_BINS x AR_SPECTRUM_BINS frequencies. |
387 | | class ArSpectrum |
388 | | { |
389 | | public: |
390 | | ArSpectrum() |
391 | 0 | { |
392 | 0 | constexpr int N = 16; |
393 | 0 | constexpr double PI = 3.14159265358979323846; |
394 | 0 | for( int k = 0; k < AR_SPECTRUM_BINS; k++ ) |
395 | 0 | { |
396 | 0 | const double w = 2.0 * PI * k / AR_SPECTRUM_BINS; |
397 | 0 | m_cos[ k ] = std::cos( w ); |
398 | 0 | m_sin[ k ] = std::sin( w ); |
399 | 0 | for( int u = 0; u < N; u++ ) |
400 | 0 | { |
401 | 0 | double re = 0.0, im = 0.0; |
402 | 0 | for( int n = 0; n < N; n++ ) |
403 | 0 | { |
404 | 0 | const double b = std::sqrt( ( u ? 2.0 : 1.0 ) / N ) * std::cos( PI * ( 2 * n + 1 ) * u / ( 2 * N ) ); |
405 | 0 | re += b * std::cos( w * n ); |
406 | 0 | im -= b * std::sin( w * n ); |
407 | 0 | } |
408 | 0 | m_basisPower[ u ][ k ] = re * re + im * im; |
409 | 0 | } |
410 | 0 | } |
411 | 0 | } |
412 | | |
413 | | // Scratch space of compute(), reused by the caller over the calls of a fit. |
414 | | static constexpr size_t SCRATCH_SIZE = AR_SPECTRUM_BINS * AR_SPECTRUM_BINS + 16 * AR_SPECTRUM_BINS; |
415 | | |
416 | | // spec: 256 values, the DC coefficient set to 0 and the rest normalized to sum 1; returns the share of the power |
417 | | // outside the DC coefficient. scratch: SCRATCH_SIZE values. |
418 | | double compute( double a1, double a3, double a5, double r, double* spec, std::vector<double>& scratch ) const |
419 | 0 | { |
420 | 0 | constexpr int N = 16, K = AR_SPECTRUM_BINS; |
421 | 0 | scratch.resize( SCRATCH_SIZE ); |
422 | 0 | double* psd = scratch.data(); |
423 | 0 | double* rows = psd + K * K; |
424 | 0 | for( int ky = 0; ky < K; ky++ ) |
425 | 0 | { |
426 | 0 | for( int kx = 0; kx < K; kx++ ) |
427 | 0 | { |
428 | | // A(w) = sum of coefficient * e^(-i w . offset) over the filter taps |
429 | 0 | const int sum = ( kx + ky ) % K, diff = ( ky - kx + K ) % K, x2 = ( 2 * kx ) % K, y2 = ( 2 * ky ) % K; |
430 | 0 | const double re = a1 * m_cos[ kx ] + a1 * r * m_cos[ ky ] + a3 * r * ( m_cos[ sum ] + m_cos[ diff ] ) |
431 | 0 | + a5 * m_cos[ x2 ] + a5 * r * r * m_cos[ y2 ]; |
432 | 0 | const double im = -( a1 * m_sin[ kx ] + a1 * r * m_sin[ ky ] + a3 * r * ( m_sin[ sum ] + m_sin[ diff ] ) |
433 | 0 | + a5 * m_sin[ x2 ] + a5 * r * r * m_sin[ y2 ] ); |
434 | 0 | psd[ ky * K + kx ] = 1.0 / std::max( ( 1.0 - re ) * ( 1.0 - re ) + im * im, 1e-6 ); |
435 | 0 | } |
436 | 0 | } |
437 | 0 | for( int v = 0; v < N; v++ ) |
438 | 0 | { |
439 | 0 | for( int kx = 0; kx < K; kx++ ) |
440 | 0 | { |
441 | 0 | double s = 0.0; |
442 | 0 | for( int ky = 0; ky < K; ky++ ) |
443 | 0 | { |
444 | 0 | s += m_basisPower[ v ][ ky ] * psd[ ky * K + kx ]; |
445 | 0 | } |
446 | 0 | rows[ v * K + kx ] = s; |
447 | 0 | } |
448 | 0 | } |
449 | 0 | double total = 0.0; |
450 | 0 | for( int v = 0; v < N; v++ ) |
451 | 0 | { |
452 | 0 | for( int h = 0; h < N; h++ ) |
453 | 0 | { |
454 | 0 | double s = 0.0; |
455 | 0 | for( int kx = 0; kx < K; kx++ ) |
456 | 0 | { |
457 | 0 | s += rows[ v * K + kx ] * m_basisPower[ h ][ kx ]; |
458 | 0 | } |
459 | 0 | spec[ v * N + h ] = s; |
460 | 0 | total += s; |
461 | 0 | } |
462 | 0 | } |
463 | 0 | const double nonDc = total - spec[ 0 ]; |
464 | 0 | spec[ 0 ] = 0.0; |
465 | 0 | for( int i = 0; i < N * N; i++ ) |
466 | 0 | { |
467 | 0 | spec[ i ] /= nonDc; |
468 | 0 | } |
469 | 0 | return nonDc / total; |
470 | 0 | } |
471 | | |
472 | | private: |
473 | | double m_cos[ AR_SPECTRUM_BINS ], m_sin[ AR_SPECTRUM_BINS ]; |
474 | | double m_basisPower[ 16 ][ AR_SPECTRUM_BINS ]; |
475 | | }; |
476 | | |
477 | | // The filter coefficients of vvdec's SEI AR mode as it computes them, in units of 2^-log2: left, top, diagonal |
478 | | // (top-left and top-right), left-left and top-top. |
479 | | struct ArTaps |
480 | | { |
481 | | int left, top, diag, leftLeft, topTop; |
482 | | ArTaps( const int c[ 6 ], int log2 ) |
483 | 0 | : left( c[ 1 ] ), top( ( c[ 1 ] * c[ 4 ] ) >> log2 ), diag( ( c[ 3 ] * c[ 4 ] ) >> log2 ), leftLeft( c[ 5 ] ), |
484 | 0 | topTop( ( c[ 5 ] * c[ 4 ] * c[ 4 ] ) >> ( 2 * log2 ) ) {} |
485 | | }; |
486 | | |
487 | | // Energy of the impulse response of vvdec's AR filter with the signalled (integer) coefficients c[ 1 ] to c[ 5 ]. |
488 | | double arEnergy( const int c[ 6 ], int log2 ) |
489 | 0 | { |
490 | 0 | constexpr int SIZE = 96; |
491 | 0 | const ArTaps t( c, log2 ); |
492 | 0 | const double one = 1 << log2; |
493 | 0 | std::vector<double> h( SIZE * SIZE, 0.0 ); |
494 | 0 | double energy = 0.0; |
495 | 0 | for( int y = 0; y < SIZE; y++ ) |
496 | 0 | { |
497 | 0 | for( int x = 2; x < SIZE - 2; x++ ) |
498 | 0 | { |
499 | 0 | double g = t.left * h[ y * SIZE + x - 1 ] + t.leftLeft * h[ y * SIZE + x - 2 ]; |
500 | 0 | if( y >= 1 ) |
501 | 0 | { |
502 | 0 | g += t.top * h[ ( y - 1 ) * SIZE + x ] + t.diag * ( h[ ( y - 1 ) * SIZE + x - 1 ] + h[ ( y - 1 ) * SIZE + x + 1 ] ); |
503 | 0 | } |
504 | 0 | if( y >= 2 ) |
505 | 0 | { |
506 | 0 | g += t.topTop * h[ ( y - 2 ) * SIZE + x ]; |
507 | 0 | } |
508 | 0 | h[ y * SIZE + x ] = g / one + ( y == 0 && x == SIZE / 2 ? 1.0 : 0.0 ); |
509 | 0 | energy += h[ y * SIZE + x ] * h[ y * SIZE + x ]; |
510 | 0 | } |
511 | 0 | } |
512 | 0 | return energy; |
513 | 0 | } |
514 | | |
515 | | double ldpow( double n, unsigned p ) |
516 | 0 | { |
517 | 0 | double x = 1; |
518 | 0 | for( unsigned i = 0; i < p; i++ ) |
519 | 0 | { |
520 | 0 | x = x * n; |
521 | 0 | } |
522 | 0 | return x; |
523 | 0 | } |
524 | | |
525 | | Pel rowMax( const Pel* src, int n ) |
526 | 0 | { |
527 | 0 | return fgaRowMaxCore( src, n ); |
528 | 0 | } |
529 | | |
530 | | void classify( Pel* row, int n, Pel low, Pel high, Pel weak, Pel strong ) |
531 | 0 | { |
532 | 0 | fgaClassifyCore( row, n, low, high, weak, strong ); |
533 | 0 | } |
534 | | |
535 | | void sobelRow( const Pel* above, const Pel* cur, const Pel* below, int width, Pel maxVal, Pel* mag, Pel* dir ) |
536 | 0 | { |
537 | 0 | fgaSobelCore( above, cur, below, width, 0, width, maxVal, mag, dir ); |
538 | 0 | } |
539 | | |
540 | | void nonMaxRow( const Pel* magAbove, const Pel* mag, const Pel* magBelow, const Pel* dir, int width, Pel* dst ) |
541 | 0 | { |
542 | 0 | fgaNonMaxCore( magAbove, mag, magBelow, dir, width, 0, width, dst ); |
543 | 0 | } |
544 | | |
545 | | void morphRow( const Pel* src, int width, bool dilate, Pel* dst ) |
546 | 0 | { |
547 | 0 | fgaMorphRowCore( src, width, 0, width, dilate, dst ); |
548 | 0 | } |
549 | | |
550 | | void morphCol( const Pel* above, const Pel* cur, const Pel* below, int width, bool dilate, Pel* dst ) |
551 | 0 | { |
552 | 0 | fgaMorphColCore( above, cur, below, 0, width, dilate, dst ); |
553 | 0 | } |
554 | | |
555 | | // Gradient magnitude after non-maximum suppression along the edge direction. |
556 | | void gradientWithNonMaxSuppression( const FgaRowKernels& k, const Plane& p, Plane& out, int bitDepth, Plane& mag, Plane& dir ) |
557 | 0 | { |
558 | 0 | const int w = p.width, h = p.height; |
559 | 0 | const Pel maxVal = (Pel) ( ( 1 << bitDepth ) - 1 ); |
560 | 0 | mag.resize( w, h ); |
561 | 0 | dir.resize( w, h ); |
562 | 0 | for( int y = 0; y < h; y++ ) |
563 | 0 | { |
564 | 0 | k.sobel( p.row( std::max( y - 1, 0 ) ), p.row( y ), p.row( std::min( y + 1, h - 1 ) ), w, maxVal, mag.row( y ), dir.row( y ) ); |
565 | 0 | } |
566 | 0 | out.resize( w, h ); |
567 | 0 | for( int y = 0; y < h; y++ ) |
568 | 0 | { |
569 | 0 | k.nonMax( mag.row( std::max( y - 1, 0 ) ), mag.row( y ), mag.row( std::min( y + 1, h - 1 ) ), dir.row( y ), w, out.row( y ) ); |
570 | 0 | } |
571 | 0 | } |
572 | | |
573 | | // One 3x3 dilation or erosion of a binary mask (values 0 and "edge"): on such a mask, dilation is the 3x3 maximum and |
574 | | // erosion the 3x3 minimum, and both are separable. Borders are replicated. |
575 | | void morphStep( const FgaRowKernels& k, Plane& mask, Plane& tmp, bool dilate ) |
576 | 0 | { |
577 | 0 | const int w = mask.width, h = mask.height; |
578 | 0 | tmp.resize( w, h ); |
579 | 0 | for( int y = 0; y < h; y++ ) |
580 | 0 | { |
581 | 0 | k.morphRow( mask.row( y ), w, dilate, tmp.row( y ) ); |
582 | 0 | } |
583 | 0 | for( int y = 0; y < h; y++ ) |
584 | 0 | { |
585 | 0 | k.morphCol( tmp.row( std::max( y - 1, 0 ) ), tmp.row( y ), tmp.row( std::min( y + 1, h - 1 ) ), w, dilate, mask.row( y ) ); |
586 | 0 | } |
587 | 0 | } |
588 | | |
589 | | } // anonymous namespace |
590 | | |
591 | | // ==================================================================================================================== |
592 | | // Scalar kernels |
593 | | // ==================================================================================================================== |
594 | | |
595 | | Pel fgaRowMaxCore( const Pel* src, int n ) |
596 | 0 | { |
597 | 0 | Pel highest = 0; |
598 | 0 | for( int i = 0; i < n; i++ ) |
599 | 0 | { |
600 | 0 | highest = std::max( highest, src[ i ] ); |
601 | 0 | } |
602 | 0 | return highest; |
603 | 0 | } |
604 | | |
605 | | void fgaClassifyCore( Pel* row, int n, Pel low, Pel high, Pel weak, Pel strong ) |
606 | 0 | { |
607 | 0 | for( int i = 0; i < n; i++ ) |
608 | 0 | { |
609 | 0 | row[ i ] = row[ i ] > high ? strong : row[ i ] > low ? weak : 0; |
610 | 0 | } |
611 | 0 | } |
612 | | |
613 | | int fgaFindEqualCore( const Pel* row, int n, Pel value, int* positions ) |
614 | 0 | { |
615 | 0 | int count = 0; |
616 | 0 | for( int i = 0; i < n; i++ ) |
617 | 0 | { |
618 | 0 | if( row[ i ] == value ) |
619 | 0 | { |
620 | 0 | positions[ count++ ] = i; |
621 | 0 | } |
622 | 0 | } |
623 | 0 | return count; |
624 | 0 | } |
625 | | |
626 | | bool fgaIsZeroBlockCore( const Pel* src, int stride ) |
627 | 0 | { |
628 | 0 | for( int y = 0; y < 16; y++, src += stride ) |
629 | 0 | { |
630 | 0 | for( int x = 0; x < 16; x++ ) |
631 | 0 | { |
632 | 0 | if( src[ x ] ) |
633 | 0 | { |
634 | 0 | return false; |
635 | 0 | } |
636 | 0 | } |
637 | 0 | } |
638 | 0 | return true; |
639 | 0 | } |
640 | | |
641 | | void fgaBlockSumsCore( const Pel* src, int stride, int64_t& sum, int64_t& sumSq ) |
642 | 0 | { |
643 | 0 | sum = sumSq = 0; |
644 | 0 | for( int y = 0; y < 16; y++, src += stride ) |
645 | 0 | { |
646 | 0 | for( int x = 0; x < 16; x++ ) |
647 | 0 | { |
648 | 0 | sum += src[ x ]; |
649 | 0 | sumSq += src[ x ] * src[ x ]; |
650 | 0 | } |
651 | 0 | } |
652 | 0 | } |
653 | | |
654 | | void fgaDownsample4Core( const Pel* src, int stride, int n, Pel* dst ) |
655 | 0 | { |
656 | 0 | for( int x = 0; x < n; x++, src += 4 ) |
657 | 0 | { |
658 | 0 | int sum = 8; |
659 | 0 | for( int y = 0; y < 4; y++ ) |
660 | 0 | { |
661 | 0 | sum += src[ y * stride ] + src[ y * stride + 1 ] + src[ y * stride + 2 ] + src[ y * stride + 3 ]; |
662 | 0 | } |
663 | 0 | dst[ x ] = (Pel) ( sum >> 4 ); |
664 | 0 | } |
665 | 0 | } |
666 | | |
667 | | void fgaSad4x4RowCore( const Pel* cur, int curStride, const Pel* ref, int refStride, int n, int* sad ) |
668 | 0 | { |
669 | 0 | for( int b = 0; b < n; b++ ) |
670 | 0 | { |
671 | 0 | int sum = 0; |
672 | 0 | for( int y = 0; y < 4; y++ ) |
673 | 0 | { |
674 | 0 | for( int x = 4 * b; x < 4 * b + 4; x++ ) |
675 | 0 | { |
676 | 0 | sum += abs( cur[ y * curStride + x ] - ref[ y * refStride + x ] ); |
677 | 0 | } |
678 | 0 | } |
679 | 0 | sad[ b ] = sum; |
680 | 0 | } |
681 | 0 | } |
682 | | |
683 | | void fgaResidualRowCore( const Pel* cur, const Pel* prev, const Pel* next, int n, Pel* dst ) |
684 | 0 | { |
685 | 0 | if( next ) |
686 | 0 | { |
687 | 0 | for( int x = 0; x < n; x++ ) |
688 | 0 | { |
689 | 0 | dst[ x ] = (Pel) ( 2 * cur[ x ] - prev[ x ] - next[ x ] ); |
690 | 0 | } |
691 | 0 | } |
692 | 0 | else |
693 | 0 | { |
694 | 0 | for( int x = 0; x < n; x++ ) |
695 | 0 | { |
696 | 0 | dst[ x ] = (Pel) ( cur[ x ] - prev[ x ] ); |
697 | 0 | } |
698 | 0 | } |
699 | 0 | } |
700 | | |
701 | | // VVC's 16-point DCT-II as a matrix multiplication, as VTM's film grain analysis computes it. |
702 | | void fgaSquaredDct16Core( const Pel* src, int stride, int maxRange, double* sum ) |
703 | 0 | { |
704 | 0 | constexpr int N = 16; |
705 | 0 | const TMatrixCoeff* tr = g_trCoreDCT2P16[ TRANSFORM_FORWARD ][ 0 ]; |
706 | 0 | int horizontal[ N ][ N ]; // [horizontal frequency][row] |
707 | 0 | for( int u = 0; u < N; u++ ) |
708 | 0 | { |
709 | 0 | for( int y = 0; y < N; y++ ) |
710 | 0 | { |
711 | 0 | const Pel* row = src + y * stride; |
712 | 0 | int acc = 0; |
713 | 0 | for( int k = 0; k < N; k++ ) |
714 | 0 | { |
715 | 0 | acc += tr[ u * N + k ] * row[ k ]; |
716 | 0 | } |
717 | 0 | horizontal[ u ][ y ] = ( acc + 128 ) >> 8; |
718 | 0 | } |
719 | 0 | } |
720 | 0 | for( int u = 0; u < N; u++ ) |
721 | 0 | { |
722 | 0 | for( int v = 0; v < N; v++ ) |
723 | 0 | { |
724 | 0 | int acc = 0; |
725 | 0 | for( int k = 0; k < N; k++ ) |
726 | 0 | { |
727 | 0 | acc += horizontal[ u ][ k ] * tr[ v * N + k ]; |
728 | 0 | } |
729 | 0 | const int64_t c = Clip3( -maxRange, maxRange, ( acc + 128 ) >> 8 ); |
730 | 0 | sum[ u * N + v ] += (double) ( c * c ); |
731 | 0 | } |
732 | 0 | } |
733 | 0 | } |
734 | | |
735 | | // Sobel gradients as in VTM: "gx" is the difference of the rows below and above (a vertical derivative), "gy" the |
736 | | // difference of the columns right and left. VTM quantizes the angle atan2(gx, gy) to 0, 45, 90 or 135 degrees with |
737 | | // bounds at 22.5 + k * 45 degrees. As tan(22.5) = sqrt(2) - 1 and tan(67.5) = sqrt(2) + 1, with a = |gx| and b = |gy| |
738 | | // a <= (sqrt(2) - 1) * b <=> (a + b)^2 <= 2 * b^2 : 0 degrees, |
739 | | // a > (sqrt(2) + 1) * b <=> a > b and (a - b)^2 > 2 * b^2 : 90 degrees, |
740 | | // otherwise 45 degrees when gx and gy have the same sign and 135 when not. This integer rule gives the same direction |
741 | | // as atan2 for every 10-bit gradient pair (checked exhaustively) and needs no floating point. |
742 | | void fgaSobelCore( const Pel* above, const Pel* cur, const Pel* below, int width, int xStart, int xEnd, Pel maxVal, Pel* mag, Pel* dir ) |
743 | 0 | { |
744 | 0 | for( int x = xStart; x < xEnd; x++ ) |
745 | 0 | { |
746 | 0 | const int l = std::max( x - 1, 0 ), r = std::min( x + 1, width - 1 ); |
747 | 0 | const int gx = ( below[ l ] + 2 * below[ x ] + below[ r ] ) - ( above[ l ] + 2 * above[ x ] + above[ r ] ); |
748 | 0 | const int gy = ( above[ r ] + 2 * cur[ r ] + below[ r ] ) - ( above[ l ] + 2 * cur[ l ] + below[ l ] ); |
749 | 0 | const int a = abs( gx ), b = abs( gy ); |
750 | 0 | mag[ x ] = (Pel) std::min<int>( ( a + b ) / 2, maxVal ); |
751 | 0 | if( ( a + b ) * ( a + b ) <= 2 * b * b ) |
752 | 0 | { |
753 | 0 | dir[ x ] = 0; |
754 | 0 | } |
755 | 0 | else if( a > b && ( a - b ) * ( a - b ) > 2 * b * b ) |
756 | 0 | { |
757 | 0 | dir[ x ] = 90; |
758 | 0 | } |
759 | 0 | else |
760 | 0 | { |
761 | 0 | dir[ x ] = ( gx ^ gy ) >= 0 ? 45 : 135; |
762 | 0 | } |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | // Neighbours along the direction: 0 degrees compares left and right, 45 lower right and upper left, 90 above and |
767 | | // below, 135 lower left and upper right. |
768 | | void fgaNonMaxCore( const Pel* magAbove, const Pel* mag, const Pel* magBelow, const Pel* dir, int width, int xStart, int xEnd, Pel* dst ) |
769 | 0 | { |
770 | 0 | for( int x = xStart; x < xEnd; x++ ) |
771 | 0 | { |
772 | 0 | const int l = std::max( x - 1, 0 ), r = std::min( x + 1, width - 1 ); |
773 | 0 | Pel n1, n2; |
774 | 0 | switch( dir[ x ] ) |
775 | 0 | { |
776 | 0 | case 0: n1 = mag[ r ]; n2 = mag[ l ]; break; |
777 | 0 | case 45: n1 = magBelow[ r ]; n2 = magAbove[ l ]; break; |
778 | 0 | case 90: n1 = magBelow[ x ]; n2 = magAbove[ x ]; break; |
779 | 0 | default: n1 = magBelow[ l ]; n2 = magAbove[ r ]; break; |
780 | 0 | } |
781 | 0 | dst[ x ] = mag[ x ] < n1 || mag[ x ] < n2 ? 0 : mag[ x ]; |
782 | 0 | } |
783 | 0 | } |
784 | | |
785 | | void fgaMorphRowCore( const Pel* src, int width, int xStart, int xEnd, bool dilate, Pel* dst ) |
786 | 0 | { |
787 | 0 | for( int x = xStart; x < xEnd; x++ ) |
788 | 0 | { |
789 | 0 | const Pel l = src[ std::max( x - 1, 0 ) ], r = src[ std::min( x + 1, width - 1 ) ]; |
790 | 0 | dst[ x ] = dilate ? std::max( { l, src[ x ], r } ) : std::min( { l, src[ x ], r } ); |
791 | 0 | } |
792 | 0 | } |
793 | | |
794 | | void fgaMorphColCore( const Pel* above, const Pel* cur, const Pel* below, int xStart, int xEnd, bool dilate, Pel* dst ) |
795 | 0 | { |
796 | 0 | for( int x = xStart; x < xEnd; x++ ) |
797 | 0 | { |
798 | 0 | dst[ x ] = dilate ? std::max( { above[ x ], cur[ x ], below[ x ] } ) : std::min( { above[ x ], cur[ x ], below[ x ] } ); |
799 | 0 | } |
800 | 0 | } |
801 | | |
802 | | Pel FGAnalyzer::Plane::clampedAt( int x, int y ) const |
803 | 0 | { |
804 | 0 | return at( Clip3( 0, width - 1, x ), Clip3( 0, height - 1, y ) ); |
805 | 0 | } |
806 | | |
807 | | // ==================================================================================================================== |
808 | | // Initialization |
809 | | // ==================================================================================================================== |
810 | | |
811 | | FGAnalyzer::FGAnalyzer() |
812 | 1.49k | { |
813 | 1.49k | initKernels( m_kernels ); |
814 | 1.49k | } |
815 | | |
816 | | void FGAnalyzer::initKernels( FgaRowKernels& k ) |
817 | 2.98k | { |
818 | 2.98k | k.sobel = sobelRow; |
819 | 2.98k | k.nonMax = nonMaxRow; |
820 | 2.98k | k.morphRow = morphRow; |
821 | 2.98k | k.morphCol = morphCol; |
822 | 2.98k | k.rowMax = rowMax; |
823 | 2.98k | k.classify = classify; |
824 | 2.98k | k.findEqual = fgaFindEqualCore; |
825 | 2.98k | k.isZeroBlock = fgaIsZeroBlockCore; |
826 | 2.98k | k.blockSums = fgaBlockSumsCore; |
827 | 2.98k | k.squaredDct16 = fgaSquaredDct16Core; |
828 | 2.98k | k.downsample4 = fgaDownsample4Core; |
829 | 2.98k | k.sad4x4Row = fgaSad4x4RowCore; |
830 | 2.98k | k.residualRow = fgaResidualRowCore; |
831 | | #if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 ) |
832 | | initFGAnalyzerX86( k ); |
833 | | #endif |
834 | | #if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM ) |
835 | | initFGAnalyzerARM( k ); |
836 | | #endif |
837 | 2.98k | } |
838 | | |
839 | | void FGAnalyzer::init( int width, int height, ChromaFormat chromaFormat, const int bitDepths[ MAX_NUM_CH ], const bool doAnalysis[ MAX_NUM_COMP ], bool improved, bool arModel ) |
840 | 0 | { |
841 | 0 | m_improved = improved || arModel; |
842 | 0 | m_arModel = arModel; |
843 | 0 | for( ArFilter& f : m_arFilter ) |
844 | 0 | { |
845 | 0 | f = ArFilter(); |
846 | 0 | } |
847 | 0 | m_strengthScale = m_improved ? IMPROVED_STRENGTH_SCALE : 1; |
848 | 0 | m_width = width; |
849 | 0 | m_height = height; |
850 | 0 | m_chromaFormat = chromaFormat; |
851 | 0 | for( int ch = 0; ch < MAX_NUM_CH; ch++ ) |
852 | 0 | { |
853 | 0 | m_bitDepths[ ch ] = bitDepths[ ch ]; |
854 | 0 | } |
855 | | |
856 | | // No grain is signalled until an analysis has measured it. VTM signals default parameters (scaling factor 16 for |
857 | | // luma and 8 for chroma) until its first successful fit, which adds strong grain to video without grain, where the |
858 | | // fit never succeeds. |
859 | 0 | for( int c = 0; c < MAX_NUM_COMP; c++ ) |
860 | 0 | { |
861 | 0 | SeiFgc::CompModel& cm = m_compModel[ c ]; |
862 | 0 | cm.presentFlag = false; |
863 | 0 | cm.numModelValues = 1; |
864 | 0 | cm.numIntensityIntervals = 0; |
865 | 0 | cm.intensityValues.resize( MAX_NUM_INTENSITIES ); |
866 | 0 | for( auto& iv : cm.intensityValues ) |
867 | 0 | { |
868 | 0 | iv.intensityIntervalLowerBound = 0; |
869 | 0 | iv.intensityIntervalUpperBound = 0; |
870 | 0 | iv.compModelValue.assign( MAX_ALLOWED_MODEL_VALUES, 0 ); |
871 | 0 | } |
872 | 0 | m_doAnalysis[ c ] = doAnalysis[ c ]; |
873 | 0 | m_cutoffPairs[ c ] = 0; |
874 | 0 | m_failedFits [ c ] = 0; |
875 | 0 | resetStatistics( c ); |
876 | 0 | } |
877 | 0 | } |
878 | | |
879 | | // Drops the data accumulated over the analysed pictures of a component. |
880 | | void FGAnalyzer::resetStatistics( int c ) |
881 | 0 | { |
882 | 0 | const int numMeans = 1 << m_bitDepths[ toChannelType( ComponentID( c ) ) ]; |
883 | 0 | m_squaredDctSums[ c ].assign( numMeans, SquaredDctSum( BLOCK_SIZE * BLOCK_SIZE, 0.0 ) ); |
884 | 0 | m_numDctBlocks [ c ].assign( numMeans, 0.0 ); |
885 | 0 | m_storedMeanIntensity[ c ].clear(); |
886 | 0 | m_storedStrengthIntensity[ c ].clear(); |
887 | 0 | m_storedElementsPerInterval[ c ].clear(); |
888 | 0 | } |
889 | | |
890 | | // ==================================================================================================================== |
891 | | // Temporal residual |
892 | | // ==================================================================================================================== |
893 | | |
894 | | FgaTemporalResidual::FgaTemporalResidual() |
895 | 1.49k | { |
896 | 1.49k | FGAnalyzer::initKernels( m_kernels ); |
897 | 1.49k | } |
898 | | |
899 | | void FgaTemporalResidual::downsample( const CPelBuf& src, FGAnalyzer::Plane& dst ) const |
900 | 0 | { |
901 | 0 | dst.resize( src.width / 4, src.height / 4 ); |
902 | 0 | for( int y = 0; y < dst.height; y++ ) |
903 | 0 | { |
904 | 0 | m_kernels.downsample4( src.bufAt( 0, 4 * y ), (int) src.stride, dst.width, dst.row( y ) ); |
905 | 0 | } |
906 | 0 | } |
907 | | |
908 | | void FgaTemporalResidual::search( const FGAnalyzer::Plane& cur, const FGAnalyzer::Plane& ref, int numBlocksX, int numBlocksY, Motion& motion ) const |
909 | 0 | { |
910 | 0 | const int numBlocks = numBlocksX * numBlocksY; |
911 | 0 | const int range = cur.width * 4 > 2048 ? RESIDUAL_RANGE_WIDE : RESIDUAL_RANGE; |
912 | 0 | motion.dx.assign( numBlocks, 0 ); |
913 | 0 | motion.dy.assign( numBlocks, 0 ); |
914 | 0 | std::vector<int> bestSad( numBlocks ), bestScore( numBlocks ), sad( numBlocksX ); |
915 | | |
916 | | // zero motion; another vector must have a SAD below ZERO_BIAS_NUM / ZERO_BIAS_DEN of it |
917 | 0 | for( int by = 0; by < numBlocksY; by++ ) |
918 | 0 | { |
919 | 0 | int* s = &bestSad[ by * numBlocksX ]; |
920 | 0 | m_kernels.sad4x4Row( cur.row( 4 * by ), cur.width, ref.row( 4 * by ), ref.width, numBlocksX, s ); |
921 | 0 | for( int bx = 0; bx < numBlocksX; bx++ ) |
922 | 0 | { |
923 | 0 | bestScore[ by * numBlocksX + bx ] = s[ bx ] * ZERO_BIAS_NUM; |
924 | 0 | } |
925 | 0 | } |
926 | 0 | for( int dy = -range; dy <= range; dy++ ) |
927 | 0 | { |
928 | 0 | for( int dx = -range; dx <= range; dx++ ) |
929 | 0 | { |
930 | 0 | if( dx == 0 && dy == 0 ) |
931 | 0 | { |
932 | 0 | continue; |
933 | 0 | } |
934 | | // the blocks whose displaced block lies inside the reference |
935 | 0 | const int bx0 = dx < 0 ? ( 3 - dx ) / 4 : 0; |
936 | 0 | const int bx1 = ref.width - 4 - dx < 0 ? 0 : std::min( numBlocksX, ( ref.width - 4 - dx ) / 4 + 1 ); |
937 | 0 | if( bx1 <= bx0 ) |
938 | 0 | { |
939 | 0 | continue; |
940 | 0 | } |
941 | 0 | for( int by = 0; by < numBlocksY; by++ ) |
942 | 0 | { |
943 | 0 | const int ry = 4 * by + dy; |
944 | 0 | if( ry < 0 || ry + 4 > ref.height ) |
945 | 0 | { |
946 | 0 | continue; |
947 | 0 | } |
948 | 0 | m_kernels.sad4x4Row( cur.row( 4 * by ) + 4 * bx0, cur.width, ref.row( ry ) + 4 * bx0 + dx, ref.width, bx1 - bx0, sad.data() ); |
949 | 0 | for( int bx = bx0; bx < bx1; bx++ ) |
950 | 0 | { |
951 | 0 | const int b = by * numBlocksX + bx; |
952 | 0 | const int score = sad[ bx - bx0 ] * ZERO_BIAS_DEN; |
953 | 0 | if( score < bestScore[ b ] ) |
954 | 0 | { |
955 | 0 | bestScore[ b ] = score; |
956 | 0 | bestSad [ b ] = sad[ bx - bx0 ]; |
957 | 0 | motion.dx[ b ] = (int8_t) dx; |
958 | 0 | motion.dy[ b ] = (int8_t) dy; |
959 | 0 | } |
960 | 0 | } |
961 | 0 | } |
962 | 0 | } |
963 | 0 | } |
964 | 0 | int64_t sum = 0; |
965 | 0 | for( int s : bestSad ) |
966 | 0 | { |
967 | 0 | sum += s; |
968 | 0 | } |
969 | 0 | motion.meanSad = (double) sum / ( 16.0 * numBlocks ); |
970 | 0 | } |
971 | | |
972 | | int FgaTemporalResidual::compute( const CPelUnitBuf& cur, const CPelUnitBuf* prev, const CPelUnitBuf* next, PelStorage& residual ) const |
973 | 0 | { |
974 | 0 | const CPelBuf curY = cur.get( COMP_Y ); |
975 | 0 | const int numBlocksX = curY.width / 16; |
976 | 0 | const int numBlocksY = curY.height / 16; |
977 | 0 | if( ( !prev && !next ) || numBlocksX == 0 || numBlocksY == 0 ) |
978 | 0 | { |
979 | 0 | return 0; |
980 | 0 | } |
981 | | |
982 | 0 | FGAnalyzer::Plane curQ, refQ; |
983 | 0 | downsample( curY, curQ ); |
984 | 0 | Motion motionPrev, motionNext; |
985 | 0 | if( prev ) |
986 | 0 | { |
987 | 0 | downsample( prev->get( COMP_Y ), refQ ); |
988 | 0 | search( curQ, refQ, numBlocksX, numBlocksY, motionPrev ); |
989 | 0 | } |
990 | 0 | if( next ) |
991 | 0 | { |
992 | 0 | downsample( next->get( COMP_Y ), refQ ); |
993 | 0 | search( curQ, refQ, numBlocksX, numBlocksY, motionNext ); |
994 | 0 | } |
995 | 0 | if( prev && next ) |
996 | 0 | { |
997 | 0 | if( motionPrev.meanSad > 2 * motionNext.meanSad + NEIGHBOUR_SAD_MARGIN ) |
998 | 0 | { |
999 | 0 | prev = nullptr; |
1000 | 0 | } |
1001 | 0 | else if( motionNext.meanSad > 2 * motionPrev.meanSad + NEIGHBOUR_SAD_MARGIN ) |
1002 | 0 | { |
1003 | 0 | next = nullptr; |
1004 | 0 | } |
1005 | 0 | } |
1006 | | // the first neighbour of the residual, and the second one if both are used |
1007 | 0 | const CPelUnitBuf* first = prev ? prev : next; |
1008 | 0 | const CPelUnitBuf* second = prev ? next : nullptr; |
1009 | 0 | const Motion& motionFirst = prev ? motionPrev : motionNext; |
1010 | 0 | const Motion& motionSecond = motionNext; |
1011 | |
|
1012 | 0 | residual.destroy(); |
1013 | 0 | residual.create( cur.chromaFormat, Area( 0, 0, curY.width, curY.height ) ); |
1014 | 0 | residual.fill( 0 ); // the samples outside the 16x16 blocks are not analysed |
1015 | 0 | for( int c = 0; c < getNumberValidComponents( cur.chromaFormat ); c++ ) |
1016 | 0 | { |
1017 | 0 | const ComponentID compId = ComponentID( c ); |
1018 | 0 | const int csx = getComponentScaleX( compId, cur.chromaFormat ); |
1019 | 0 | const int csy = getComponentScaleY( compId, cur.chromaFormat ); |
1020 | 0 | const int bw = 16 >> csx; |
1021 | 0 | const int bh = 16 >> csy; |
1022 | 0 | const CPelBuf o = cur.get( compId ); |
1023 | 0 | const CPelBuf f = first->get( compId ); |
1024 | 0 | const CPelBuf s = second ? second->get( compId ) : CPelBuf(); |
1025 | 0 | PelBuf r = residual.get( compId ); |
1026 | 0 | for( int by = 0; by < numBlocksY; by++ ) |
1027 | 0 | { |
1028 | 0 | for( int bx = 0; bx < numBlocksX; bx++ ) |
1029 | 0 | { |
1030 | 0 | const int b = by * numBlocksX + bx; |
1031 | 0 | const int x0 = bx * bw; |
1032 | 0 | const int y0 = by * bh; |
1033 | | // vectors in samples of this component (4 full resolution samples per downsampled sample) |
1034 | 0 | const int fx = ( 4 * motionFirst.dx[ b ] ) >> csx, fy = ( 4 * motionFirst.dy[ b ] ) >> csy; |
1035 | 0 | const int sx = second ? ( 4 * motionSecond.dx[ b ] ) >> csx : 0; |
1036 | 0 | const int sy = second ? ( 4 * motionSecond.dy[ b ] ) >> csy : 0; |
1037 | 0 | for( int y = 0; y < bh; y++ ) |
1038 | 0 | { |
1039 | 0 | m_kernels.residualRow( o.bufAt( x0, y0 + y ), f.bufAt( x0 + fx, y0 + y + fy ), |
1040 | 0 | second ? s.bufAt( x0 + sx, y0 + y + sy ) : nullptr, bw, r.bufAt( x0, y0 + y ) ); |
1041 | 0 | } |
1042 | 0 | } |
1043 | 0 | } |
1044 | 0 | } |
1045 | 0 | return second ? 6 : 2; |
1046 | 0 | } |
1047 | | |
1048 | | // VTM's scaling factors: twice the standard deviation, at log2 scale factor 2. As in getSignalledParameters(), the 4:2:0 |
1049 | | // chroma cut-off frequencies are halved and the scaling factors stay at MAX_SCALING_FACTOR or below. |
1050 | | void FGAnalyzer::getVtmParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const |
1051 | 0 | { |
1052 | 0 | log2ScaleFactor = 2; |
1053 | 0 | for( int c = 0; c < MAX_NUM_COMP; c++ ) |
1054 | 0 | { |
1055 | 0 | SeiFgc::CompModel& cm = compModel[ c ]; |
1056 | 0 | cm = m_compModel[ c ]; |
1057 | 0 | if( !cm.presentFlag || strength <= 0.0 ) |
1058 | 0 | { |
1059 | 0 | cm.presentFlag = false; |
1060 | 0 | continue; |
1061 | 0 | } |
1062 | 0 | const bool chroma420 = c != COMP_Y && m_chromaFormat == CHROMA_420; |
1063 | 0 | const int bitDepth = m_bitDepths[ toChannelType( ComponentID( c ) ) ]; |
1064 | 0 | bool anyGrain = false; |
1065 | 0 | for( int i = 0; i < cm.numIntensityIntervals; i++ ) |
1066 | 0 | { |
1067 | 0 | std::vector<int>& v = cm.intensityValues[ i ].compModelValue; |
1068 | 0 | for( int k = 1; chroma420 && k < cm.numModelValues; k++ ) |
1069 | 0 | { |
1070 | 0 | v[ k ] = Clip3( 2, 14, ( v[ k ] + 1 ) >> 1 ); |
1071 | 0 | } |
1072 | 0 | const int scale = ( v[ 0 ] << log2ScaleFactor ) >> ( bitDepth - BIT_DEPTH_8 ); |
1073 | 0 | v[ 0 ] = std::min( MAX_SCALING_FACTOR, (int) ( scale * strength + 0.5 ) ); |
1074 | 0 | anyGrain = anyGrain || v[ 0 ] > 0; |
1075 | 0 | } |
1076 | 0 | cm.presentFlag = anyGrain; |
1077 | 0 | } |
1078 | 0 | } |
1079 | | |
1080 | | // The parameters of the auto-regressive model: model values scale, a1, 0 (no correlation between the components), a3, |
1081 | | // r and a5, the last four in units of 2^-log2ScaleFactor. The scaling factors are calibrated with the impulse response |
1082 | | // energy of the quantized filter (AR_PATTERN_STDDEV); the log2 scale factor is the largest (2 to 7) at which they stay |
1083 | | // at MAX_SCALING_FACTOR or below, for the finest steps of both the scaling factors and the filter coefficients. |
1084 | | void FGAnalyzer::getArParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const |
1085 | 0 | { |
1086 | 0 | for( log2ScaleFactor = 7; log2ScaleFactor >= 2; log2ScaleFactor-- ) |
1087 | 0 | { |
1088 | 0 | const int one = 1 << log2ScaleFactor; |
1089 | 0 | bool fits = true; |
1090 | 0 | for( int c = 0; c < MAX_NUM_COMP; c++ ) |
1091 | 0 | { |
1092 | 0 | SeiFgc::CompModel& cm = compModel[ c ]; |
1093 | 0 | cm = m_compModel[ c ]; |
1094 | 0 | const ArFilter& f = m_arFilter[ c ]; |
1095 | 0 | if( !cm.presentFlag || strength <= 0.0 || !f.valid ) |
1096 | 0 | { |
1097 | 0 | cm.presentFlag = false; |
1098 | 0 | continue; |
1099 | 0 | } |
1100 | 0 | const int coef[ 6 ] = { 0, (int) std::lround( f.a1 * one ), 0, (int) std::lround( f.a3 * one ), (int) std::lround( f.r * one ), |
1101 | 0 | (int) std::lround( f.a5 * one ) }; |
1102 | 0 | const int bitDepth = m_bitDepths[ toChannelType( ComponentID( c ) ) ]; |
1103 | 0 | const double patternStddev = AR_PATTERN_STDDEV[ c == COMP_Y ? 0 : 1 ] * std::sqrt( arEnergy( coef, log2ScaleFactor ) ); |
1104 | 0 | bool anyGrain = false; |
1105 | 0 | cm.numModelValues = 6; |
1106 | 0 | for( int i = 0; i < cm.numIntensityIntervals; i++ ) |
1107 | 0 | { |
1108 | 0 | std::vector<int>& v = cm.intensityValues[ i ].compModelValue; |
1109 | 0 | const double stddev = v[ 0 ] / ( 2.0 * m_strengthScale ) / std::sqrt( f.nonDcShare ); |
1110 | 0 | const double scale = std::ldexp( stddev * strength, log2ScaleFactor + 10 - bitDepth ) / patternStddev; |
1111 | 0 | fits = fits && scale <= MAX_SCALING_FACTOR + 0.5; |
1112 | 0 | v.assign( 6, 0 ); |
1113 | 0 | v[ 0 ] = std::min( MAX_SCALING_FACTOR, (int) ( scale + 0.5 ) ); |
1114 | 0 | std::copy( coef + 1, coef + 6, v.begin() + 1 ); |
1115 | 0 | anyGrain = anyGrain || v[ 0 ] > 0; |
1116 | 0 | } |
1117 | 0 | cm.presentFlag = anyGrain; |
1118 | 0 | } |
1119 | 0 | if( fits || log2ScaleFactor == 2 ) |
1120 | 0 | { |
1121 | 0 | return; |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | } |
1125 | | |
1126 | | // The scaling factors are calibrated, because the standard deviation of the synthesized grain depends on the cut-off |
1127 | | // frequencies by a factor of up to 5 (PATTERN_STDDEV), and the block measurement misses up to 9% of the grain |
1128 | | // variance (BLOCK_VARIANCE_SHARE). VTM signals twice the standard deviation, which fits only cut-off frequencies near 8. |
1129 | | // The log2 scale factor is the largest (2 to 7, the range of the synthesis) at which all scaling factors stay at |
1130 | | // MAX_SCALING_FACTOR or below, for the finest steps. |
1131 | | void FGAnalyzer::getSignalledParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const |
1132 | 0 | { |
1133 | 0 | if( !m_improved ) |
1134 | 0 | { |
1135 | 0 | getVtmParameters( strength, log2ScaleFactor, compModel ); |
1136 | 0 | return; |
1137 | 0 | } |
1138 | 0 | if( m_arModel ) |
1139 | 0 | { |
1140 | 0 | getArParameters( strength, log2ScaleFactor, compModel ); |
1141 | 0 | return; |
1142 | 0 | } |
1143 | 0 | std::vector<double> scales[ MAX_NUM_COMP ]; // per interval, at log2 scale factor 0 |
1144 | 0 | double maxScale = 0.0; |
1145 | 0 | for( int c = 0; c < MAX_NUM_COMP; c++ ) |
1146 | 0 | { |
1147 | 0 | SeiFgc::CompModel& cm = compModel[ c ]; |
1148 | 0 | cm = m_compModel[ c ]; |
1149 | 0 | if( !cm.presentFlag || strength <= 0.0 ) |
1150 | 0 | { |
1151 | 0 | cm.presentFlag = false; |
1152 | 0 | continue; |
1153 | 0 | } |
1154 | | // The analysis measures the chroma grain on the subsampled chroma planes, but for 4:2:0 the synthesis (VTM, vvdec) |
1155 | | // treats the chroma cut-off frequencies as luma resolution values and doubles them: they are halved here. Its |
1156 | | // halving of the chroma scaling factors is part of the chroma calibration tables. |
1157 | 0 | const bool chroma420 = c != COMP_Y && m_chromaFormat == CHROMA_420; |
1158 | 0 | const int table = chroma420 ? 1 : 0; |
1159 | 0 | const int bitDepth = m_bitDepths[ toChannelType( ComponentID( c ) ) ]; |
1160 | 0 | for( int i = 0; i < cm.numIntensityIntervals; i++ ) |
1161 | 0 | { |
1162 | 0 | std::vector<int>& v = cm.intensityValues[ i ].compModelValue; |
1163 | 0 | for( int k = 1; chroma420 && k < cm.numModelValues; k++ ) |
1164 | 0 | { |
1165 | 0 | v[ k ] = Clip3( 2, 14, ( v[ k ] + 1 ) >> 1 ); |
1166 | 0 | } |
1167 | 0 | const int h = cm.numModelValues > 1 ? Clip3( 2, 14, v[ 1 ] ) : 8; |
1168 | 0 | const int w = cm.numModelValues > 2 ? Clip3( 2, 14, v[ 2 ] ) : h; |
1169 | 0 | const double stddev = v[ 0 ] / ( 2.0 * m_strengthScale ) / std::sqrt( BLOCK_VARIANCE_SHARE[ table ][ h - 2 ][ w - 2 ] ); |
1170 | 0 | scales[ c ].push_back( stddev * strength * ( 1 << ( 14 - bitDepth ) ) / PATTERN_STDDEV[ table ][ h - 2 ][ w - 2 ] ); |
1171 | 0 | maxScale = std::max( maxScale, scales[ c ].back() ); |
1172 | 0 | } |
1173 | 0 | } |
1174 | |
|
1175 | 0 | log2ScaleFactor = 7; |
1176 | 0 | while( log2ScaleFactor > 2 && maxScale * ( 1 << log2ScaleFactor ) > MAX_SCALING_FACTOR + 0.5 ) |
1177 | 0 | { |
1178 | 0 | log2ScaleFactor--; |
1179 | 0 | } |
1180 | 0 | for( int c = 0; c < MAX_NUM_COMP; c++ ) |
1181 | 0 | { |
1182 | 0 | SeiFgc::CompModel& cm = compModel[ c ]; |
1183 | 0 | if( !cm.presentFlag ) |
1184 | 0 | { |
1185 | 0 | continue; |
1186 | 0 | } |
1187 | 0 | bool anyGrain = false; |
1188 | 0 | for( int i = 0; i < cm.numIntensityIntervals; i++ ) |
1189 | 0 | { |
1190 | 0 | int& scale = cm.intensityValues[ i ].compModelValue[ 0 ]; |
1191 | 0 | scale = std::min( MAX_SCALING_FACTOR, (int) ( scales[ c ][ i ] * ( 1 << log2ScaleFactor ) + 0.5 ) ); |
1192 | 0 | anyGrain = anyGrain || scale > 0; |
1193 | 0 | } |
1194 | 0 | cm.presentFlag = anyGrain; |
1195 | 0 | } |
1196 | 0 | } |
1197 | | |
1198 | | // ==================================================================================================================== |
1199 | | // Analysis of one picture |
1200 | | // ==================================================================================================================== |
1201 | | |
1202 | | void FGAnalyzer::estimateGrainParameters( const CPelUnitBuf& orig, const CPelUnitBuf& denoised, const CPelUnitBuf* residual, int residualWeight, |
1203 | | bool sceneCut ) |
1204 | 0 | { |
1205 | 0 | const int numComp = getNumberValidComponents( m_chromaFormat ); |
1206 | 0 | m_grainWeight = residual ? residualWeight : 1; |
1207 | 0 | for( int c = 0; sceneCut && c < numComp; c++ ) |
1208 | 0 | { |
1209 | 0 | resetStatistics( c ); |
1210 | 0 | } |
1211 | 0 | for( int c = 0; c < numComp; c++ ) |
1212 | 0 | { |
1213 | 0 | const ComponentID compId = ComponentID( c ); |
1214 | 0 | const CPelBuf o = orig.get( compId ); |
1215 | 0 | const CPelBuf d = denoised.get( compId ); |
1216 | 0 | m_denoised[ c ].resize( d.width, d.height ); |
1217 | 0 | m_grain [ c ].resize( d.width, d.height ); |
1218 | 0 | for( int y = 0; y < (int) o.height; y++ ) |
1219 | 0 | { |
1220 | 0 | const Pel* ro = o.bufAt( 0, y ); |
1221 | 0 | const Pel* rd = d.bufAt( 0, y ); |
1222 | 0 | Pel* td = m_denoised[ c ].row( y ); |
1223 | 0 | Pel* tg = m_grain [ c ].row( y ); |
1224 | 0 | std::copy( rd, rd + o.width, td ); |
1225 | 0 | if( residual ) |
1226 | 0 | { |
1227 | 0 | const Pel* rr = residual->get( compId ).bufAt( 0, y ); |
1228 | 0 | std::copy( rr, rr + o.width, tg ); |
1229 | 0 | } |
1230 | 0 | else |
1231 | 0 | { |
1232 | 0 | for( int x = 0; x < (int) o.width; x++ ) |
1233 | 0 | { |
1234 | 0 | tg[ x ] = (Pel) ( rd[ x ] - ro[ x ] ); |
1235 | 0 | } |
1236 | 0 | } |
1237 | 0 | } |
1238 | 0 | if( m_doAnalysis[ c ] ) |
1239 | 0 | { |
1240 | 0 | findMask( compId ); |
1241 | 0 | } |
1242 | 0 | } |
1243 | |
|
1244 | 0 | m_cutoffPairs[ COMP_Y ] = m_cutoffPairs[ COMP_Cb ] = m_cutoffPairs[ COMP_Cr ] = 0; |
1245 | 0 | for( int c = 0; c < numComp; c++ ) |
1246 | 0 | { |
1247 | 0 | estimateComponent( ComponentID( c ) ); |
1248 | 0 | } |
1249 | 0 | } |
1250 | | |
1251 | | // Mask of the samples that are not part of a flat area: edges found at full, half and quarter resolution, dilated. |
1252 | | void FGAnalyzer::findMask( ComponentID compId ) |
1253 | 0 | { |
1254 | 0 | const int bitDepth = m_bitDepths[ toChannelType( compId ) ]; |
1255 | 0 | const Plane& picture = m_denoised[ compId ]; |
1256 | 0 | Plane& mask = m_mask[ compId ]; |
1257 | |
|
1258 | 0 | Scratch& s = m_scratch; |
1259 | 0 | subsample( picture, s.half, 2 ); |
1260 | 0 | subsample( picture, s.quarter, 4 ); |
1261 | |
|
1262 | 0 | maskForScale( picture, mask, bitDepth, 4 ); |
1263 | 0 | maskForScale( s.half, s.lowMask, bitDepth, 3 ); |
1264 | 0 | combineUpsampled( mask, s.lowMask, 2, s.wide ); |
1265 | 0 | maskForScale( s.quarter, s.lowMask, bitDepth, 2 ); |
1266 | 0 | combineUpsampled( mask, s.lowMask, 4, s.wide ); |
1267 | | |
1268 | | // final dilation to fill the holes, then erosion |
1269 | 0 | morphStep( m_kernels, mask, s.morph, true ); |
1270 | 0 | morphStep( m_kernels, mask, s.morph, true ); |
1271 | 0 | morphStep( m_kernels, mask, s.morph, false ); |
1272 | 0 | } |
1273 | | |
1274 | | void FGAnalyzer::maskForScale( const Plane& picture, Plane& mask, int bitDepth, int numDilations ) |
1275 | 0 | { |
1276 | 0 | Scratch& s = m_scratch; |
1277 | 0 | gradientWithNonMaxSuppression( m_kernels, picture, mask, bitDepth, s.magnitude, s.direction ); |
1278 | 0 | doubleThreshold( m_kernels, mask, bitDepth ); |
1279 | 0 | edgeTracking( m_kernels, mask, bitDepth, s ); |
1280 | 0 | suppressLowIntensity( picture, mask, bitDepth ); |
1281 | 0 | for( int i = 0; i < numDilations; i++ ) |
1282 | 0 | { |
1283 | 0 | morphStep( m_kernels, mask, s.morph, true ); |
1284 | 0 | } |
1285 | 0 | } |
1286 | | |
1287 | | // Collects the block statistics of one component and estimates its parameters. |
1288 | | void FGAnalyzer::estimateComponent( ComponentID compId ) |
1289 | 0 | { |
1290 | 0 | if( !m_doAnalysis[ compId ] || ( compId > COMP_Y && !m_compModel[ COMP_Y ].presentFlag ) ) |
1291 | 0 | { |
1292 | 0 | m_compModel[ compId ].presentFlag = false; |
1293 | 0 | m_cutoffPairs[ compId ] = 0; |
1294 | 0 | return; |
1295 | 0 | } |
1296 | | |
1297 | 0 | const int bitDepth = m_bitDepths[ toChannelType( compId ) ]; |
1298 | 0 | const Plane& denoised = m_denoised[ compId ]; |
1299 | 0 | const Plane& grain = m_grain[ compId ]; |
1300 | 0 | const Plane& mask = m_mask[ compId ]; |
1301 | 0 | const int maxStrength = MAX_REAL_SCALE << ( bitDepth - BIT_DEPTH_8 ); |
1302 | |
|
1303 | 0 | static_assert( BLOCK_SIZE == 16, "the block kernels work on 16x16 blocks" ); |
1304 | 0 | std::vector<int> vecMean; |
1305 | 0 | std::vector<int> vecStrength; |
1306 | | |
1307 | | // older pictures count less |
1308 | 0 | for( size_t m = 0; m < m_numDctBlocks[ compId ].size(); m++ ) |
1309 | 0 | { |
1310 | 0 | if( m_numDctBlocks[ compId ][ m ] > 0 ) |
1311 | 0 | { |
1312 | 0 | m_numDctBlocks[ compId ][ m ] *= ACCUMULATION_DECAY; |
1313 | 0 | for( double& v : m_squaredDctSums[ compId ][ m ] ) |
1314 | 0 | { |
1315 | 0 | v *= ACCUMULATION_DECAY; |
1316 | 0 | } |
1317 | 0 | } |
1318 | 0 | } |
1319 | | |
1320 | | // The flat blocks, visited column by column as in VTM; the order matters for the floating point sums of the fit. |
1321 | 0 | struct FlatBlock |
1322 | 0 | { |
1323 | 0 | int x, y, mean; |
1324 | 0 | double stddev; // grain standard deviation |
1325 | 0 | }; |
1326 | 0 | std::vector<FlatBlock> blocks; |
1327 | 0 | const double weightStddev = std::sqrt( (double) m_grainWeight ); // standard deviation of m_grain per grain standard deviation |
1328 | 0 | for( int x = 0; x + BLOCK_SIZE <= denoised.width; x += BLOCK_SIZE ) |
1329 | 0 | { |
1330 | 0 | for( int y = 0; y + BLOCK_SIZE <= denoised.height; y += BLOCK_SIZE ) |
1331 | 0 | { |
1332 | 0 | if( !m_kernels.isZeroBlock( mask.row( y ) + x, mask.width ) ) |
1333 | 0 | { |
1334 | 0 | continue; // not flat |
1335 | 0 | } |
1336 | 0 | const BlockStats picStats = meanStdDev( m_kernels, denoised, x, y ); |
1337 | 0 | if( 2 * picStats.stddev >= ( maxStrength >> 1 ) ) |
1338 | 0 | { |
1339 | 0 | continue; // the denoised block is not flat enough: the mask missed some texture |
1340 | 0 | } |
1341 | 0 | blocks.push_back( { x, y, picStats.mean, meanStdDev( m_kernels, grain, x, y ).stddev / weightStddev } ); |
1342 | 0 | } |
1343 | 0 | } |
1344 | | |
1345 | | // Where the motion compensation of the temporal residual failed, the residual of a block is much larger than the |
1346 | | // grain of the other blocks of its intensity: such blocks are left out. |
1347 | 0 | std::vector<double> maxVariance( NUM_OUTLIER_BINS, std::numeric_limits<double>::max() ); |
1348 | 0 | if( m_grainWeight > 1 ) |
1349 | 0 | { |
1350 | 0 | const int binShift = bitDepth - 4; |
1351 | 0 | std::vector<std::vector<double>> binVariances( NUM_OUTLIER_BINS ); |
1352 | 0 | for( const FlatBlock& b : blocks ) |
1353 | 0 | { |
1354 | 0 | binVariances[ b.mean >> binShift ].push_back( b.stddev * b.stddev ); |
1355 | 0 | } |
1356 | 0 | for( int i = 0; i < NUM_OUTLIER_BINS; i++ ) |
1357 | 0 | { |
1358 | 0 | std::vector<double>& v = binVariances[ i ]; |
1359 | 0 | if( (int) v.size() >= MIN_OUTLIER_BIN_BLOCKS ) |
1360 | 0 | { |
1361 | 0 | std::nth_element( v.begin(), v.begin() + v.size() / 2, v.end() ); |
1362 | 0 | maxVariance[ i ] = OUTLIER_VARIANCE_RATIO * v[ v.size() / 2 ]; |
1363 | 0 | } |
1364 | 0 | } |
1365 | 0 | } |
1366 | |
|
1367 | 0 | std::vector<const FlatBlock*> grainBlocks; |
1368 | 0 | for( const FlatBlock& b : blocks ) |
1369 | 0 | { |
1370 | 0 | if( b.stddev * b.stddev > maxVariance[ b.mean >> ( bitDepth - 4 ) ] ) |
1371 | 0 | { |
1372 | 0 | continue; // motion compensation failure |
1373 | 0 | } |
1374 | 0 | const int strength = (int) ( 2 * m_strengthScale * b.stddev + 0.5 ); |
1375 | 0 | if( strength >= maxStrength * m_strengthScale ) |
1376 | 0 | { |
1377 | 0 | continue; // too strong to be grain: probably texture or motion left in the grain estimate |
1378 | 0 | } |
1379 | 0 | vecMean.push_back( b.mean ); |
1380 | 0 | vecStrength.push_back( strength ); |
1381 | 0 | grainBlocks.push_back( &b ); |
1382 | 0 | } |
1383 | | |
1384 | | // When the grain of this picture is much stronger or weaker than that of the accumulated data, a new scene began: |
1385 | | // the accumulated data are dropped. Otherwise the second pass of the fit would discard the new scene's points as |
1386 | | // outliers of the old fit for several analyses, and the old grain would stay on. |
1387 | 0 | double storedStrength = 0.0, storedElements = 0.0; |
1388 | 0 | for( size_t i = 0; i < m_storedStrengthIntensity[ compId ].size(); i++ ) |
1389 | 0 | { |
1390 | 0 | storedStrength += m_storedStrengthIntensity [ compId ][ i ]; |
1391 | 0 | storedElements += m_storedElementsPerInterval[ compId ][ i ]; |
1392 | 0 | } |
1393 | 0 | if( m_improved && (int) vecStrength.size() >= GRAIN_CHANGE_MIN_BLOCKS && storedElements > 0.0 ) |
1394 | 0 | { |
1395 | 0 | const double mean = std::accumulate( vecStrength.begin(), vecStrength.end(), 0.0 ) / vecStrength.size(); |
1396 | 0 | const double stored = storedStrength / storedElements; |
1397 | 0 | if( mean > GRAIN_CHANGE_RATIO * stored || stored > GRAIN_CHANGE_RATIO * mean ) |
1398 | 0 | { |
1399 | 0 | resetStatistics( compId ); |
1400 | 0 | } |
1401 | 0 | } |
1402 | |
|
1403 | 0 | SquaredDctSum blockDct( BLOCK_SIZE * BLOCK_SIZE ); |
1404 | 0 | for( const FlatBlock* bp : grainBlocks ) |
1405 | 0 | { |
1406 | 0 | const FlatBlock& b = *bp; |
1407 | 0 | double* dctSum = m_squaredDctSums[ compId ][ b.mean ].data(); |
1408 | 0 | if( m_grainWeight == 1 ) |
1409 | 0 | { |
1410 | 0 | m_kernels.squaredDct16( grain.row( b.y ) + b.x, grain.width, ( 1 << ( bitDepth + 4 ) ) - 1, dctSum ); |
1411 | 0 | } |
1412 | 0 | else |
1413 | 0 | { |
1414 | | // the spectrum in grain units, so that pictures with one or two neighbours add up correctly |
1415 | 0 | std::fill( blockDct.begin(), blockDct.end(), 0.0 ); |
1416 | 0 | m_kernels.squaredDct16( grain.row( b.y ) + b.x, grain.width, ( 1 << ( bitDepth + 4 ) ) - 1, blockDct.data() ); |
1417 | 0 | for( int i = 0; i < BLOCK_SIZE * BLOCK_SIZE; i++ ) |
1418 | 0 | { |
1419 | 0 | dctSum[ i ] += blockDct[ i ] / m_grainWeight; |
1420 | 0 | } |
1421 | 0 | } |
1422 | 0 | m_numDctBlocks[ compId ][ b.mean ]++; |
1423 | 0 | } |
1424 | |
|
1425 | 0 | estimateScalingFactors( vecMean, vecStrength, bitDepth, compId ); |
1426 | 0 | if( m_arModel ) |
1427 | 0 | { |
1428 | 0 | fitArFilter( compId ); |
1429 | 0 | } |
1430 | 0 | else |
1431 | 0 | { |
1432 | 0 | estimateCutoffFreq( compId, bitDepth ); |
1433 | 0 | } |
1434 | 0 | } |
1435 | | |
1436 | | // ==================================================================================================================== |
1437 | | // Scaling function |
1438 | | // ==================================================================================================================== |
1439 | | |
1440 | | void FGAnalyzer::estimateScalingFactors( std::vector<int>& dataX, std::vector<int>& dataY, int bitDepth, ComponentID compId ) |
1441 | 0 | { |
1442 | 0 | std::vector<double> coeffs; |
1443 | 0 | std::vector<double> scalingVec; |
1444 | 0 | std::vector<int> quantVec; |
1445 | | |
1446 | | // fit a polynomial to the points; the second pass removes points far from the first fit and stores the data |
1447 | 0 | bool valid = false; |
1448 | 0 | bool stored = false; |
1449 | 0 | for( int i = 0; i < NUM_PASSES; i++ ) |
1450 | 0 | { |
1451 | 0 | valid = fitFunction( dataX, dataY, coeffs, scalingVec, ORDER, bitDepth, i == 1, compId ); |
1452 | 0 | stored = i == 1; |
1453 | 0 | if( !valid ) |
1454 | 0 | { |
1455 | 0 | break; |
1456 | 0 | } |
1457 | 0 | } |
1458 | 0 | if( valid ) |
1459 | 0 | { |
1460 | 0 | valid = lloydMax( scalingVec, quantVec, QUANT_LEVELS, bitDepth ); |
1461 | 0 | } |
1462 | 0 | if( valid ) |
1463 | 0 | { |
1464 | 0 | setEstimatedParameters( quantVec, bitDepth, compId ); |
1465 | 0 | m_failedFits[ compId ] = 0; |
1466 | 0 | return; |
1467 | 0 | } |
1468 | 0 | if( !m_improved ) |
1469 | 0 | { |
1470 | 0 | return; // VTM keeps the previous parameters |
1471 | 0 | } |
1472 | | |
1473 | | // Without a fit, for example because there were too few points, the previous parameters are kept for a while (VTM |
1474 | | // keeps them for good). The stored data age as if a picture without data had been added, and after MAX_FAILED_FITS |
1475 | | // failures in a row the grain is no longer signalled, as it was measured on pictures too long ago: otherwise the |
1476 | | // grain of a grainy scene stays on in the following clean scene when the fit fails there. |
1477 | 0 | if( !stored ) |
1478 | 0 | { |
1479 | 0 | for( auto* data : { &m_storedMeanIntensity[ compId ], &m_storedStrengthIntensity[ compId ], &m_storedElementsPerInterval[ compId ] } ) |
1480 | 0 | { |
1481 | 0 | for( double& v : *data ) |
1482 | 0 | { |
1483 | 0 | v *= ACCUMULATION_DECAY; |
1484 | 0 | } |
1485 | 0 | } |
1486 | 0 | } |
1487 | 0 | if( ++m_failedFits[ compId ] >= MAX_FAILED_FITS ) |
1488 | 0 | { |
1489 | 0 | m_compModel [ compId ].presentFlag = false; |
1490 | 0 | m_cutoffPairs[ compId ] = 0; |
1491 | 0 | } |
1492 | 0 | } |
1493 | | |
1494 | | // Fits a polynomial of the given order to the strength over intensity points, averaged per intensity interval and |
1495 | | // accumulated with the points of the previous pictures. scalingVec receives the function sampled from xmin to xmax, |
1496 | | // followed by xmax and xmin. |
1497 | | bool FGAnalyzer::fitFunction( std::vector<int>& dataX, std::vector<int>& dataY, std::vector<double>& coeffs, std::vector<double>& scalingVec, |
1498 | | int order, int bitDepth, bool secondPass, ComponentID compId ) |
1499 | 0 | { |
1500 | 0 | const int intervalSize = INTERVAL_SIZE << ( ( bitDepth - BIT_DEPTH_8 ) >> 1 ); |
1501 | 0 | const int numIntervals = ( 1 << bitDepth ) / intervalSize; |
1502 | 0 | std::vector<double> meanIntensity( numIntervals, 0.0 ); |
1503 | 0 | std::vector<double> strengthIntensity( numIntervals, 0.0 ); |
1504 | 0 | std::vector<double> elementsPerInterval( numIntervals, 0.0 ); |
1505 | 0 | std::vector<int> tmpDataX; |
1506 | 0 | std::vector<int> tmpDataY; |
1507 | 0 | double xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0; |
1508 | 0 | double mn = 0.0, sd = 0.0; |
1509 | |
|
1510 | 0 | if( secondPass ) |
1511 | 0 | { |
1512 | 0 | xmin = scalingVec.back(); |
1513 | 0 | scalingVec.pop_back(); |
1514 | 0 | xmax = scalingVec.back(); |
1515 | 0 | scalingVec.pop_back(); |
1516 | 0 | const int n = (int) dataY.size(); |
1517 | 0 | if( n != 0 ) |
1518 | 0 | { |
1519 | 0 | mn = std::accumulate( dataY.begin(), dataY.end(), 0.0 ) / n; |
1520 | 0 | for( int i = 0; i < n; i++ ) |
1521 | 0 | { |
1522 | 0 | sd += ( dataY[ i ] - mn ) * ( dataY[ i ] - mn ); |
1523 | 0 | } |
1524 | 0 | sd /= n; |
1525 | 0 | sd = std::sqrt( sd ); |
1526 | 0 | } |
1527 | 0 | } |
1528 | |
|
1529 | 0 | for( size_t i = 0; i < dataX.size(); i++ ) |
1530 | 0 | { |
1531 | 0 | if( secondPass ) |
1532 | 0 | { |
1533 | 0 | if( dataX[ i ] >= xmin && dataX[ i ] <= xmax ) |
1534 | 0 | { |
1535 | 0 | const double fitted = scalingVec[ dataX[ i ] - (int) xmin ]; |
1536 | 0 | if( dataY[ i ] < fitted + sd * VAR_SCALE_UP && dataY[ i ] > fitted - sd * VAR_SCALE_DOWN ) |
1537 | 0 | { |
1538 | 0 | const int idx = dataX[ i ] / intervalSize; |
1539 | 0 | meanIntensity[ idx ] += dataX[ i ]; |
1540 | 0 | strengthIntensity[ idx ] += dataY[ i ]; |
1541 | 0 | elementsPerInterval[ idx ]++; |
1542 | 0 | } |
1543 | 0 | } |
1544 | 0 | } |
1545 | 0 | else |
1546 | 0 | { |
1547 | 0 | const int idx = dataX[ i ] / intervalSize; |
1548 | 0 | meanIntensity[ idx ] += dataX[ i ]; |
1549 | 0 | strengthIntensity[ idx ] += dataY[ i ]; |
1550 | 0 | elementsPerInterval[ idx ]++; |
1551 | 0 | } |
1552 | 0 | } |
1553 | | |
1554 | | // the data of the previous pictures, with decreasing weight |
1555 | 0 | if( !m_storedMeanIntensity[ compId ].empty() && !m_storedStrengthIntensity[ compId ].empty() ) |
1556 | 0 | { |
1557 | 0 | for( int i = 0; i < numIntervals; i++ ) |
1558 | 0 | { |
1559 | 0 | elementsPerInterval[ i ] += ACCUMULATION_DECAY * m_storedElementsPerInterval[ compId ][ i ]; |
1560 | 0 | meanIntensity[ i ] += ACCUMULATION_DECAY * m_storedMeanIntensity[ compId ][ i ]; |
1561 | 0 | strengthIntensity[ i ] += ACCUMULATION_DECAY * m_storedStrengthIntensity[ compId ][ i ]; |
1562 | 0 | } |
1563 | 0 | } |
1564 | |
|
1565 | 0 | for( int i = 0; i < numIntervals; i++ ) |
1566 | 0 | { |
1567 | 0 | if( elementsPerInterval[ i ] >= MIN_ELEMENTS_PER_INTERVAL ) |
1568 | 0 | { |
1569 | 0 | tmpDataX.push_back( (int) ( meanIntensity[ i ] / elementsPerInterval[ i ] ) ); |
1570 | 0 | tmpDataY.push_back( (int) ( strengthIntensity[ i ] / elementsPerInterval[ i ] + 0.5 ) ); // VTM truncates |
1571 | 0 | } |
1572 | 0 | } |
1573 | |
|
1574 | 0 | if( secondPass ) |
1575 | 0 | { |
1576 | | // keep the data to improve the estimate of the next pictures |
1577 | 0 | m_storedMeanIntensity[ compId ] = meanIntensity; |
1578 | 0 | m_storedStrengthIntensity[ compId ] = strengthIntensity; |
1579 | 0 | m_storedElementsPerInterval[ compId ] = elementsPerInterval; |
1580 | 0 | } |
1581 | | |
1582 | | // remove single points |
1583 | 0 | for( int i = 0; i < (int) tmpDataX.size(); i++ ) |
1584 | 0 | { |
1585 | 0 | int check = 0; |
1586 | 0 | for( int j = -WINDOW; j <= WINDOW; j++ ) |
1587 | 0 | { |
1588 | 0 | const int idx = i + j; |
1589 | 0 | if( idx >= 0 && idx < (int) tmpDataX.size() && j != 0 ) |
1590 | 0 | { |
1591 | 0 | check += abs( tmpDataX[ i ] / intervalSize - tmpDataX[ idx ] / intervalSize ) <= WINDOW ? 1 : 0; |
1592 | 0 | } |
1593 | 0 | } |
1594 | 0 | if( check < NBRS ) |
1595 | 0 | { |
1596 | 0 | tmpDataX.erase( tmpDataX.begin() + i ); |
1597 | 0 | tmpDataY.erase( tmpDataY.begin() + i ); |
1598 | 0 | i--; |
1599 | 0 | } |
1600 | 0 | } |
1601 | | |
1602 | | // VTM needs order + 1 measured points here. Chroma block means usually fall into only a few intervals around the |
1603 | | // middle of the range, so the chroma fit failed for most content and VTM signalled its default chroma parameters. |
1604 | | // The points added by extendPoints() model the fall-off on both sides of the measured range, which is enough for the |
1605 | | // improved fit when at least two measured points are left. |
1606 | 0 | if( (int) tmpDataX.size() < ( m_improved ? MIN_MEASURED_POINTS : order + 1 ) ) |
1607 | 0 | { |
1608 | 0 | return false; // too few points to fit the function |
1609 | 0 | } |
1610 | 0 | extendPoints( tmpDataX, tmpDataY, bitDepth ); |
1611 | 0 | CHECK( tmpDataX.size() > MAXPAIRS, "Maximum dataset size exceeded." ); |
1612 | 0 | if( (int) tmpDataX.size() < order + 1 ) |
1613 | 0 | { |
1614 | 0 | return false; |
1615 | 0 | } |
1616 | | |
1617 | 0 | xmin = xmax = tmpDataX[ 0 ]; |
1618 | 0 | ymin = ymax = tmpDataY[ 0 ]; |
1619 | 0 | for( size_t i = 0; i < tmpDataX.size(); i++ ) |
1620 | 0 | { |
1621 | 0 | xmin = std::min<double>( xmin, tmpDataX[ i ] ); |
1622 | 0 | xmax = std::max<double>( xmax, tmpDataX[ i ] ); |
1623 | 0 | ymin = std::min<double>( ymin, tmpDataY[ i ] ); |
1624 | 0 | ymax = std::max<double>( ymax, tmpDataY[ i ] ); |
1625 | 0 | } |
1626 | 0 | double xlow = xmax; |
1627 | 0 | double ylow = ymax; |
1628 | |
|
1629 | 0 | const int dataPairs = (int) tmpDataX.size(); |
1630 | 0 | std::vector<double> dataXd( dataPairs + 1 ), dataYd( dataPairs + 1 ); |
1631 | 0 | for( int i = 0; i < dataPairs; i++ ) |
1632 | 0 | { |
1633 | 0 | dataXd[ i + 1 ] = (double) tmpDataX[ i ]; |
1634 | 0 | dataYd[ i + 1 ] = (double) tmpDataY[ i ]; |
1635 | 0 | } |
1636 | 0 | if( secondPass ) |
1637 | 0 | { |
1638 | 0 | std::vector<int>().swap( dataX ); |
1639 | 0 | std::vector<int>().swap( dataY ); |
1640 | 0 | std::vector<double>().swap( coeffs ); |
1641 | 0 | std::vector<double>().swap( scalingVec ); |
1642 | 0 | } |
1643 | |
|
1644 | 0 | for( int i = 1; i <= dataPairs; i++ ) |
1645 | 0 | { |
1646 | 0 | if( dataXd[ i ] < xlow && dataXd[ i ] != 0 ) |
1647 | 0 | { |
1648 | 0 | xlow = dataXd[ i ]; |
1649 | 0 | } |
1650 | 0 | if( dataYd[ i ] < ylow && dataYd[ i ] != 0 ) |
1651 | 0 | { |
1652 | 0 | ylow = dataYd[ i ]; |
1653 | 0 | } |
1654 | 0 | } |
1655 | 0 | const double xscale = ( xlow < .001 && xmax < 1000 ) ? 1 / xlow : ( xmax > 1000 && xlow > .001 ) ? 1 / xmax : 1; |
1656 | 0 | const double yscale = ( ylow < .001 && ymax < 1000 ) ? 1 / ylow : ( ymax > 1000 && ylow > .001 ) ? 1 / ymax : 1; |
1657 | | |
1658 | | // least squares normal equations, solved by Gaussian elimination with partial pivoting (indices from 1) |
1659 | 0 | double a[ MAXORDER + 1 ][ MAXORDER + 1 ] = { { 0 } }; |
1660 | 0 | double B[ MAXORDER + 1 ] = { 0 }, S[ MAXORDER + 1 ] = { 0 }; |
1661 | 0 | double polycoefs[ MAXORDER + 1 ] = { 0 }; |
1662 | 0 | double Y1 = 0; |
1663 | 0 | const double maxVal = std::numeric_limits<double>::max(); |
1664 | 0 | for( int j = 1; j <= dataPairs; j++ ) |
1665 | 0 | { |
1666 | 0 | for( int i = 1; i <= order; i++ ) |
1667 | 0 | { |
1668 | 0 | B[ i ] = B[ i ] + dataYd[ j ] * yscale * ldpow( dataXd[ j ] * xscale, i ); |
1669 | 0 | if( B[ i ] == maxVal ) |
1670 | 0 | { |
1671 | 0 | return false; |
1672 | 0 | } |
1673 | 0 | for( int k = 1; k <= order; k++ ) |
1674 | 0 | { |
1675 | 0 | a[ i ][ k ] = a[ i ][ k ] + ldpow( dataXd[ j ] * xscale, i + k ); |
1676 | 0 | if( a[ i ][ k ] == maxVal ) |
1677 | 0 | { |
1678 | 0 | return false; |
1679 | 0 | } |
1680 | 0 | } |
1681 | 0 | S[ i ] = S[ i ] + ldpow( dataXd[ j ] * xscale, i ); |
1682 | 0 | if( S[ i ] == maxVal ) |
1683 | 0 | { |
1684 | 0 | return false; |
1685 | 0 | } |
1686 | 0 | } |
1687 | 0 | Y1 = Y1 + dataYd[ j ] * yscale; |
1688 | 0 | if( Y1 == maxVal ) |
1689 | 0 | { |
1690 | 0 | return false; |
1691 | 0 | } |
1692 | 0 | } |
1693 | | |
1694 | 0 | for( int i = 1; i <= order; i++ ) |
1695 | 0 | { |
1696 | 0 | for( int j = 1; j <= order; j++ ) |
1697 | 0 | { |
1698 | 0 | a[ i ][ j ] = a[ i ][ j ] - S[ i ] * S[ j ] / (double) dataPairs; |
1699 | 0 | if( a[ i ][ j ] == maxVal ) |
1700 | 0 | { |
1701 | 0 | return false; |
1702 | 0 | } |
1703 | 0 | } |
1704 | 0 | B[ i ] = B[ i ] - Y1 * S[ i ] / (double) dataPairs; |
1705 | 0 | if( B[ i ] == maxVal ) |
1706 | 0 | { |
1707 | 0 | return false; |
1708 | 0 | } |
1709 | 0 | } |
1710 | | |
1711 | 0 | for( int k = 1; k <= order; k++ ) |
1712 | 0 | { |
1713 | 0 | int R = k; |
1714 | 0 | double A1 = 0; |
1715 | 0 | for( int L = k; L <= order; L++ ) |
1716 | 0 | { |
1717 | 0 | const double A2 = std::fabs( a[ L ][ k ] ); |
1718 | 0 | if( A2 > A1 ) |
1719 | 0 | { |
1720 | 0 | A1 = A2; |
1721 | 0 | R = L; |
1722 | 0 | } |
1723 | 0 | } |
1724 | 0 | if( A1 == 0 ) |
1725 | 0 | { |
1726 | 0 | return false; |
1727 | 0 | } |
1728 | 0 | if( R != k ) |
1729 | 0 | { |
1730 | 0 | for( int j = k; j <= order; j++ ) |
1731 | 0 | { |
1732 | 0 | std::swap( a[ R ][ j ], a[ k ][ j ] ); |
1733 | 0 | } |
1734 | 0 | std::swap( B[ R ], B[ k ] ); |
1735 | 0 | } |
1736 | 0 | for( int i = k; i <= order; i++ ) |
1737 | 0 | { |
1738 | 0 | const double m = a[ i ][ k ]; |
1739 | 0 | for( int j = k; j <= order; j++ ) |
1740 | 0 | { |
1741 | 0 | a[ i ][ j ] = i == k ? a[ i ][ j ] / m : a[ i ][ j ] - m * a[ k ][ j ]; |
1742 | 0 | } |
1743 | 0 | B[ i ] = i == k ? B[ i ] / m : B[ i ] - m * B[ k ]; |
1744 | 0 | } |
1745 | 0 | } |
1746 | | |
1747 | 0 | polycoefs[ order ] = B[ order ]; |
1748 | 0 | for( int k = 1; k <= order - 1; k++ ) |
1749 | 0 | { |
1750 | 0 | const int i = order - k; |
1751 | 0 | double S1 = 0; |
1752 | 0 | for( int j = 1; j <= order; j++ ) |
1753 | 0 | { |
1754 | 0 | S1 = S1 + a[ i ][ j ] * polycoefs[ j ]; |
1755 | 0 | if( S1 == maxVal ) |
1756 | 0 | { |
1757 | 0 | return false; |
1758 | 0 | } |
1759 | 0 | } |
1760 | 0 | polycoefs[ i ] = B[ i ] - S1; |
1761 | 0 | } |
1762 | | |
1763 | 0 | double S1 = 0; |
1764 | 0 | for( int i = 1; i <= order; i++ ) |
1765 | 0 | { |
1766 | 0 | S1 = S1 + polycoefs[ i ] * S[ i ] / (double) dataPairs; |
1767 | 0 | if( S1 == maxVal ) |
1768 | 0 | { |
1769 | 0 | return false; |
1770 | 0 | } |
1771 | 0 | } |
1772 | 0 | polycoefs[ 0 ] = Y1 / (double) dataPairs - S1; |
1773 | | |
1774 | | // zero all coefficients smaller than +-1e-11 (avoids -0) |
1775 | 0 | for( int i = 0; i <= order; i++ ) |
1776 | 0 | { |
1777 | 0 | if( std::fabs( polycoefs[ i ] * 100000000000 ) < 1 ) |
1778 | 0 | { |
1779 | 0 | polycoefs[ i ] = 0; |
1780 | 0 | } |
1781 | 0 | } |
1782 | |
|
1783 | 0 | for( int i = 0; i <= order; i++ ) |
1784 | 0 | { |
1785 | 0 | polycoefs[ i ] = ( 1 / yscale ) * polycoefs[ i ] * ldpow( xscale, i ); |
1786 | 0 | coeffs.push_back( polycoefs[ i ] ); |
1787 | 0 | } |
1788 | | |
1789 | | // sample the scaling function from xmin to xmax |
1790 | 0 | for( int i = (int) xmin; i <= (int) xmax; i++ ) |
1791 | 0 | { |
1792 | 0 | double val = coeffs[ 0 ]; |
1793 | 0 | for( size_t j = 1; j < coeffs.size(); j++ ) |
1794 | 0 | { |
1795 | 0 | val += coeffs[ j ] * ldpow( i, (unsigned) j ); |
1796 | 0 | } |
1797 | 0 | scalingVec.push_back( Clip3( 0.0, (double) ( 1 << bitDepth ) - 1, val ) ); |
1798 | 0 | } |
1799 | 0 | scalingVec.push_back( xmax ); |
1800 | 0 | scalingVec.push_back( xmin ); |
1801 | 0 | return true; |
1802 | 0 | } |
1803 | | |
1804 | | // Adds up to four points beyond the lowest and the highest intensity, each a step further and weaker, then drops the |
1805 | | // points outside the usable intensity range. |
1806 | | void FGAnalyzer::extendPoints( std::vector<int>& dataX, std::vector<int>& dataY, int bitDepth ) const |
1807 | 0 | { |
1808 | 0 | const int minInt = bitDepth < 10 ? MIN_INTENSITY >> ( 10 - bitDepth ) : MIN_INTENSITY << ( bitDepth - 10 ); |
1809 | 0 | const int maxInt = bitDepth < 10 ? MAX_INTENSITY >> ( 10 - bitDepth ) : MAX_INTENSITY << ( bitDepth - 10 ); |
1810 | |
|
1811 | 0 | int xmin = dataX[ 0 ], xmax = dataX[ 0 ]; |
1812 | 0 | int ymin = dataY[ 0 ], ymax = dataY[ 0 ]; // strengths at xmin and xmax |
1813 | 0 | for( size_t i = 0; i < dataX.size(); i++ ) |
1814 | 0 | { |
1815 | 0 | if( dataX[ i ] < xmin ) |
1816 | 0 | { |
1817 | 0 | xmin = dataX[ i ]; |
1818 | 0 | ymin = dataY[ i ]; |
1819 | 0 | } |
1820 | 0 | if( dataX[ i ] > xmax ) |
1821 | 0 | { |
1822 | 0 | xmax = dataX[ i ]; |
1823 | 0 | ymax = dataY[ i ]; |
1824 | 0 | } |
1825 | 0 | } |
1826 | |
|
1827 | 0 | int numLeft = MAX_POINTS_TO_EXTEND, numRight = MAX_POINTS_TO_EXTEND; |
1828 | 0 | while( xmin >= POINT_STEP && ymin > m_strengthScale && numLeft > 0 ) |
1829 | 0 | { |
1830 | 0 | xmin -= POINT_STEP; |
1831 | 0 | ymin = m_improved ? ymin : (int) ( ymin / POINT_SCALE ); |
1832 | 0 | dataX.push_back( xmin ); |
1833 | 0 | dataY.push_back( ymin ); |
1834 | 0 | numLeft--; |
1835 | 0 | } |
1836 | 0 | while( xmax + POINT_STEP <= ( 1 << bitDepth ) - 1 && ymax > m_strengthScale && numRight > 0 ) |
1837 | 0 | { |
1838 | 0 | xmax += POINT_STEP; |
1839 | 0 | ymax = m_improved ? ymax : (int) ( ymax / POINT_SCALE ); |
1840 | 0 | dataX.push_back( xmax ); |
1841 | 0 | dataY.push_back( ymax ); |
1842 | 0 | numRight--; |
1843 | 0 | } |
1844 | 0 | for( int i = 0; i < (int) dataX.size(); i++ ) |
1845 | 0 | { |
1846 | 0 | if( dataX[ i ] < minInt || dataX[ i ] > maxInt ) |
1847 | 0 | { |
1848 | 0 | dataX.erase( dataX.begin() + i ); |
1849 | 0 | dataY.erase( dataY.begin() + i ); |
1850 | 0 | i--; |
1851 | 0 | } |
1852 | 0 | } |
1853 | 0 | } |
1854 | | |
1855 | | // Lloyd-Max quantization of the sampled scaling function into numQuantizedLevels levels; quantizedVec receives the |
1856 | | // quantized function for every intensity. |
1857 | | bool FGAnalyzer::lloydMax( std::vector<double>& scalingVec, std::vector<int>& quantizedVec, int numQuantizedLevels, int bitDepth ) const |
1858 | 0 | { |
1859 | 0 | if( scalingVec.size() <= 2 ) |
1860 | 0 | { |
1861 | 0 | return false; // no sampled function (only xmax and xmin) |
1862 | 0 | } |
1863 | | |
1864 | 0 | const int xmin = (int) scalingVec.back(); |
1865 | 0 | scalingVec.pop_back(); |
1866 | 0 | scalingVec.pop_back(); // xmax |
1867 | |
|
1868 | 0 | const double tolerance = 0.0000001; |
1869 | 0 | double ymin = scalingVec[ 0 ], ymax = scalingVec[ 0 ]; |
1870 | 0 | for( double v : scalingVec ) |
1871 | 0 | { |
1872 | 0 | ymin = std::min( ymin, v ); |
1873 | 0 | ymax = std::max( ymax, v ); |
1874 | 0 | } |
1875 | 0 | const double initTraining = ( ymax - ymin ) / numQuantizedLevels; |
1876 | 0 | if( initTraining <= 0 ) |
1877 | 0 | { |
1878 | | // A constant scaling function needs no quantization. VTM returns failure here, so grain of the same strength at |
1879 | | // all measured intensities was never signalled. |
1880 | 0 | quantizedVec.assign( (size_t) 1 << bitDepth, 0 ); |
1881 | 0 | for( size_t i = 0; i < scalingVec.size(); i++ ) |
1882 | 0 | { |
1883 | 0 | quantizedVec[ i + xmin ] = Clip3( 0, ( MAX_STANDARD_DEVIATION * m_strengthScale ) << ( bitDepth - BIT_DEPTH_8 ), (int) ( ymin + .5 ) ); |
1884 | 0 | } |
1885 | 0 | return true; |
1886 | 0 | } |
1887 | | |
1888 | 0 | std::vector<double> codebook( numQuantizedLevels ); |
1889 | 0 | std::vector<double> partition( numQuantizedLevels - 1 ); |
1890 | 0 | const double step = initTraining / 2; |
1891 | 0 | for( int i = 0; i < numQuantizedLevels; i++ ) |
1892 | 0 | { |
1893 | 0 | codebook[ i ] = ymin + i * initTraining + step; |
1894 | 0 | } |
1895 | 0 | for( int i = 0; i < numQuantizedLevels - 1; i++ ) |
1896 | 0 | { |
1897 | 0 | partition[ i ] = ( codebook[ i ] + codebook[ i + 1 ] ) / 2; |
1898 | 0 | } |
1899 | | |
1900 | | // Quantizes the scaling function with the current partition and codebook and returns the mean squared error. |
1901 | 0 | std::vector<double> quantized( scalingVec.size(), 0.0 ); |
1902 | 0 | auto quantize = [ & ]() |
1903 | 0 | { |
1904 | 0 | for( size_t i = 0; i < scalingVec.size(); i++ ) |
1905 | 0 | { |
1906 | 0 | double level = 0.0; |
1907 | 0 | for( size_t j = 0; j < partition.size(); j++ ) |
1908 | 0 | { |
1909 | 0 | level = level + ( scalingVec[ i ] > partition[ j ] ); |
1910 | 0 | } |
1911 | 0 | quantized[ i ] = codebook[ (int) level ]; |
1912 | 0 | } |
1913 | 0 | double distortion = 0.0; |
1914 | 0 | for( size_t i = 0; i < scalingVec.size(); i++ ) |
1915 | 0 | { |
1916 | 0 | distortion += ( scalingVec[ i ] - quantized[ i ] ) * ( scalingVec[ i ] - quantized[ i ] ); |
1917 | 0 | } |
1918 | 0 | return distortion / scalingVec.size(); |
1919 | 0 | }; |
1920 | |
|
1921 | 0 | double distortion = quantize(); |
1922 | 0 | double lastDistortion = 0.0; |
1923 | 0 | const double tolerance2 = std::numeric_limits<double>::epsilon() * ymax; |
1924 | 0 | double relDistortion = distortion > tolerance2 ? std::fabs( distortion - lastDistortion ) / distortion : distortion; |
1925 | |
|
1926 | 0 | while( relDistortion > tolerance && relDistortion > tolerance2 ) |
1927 | 0 | { |
1928 | 0 | for( int i = 0; i < numQuantizedLevels; i++ ) |
1929 | 0 | { |
1930 | 0 | int count = 0; |
1931 | 0 | double sum = 0.0; |
1932 | 0 | for( size_t j = 0; j < quantized.size(); j++ ) |
1933 | 0 | { |
1934 | 0 | if( codebook[ i ] == quantized[ j ] ) |
1935 | 0 | { |
1936 | 0 | count++; |
1937 | 0 | sum += scalingVec[ j ]; |
1938 | 0 | } |
1939 | 0 | } |
1940 | 0 | if( count ) |
1941 | 0 | { |
1942 | 0 | codebook[ i ] = sum / (double) count; |
1943 | 0 | continue; |
1944 | 0 | } |
1945 | | // empty cell: use the mean of the samples in its partition range, or the middle of the range |
1946 | 0 | sum = 0.0; |
1947 | 0 | count = 0; |
1948 | 0 | const bool first = i == 0, last = i == numQuantizedLevels - 1; |
1949 | 0 | for( size_t j = 0; j < quantized.size(); j++ ) |
1950 | 0 | { |
1951 | 0 | const bool inCell = first ? scalingVec[ j ] <= partition[ i ] |
1952 | 0 | : last ? scalingVec[ j ] >= partition[ i - 1 ] |
1953 | 0 | : scalingVec[ j ] >= partition[ i - 1 ] && scalingVec[ j ] <= partition[ i ]; |
1954 | 0 | if( inCell ) |
1955 | 0 | { |
1956 | 0 | count++; |
1957 | 0 | sum += scalingVec[ j ]; |
1958 | 0 | } |
1959 | 0 | } |
1960 | 0 | if( count ) |
1961 | 0 | { |
1962 | 0 | codebook[ i ] = sum / (double) count; |
1963 | 0 | } |
1964 | 0 | else |
1965 | 0 | { |
1966 | 0 | codebook[ i ] = first ? ( partition[ i ] + ymin ) / 2 : last ? ( partition[ i - 1 ] + ymax ) / 2 : ( partition[ i - 1 ] + partition[ i ] ) / 2; |
1967 | 0 | } |
1968 | 0 | } |
1969 | |
|
1970 | 0 | for( int i = 0; i < numQuantizedLevels - 1; i++ ) |
1971 | 0 | { |
1972 | 0 | partition[ i ] = ( codebook[ i ] + codebook[ i + 1 ] ) / 2; |
1973 | 0 | } |
1974 | 0 | std::sort( partition.begin(), partition.end() ); |
1975 | |
|
1976 | 0 | lastDistortion = distortion; |
1977 | 0 | distortion = quantize(); |
1978 | 0 | relDistortion = distortion > tolerance2 ? std::fabs( distortion - lastDistortion ) / distortion : distortion; |
1979 | 0 | } |
1980 | |
|
1981 | 0 | quantizedVec.assign( (size_t) 1 << bitDepth, 0 ); |
1982 | 0 | for( size_t i = 0; i < quantized.size(); i++ ) |
1983 | 0 | { |
1984 | 0 | quantizedVec[ i + xmin ] = Clip3( 0, ( MAX_STANDARD_DEVIATION * m_strengthScale ) << ( bitDepth - BIT_DEPTH_8 ), (int) ( quantized[ i ] + .5 ) ); |
1985 | 0 | } |
1986 | 0 | return true; |
1987 | 0 | } |
1988 | | |
1989 | | // Turns the quantized scaling function into intensity intervals and scaling factors of the SEI message (8-bit |
1990 | | // intensities). The cut-off frequencies are set afterwards. |
1991 | | void FGAnalyzer::setEstimatedParameters( std::vector<int>& quantizedVec, int bitDepth, ComponentID compId ) |
1992 | 0 | { |
1993 | | // strengths below half of VTM's unit count as no grain, as they did in VTM |
1994 | 0 | for( int& q : quantizedVec ) |
1995 | 0 | { |
1996 | 0 | q = q < m_strengthScale / 2 ? 0 : q; |
1997 | 0 | } |
1998 | 0 | if( m_improved ) |
1999 | 0 | { |
2000 | 0 | holdEdgeStrengths( quantizedVec, bitDepth ); |
2001 | 0 | } |
2002 | |
|
2003 | 0 | std::vector<std::vector<int>> parameters( 3 ); // lower bounds, upper bounds, scaling factors |
2004 | | |
2005 | | // intervals of constant value |
2006 | 0 | parameters[ 0 ].push_back( 0 ); |
2007 | 0 | parameters[ 2 ].push_back( quantizedVec[ 0 ] ); |
2008 | 0 | for( size_t i = 0; i + 1 < quantizedVec.size(); i++ ) |
2009 | 0 | { |
2010 | 0 | if( quantizedVec[ i ] != quantizedVec[ i + 1 ] ) |
2011 | 0 | { |
2012 | 0 | parameters[ 0 ].push_back( (int) i + 1 ); |
2013 | 0 | parameters[ 1 ].push_back( (int) i ); |
2014 | 0 | parameters[ 2 ].push_back( quantizedVec[ i + 1 ] ); |
2015 | 0 | } |
2016 | 0 | } |
2017 | 0 | parameters[ 1 ].push_back( ( 1 << bitDepth ) - 1 ); |
2018 | |
|
2019 | 0 | mergeIntervals( parameters, bitDepth ); |
2020 | 0 | scaleDown( parameters, bitDepth ); |
2021 | | |
2022 | | // the scaling to 8 bits can make intervals overlap: make the bounds strictly increasing |
2023 | 0 | std::vector<int> bounds; |
2024 | 0 | for( size_t i = 0; i < parameters[ 2 ].size(); i++ ) |
2025 | 0 | { |
2026 | 0 | bounds.push_back( parameters[ 0 ][ i ] ); |
2027 | 0 | bounds.push_back( parameters[ 1 ][ i ] ); |
2028 | 0 | } |
2029 | 0 | for( size_t i = 0; i + 1 < bounds.size(); i++ ) |
2030 | 0 | { |
2031 | 0 | if( bounds[ i ] >= bounds[ i + 1 ] ) |
2032 | 0 | { |
2033 | 0 | bounds[ i + 1 ] = bounds[ i ] + 1; |
2034 | 0 | } |
2035 | 0 | } |
2036 | 0 | for( size_t i = 0; i < parameters[ 2 ].size(); i++ ) |
2037 | 0 | { |
2038 | 0 | parameters[ 0 ][ i ] = bounds[ 2 * i ]; |
2039 | 0 | parameters[ 1 ][ i ] = bounds[ 2 * i + 1 ]; |
2040 | 0 | } |
2041 | | |
2042 | | // Intervals with scaling factor 0 are not signalled. The component is signalled again as soon as a later picture |
2043 | | // gives a scaling factor above 0 (in VTM a component stays off for the rest of the sequence once all its scaling |
2044 | | // factors were 0, and then the chroma components are no longer analysed either). |
2045 | 0 | SeiFgc::CompModel& cm = m_compModel[ compId ]; |
2046 | 0 | cm.numIntensityIntervals = (uint8_t) ( parameters[ 2 ].size() - std::count( parameters[ 2 ].begin(), parameters[ 2 ].end(), 0 ) ); |
2047 | 0 | cm.presentFlag = cm.numIntensityIntervals > 0; |
2048 | 0 | if( !cm.presentFlag ) |
2049 | 0 | { |
2050 | 0 | m_cutoffPairs[ compId ] = 0; |
2051 | 0 | return; |
2052 | 0 | } |
2053 | 0 | int j = 0; |
2054 | 0 | for( size_t i = 0; i < parameters[ 2 ].size(); i++ ) |
2055 | 0 | { |
2056 | 0 | if( parameters[ 2 ][ i ] != 0 ) |
2057 | 0 | { |
2058 | 0 | auto& iv = cm.intensityValues[ j++ ]; |
2059 | 0 | iv.intensityIntervalLowerBound = (uint8_t) parameters[ 0 ][ i ]; |
2060 | 0 | iv.intensityIntervalUpperBound = (uint8_t) parameters[ 1 ][ i ]; |
2061 | 0 | iv.compModelValue[ 0 ] = parameters[ 2 ][ i ]; |
2062 | 0 | iv.compModelValue[ 1 ] = -1; // cut-off frequencies are estimated later |
2063 | 0 | iv.compModelValue[ 2 ] = -1; |
2064 | 0 | } |
2065 | 0 | } |
2066 | 0 | } |
2067 | | |
2068 | | // The scaling function only covers the intensities with flat blocks, plus a few extension points. VTM lets it fall off |
2069 | | // by a factor of 1.25 per extension point and signals no grain beyond, but film grain goes on there: on Beekeeper the |
2070 | | // darkest intensities have the strongest grain of the scene, and they got half of it. The improved analysis keeps the |
2071 | | // level of the extension points (extendPoints()) and here holds the first and last strength out to the intensity |
2072 | | // limits of the analysis (MIN_INTENSITY and MAX_INTENSITY). |
2073 | | void FGAnalyzer::holdEdgeStrengths( std::vector<int>& quantizedVec, int bitDepth ) const |
2074 | 0 | { |
2075 | 0 | const int minInt = bitDepth < 10 ? MIN_INTENSITY >> ( 10 - bitDepth ) : MIN_INTENSITY << ( bitDepth - 10 ); |
2076 | 0 | const int maxInt = bitDepth < 10 ? MAX_INTENSITY >> ( 10 - bitDepth ) : MAX_INTENSITY << ( bitDepth - 10 ); |
2077 | 0 | int first = -1, last = -1; |
2078 | 0 | for( int i = 0; i < (int) quantizedVec.size(); i++ ) |
2079 | 0 | { |
2080 | 0 | if( quantizedVec[ i ] > 0 ) |
2081 | 0 | { |
2082 | 0 | first = first < 0 ? i : first; |
2083 | 0 | last = i; |
2084 | 0 | } |
2085 | 0 | } |
2086 | 0 | if( first < 0 ) |
2087 | 0 | { |
2088 | 0 | return; // no grain |
2089 | 0 | } |
2090 | 0 | for( int i = minInt; i < first; i++ ) |
2091 | 0 | { |
2092 | 0 | quantizedVec[ i ] = quantizedVec[ first ]; |
2093 | 0 | } |
2094 | 0 | for( int i = last + 1; i <= maxInt && i < (int) quantizedVec.size(); i++ ) |
2095 | 0 | { |
2096 | 0 | quantizedVec[ i ] = quantizedVec[ last ]; |
2097 | 0 | } |
2098 | 0 | } |
2099 | | |
2100 | | // Merges intervals narrower than 2 (at 8 bits) into the neighbour with the closer scaling factor. |
2101 | | void FGAnalyzer::mergeIntervals( std::vector<std::vector<int>>& parameters, int bitDepth ) const |
2102 | 0 | { |
2103 | 0 | for( int i = 0; i < (int) parameters[ 2 ].size(); i++ ) |
2104 | 0 | { |
2105 | 0 | const int width = parameters[ 1 ][ i ] - parameters[ 0 ][ i ]; |
2106 | 0 | if( width >= ( 2 << ( bitDepth - BIT_DEPTH_8 ) ) ) |
2107 | 0 | { |
2108 | 0 | continue; |
2109 | 0 | } |
2110 | 0 | const int last = (int) parameters[ 2 ].size() - 1; |
2111 | 0 | const int diffRight = ( i == last || parameters[ 2 ][ i + 1 ] == 0 ) ? std::numeric_limits<int>::max() : abs( parameters[ 2 ][ i ] - parameters[ 2 ][ i + 1 ] ); |
2112 | 0 | const int diffLeft = ( i == 0 || parameters[ 2 ][ i - 1 ] == 0 ) ? std::numeric_limits<int>::max() : abs( parameters[ 2 ][ i ] - parameters[ 2 ][ i - 1 ] ); |
2113 | | // VTM merges to the right when both neighbours are unusable; at the last interval there is no right neighbour. |
2114 | 0 | const bool mergeLeft = diffLeft < diffRight || ( i == last && i > 0 ); |
2115 | 0 | if( !mergeLeft && i == last ) |
2116 | 0 | { |
2117 | 0 | continue; // a single interval cannot be merged |
2118 | 0 | } |
2119 | 0 | const int n = mergeLeft ? i - 1 : i + 1; |
2120 | 0 | const int otherWidth = parameters[ 1 ][ n ] - parameters[ 0 ][ n ]; |
2121 | 0 | const int newScale = ( otherWidth * parameters[ 2 ][ n ] + width * parameters[ 2 ][ i ] ) / ( otherWidth + width ); |
2122 | 0 | if( mergeLeft ) |
2123 | 0 | { |
2124 | 0 | parameters[ 1 ][ i - 1 ] = parameters[ 1 ][ i ]; |
2125 | 0 | parameters[ 2 ][ i - 1 ] = newScale; |
2126 | 0 | for( auto& p : parameters ) |
2127 | 0 | { |
2128 | 0 | p.erase( p.begin() + i ); |
2129 | 0 | } |
2130 | 0 | } |
2131 | 0 | else |
2132 | 0 | { |
2133 | 0 | parameters[ 1 ][ i ] = parameters[ 1 ][ i + 1 ]; |
2134 | 0 | parameters[ 2 ][ i ] = newScale; |
2135 | 0 | for( auto& p : parameters ) |
2136 | 0 | { |
2137 | 0 | p.erase( p.begin() + i + 1 ); |
2138 | 0 | } |
2139 | 0 | } |
2140 | 0 | i--; |
2141 | 0 | } |
2142 | 0 | } |
2143 | | |
2144 | | // Scales intensities to 8 bits and the scaling factors to the SEI representation. |
2145 | | void FGAnalyzer::scaleDown( std::vector<std::vector<int>>& parameters, int bitDepth ) const |
2146 | 0 | { |
2147 | 0 | for( size_t i = 0; i < parameters[ 2 ].size(); i++ ) |
2148 | 0 | { |
2149 | | // the intensities to 8 bits; the strengths stay as they are, getSignalledParameters() converts them |
2150 | 0 | parameters[ 0 ][ i ] >>= ( bitDepth - BIT_DEPTH_8 ); |
2151 | 0 | parameters[ 1 ][ i ] >>= ( bitDepth - BIT_DEPTH_8 ); |
2152 | 0 | } |
2153 | 0 | } |
2154 | | |
2155 | | // ==================================================================================================================== |
2156 | | // Cut-off frequencies |
2157 | | // ==================================================================================================================== |
2158 | | |
2159 | | // The cut-off pair whose synthesized grain has the spectrum closest to the measured one, for the improved analysis: |
2160 | | // the horizontal and vertical marginals of the mean squared DCT (without the DC coefficient), normalized, against those |
2161 | | // of the synthesis (GRAIN_SPECTRUM_MARGINALS), by the sum of squared differences. AN0237's estimator instead takes, |
2162 | | // per direction, the frequency where the marginal falls closest to a fixed fraction of its low-frequency level, which |
2163 | | // reads about one step too high on grain of the model itself (9/9 for grain synthesized with 8/8, 13/6 for 12/5), and |
2164 | | // the horizontal and vertical cut-offs together decide the shape of both marginals, which a per-direction rule cannot |
2165 | | // take into account. For 4:2:0 chroma the measured spectrum is that of the chroma plane, where the |
2166 | | // synthesis doubles the signalled cut-offs; the result is returned at chroma plane resolution like AN0237's, twice the |
2167 | | // signalled pair, which getSignalledParameters() halves. |
2168 | | void FGAnalyzer::fitCutoffPair( const std::vector<double>& meanSq, bool chroma420, int& horizontal, int& vertical ) const |
2169 | 0 | { |
2170 | 0 | constexpr int N = BLOCK_SIZE; |
2171 | 0 | double measured[ 2 ][ N ] = { { 0 } }; // horizontal and vertical marginal |
2172 | 0 | double total = 0.0; |
2173 | 0 | for( int x = 0; x < N; x++ ) |
2174 | 0 | { |
2175 | 0 | for( int y = 0; y < N; y++ ) |
2176 | 0 | { |
2177 | 0 | const double p = x || y ? meanSq[ x * N + y ] : 0.0; |
2178 | 0 | measured[ 0 ][ x ] += p; |
2179 | 0 | measured[ 1 ][ y ] += p; |
2180 | 0 | total += p; |
2181 | 0 | } |
2182 | 0 | } |
2183 | 0 | horizontal = vertical = -1; |
2184 | 0 | if( total <= 0.0 ) |
2185 | 0 | { |
2186 | 0 | return; |
2187 | 0 | } |
2188 | 0 | for( int k = 0; k < N; k++ ) |
2189 | 0 | { |
2190 | 0 | measured[ 0 ][ k ] /= total; |
2191 | 0 | measured[ 1 ][ k ] /= total; |
2192 | 0 | } |
2193 | |
|
2194 | 0 | const int table = chroma420 ? 1 : 0; |
2195 | 0 | double bestError = std::numeric_limits<double>::max(); |
2196 | 0 | for( int h = 0; h < NUM_CUTOFFS; h++ ) |
2197 | 0 | { |
2198 | 0 | for( int v = 0; v < NUM_CUTOFFS; v++ ) |
2199 | 0 | { |
2200 | 0 | double error = 0.0; |
2201 | 0 | for( int d = 0; d < 2; d++ ) |
2202 | 0 | { |
2203 | 0 | for( int k = 0; k < N; k++ ) |
2204 | 0 | { |
2205 | 0 | const double diff = measured[ d ][ k ] - GRAIN_SPECTRUM_MARGINALS[ table ][ h ][ v ][ d ][ k ]; |
2206 | 0 | error += diff * diff; |
2207 | 0 | } |
2208 | 0 | } |
2209 | 0 | if( error < bestError ) |
2210 | 0 | { |
2211 | 0 | bestError = error; |
2212 | 0 | horizontal = h + 2; |
2213 | 0 | vertical = v + 2; |
2214 | 0 | } |
2215 | 0 | } |
2216 | 0 | } |
2217 | 0 | if( chroma420 ) |
2218 | 0 | { |
2219 | 0 | horizontal *= 2; |
2220 | 0 | vertical *= 2; |
2221 | 0 | } |
2222 | 0 | } |
2223 | | |
2224 | | // Fits the AR filter of a component to the spectrum of its grain, accumulated over all intensities: the Nelder-Mead |
2225 | | // minimum of the L1 distance between the normalized spectra, starting from the previous filter, or from a mild |
2226 | | // low-pass one for the first fit. Filters that are not stable (sum of the absolute tap weights 0.98 or more) are |
2227 | | // excluded. For chroma the model spectrum is the one vvdec synthesizes, the analytic spectrum times |
2228 | | // AR_SYNTHESIS_RATIO_CHROMA, renormalized: the fitted filters then give 0.02-0.06 lower chroma spectrum errors on |
2229 | | // Beekeeper. Luma is fitted to the analytic spectrum: the same correction for luma brought no gain (there the fixed |
2230 | | // pattern of the synthesis is the limit) and made one 4K clip worse. |
2231 | | void FGAnalyzer::fitArFilter( ComponentID compId ) |
2232 | 0 | { |
2233 | 0 | constexpr int N = BLOCK_SIZE; |
2234 | 0 | const SeiFgc::CompModel& cm = m_compModel[ compId ]; |
2235 | 0 | if( !cm.presentFlag ) |
2236 | 0 | { |
2237 | 0 | return; |
2238 | 0 | } |
2239 | 0 | double measured[ N * N ] = { 0 }; // [ vertical * N + horizontal ] |
2240 | 0 | double total = 0.0; |
2241 | 0 | for( const SquaredDctSum& s : m_squaredDctSums[ compId ] ) |
2242 | 0 | { |
2243 | 0 | for( int x = 0; x < N; x++ ) |
2244 | 0 | { |
2245 | 0 | for( int y = 0; y < N; y++ ) |
2246 | 0 | { |
2247 | 0 | measured[ y * N + x ] += x || y ? s[ x * N + y ] : 0.0; |
2248 | 0 | } |
2249 | 0 | } |
2250 | 0 | } |
2251 | 0 | for( double v : measured ) |
2252 | 0 | { |
2253 | 0 | total += v; |
2254 | 0 | } |
2255 | 0 | if( total <= 0.0 ) |
2256 | 0 | { |
2257 | 0 | return; |
2258 | 0 | } |
2259 | 0 | for( double& v : measured ) |
2260 | 0 | { |
2261 | 0 | v /= total; |
2262 | 0 | } |
2263 | |
|
2264 | 0 | static const ArSpectrum arSpectrum; |
2265 | 0 | const float* ratio = compId == COMP_Y ? nullptr : &AR_SYNTHESIS_RATIO_CHROMA[ 0 ][ 0 ]; // [ vertical * N + horizontal ] |
2266 | 0 | double model[ N * N ]; |
2267 | 0 | std::vector<double> scratch( ArSpectrum::SCRATCH_SIZE ); |
2268 | 0 | auto error = [ & ]( const double p[ 4 ] ) |
2269 | 0 | { |
2270 | 0 | const double a1 = p[ 0 ], a3 = p[ 1 ], a5 = p[ 2 ], r = p[ 3 ]; |
2271 | 0 | if( r < 0.0 || std::fabs( a1 ) * ( 1 + r ) + 2 * std::fabs( a3 * r ) + std::fabs( a5 ) * ( 1 + r * r ) >= 0.98 ) |
2272 | 0 | { |
2273 | 0 | return std::numeric_limits<double>::max(); |
2274 | 0 | } |
2275 | 0 | arSpectrum.compute( a1, a3, a5, r, model, scratch ); |
2276 | 0 | double sum = 1.0; // the analytic spectrum is normalized; the one weighted by the ratio is renormalized |
2277 | 0 | if( ratio ) |
2278 | 0 | { |
2279 | 0 | sum = 0.0; |
2280 | 0 | for( int i = 0; i < N * N; i++ ) |
2281 | 0 | { |
2282 | 0 | model[ i ] *= ratio[ i ]; |
2283 | 0 | sum += model[ i ]; |
2284 | 0 | } |
2285 | 0 | } |
2286 | 0 | double e = 0.0; |
2287 | 0 | for( int i = 0; i < N * N; i++ ) |
2288 | 0 | { |
2289 | 0 | e += std::fabs( model[ i ] / sum - measured[ i ] ); |
2290 | 0 | } |
2291 | 0 | return e; |
2292 | 0 | }; |
2293 | |
|
2294 | 0 | ArFilter& f = m_arFilter[ compId ]; |
2295 | 0 | const double starts[ 2 ][ 4 ] = { { f.a1, f.a3, f.a5, f.r }, { 0.3, 0.0, 0.0, 1.0 } }; |
2296 | 0 | double best[ 4 ] = { 0 }, bestError = std::numeric_limits<double>::max(); |
2297 | 0 | for( int s = f.valid ? 0 : 1; s < ( f.valid ? 1 : 2 ); s++ ) |
2298 | 0 | { |
2299 | | // Nelder-Mead on the four parameters |
2300 | 0 | double pts[ 5 ][ 4 ], vals[ 5 ]; |
2301 | 0 | for( int i = 0; i < 5; i++ ) |
2302 | 0 | { |
2303 | 0 | for( int j = 0; j < 4; j++ ) |
2304 | 0 | { |
2305 | 0 | pts[ i ][ j ] = starts[ s ][ j ] + ( i == j + 1 ? 0.15 : 0.0 ); |
2306 | 0 | } |
2307 | 0 | vals[ i ] = error( pts[ i ] ); |
2308 | 0 | } |
2309 | 0 | for( int it = 0; it < AR_FIT_ITERATIONS; it++ ) |
2310 | 0 | { |
2311 | 0 | int order[ 5 ] = { 0, 1, 2, 3, 4 }; |
2312 | 0 | std::sort( order, order + 5, [ & ]( int a, int b ) { return vals[ a ] < vals[ b ]; } ); |
2313 | 0 | const int worst = order[ 4 ], second = order[ 3 ], bestIdx = order[ 0 ]; |
2314 | 0 | if( vals[ worst ] - vals[ bestIdx ] < AR_FIT_TOLERANCE ) |
2315 | 0 | { |
2316 | 0 | break; // converged |
2317 | 0 | } |
2318 | 0 | double c[ 4 ] = { 0 }, xr[ 4 ], xe[ 4 ], xc[ 4 ]; |
2319 | 0 | for( int i = 0; i < 5; i++ ) |
2320 | 0 | { |
2321 | 0 | for( int j = 0; j < 4 && i != worst; j++ ) |
2322 | 0 | { |
2323 | 0 | c[ j ] += pts[ i ][ j ] / 4; |
2324 | 0 | } |
2325 | 0 | } |
2326 | 0 | for( int j = 0; j < 4; j++ ) |
2327 | 0 | { |
2328 | 0 | xr[ j ] = c[ j ] + ( c[ j ] - pts[ worst ][ j ] ); |
2329 | 0 | } |
2330 | 0 | const double fr = error( xr ); |
2331 | 0 | if( fr < vals[ bestIdx ] ) |
2332 | 0 | { |
2333 | 0 | for( int j = 0; j < 4; j++ ) |
2334 | 0 | { |
2335 | 0 | xe[ j ] = c[ j ] + 2 * ( c[ j ] - pts[ worst ][ j ] ); |
2336 | 0 | } |
2337 | 0 | const double fe = error( xe ); |
2338 | 0 | std::copy( fe < fr ? xe : xr, ( fe < fr ? xe : xr ) + 4, pts[ worst ] ); |
2339 | 0 | vals[ worst ] = std::min( fe, fr ); |
2340 | 0 | } |
2341 | 0 | else if( fr < vals[ second ] ) |
2342 | 0 | { |
2343 | 0 | std::copy( xr, xr + 4, pts[ worst ] ); |
2344 | 0 | vals[ worst ] = fr; |
2345 | 0 | } |
2346 | 0 | else |
2347 | 0 | { |
2348 | 0 | for( int j = 0; j < 4; j++ ) |
2349 | 0 | { |
2350 | 0 | xc[ j ] = c[ j ] + 0.5 * ( pts[ worst ][ j ] - c[ j ] ); |
2351 | 0 | } |
2352 | 0 | const double fc = error( xc ); |
2353 | 0 | if( fc < vals[ worst ] ) |
2354 | 0 | { |
2355 | 0 | std::copy( xc, xc + 4, pts[ worst ] ); |
2356 | 0 | vals[ worst ] = fc; |
2357 | 0 | } |
2358 | 0 | else |
2359 | 0 | { |
2360 | 0 | for( int i = 0; i < 5; i++ ) |
2361 | 0 | { |
2362 | 0 | if( i == bestIdx ) |
2363 | 0 | { |
2364 | 0 | continue; |
2365 | 0 | } |
2366 | 0 | for( int j = 0; j < 4; j++ ) |
2367 | 0 | { |
2368 | 0 | pts[ i ][ j ] = pts[ bestIdx ][ j ] + 0.5 * ( pts[ i ][ j ] - pts[ bestIdx ][ j ] ); |
2369 | 0 | } |
2370 | 0 | vals[ i ] = error( pts[ i ] ); |
2371 | 0 | } |
2372 | 0 | } |
2373 | 0 | } |
2374 | 0 | } |
2375 | 0 | for( int i = 0; i < 5; i++ ) |
2376 | 0 | { |
2377 | 0 | if( vals[ i ] < bestError ) |
2378 | 0 | { |
2379 | 0 | bestError = vals[ i ]; |
2380 | 0 | std::copy( pts[ i ], pts[ i ] + 4, best ); |
2381 | 0 | } |
2382 | 0 | } |
2383 | 0 | } |
2384 | 0 | if( bestError == std::numeric_limits<double>::max() ) |
2385 | 0 | { |
2386 | 0 | return; |
2387 | 0 | } |
2388 | 0 | f.a1 = best[ 0 ]; |
2389 | 0 | f.a3 = best[ 1 ]; |
2390 | 0 | f.a5 = best[ 2 ]; |
2391 | 0 | f.r = best[ 3 ]; |
2392 | 0 | f.nonDcShare = arSpectrum.compute( f.a1, f.a3, f.a5, f.r, model, scratch ); |
2393 | 0 | f.valid = true; |
2394 | 0 | } |
2395 | | |
2396 | | // Estimates horizontal and vertical cut-off frequencies per intensity interval from the accumulated squared DCT of the |
2397 | | // grain, then fills missing values, limits the spread and the number of different pairs. |
2398 | | void FGAnalyzer::estimateCutoffFreq( ComponentID compId, int bitDepth ) |
2399 | 0 | { |
2400 | 0 | SeiFgc::CompModel& cm = m_compModel[ compId ]; |
2401 | 0 | if( !cm.presentFlag ) |
2402 | 0 | { |
2403 | 0 | m_cutoffPairs[ compId ] = 0; |
2404 | 0 | return; |
2405 | 0 | } |
2406 | 0 | constexpr int N = BLOCK_SIZE; |
2407 | 0 | const int intervals = cm.numIntensityIntervals; |
2408 | | |
2409 | | // intensity interval of every 8-bit intensity |
2410 | 0 | int intensityInterval[ MAX_NUM_INTENSITIES ]; |
2411 | 0 | std::fill_n( intensityInterval, MAX_NUM_INTENSITIES, -1 ); |
2412 | 0 | for( int i = 0; i < intervals; i++ ) |
2413 | 0 | { |
2414 | 0 | for( int v = cm.intensityValues[ i ].intensityIntervalLowerBound; v <= cm.intensityValues[ i ].intensityIntervalUpperBound; v++ ) |
2415 | 0 | { |
2416 | 0 | intensityInterval[ v ] = i; |
2417 | 0 | } |
2418 | 0 | } |
2419 | |
|
2420 | 0 | const std::vector<SquaredDctSum>& blocks = m_squaredDctSums[ compId ]; |
2421 | 0 | const std::vector<double>& numEl = m_numDctBlocks[ compId ]; |
2422 | 0 | std::vector<double> blocksPerInterval( intervals, 0.0 ); |
2423 | 0 | for( size_t i = 0; i < blocks.size(); i++ ) |
2424 | 0 | { |
2425 | 0 | const int idx = intensityInterval[ i >> ( bitDepth - BIT_DEPTH_8 ) ]; |
2426 | 0 | if( idx != -1 ) |
2427 | 0 | { |
2428 | 0 | blocksPerInterval[ idx ] += numEl[ i ]; |
2429 | 0 | } |
2430 | 0 | } |
2431 | | |
2432 | | // mean squared DCT per interval, and its row and column averages without the DC coefficient |
2433 | 0 | std::vector<std::vector<double>> meanSq( intervals, std::vector<double>( N * N, 0.0 ) ); |
2434 | 0 | std::vector<std::vector<double>> rowMean( intervals, std::vector<double>( N, 0.0 ) ); // per horizontal frequency |
2435 | 0 | std::vector<std::vector<double>> colMean( intervals, std::vector<double>( N, 0.0 ) ); // per vertical frequency |
2436 | 0 | for( int x = 0; x < N; x++ ) |
2437 | 0 | { |
2438 | 0 | for( int y = 0; y < N; y++ ) |
2439 | 0 | { |
2440 | 0 | for( size_t i = 0; i < blocks.size(); i++ ) |
2441 | 0 | { |
2442 | 0 | const int idx = intensityInterval[ i >> ( bitDepth - BIT_DEPTH_8 ) ]; |
2443 | 0 | if( idx != -1 ) |
2444 | 0 | { |
2445 | 0 | meanSq[ idx ][ x * N + y ] += blocks[ i ][ x * N + y ]; |
2446 | 0 | } |
2447 | 0 | } |
2448 | 0 | for( int i = 0; i < intervals; i++ ) |
2449 | 0 | { |
2450 | 0 | if( blocksPerInterval[ i ] >= MIN_BLOCK_WEIGHT ) |
2451 | 0 | { |
2452 | 0 | meanSq[ i ][ x * N + y ] /= blocksPerInterval[ i ]; |
2453 | 0 | rowMean[ i ][ x ] += ( x != 0 || y != 0 ) ? meanSq[ i ][ x * N + y ] : 0.0; |
2454 | 0 | colMean[ i ][ y ] += ( x != 0 || y != 0 ) ? meanSq[ i ][ x * N + y ] : 0.0; |
2455 | 0 | } |
2456 | 0 | } |
2457 | 0 | } |
2458 | 0 | } |
2459 | |
|
2460 | 0 | int avgCutoffHorizontal = 0, avgCutoffVertical = 0; |
2461 | 0 | int numCutoffHorizontal = 0, numCutoffVertical = 0; |
2462 | 0 | cm.numModelValues = 3; // always signal all three values |
2463 | 0 | for( int i = 0; i < intervals; i++ ) |
2464 | 0 | { |
2465 | 0 | auto& iv = cm.intensityValues[ i ]; |
2466 | 0 | if( blocksPerInterval[ i ] < MIN_BLOCK_WEIGHT ) |
2467 | 0 | { |
2468 | 0 | iv.compModelValue[ 1 ] = -1; |
2469 | 0 | iv.compModelValue[ 2 ] = -1; |
2470 | 0 | continue; |
2471 | 0 | } |
2472 | 0 | for( int x = 0; x < N; x++ ) |
2473 | 0 | { |
2474 | 0 | rowMean[ i ][ x ] /= x == 0 ? N - 1 : N; |
2475 | 0 | colMean[ i ][ x ] /= x == 0 ? N - 1 : N; |
2476 | 0 | } |
2477 | | // rowMean is indexed by the horizontal frequency, colMean by the vertical one. VTM takes the vertical cut-off from |
2478 | | // rowMean and the horizontal one from colMean, so its grain comes out rotated by 90 degrees (comp_model_value[1] |
2479 | | // is the horizontal cut-off, for VTM's own synthesis as well). |
2480 | 0 | int cutoffHorizontal = -1, cutoffVertical = -1; |
2481 | 0 | if( m_improved ) |
2482 | 0 | { |
2483 | 0 | fitCutoffPair( meanSq[ i ], compId != COMP_Y && m_chromaFormat == CHROMA_420, cutoffHorizontal, cutoffVertical ); |
2484 | 0 | } |
2485 | 0 | else |
2486 | 0 | { |
2487 | 0 | cutoffHorizontal = cutoffFrequency( rowMean[ i ] ); |
2488 | 0 | cutoffVertical = cutoffFrequency( colMean[ i ] ); |
2489 | 0 | } |
2490 | 0 | const int intervalWidth = iv.intensityIntervalUpperBound - iv.intensityIntervalLowerBound; |
2491 | 0 | if( cutoffHorizontal != -1 ) |
2492 | 0 | { |
2493 | 0 | iv.compModelValue[ 1 ] = cutoffHorizontal; |
2494 | 0 | avgCutoffHorizontal += intervalWidth * cutoffHorizontal; |
2495 | 0 | numCutoffHorizontal += intervalWidth; |
2496 | 0 | } |
2497 | 0 | if( cutoffVertical != -1 ) |
2498 | 0 | { |
2499 | 0 | iv.compModelValue[ 2 ] = cutoffVertical; |
2500 | 0 | avgCutoffVertical += intervalWidth * cutoffVertical; |
2501 | 0 | numCutoffVertical += intervalWidth; |
2502 | 0 | } |
2503 | 0 | } |
2504 | |
|
2505 | 0 | avgCutoffHorizontal = numCutoffHorizontal ? avgCutoffHorizontal / numCutoffHorizontal : -1; |
2506 | 0 | if( numCutoffVertical ) |
2507 | 0 | { |
2508 | 0 | avgCutoffVertical = avgCutoffVertical / numCutoffVertical; |
2509 | 0 | } |
2510 | 0 | else |
2511 | 0 | { |
2512 | 0 | avgCutoffVertical = -1; // VTM master sets avgCutoffHorizontal here, losing the valid horizontal average |
2513 | 0 | } |
2514 | |
|
2515 | 0 | replaceCutoff( compId, avgCutoffHorizontal, avgCutoffVertical ); |
2516 | 0 | limitCutoff( compId, avgCutoffHorizontal, avgCutoffVertical ); |
2517 | 0 | limitCutoffConsecutive( compId ); |
2518 | 0 | if( !cm.presentFlag ) |
2519 | 0 | { |
2520 | 0 | m_cutoffPairs[ compId ] = 0; |
2521 | 0 | return; |
2522 | 0 | } |
2523 | | |
2524 | | // limit the number of different cut-off pairs to what the synthesis supports, shared by the components |
2525 | 0 | std::vector<CutoffPair> pairs( intervals ); |
2526 | 0 | for( int i = 0; i < intervals; i++ ) |
2527 | 0 | { |
2528 | 0 | pairs[ i ].p = { cm.intensityValues[ i ].compModelValue[ 1 ], cm.intensityValues[ i ].compModelValue[ 2 ] }; |
2529 | 0 | pairs[ i ].w = cm.intensityValues[ i ].intensityIntervalUpperBound - cm.intensityValues[ i ].intensityIntervalLowerBound; |
2530 | 0 | } |
2531 | 0 | const int numLumaPairs = MAX_CUTOFF_PAIRS - 2 + !m_doAnalysis[ COMP_Cb ] + !m_doAnalysis[ COMP_Cr ]; |
2532 | 0 | int maxUnique = numLumaPairs; |
2533 | 0 | if( compId == COMP_Cb ) |
2534 | 0 | { |
2535 | 0 | maxUnique = 1 + !m_doAnalysis[ COMP_Cr ] + ( numLumaPairs - m_cutoffPairs[ COMP_Y ] ) / ( m_doAnalysis[ COMP_Cb ] + m_doAnalysis[ COMP_Cr ] ); |
2536 | 0 | } |
2537 | 0 | else if( compId == COMP_Cr ) |
2538 | 0 | { |
2539 | 0 | maxUnique = MAX_CUTOFF_PAIRS - m_cutoffPairs[ COMP_Y ] - m_cutoffPairs[ COMP_Cb ]; |
2540 | 0 | } |
2541 | 0 | m_cutoffPairs[ compId ] = limitCutoffPairs( pairs, maxUnique ); |
2542 | 0 | for( int i = 0; i < intervals; i++ ) |
2543 | 0 | { |
2544 | 0 | cm.intensityValues[ i ].compModelValue[ 1 ] = pairs[ i ].p.first; |
2545 | 0 | cm.intensityValues[ i ].compModelValue[ 2 ] = pairs[ i ].p.second; |
2546 | 0 | } |
2547 | 0 | } |
2548 | | |
2549 | | // Cut-off frequency of a 16-sample row or column average: the smoothed average is compared with a threshold derived |
2550 | | // from its low frequencies; the frequency closest to the threshold is the cut-off. Returns -1 when no cut-off is found. |
2551 | | int FGAnalyzer::cutoffFrequency( const std::vector<double>& mean ) const |
2552 | 0 | { |
2553 | 0 | constexpr int NUM = 13; // cut-off frequencies 2 to 14 |
2554 | 0 | constexpr int a = 6; |
2555 | 0 | constexpr int w[ NUM ] = { 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
2556 | |
|
2557 | 0 | double sum[ NUM ]; |
2558 | 0 | for( int j = 2; j < 15; j++ ) |
2559 | 0 | { |
2560 | 0 | sum[ j - 2 ] = ( TAP_FILTER[ 0 ] * mean[ j - 1 ] + TAP_FILTER[ 1 ] * mean[ j ] + TAP_FILTER[ 2 ] * mean[ j + 1 ] ) / NORM_TAP; |
2561 | 0 | } |
2562 | 0 | double target = 0; |
2563 | 0 | for( int j = 0; j < NUM; j++ ) |
2564 | 0 | { |
2565 | 0 | target += w[ j ] * sum[ j ]; |
2566 | 0 | } |
2567 | 0 | target /= a; |
2568 | |
|
2569 | 0 | double bestDiff = std::numeric_limits<double>::max(); |
2570 | 0 | int bestIdx = -1; |
2571 | 0 | for( int j = 0; j < NUM; j++ ) |
2572 | 0 | { |
2573 | 0 | const double d = std::fabs( sum[ j ] - target ); |
2574 | 0 | if( d < bestDiff || ( d == bestDiff && j > bestIdx ) ) // the larger index when equally close |
2575 | 0 | { |
2576 | 0 | bestDiff = d; |
2577 | 0 | bestIdx = j; |
2578 | 0 | } |
2579 | 0 | } |
2580 | 0 | if( bestIdx == -1 || ( bestIdx == NUM - 1 && target < sum[ NUM - 1 ] && bestDiff > target / 2 ) ) |
2581 | 0 | { |
2582 | 0 | return -1; |
2583 | 0 | } |
2584 | 0 | return Clip3( 2, 14, bestIdx + 2 ); // RDD 5 range |
2585 | 0 | } |
2586 | | |
2587 | | // Replaces missing cut-offs (-1) with the averages; one missing average is replaced by the other. |
2588 | | void FGAnalyzer::replaceCutoff( ComponentID compId, int& replacementH, int& replacementV ) |
2589 | 0 | { |
2590 | 0 | SeiFgc::CompModel& cm = m_compModel[ compId ]; |
2591 | 0 | if( !cm.presentFlag ) |
2592 | 0 | { |
2593 | 0 | return; |
2594 | 0 | } |
2595 | 0 | if( replacementH == -1 && replacementV == -1 ) |
2596 | 0 | { |
2597 | 0 | cm.presentFlag = false; |
2598 | 0 | return; |
2599 | 0 | } |
2600 | 0 | if( replacementH == -1 ) |
2601 | 0 | { |
2602 | 0 | replacementH = replacementV; |
2603 | 0 | } |
2604 | 0 | else if( replacementV == -1 ) |
2605 | 0 | { |
2606 | 0 | replacementV = replacementH; |
2607 | 0 | } |
2608 | 0 | for( int i = 0; i < cm.numIntensityIntervals; i++ ) |
2609 | 0 | { |
2610 | 0 | auto& v = cm.intensityValues[ i ].compModelValue; |
2611 | 0 | if( v[ 1 ] == -1 ) |
2612 | 0 | { |
2613 | 0 | v[ 1 ] = replacementH; |
2614 | 0 | } |
2615 | 0 | if( v[ 2 ] == -1 ) |
2616 | 0 | { |
2617 | 0 | v[ 2 ] = replacementV; |
2618 | 0 | } |
2619 | 0 | } |
2620 | 0 | } |
2621 | | |
2622 | | // Limits every cut-off to the average +-2. |
2623 | | void FGAnalyzer::limitCutoff( ComponentID compId, int avgH, int avgV ) |
2624 | 0 | { |
2625 | 0 | SeiFgc::CompModel& cm = m_compModel[ compId ]; |
2626 | 0 | if( !cm.presentFlag ) |
2627 | 0 | { |
2628 | 0 | return; |
2629 | 0 | } |
2630 | 0 | constexpr int delta = 2; |
2631 | 0 | for( int i = 0; i < cm.numIntensityIntervals; i++ ) |
2632 | 0 | { |
2633 | 0 | auto& v = cm.intensityValues[ i ].compModelValue; |
2634 | 0 | v[ 1 ] = Clip3( avgH - delta, avgH + delta, v[ 1 ] ); |
2635 | 0 | v[ 2 ] = Clip3( avgV - delta, avgV + delta, v[ 2 ] ); |
2636 | 0 | } |
2637 | 0 | } |
2638 | | |
2639 | | // Smooths the cut-offs over consecutive intervals with a [1 2 1] filter ([3 1] at the ends), in place as in VTM. |
2640 | | void FGAnalyzer::limitCutoffConsecutive( ComponentID compId ) |
2641 | 0 | { |
2642 | 0 | SeiFgc::CompModel& cm = m_compModel[ compId ]; |
2643 | 0 | if( !cm.presentFlag ) |
2644 | 0 | { |
2645 | 0 | return; |
2646 | 0 | } |
2647 | 0 | const int n = cm.numIntensityIntervals; |
2648 | 0 | for( int i = 0; i < n; i++ ) |
2649 | 0 | { |
2650 | 0 | for( int k = 1; k <= 2; k++ ) |
2651 | 0 | { |
2652 | 0 | int& cur = cm.intensityValues[ i ].compModelValue[ k ]; |
2653 | 0 | if( i == 0 ) |
2654 | 0 | { |
2655 | 0 | cur = ( 3 * cur + cm.intensityValues[ i + 1 ].compModelValue[ k ] + 2 ) / 4; |
2656 | 0 | } |
2657 | 0 | else if( i == n - 1 ) |
2658 | 0 | { |
2659 | 0 | cur = ( cm.intensityValues[ i - 1 ].compModelValue[ k ] + 3 * cur + 2 ) / 4; |
2660 | 0 | } |
2661 | 0 | else |
2662 | 0 | { |
2663 | 0 | cur = ( cm.intensityValues[ i - 1 ].compModelValue[ k ] + 2 * cur + cm.intensityValues[ i + 1 ].compModelValue[ k ] + 2 ) / 4; |
2664 | 0 | } |
2665 | 0 | } |
2666 | 0 | } |
2667 | 0 | } |
2668 | | |
2669 | | // Reduces the number of different cut-off pairs to maxUnique with a weighted k-medoids clustering (Manhattan distance, |
2670 | | // weights are interval widths) and maps every interval to its medoid. Returns the number of different pairs. |
2671 | | int FGAnalyzer::limitCutoffPairs( std::vector<CutoffPair>& pairs, int maxUnique ) const |
2672 | 0 | { |
2673 | 0 | const int s = (int) pairs.size(); |
2674 | 0 | if( s == 0 ) |
2675 | 0 | { |
2676 | 0 | return 0; |
2677 | 0 | } |
2678 | | |
2679 | | // distinct pairs in order of first appearance, with summed weights and the intervals using them |
2680 | 0 | std::vector<CutoffPair> nodes; |
2681 | 0 | for( int i = 0; i < s; i++ ) |
2682 | 0 | { |
2683 | 0 | auto it = std::find_if( nodes.begin(), nodes.end(), [ & ]( const CutoffPair& n ) { return n.p == pairs[ i ].p; } ); |
2684 | 0 | if( it == nodes.end() ) |
2685 | 0 | { |
2686 | 0 | nodes.push_back( CutoffPair() ); |
2687 | 0 | nodes.back().p = pairs[ i ].p; |
2688 | 0 | nodes.back().w = pairs[ i ].w; |
2689 | 0 | nodes.back().idx.push_back( i ); |
2690 | 0 | } |
2691 | 0 | else |
2692 | 0 | { |
2693 | 0 | it->w += pairs[ i ].w; |
2694 | 0 | it->idx.push_back( i ); |
2695 | 0 | } |
2696 | 0 | } |
2697 | 0 | const int numNodes = (int) nodes.size(); |
2698 | 0 | if( numNodes <= maxUnique ) |
2699 | 0 | { |
2700 | 0 | return numNodes; |
2701 | 0 | } |
2702 | | |
2703 | 0 | std::vector<std::vector<int>> dist( numNodes, std::vector<int>( numNodes, 0 ) ); |
2704 | 0 | for( int u = 0; u < numNodes; u++ ) |
2705 | 0 | { |
2706 | 0 | for( int v = u + 1; v < numNodes; v++ ) |
2707 | 0 | { |
2708 | 0 | dist[ u ][ v ] = dist[ v ][ u ] = abs( nodes[ u ].p.first - nodes[ v ].p.first ) + abs( nodes[ u ].p.second - nodes[ v ].p.second ); |
2709 | 0 | } |
2710 | 0 | } |
2711 | | |
2712 | | // start with the heaviest pair, then add heavy pairs far from the medoids chosen so far |
2713 | 0 | std::vector<int> md; |
2714 | 0 | int first = 0; |
2715 | 0 | for( int u = 1; u < numNodes; u++ ) |
2716 | 0 | { |
2717 | 0 | if( nodes[ u ].w > nodes[ first ].w ) |
2718 | 0 | { |
2719 | 0 | first = u; |
2720 | 0 | } |
2721 | 0 | } |
2722 | 0 | md.push_back( first ); |
2723 | 0 | auto isMedoid = [ & ]( int u ) { return std::find( md.begin(), md.end(), u ) != md.end(); }; |
2724 | 0 | while( (int) md.size() < maxUnique ) |
2725 | 0 | { |
2726 | 0 | long long bestScore = -1; |
2727 | 0 | int bestU = -1; |
2728 | 0 | for( int u = 0; u < numNodes; u++ ) |
2729 | 0 | { |
2730 | 0 | if( isMedoid( u ) ) |
2731 | 0 | { |
2732 | 0 | continue; |
2733 | 0 | } |
2734 | 0 | int dmin = std::numeric_limits<int>::max(); |
2735 | 0 | for( int m : md ) |
2736 | 0 | { |
2737 | 0 | dmin = std::min( dmin, dist[ u ][ m ] ); |
2738 | 0 | } |
2739 | 0 | const long long score = 1LL * nodes[ u ].w * dmin; |
2740 | 0 | if( score > bestScore ) |
2741 | 0 | { |
2742 | 0 | bestScore = score; |
2743 | 0 | bestU = u; |
2744 | 0 | } |
2745 | 0 | } |
2746 | 0 | if( bestU < 0 ) |
2747 | 0 | { |
2748 | 0 | break; |
2749 | 0 | } |
2750 | 0 | md.push_back( bestU ); |
2751 | 0 | } |
2752 | |
|
2753 | 0 | std::vector<int> assignTo( numNodes, -1 ); |
2754 | 0 | auto assignAll = [ & ]() |
2755 | 0 | { |
2756 | 0 | for( int u = 0; u < numNodes; u++ ) |
2757 | 0 | { |
2758 | 0 | int bestM = md.front(); |
2759 | 0 | int bestD = dist[ u ][ bestM ]; |
2760 | 0 | for( int m : md ) |
2761 | 0 | { |
2762 | 0 | const int d = dist[ u ][ m ]; |
2763 | 0 | if( d < bestD || ( d == bestD && m < bestM ) ) |
2764 | 0 | { |
2765 | 0 | bestD = d; |
2766 | 0 | bestM = m; |
2767 | 0 | } |
2768 | 0 | } |
2769 | 0 | assignTo[ u ] = bestM; |
2770 | 0 | } |
2771 | 0 | }; |
2772 | 0 | assignAll(); |
2773 | | |
2774 | | // swap a medoid with a non-medoid while that lowers the total weighted distance (at most 15 times) |
2775 | 0 | bool improved = true; |
2776 | 0 | for( int iter = 0; improved && iter < 15; iter++ ) |
2777 | 0 | { |
2778 | 0 | improved = false; |
2779 | 0 | long long bestDelta = 0; |
2780 | 0 | int bestOut = -1, bestIn = -1; |
2781 | 0 | std::vector<char> isM( numNodes, 0 ); |
2782 | 0 | for( int m : md ) |
2783 | 0 | { |
2784 | 0 | isM[ m ] = 1; |
2785 | 0 | } |
2786 | 0 | for( int mOut : md ) |
2787 | 0 | { |
2788 | 0 | for( int o = 0; o < numNodes; o++ ) |
2789 | 0 | { |
2790 | 0 | if( isM[ o ] ) |
2791 | 0 | { |
2792 | 0 | continue; |
2793 | 0 | } |
2794 | 0 | long long delta = 0; |
2795 | 0 | for( int u = 0; u < numNodes; u++ ) |
2796 | 0 | { |
2797 | 0 | const int duOld = dist[ u ][ assignTo[ u ] ]; |
2798 | 0 | const int dToO = dist[ u ][ o ]; |
2799 | 0 | int duNew; |
2800 | 0 | if( assignTo[ u ] == mOut ) |
2801 | 0 | { |
2802 | 0 | int dAlt = std::numeric_limits<int>::max(); |
2803 | 0 | for( int m : md ) |
2804 | 0 | { |
2805 | 0 | if( m != mOut ) |
2806 | 0 | { |
2807 | 0 | dAlt = std::min( dAlt, dist[ u ][ m ] ); |
2808 | 0 | } |
2809 | 0 | } |
2810 | 0 | duNew = std::min( dAlt, dToO ); |
2811 | 0 | } |
2812 | 0 | else |
2813 | 0 | { |
2814 | 0 | duNew = std::min( duOld, dToO ); |
2815 | 0 | } |
2816 | 0 | delta += 1LL * nodes[ u ].w * ( duNew - duOld ); |
2817 | 0 | } |
2818 | 0 | if( delta < bestDelta ) |
2819 | 0 | { |
2820 | 0 | bestDelta = delta; |
2821 | 0 | bestOut = mOut; |
2822 | 0 | bestIn = o; |
2823 | 0 | } |
2824 | 0 | } |
2825 | 0 | } |
2826 | 0 | if( bestDelta < 0 ) |
2827 | 0 | { |
2828 | 0 | *std::find( md.begin(), md.end(), bestOut ) = bestIn; |
2829 | 0 | assignAll(); |
2830 | 0 | improved = true; |
2831 | 0 | } |
2832 | 0 | } |
2833 | |
|
2834 | 0 | for( int u = 0; u < numNodes; u++ ) |
2835 | 0 | { |
2836 | 0 | for( int idx : nodes[ u ].idx ) |
2837 | 0 | { |
2838 | 0 | pairs[ idx ].p = nodes[ assignTo[ u ] ].p; |
2839 | 0 | } |
2840 | 0 | } |
2841 | 0 | return (int) md.size(); |
2842 | 0 | } |
2843 | | |
2844 | | } // namespace vvenc |