Coverage Report

Created: 2026-05-16 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/svt-av1/Source/Lib/Codec/ransac.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2016, Alliance for Open Media. All rights reserved
3
 *
4
 * This source code is subject to the terms of the BSD 2 Clause License and
5
 * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
6
 * was not distributed with this source code in the LICENSE file, you can
7
 * obtain it at https://www.aomedia.org/license/software-license. If the Alliance for Open
8
 * Media Patent License 1.0 was not distributed with this source code in the
9
 * PATENTS file, you can obtain it at https://www.aomedia.org/license/patent-license.
10
 */
11
#include <memory.h>
12
#include <math.h>
13
#include <stdlib.h>
14
#include <assert.h>
15
16
#include "ransac.h"
17
#include "mathutils.h"
18
#include "random.h"
19
#include "common_dsp_rtcd.h"
20
#include "utility.h"
21
22
#define MAX_MINPTS 4
23
#define MAX_DEGENERATE_ITER 10
24
0
#define MINPTS_MULTIPLIER 5
25
26
0
#define INLIER_THRESHOLD_SQUARED 1.5625 /*(1.25 * 1.25)*/
27
28
// Number of initial models to generate
29
0
#define NUM_TRIALS 20
30
31
// Number of times to refine the best model found
32
0
#define NUM_REFINES 5
33
#define MIN_TRIALS 20
34
35
////////////////////////////////////////////////////////////////////////////////
36
// ransac
37
38
// Return -1 if 'a' is a better motion, 1 if 'b' is better, 0 otherwise.
39
static int compare_motions(const void* arg_a, const void* arg_b) {
40
    const RANSAC_MOTION* motion_a = (RANSAC_MOTION*)arg_a;
41
    const RANSAC_MOTION* motion_b = (RANSAC_MOTION*)arg_b;
42
43
    if (motion_a->num_inliers > motion_b->num_inliers) {
44
        return -1;
45
    }
46
    if (motion_a->num_inliers < motion_b->num_inliers) {
47
        return 1;
48
    }
49
    if (motion_a->sse < motion_b->sse) {
50
        return -1;
51
    }
52
    if (motion_a->sse > motion_b->sse) {
53
        return 1;
54
    }
55
    return 0;
56
}
57
58
static int is_better_motion(const RANSAC_MOTION* motion_a, const RANSAC_MOTION* motion_b) {
59
    return compare_motions(motion_a, motion_b) < 0;
60
}
61
62
0
static void score_translation(const double* mat, const Correspondence* points, int num_points, RANSAC_MOTION* model) {
63
0
    model->num_inliers = 0;
64
0
    model->sse         = 0.0;
65
66
0
    for (int i = 0; i < num_points; ++i) {
67
0
        const double x1 = points[i].x;
68
0
        const double y1 = points[i].y;
69
0
        const double x2 = points[i].rx;
70
0
        const double y2 = points[i].ry;
71
72
0
        const double proj_x = x1 + mat[0];
73
0
        const double proj_y = y1 + mat[1];
74
75
0
        const double dx  = proj_x - x2;
76
0
        const double dy  = proj_y - y2;
77
0
        const double sse = dx * dx + dy * dy;
78
79
0
        if (sse < INLIER_THRESHOLD_SQUARED) {
80
0
            model->inlier_indices[model->num_inliers++] = i;
81
0
            model->sse += sse;
82
0
        }
83
0
    }
84
0
}
85
86
0
static void score_affine(const double* mat, const Correspondence* points, int num_points, RANSAC_MOTION* model) {
87
0
    model->num_inliers = 0;
88
0
    model->sse         = 0.0;
89
90
0
    for (int i = 0; i < num_points; ++i) {
91
0
        const double x1 = points[i].x;
92
0
        const double y1 = points[i].y;
93
0
        const double x2 = points[i].rx;
94
0
        const double y2 = points[i].ry;
95
96
0
        const double proj_x = mat[2] * x1 + mat[3] * y1 + mat[0];
97
0
        const double proj_y = mat[4] * x1 + mat[5] * y1 + mat[1];
98
99
0
        const double dx  = proj_x - x2;
100
0
        const double dy  = proj_y - y2;
101
0
        const double sse = dx * dx + dy * dy;
102
103
0
        if (sse < INLIER_THRESHOLD_SQUARED) {
104
0
            model->inlier_indices[model->num_inliers++] = i;
105
0
            model->sse += sse;
106
0
        }
107
0
    }
108
0
}
109
110
static bool find_translation(const Correspondence* points, const int* indices, int num_indices, double* params) {
111
    double sumx = 0;
112
    double sumy = 0;
113
114
    for (int i = 0; i < num_indices; ++i) {
115
        int          index = indices[i];
116
        const double sx    = points[index].x;
117
        const double sy    = points[index].y;
118
        const double dx    = points[index].rx;
119
        const double dy    = points[index].ry;
120
121
        sumx += dx - sx;
122
        sumy += dy - sy;
123
    }
124
125
    params[0] = sumx / num_indices;
126
    params[1] = sumy / num_indices;
127
    params[2] = 1;
128
    params[3] = 0;
129
    params[4] = 0;
130
    params[5] = 1;
131
    return true;
132
}
133
134
static bool find_rotzoom(const Correspondence* points, const int* indices, int num_indices, double* params) {
135
    const int n = 4; // Size of least-squares problem
136
    double    mat[4 * 4]; // Accumulator for A'A
137
    double    y[4]; // Accumulator for A'b
138
    double    a[4]; // Single row of A
139
140
    least_squares_init(mat, y, n);
141
    for (int i = 0; i < num_indices; ++i) {
142
        int          index = indices[i];
143
        const double sx    = points[index].x;
144
        const double sy    = points[index].y;
145
        const double dx    = points[index].rx;
146
        const double dy    = points[index].ry;
147
148
        a[0]     = 1;
149
        a[1]     = 0;
150
        a[2]     = sx;
151
        a[3]     = sy;
152
        double b = dx; // Single element of b
153
        least_squares_accumulate(mat, y, a, b, n);
154
155
        a[0] = 0;
156
        a[1] = 1;
157
        a[2] = sy;
158
        a[3] = -sx;
159
        b    = dy;
160
        least_squares_accumulate(mat, y, a, b, n);
161
    }
162
163
    // Fill in params[0] .. params[3] with output model
164
    if (!least_squares_solve(mat, y, params, n)) {
165
        return false;
166
    }
167
168
    // Fill in remaining parameters
169
    params[4] = -params[3];
170
    params[5] = params[2];
171
172
    return true;
173
}
174
175
static bool find_affine(const Correspondence* points, const int* indices, int num_indices, double* params) {
176
    // Note: The least squares problem for affine models is 6-dimensional,
177
    // but it splits into two independent 3-dimensional subproblems.
178
    // Solving these two subproblems separately and recombining at the end
179
    // results in less total computation than solving the 6-dimensional
180
    // problem directly.
181
    //
182
    // The two subproblems correspond to all the parameters which contribute
183
    // to the x output of the model, and all the parameters which contribute
184
    // to the y output, respectively.
185
186
    const int n = 3; // Size of each least-squares problem
187
    double    mat[2][3 * 3]; // Accumulator for A'A
188
    double    y[2][3]; // Accumulator for A'b
189
    double    x[2][3]; // Output vector
190
    double    a[2][3]; // Single row of A
191
    double    b[2]; // Single element of b
192
193
    least_squares_init(mat[0], y[0], n);
194
    least_squares_init(mat[1], y[1], n);
195
    for (int i = 0; i < num_indices; ++i) {
196
        int          index = indices[i];
197
        const double sx    = points[index].x;
198
        const double sy    = points[index].y;
199
        const double dx    = points[index].rx;
200
        const double dy    = points[index].ry;
201
202
        a[0][0] = 1;
203
        a[0][1] = sx;
204
        a[0][2] = sy;
205
        b[0]    = dx;
206
        least_squares_accumulate(mat[0], y[0], a[0], b[0], n);
207
208
        a[1][0] = 1;
209
        a[1][1] = sx;
210
        a[1][2] = sy;
211
        b[1]    = dy;
212
        least_squares_accumulate(mat[1], y[1], a[1], b[1], n);
213
    }
214
215
    if (!least_squares_solve(mat[0], y[0], x[0], n)) {
216
        return false;
217
    }
218
    if (!least_squares_solve(mat[1], y[1], x[1], n)) {
219
        return false;
220
    }
221
222
    // Rearrange least squares result to form output model
223
    params[0] = x[0][0];
224
    params[1] = x[1][0];
225
    params[2] = x[0][1];
226
    params[3] = x[0][2];
227
    params[4] = x[1][1];
228
    params[5] = x[1][2];
229
230
    return true;
231
}
232
233
// Returns true on success, false on error
234
static bool ransac_internal(const Correspondence* matched_points, int npoints, MotionModel* motion_models,
235
0
                            int num_desired_motions, const RansacModelInfo* model_info, bool* mem_alloc_failed) {
236
0
    assert(npoints >= 0);
237
0
    int  i       = 0;
238
0
    int  minpts  = model_info->minpts;
239
0
    bool ret_val = true;
240
241
0
    unsigned int seed = (unsigned int)npoints;
242
243
0
    int indices[MAX_MINPTS] = {0};
244
245
    // Store information for the num_desired_motions best transformations found
246
    // and the worst motion among them, as well as the motion currently under
247
    // consideration.
248
0
    RANSAC_MOTION *motions, *worst_kept_motion = NULL;
249
0
    RANSAC_MOTION  current_motion;
250
251
    // Store the parameters and the indices of the inlier points for the motion
252
    // currently under consideration.
253
0
    double params_this_motion[MAX_PARAMDIM];
254
255
    // Initialize output models, as a fallback in case we can't find a model
256
0
    for (i = 0; i < num_desired_motions; i++) {
257
0
        memcpy(motion_models[i].params, kIdentityParams, MAX_PARAMDIM * sizeof(*(motion_models[i].params)));
258
0
        motion_models[i].num_inliers = 0;
259
0
    }
260
261
0
    if (npoints < minpts * MINPTS_MULTIPLIER || npoints == 0) {
262
0
        return false;
263
0
    }
264
265
0
    int min_inliers = AOMMAX((int)(MIN_INLIER_PROB * npoints), minpts);
266
267
0
    EB_CALLOC_ARRAY_NO_CHECK(motions, num_desired_motions);
268
269
    // Allocate one large buffer which will be carved up to store the inlier
270
    // indices for the current motion plus the num_desired_motions many
271
    // output models
272
    // This allows us to keep the allocation/deallocation logic simple, without
273
    // having to (for example) check that `motions` is non-null before allocating
274
    // the inlier arrays
275
0
    int* inlier_buffer;
276
0
    EB_MALLOC_ARRAY_NO_CHECK(inlier_buffer, npoints * (num_desired_motions + 1));
277
278
0
    if (!(motions && inlier_buffer)) {
279
0
        ret_val           = false;
280
0
        *mem_alloc_failed = true;
281
0
        goto finish_ransac;
282
0
    }
283
284
    // Once all our allocations are known-good, we can fill in our structures
285
0
    worst_kept_motion = motions;
286
287
0
    for (i = 0; i < num_desired_motions; ++i) {
288
0
        motions[i].inlier_indices = inlier_buffer + i * npoints;
289
0
    }
290
0
    memset(&current_motion, 0, sizeof(current_motion));
291
0
    current_motion.inlier_indices = inlier_buffer + num_desired_motions * npoints;
292
293
0
    for (int trial_count = 0; trial_count < NUM_TRIALS; trial_count++) {
294
0
        lcg_pick(npoints, minpts, indices, &seed);
295
296
0
        if (!model_info->find_transformation(matched_points, indices, minpts, params_this_motion)) {
297
0
            continue;
298
0
        }
299
300
0
        model_info->score_model(params_this_motion, matched_points, npoints, &current_motion);
301
302
0
        if (current_motion.num_inliers < min_inliers) {
303
            // Reject models with too few inliers
304
0
            continue;
305
0
        }
306
307
0
        if (is_better_motion(&current_motion, worst_kept_motion)) {
308
            // This motion is better than the worst currently kept motion. Remember
309
            // the inlier points and sse. The parameters for each kept motion
310
            // will be recomputed later using only the inliers.
311
0
            worst_kept_motion->num_inliers = current_motion.num_inliers;
312
0
            worst_kept_motion->sse         = current_motion.sse;
313
314
            // Rather than copying the (potentially many) inlier indices from
315
            // current_motion.inlier_indices to worst_kept_motion->inlier_indices,
316
            // we can swap the underlying pointers.
317
            //
318
            // This is okay because the next time current_motion.inlier_indices
319
            // is used will be in the next trial, where we ignore its previous
320
            // contents anyway. And both arrays will be deallocated together at the
321
            // end of this function, so there are no lifetime issues.
322
0
            int* tmp                          = worst_kept_motion->inlier_indices;
323
0
            worst_kept_motion->inlier_indices = current_motion.inlier_indices;
324
0
            current_motion.inlier_indices     = tmp;
325
326
            // Determine the new worst kept motion and its num_inliers and sse.
327
0
            for (i = 0; i < num_desired_motions; ++i) {
328
0
                if (is_better_motion(worst_kept_motion, &motions[i])) {
329
0
                    worst_kept_motion = &motions[i];
330
0
                }
331
0
            }
332
0
        }
333
0
    }
334
335
    // Sort the motions, best first.
336
0
    qsort(motions, num_desired_motions, sizeof(RANSAC_MOTION), compare_motions);
337
338
    // Refine each of the best N models using iterative estimation.
339
    //
340
    // The idea here is loosely based on the iterative method from
341
    // "Locally Optimized RANSAC" by O. Chum, J. Matas and Josef Kittler:
342
    // https://cmp.felk.cvut.cz/ftp/articles/matas/chum-dagm03.pdf
343
    //
344
    // However, we implement a simpler version than their proposal, and simply
345
    // refit the model repeatedly until the number of inliers stops increasing,
346
    // with a cap on the number of iterations to defend against edge cases which
347
    // only improve very slowly.
348
0
    for (i = 0; i < num_desired_motions; ++i) {
349
0
        if (motions[i].num_inliers <= 0) {
350
            // Output model has already been initialized to the identity model,
351
            // so just skip setup
352
0
            continue;
353
0
        }
354
355
0
        bool bad_model = false;
356
0
        for (int refine_count = 0; refine_count < NUM_REFINES; refine_count++) {
357
0
            int num_inliers = motions[i].num_inliers;
358
0
            assert(num_inliers >= min_inliers);
359
360
0
            if (!model_info->find_transformation(
361
0
                    matched_points, motions[i].inlier_indices, num_inliers, params_this_motion)) {
362
                // In the unlikely event that this model fitting fails, we don't have a
363
                // good fallback. So leave this model set to the identity model
364
0
                bad_model = true;
365
0
                break;
366
0
            }
367
368
            // Score the newly generated model
369
0
            model_info->score_model(params_this_motion, matched_points, npoints, &current_motion);
370
371
            // At this point, there are three possibilities:
372
            // 1) If we found more inliers, keep refining.
373
            // 2) If we found the same number of inliers but a lower SSE, we want to
374
            //    keep the new model, but further refinement is unlikely to gain much.
375
            //    So commit to this new model
376
            // 3) It is possible, but very unlikely, that the new model will have
377
            //    fewer inliers. If it does happen, we probably just lost a few
378
            //    borderline inliers. So treat the same as case (2).
379
0
            if (current_motion.num_inliers > motions[i].num_inliers) {
380
0
                motions[i].num_inliers        = current_motion.num_inliers;
381
0
                motions[i].sse                = current_motion.sse;
382
0
                int* tmp                      = motions[i].inlier_indices;
383
0
                motions[i].inlier_indices     = current_motion.inlier_indices;
384
0
                current_motion.inlier_indices = tmp;
385
0
            } else {
386
                // Refined model is no better, so stop
387
                // This shouldn't be significantly worse than the previous model,
388
                // so it's fine to use the parameters in params_this_motion.
389
                // This saves us from having to cache the previous iteration's params.
390
0
                break;
391
0
            }
392
0
        }
393
394
0
        if (bad_model) {
395
0
            continue;
396
0
        }
397
398
        // Fill in output struct
399
0
        memcpy(motion_models[i].params, params_this_motion, MAX_PARAMDIM * sizeof(*motion_models[i].params));
400
0
        for (int j = 0; j < motions[i].num_inliers; j++) {
401
0
            int                   index         = motions[i].inlier_indices[j];
402
0
            const Correspondence* corr          = &matched_points[index];
403
0
            motion_models[i].inliers[2 * j + 0] = (int)rint(corr->x);
404
0
            motion_models[i].inliers[2 * j + 1] = (int)rint(corr->y);
405
0
        }
406
0
        motion_models[i].num_inliers = motions[i].num_inliers;
407
0
    }
408
409
0
finish_ransac:
410
0
    EB_FREE_ARRAY(inlier_buffer);
411
0
    EB_FREE_ARRAY(motions);
412
413
0
    return ret_val;
414
0
}
415
416
static const RansacModelInfo ransac_model_info[TRANS_TYPES] = {
417
    // IDENTITY
418
    {NULL, NULL, 0},
419
    // TRANSLATION
420
    {find_translation, score_translation, 1},
421
    // ROTZOOM
422
    {find_rotzoom, score_affine, 2},
423
    // AFFINE
424
    {find_affine, score_affine, 3},
425
};
426
427
// Returns true on success, false on error
428
bool svt_aom_ransac(const Correspondence* matched_points, int npoints, TransformationType type,
429
0
                    MotionModel* motion_models, int num_desired_motions, bool* mem_alloc_failed) {
430
0
    assert(type > IDENTITY && type < TRANS_TYPES);
431
432
0
    return ransac_internal(
433
0
        matched_points, npoints, motion_models, num_desired_motions, &ransac_model_info[type], mem_alloc_failed);
434
0
}