/work/vvenc/source/Lib/EncoderLib/SEIFilmGrainAnalyzer.h
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 | | /* Film grain analysis (--fga 1, 2 and 3): a port of the film grain analysis of the VTM reference software (VTM master, |
44 | | * including JVET-AL0282 and JVET-AN0237 by InterDigital), with improvements (--fga 2) and the auto-regressive grain |
45 | | * model (--fga 3, which signals an AR filter fitted to the grain spectrum instead of cut-off frequencies). |
46 | | * |
47 | | * For every analysed picture, the grain is estimated as the difference between the original and a temporally filtered |
48 | | * (denoised) version of it, as in VTM (--fga 1). With the improvements (--fga 2) it is estimated from the temporal |
49 | | * residual instead, twice the picture minus its motion compensated neighbours (FgaTemporalResidual), which also holds |
50 | | * the part of the grain that the filter keeps. |
51 | | * Flat blocks are found with a multi-scale Canny edge detector and morphological dilation on the denoised picture. On |
52 | | * 16x16 flat blocks the analysis collects |
53 | | * - the block mean and the grain strength (twice the standard deviation of the grain), from which a polynomial |
54 | | * scaling function is fitted and quantized with Lloyd-Max into intensity intervals and scaling factors, and |
55 | | * - the squared DCT of the grain, per block mean, from which horizontal and vertical cut-off frequencies are |
56 | | * estimated per intensity interval. |
57 | | * Both are accumulated over the analysed pictures with exponentially decreasing weights, so that the estimate follows |
58 | | * the current scene. |
59 | | */ |
60 | | |
61 | | #pragma once |
62 | | |
63 | | #include "CommonLib/CommonDef.h" |
64 | | #include "CommonLib/Unit.h" |
65 | | #include "CommonLib/SEI.h" |
66 | | |
67 | | #include <utility> |
68 | | #include <vector> |
69 | | |
70 | | namespace vvenc { |
71 | | |
72 | | // Row and block kernels of the analysis, with SIMD versions in CommonLib/x86/FGAX86.h and |
73 | | // CommonLib/arm/neon/FGA_neon.cpp. A row is processed from x = 0 to width - 1; neighbours outside the picture repeat |
74 | | // the border sample. |
75 | | struct FgaRowKernels |
76 | | { |
77 | | // Sobel gradients of the row "cur": magnitude, (|gx| + |gy|) / 2 limited to maxVal, and edge direction (0, 45, 90 |
78 | | // or 135 degrees). |
79 | | void ( *sobel ) ( const Pel* above, const Pel* cur, const Pel* below, int width, Pel maxVal, Pel* mag, Pel* dir ); |
80 | | // Non-maximum suppression: the magnitude where it is not smaller than both neighbours along the direction, else 0. |
81 | | void ( *nonMax ) ( const Pel* magAbove, const Pel* mag, const Pel* magBelow, const Pel* dir, int width, Pel* dst ); |
82 | | // 3-tap maximum (dilate) or minimum (erode) along a row, and over three rows. |
83 | | void ( *morphRow )( const Pel* src, int width, bool dilate, Pel* dst ); |
84 | | void ( *morphCol )( const Pel* above, const Pel* cur, const Pel* below, int width, bool dilate, Pel* dst ); |
85 | | // Largest of n non-negative samples. |
86 | | Pel ( *rowMax ) ( const Pel* src, int n ); |
87 | | // Double threshold: strong above high, weak above low, else 0. |
88 | | void ( *classify )( Pel* row, int n, Pel low, Pel high, Pel weak, Pel strong ); |
89 | | // Positions x of the samples equal to value, in increasing order; returns their number. |
90 | | int ( *findEqual )( const Pel* row, int n, Pel value, int* positions ); |
91 | | |
92 | | // Block kernels, on 16x16 blocks. |
93 | | bool ( *isZeroBlock ) ( const Pel* src, int stride ); |
94 | | void ( *blockSums ) ( const Pel* src, int stride, int64_t& sum, int64_t& sumSq ); |
95 | | // 16x16 DCT-II of VVC (rounding shift 8 after each stage, the second stage clipped to +-maxRange); the squared |
96 | | // coefficients are added to sum[ horizontal * 16 + vertical ]. |
97 | | void ( *squaredDct16 )( const Pel* src, int stride, int maxRange, double* sum ); |
98 | | |
99 | | // Temporal residual kernels (FgaTemporalResidual). |
100 | | // 4x downsampling of n samples: dst[ x ] = ( sum of the 4x4 samples from src[ 4 x ] on + 8 ) >> 4. |
101 | | void ( *downsample4 ) ( const Pel* src, int stride, int n, Pel* dst ); |
102 | | // Sums of absolute differences of n 4x4 blocks next to each other: sad[ b ] of the blocks at x = 4 b. |
103 | | void ( *sad4x4Row ) ( const Pel* cur, int curStride, const Pel* ref, int refStride, int n, int* sad ); |
104 | | // Temporal residual of n samples: 2 cur - prev - next, or cur - prev when next is null. |
105 | | void ( *residualRow ) ( const Pel* cur, const Pel* prev, const Pel* next, int n, Pel* dst ); |
106 | | }; |
107 | | |
108 | | // Scalar row kernels for x = xStart to xEnd - 1; the SIMD kernels use them at the picture borders. |
109 | | void fgaSobelCore ( const Pel* above, const Pel* cur, const Pel* below, int width, int xStart, int xEnd, Pel maxVal, Pel* mag, Pel* dir ); |
110 | | void fgaNonMaxCore ( const Pel* magAbove, const Pel* mag, const Pel* magBelow, const Pel* dir, int width, int xStart, int xEnd, Pel* dst ); |
111 | | void fgaMorphRowCore( const Pel* src, int width, int xStart, int xEnd, bool dilate, Pel* dst ); |
112 | | void fgaMorphColCore( const Pel* above, const Pel* cur, const Pel* below, int xStart, int xEnd, bool dilate, Pel* dst ); |
113 | | Pel fgaRowMaxCore ( const Pel* src, int n ); |
114 | | int fgaFindEqualCore( const Pel* row, int n, Pel value, int* positions ); |
115 | | void fgaClassifyCore( Pel* row, int n, Pel low, Pel high, Pel weak, Pel strong ); |
116 | | bool fgaIsZeroBlockCore ( const Pel* src, int stride ); |
117 | | void fgaBlockSumsCore ( const Pel* src, int stride, int64_t& sum, int64_t& sumSq ); |
118 | | void fgaSquaredDct16Core( const Pel* src, int stride, int maxRange, double* sum ); |
119 | | void fgaDownsample4Core ( const Pel* src, int stride, int n, Pel* dst ); |
120 | | void fgaSad4x4RowCore ( const Pel* cur, int curStride, const Pel* ref, int refStride, int n, int* sad ); |
121 | | void fgaResidualRowCore ( const Pel* cur, const Pel* prev, const Pel* next, int n, Pel* dst ); |
122 | | |
123 | | class FGAnalyzer |
124 | | { |
125 | | public: |
126 | | FGAnalyzer(); |
127 | | |
128 | | // improved: the analysis with the improvements beyond VTM (--fga 2), else VTM's analysis with its bugs fixed |
129 | | // (--fga 1). arModel: the improved analysis with the auto-regressive grain model (--fga 3). See m_improved. |
130 | | void init( int width, int height, ChromaFormat chromaFormat, const int bitDepths[ MAX_NUM_CH ], const bool doAnalysis[ MAX_NUM_COMP ], bool improved, bool arModel = false ); |
131 | | |
132 | | // Analyses one picture: orig is the original, denoised its temporally filtered version (same size). residual is the |
133 | | // temporal residual of FgaTemporalResidual, whose variance is residualWeight times the grain variance; without it |
134 | | // the grain is estimated as denoised minus orig. With sceneCut the statistics of the previous pictures are dropped |
135 | | // first, as they describe another scene. |
136 | | void estimateGrainParameters( const CPelUnitBuf& orig, const CPelUnitBuf& denoised, const CPelUnitBuf* residual = nullptr, int residualWeight = 0, |
137 | | bool sceneCut = false ); |
138 | | |
139 | | // Sets the row kernels: the scalar ones, then the SIMD ones the CPU supports. |
140 | | static void initKernels( FgaRowKernels& k ); |
141 | | |
142 | | // The parameters to signal in the FGC SEI, with the grain strength set to strength percent of the analysed grain: |
143 | | // the log2 scale factor and the component models, with scaling factors calibrated against the synthesis. |
144 | | void getSignalledParameters( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const; |
145 | | |
146 | | // A plane of samples with its own storage; the analysis works on whole planes. |
147 | | struct Plane |
148 | | { |
149 | | int width = 0; |
150 | | int height = 0; |
151 | | std::vector<Pel> samples; |
152 | | |
153 | | // Sets the size; the storage only grows, so planes reused for smaller sizes are not cleared again. |
154 | 0 | void resize( int w, int h ) { width = w; height = h; if( samples.size() < (size_t) w * h ) samples.resize( (size_t) w * h ); } |
155 | 0 | Pel* row ( int y ) { return samples.data() + (size_t) y * width; } |
156 | 0 | const Pel* row( int y ) const { return samples.data() + (size_t) y * width; } |
157 | 0 | Pel at ( int x, int y ) const { return samples[ (size_t) y * width + x ]; } |
158 | | // Sample at (x, y) with the coordinates clamped to the plane, as on a border-extended buffer. |
159 | | Pel clampedAt( int x, int y ) const; |
160 | | }; |
161 | | |
162 | | private: |
163 | | static constexpr int BLOCK_SIZE = 16; // analysis block size, for scaling function and cut-off frequencies |
164 | | // Weight of the data accumulated so far when a picture is added. VTM sums the data of all pictures without limit, |
165 | | // so a new scene hardly changes the estimate; halving the old data lets it follow the scene within a few analyses. |
166 | | static constexpr double ACCUMULATION_DECAY = 0.5; |
167 | | |
168 | | // Squared DCT coefficients of the grain, summed over the blocks with one block mean. |
169 | | typedef std::vector<double> SquaredDctSum; // BLOCK_SIZE * BLOCK_SIZE entries, [horizontal][vertical] |
170 | | |
171 | | struct CutoffPair |
172 | | { |
173 | | std::pair<int, int> p; // horizontal and vertical cut-off |
174 | | int w = 0; // weight (interval width) |
175 | | std::vector<int> idx; // intensity intervals having this pair |
176 | | }; |
177 | | |
178 | | int m_width = 0; |
179 | | int m_height = 0; |
180 | | ChromaFormat m_chromaFormat = CHROMA_420; |
181 | | int m_bitDepths[ MAX_NUM_CH ] = { 10, 10 }; |
182 | | bool m_doAnalysis[ MAX_NUM_COMP ] = { false, false, false }; |
183 | | |
184 | | // The analysed parameters: intensities at 8 bits, compModelValue[ 0 ] the grain strength (m_strengthScale times twice |
185 | | // the standard deviation of the grain on 16x16 blocks, at the component's bit depth), cut-off frequencies at the |
186 | | // component's resolution. getSignalledParameters() converts them for the synthesis. |
187 | | SeiFgc::CompModel m_compModel[ MAX_NUM_COMP ]; |
188 | | int m_cutoffPairs[ MAX_NUM_COMP ] = { 0, 0, 0 }; |
189 | | int m_failedFits [ MAX_NUM_COMP ] = { 0, 0, 0 }; // analyses in a row whose scaling function fit failed |
190 | | |
191 | | // With the improvements beyond VTM (--fga 2): the grain is measured from the temporal residual when MCTF provides |
192 | | // it, the strengths are kept in finer units, the scaling function fit works with few intensity intervals (chroma), |
193 | | // the scaling factors are calibrated against the synthesis, and the statistics follow grain that changes or |
194 | | // disappears. Without them (--fga 1) the analysis is VTM's, with its bugs fixed. |
195 | | bool m_improved = true; |
196 | | int m_strengthScale = 1; // units of the strengths: twice the standard deviation / m_strengthScale |
197 | | |
198 | | // The auto-regressive grain model (--fga 3, FGC model_id 1): instead of cut-off frequencies, every component |
199 | | // signals the filter of vvdec's SEI AR mode, x = a1 * left + a1 * r * top + a3 * r * ( top-left + top-right ) |
200 | | // + a5 * left-left + a5 * r^2 * top-top + noise, fitted to the measured grain spectrum. One filter per component, |
201 | | // the same for all intensity intervals, so that the synthesis makes one pattern per component. |
202 | | struct ArFilter |
203 | | { |
204 | | double a1 = 0.0, a3 = 0.0, a5 = 0.0, r = 1.0; |
205 | | double nonDcShare = 1.0; // share of the grain variance outside the DC coefficient of 16x16 blocks |
206 | | bool valid = false; |
207 | | }; |
208 | | bool m_arModel = false; |
209 | | ArFilter m_arFilter[ MAX_NUM_COMP ]; |
210 | | |
211 | | // Data accumulated over the analysed pictures, weighted (see ACCUMULATION_DECAY). |
212 | | std::vector<SquaredDctSum> m_squaredDctSums[ MAX_NUM_COMP ]; // per block mean |
213 | | std::vector<double> m_numDctBlocks [ MAX_NUM_COMP ]; // per block mean |
214 | | std::vector<double> m_storedMeanIntensity [ MAX_NUM_COMP ]; |
215 | | std::vector<double> m_storedStrengthIntensity[ MAX_NUM_COMP ]; |
216 | | std::vector<double> m_storedElementsPerInterval[ MAX_NUM_COMP ]; |
217 | | |
218 | | // Planes of the picture being analysed. |
219 | | Plane m_denoised[ MAX_NUM_COMP ]; |
220 | | Plane m_grain [ MAX_NUM_COMP ]; // temporal residual, or denoised minus original |
221 | | int m_grainWeight = 1; // variance of m_grain relative to the grain variance |
222 | | Plane m_mask [ MAX_NUM_COMP ]; // non-zero where the picture is not flat |
223 | | |
224 | | // Scratch planes and lists of the mask search, kept to avoid allocating and clearing them for every picture. |
225 | | struct Scratch |
226 | | { |
227 | | Plane magnitude, direction, morph, half, quarter, lowMask; |
228 | | std::vector<int> positions, weakX, weakY, columnStart; |
229 | | std::vector<Pel> wide, top, bottom, left, right; |
230 | | }; |
231 | | Scratch m_scratch; |
232 | | |
233 | | // flat area mask |
234 | | void resetStatistics ( int c ); |
235 | | void findMask ( ComponentID compId ); |
236 | | void maskForScale ( const Plane& picture, Plane& mask, int bitDepth, int numDilations ); |
237 | | |
238 | | // parameter estimation |
239 | | void estimateComponent ( ComponentID compId ); |
240 | | void estimateScalingFactors( std::vector<int>& dataX, std::vector<int>& dataY, int bitDepth, ComponentID compId ); |
241 | | bool fitFunction ( std::vector<int>& dataX, std::vector<int>& dataY, std::vector<double>& coeffs, std::vector<double>& scalingVec, |
242 | | int order, int bitDepth, bool secondPass, ComponentID compId ); |
243 | | void extendPoints ( std::vector<int>& dataX, std::vector<int>& dataY, int bitDepth ) const; |
244 | | bool lloydMax ( std::vector<double>& scalingVec, std::vector<int>& quantizedVec, int numQuantizedLevels, int bitDepth ) const; |
245 | | void setEstimatedParameters( std::vector<int>& quantizedVec, int bitDepth, ComponentID compId ); |
246 | | void holdEdgeStrengths ( std::vector<int>& quantizedVec, int bitDepth ) const; |
247 | | void mergeIntervals ( std::vector<std::vector<int>>& parameters, int bitDepth ) const; |
248 | | void scaleDown ( std::vector<std::vector<int>>& parameters, int bitDepth ) const; |
249 | | |
250 | | void getVtmParameters ( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const; |
251 | | void getArParameters ( double strength, int& log2ScaleFactor, SeiFgc::CompModel compModel[ MAX_NUM_COMP ] ) const; |
252 | | void fitArFilter ( ComponentID compId ); |
253 | | |
254 | | void estimateCutoffFreq ( ComponentID compId, int bitDepth ); |
255 | | int cutoffFrequency ( const std::vector<double>& mean ) const; |
256 | | void fitCutoffPair ( const std::vector<double>& meanSq, bool chroma420, int& horizontal, int& vertical ) const; |
257 | | void replaceCutoff ( ComponentID compId, int& replacementH, int& replacementV ); |
258 | | void limitCutoff ( ComponentID compId, int avgH, int avgV ); |
259 | | void limitCutoffConsecutive( ComponentID compId ); |
260 | | int limitCutoffPairs ( std::vector<CutoffPair>& pairs, int maxUnique ) const; |
261 | | |
262 | | FgaRowKernels m_kernels; |
263 | | |
264 | | #if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_X86 ) |
265 | | static void initFGAnalyzerX86( FgaRowKernels& k ); |
266 | | template <x86_simd::X86_VEXT vext> |
267 | | static void _initFGAnalyzerX86( FgaRowKernels& k ); |
268 | | #endif |
269 | | #if ENABLE_SIMD_OPT_FGA && defined( TARGET_SIMD_ARM ) |
270 | | static void initFGAnalyzerARM( FgaRowKernels& k ); |
271 | | template <arm_simd::ARM_VEXT vext> |
272 | | static void _initFGAnalyzerARM( FgaRowKernels& k ); |
273 | | #endif |
274 | | }; |
275 | | |
276 | | // Temporal residual of the film grain for the improved analysis (--fga 2). For grain that is independent from picture |
277 | | // to picture, the residual e = 2 O - P - N of a picture O and its motion compensated neighbours P and N has six times |
278 | | // the grain variance and the spectrum of the grain, whatever the denoiser would have kept of it. MCTF computes it for |
279 | | // the analysed pictures, while their neighbours are available. |
280 | | class FgaTemporalResidual |
281 | | { |
282 | | public: |
283 | | FgaTemporalResidual(); |
284 | | |
285 | | // Computes the residual of cur from the previous and next picture (null when missing) into residual (created here). |
286 | | // The motion is searched per 16x16 block on 4x downsampled luma, with a bias towards zero motion, so that the vectors |
287 | | // do not follow the grain in flat areas. A neighbour that matches much worse than the other (a scene cut) is left |
288 | | // out. Returns the variance of the residual relative to the grain variance: 6 with both neighbours, 2 with one, and |
289 | | // 0, with no residual made, without neighbours or for pictures smaller than one block. |
290 | | int compute( const CPelUnitBuf& cur, const CPelUnitBuf* prev, const CPelUnitBuf* next, PelStorage& residual ) const; |
291 | | |
292 | | private: |
293 | | // Block motion in samples of the 4x downsampled pictures, per 16x16 block of the full picture. |
294 | | struct Motion |
295 | | { |
296 | | std::vector<int8_t> dx, dy; |
297 | | double meanSad = 0.0; // mean absolute difference per downsampled sample, at the chosen vectors |
298 | | }; |
299 | | |
300 | | void downsample( const CPelBuf& src, FGAnalyzer::Plane& dst ) const; |
301 | | void search ( const FGAnalyzer::Plane& cur, const FGAnalyzer::Plane& ref, int numBlocksX, int numBlocksY, Motion& motion ) const; |
302 | | |
303 | | FgaRowKernels m_kernels; |
304 | | }; |
305 | | |
306 | | } // namespace vvenc |