1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
|
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifdef _WIN32
#ifdef DBREG_EXPORTS
#define DBREG_API __declspec(dllexport)
#else
#define DBREG_API __declspec(dllimport)
#endif
#else
#define DBREG_API
#endif
// @jke - the next few lines are for extracting timing data. TODO: Remove after test
#define PROFILE 0
#include "dbstabsmooth.h"
#include <db_feature_detection.h>
#include <db_feature_matching.h>
#include <db_rob_image_homography.h>
#if PROFILE
#include <sys/time.h>
#endif
/*! \mainpage db_FrameToReferenceRegistration
\section intro Introduction
db_FrameToReferenceRegistration provides a simple interface to a set of sophisticated algorithms for stabilizing
video sequences. As its name suggests, the class is used to compute parameters that will allow us to warp incoming video
frames and register them with respect to a so-called <i>reference</i> frame. The reference frame is simply the first
frame of a sequence; the registration process is that of estimating the parameters of a warp that can be applied to
subsequent frames to make those frames align with the reference. A video made up of these warped frames will be more
stable than the input video.
For more technical information on the internal structure of the algorithms used within the db_FrameToRegistration class,
please follow this <a href="../Sarnoff image registration.docx">link</a>.
\section usage Usage
In addition to the class constructor, there are two main functions of db_FrameToReferenceRegistration that are of
interest to the programmer. db_FrameToReferenceRegistration::Init(...) is used to initialize the parameters of the
registration algorithm. db_FrameToReferenceRegistration::AddFrame(...) is the method by which each new video frame
is introduced to the registration algorithm, and produces the estimated registration warp parameters.
The following example illustrates how the major methods of the class db_FrameToReferenceRegistration can be used together
to calculate the registration parameters for an image sequence. In the example, the calls to the methods of
db_FrameToReferenceRegistration match those found in the API, but supporting code should be considered pseudo-code.
For a more complete example, please consult the source code for dbregtest.
\code
// feature-based image registration class:
db_FrameToReferenceRegistration reg;
// Image data
const unsigned char * const * image_storage;
// The 3x3 frame to reference registration parameters
double frame_to_ref_homography[9];
// a counter to count the number of frames processed.
unsigned long frame_counter;
// ...
// main loop - keep going while there are images to process.
while (ImagesAreAvailable)
{
// Call functions to place latest data into image_storage
// ...
// if the registration object is not yet initialized, then do so
// The arguments to this function are explained in the accompanying
// html API documentation
if (!reg.Initialized())
{
reg.Init(w,h,motion_model_type,25,linear_polish,quarter_resolution,
DB_POINT_STANDARDDEV,reference_update_period,
do_motion_smoothing,motion_smoothing_gain,
DB_DEFAULT_NR_SAMPLES,DB_DEFAULT_CHUNK_SIZE,
nr_corners,max_disparity);
}
// Present the new image data to the registration algorithm,
// with the result being stored in the frame_to_ref_homography
// variable.
reg.AddFrame(image_storage,frame_to_ref_homography);
// frame_to_ref_homography now contains the stabilizing transform
// use this to warp the latest image for display, etc.
// if this is the first frame, we need to tell the registration
// class to store the image as its reference. Otherwise, AddFrame
// takes care of that.
if (frame_counter == 0)
{
reg.UpdateReference(image_storage);
}
// increment the frame counter
frame_counter++;
}
\endcode
*/
/*!
* Performs feature-based frame to reference image registration.
*/
class DBREG_API db_FrameToReferenceRegistration
{
public:
db_FrameToReferenceRegistration(void);
~db_FrameToReferenceRegistration();
/*!
* Set parameters and allocate memory. Note: The default values of these parameters have been set to the values used for the android implementation (i.e. the demo APK).
* \param width image width
* \param height image height
* \param homography_type see definitions in \ref LMRobImageHomography
* \param max_iterations max number of polishing steps
* \param linear_polish whether to perform a linear polishing step after RANSAC
* \param quarter_resolution whether to process input images at quarter resolution (for computational efficiency)
* \param scale Cauchy scale coefficient (see db_ExpCauchyReprojectionError() )
* \param reference_update_period how often to update the alignment reference (in units of number of frames)
* \param do_motion_smoothing whether to perform display reference smoothing
* \param motion_smoothing_gain weight factor to reflect how fast the display reference must follow the current frame if motion smoothing is enabled
* \param nr_samples number of times to compute a hypothesis
* \param chunk_size size of cost chunks
* \param cd_target_nr_corners target number of corners for corner detector
* \param cm_max_disparity maximum disparity search range for corner matcher (in units of ratio of image width)
* \param cm_use_smaller_matching_window if set to true, uses a correlation window of 5x5 instead of the default 11x11
* \param cd_nr_horz_blocks the number of horizontal blocks for the corner detector to partition the image
* \param cd_nr_vert_blocks the number of vertical blocks for the corner detector to partition the image
*/
void Init(int width, int height,
int homography_type = DB_HOMOGRAPHY_TYPE_DEFAULT,
int max_iterations = DB_DEFAULT_MAX_ITERATIONS,
bool linear_polish = false,
bool quarter_resolution = true,
double scale = DB_POINT_STANDARDDEV,
unsigned int reference_update_period = 3,
bool do_motion_smoothing = false,
double motion_smoothing_gain = 0.75,
int nr_samples = DB_DEFAULT_NR_SAMPLES,
int chunk_size = DB_DEFAULT_CHUNK_SIZE,
int cd_target_nr_corners = 500,
double cm_max_disparity = 0.2,
bool cm_use_smaller_matching_window = false,
int cd_nr_horz_blocks = 5,
int cd_nr_vert_blocks = 5);
/*!
* Reset the transformation type that is being use to perform alignment. Use this to change the alignment type at run time.
* \param homography_type the type of transformation to use for performing alignment (see definitions in \ref LMRobImageHomography)
*/
void ResetHomographyType(int homography_type) { m_homography_type = homography_type; }
/*!
* Enable/Disable motion smoothing. Use this to turn motion smoothing on/off at run time.
* \param enable flag indicating whether to turn the motion smoothing on or off.
*/
void ResetSmoothing(bool enable) { m_do_motion_smoothing = enable; }
/*!
* Align an inspection image to an existing reference image, update the reference image if due and perform motion smoothing if enabled.
* \param im new inspection image
* \param H computed transformation from reference to inspection coordinate frame. Identity is returned if no reference frame was set.
* \param force_reference make this the new reference image
*/
int AddFrame(const unsigned char * const * im, double H[9], bool force_reference=false, bool prewarp=false);
/*!
* Returns true if Init() was run.
*/
bool Initialized() const { return m_initialized; }
/*!
* Returns true if the current frame is being used as the alignment reference.
*/
bool IsCurrentReference() const { return m_current_is_reference; }
/*!
* Returns true if we need to call UpdateReference now.
*/
bool NeedReferenceUpdate();
/*!
* Returns the pointer reference to the alignment reference image data
*/
unsigned char ** GetReferenceImage() { return m_reference_image; }
/*!
* Returns the pointer reference to the double array containing the homogeneous coordinates for the matched reference image corners.
*/
double * GetRefCorners() { return m_corners_ref; }
/*!
* Returns the pointer reference to the double array containing the homogeneous coordinates for the matched inspection image corners.
*/
double * GetInsCorners() { return m_corners_ins; }
/*!
* Returns the number of correspondences between the reference and inspection images.
*/
int GetNrMatches() { return m_nr_matches; }
/*!
* Returns the pointer to an array of indices that were found to be RANSAC inliers from the matched corner lists.
*/
int* GetInliers() { return m_inlier_indices; }
/*!
* Returns the number of inliers from the RANSAC matching step.
*/
int GetNrInliers() { return m_num_inlier_indices; }
//std::vector<int>& GetInliers();
//void Polish(std::vector<int> &inlier_indices);
/*!
* Perform a linear polishing step by re-estimating the alignment transformation using the RANSAC inliers.
* \param inlier_indices pointer to an array of indices that were found to be RANSAC inliers from the matched corner lists.
* \param num_inlier_indices number of inliers i.e. the length of the array passed as the first argument.
*/
void Polish(int *inlier_indices, int &num_inlier_indices);
/*!
* Reset the motion smoothing parameters to their initial values.
*/
void ResetMotionSmoothingParameters() { m_stab_smoother.Init(); }
/*!
* Update the alignment reference image to the specified image.
* \param im pointer to the image data to be used as the new alignment reference.
* \param subsample boolean flag to control whether the function should internally subsample the provided image to the size provided in the Init() function.
*/
int UpdateReference(const unsigned char * const * im, bool subsample = true, bool detect_corners = true);
/*!
* Returns the transformation from the display reference to the alignment reference frame
*/
void Get_H_dref_to_ref(double H[9]);
/*!
* Returns the transformation from the display reference to the inspection reference frame
*/
void Get_H_dref_to_ins(double H[9]);
/*!
* Set the transformation from the display reference to the inspection reference frame
* \param H the transformation to set
*/
void Set_H_dref_to_ins(double H[9]);
/*!
* Reset the display reference to the current frame.
*/
void ResetDisplayReference();
/*!
* Estimate a secondary motion model starting from the specified transformation.
* \param H the primary motion model to start from
*/
void EstimateSecondaryModel(double H[9]);
/*!
*
*/
void SelectOutliers();
char *profile_string;
protected:
void Clean();
void GenerateQuarterResImage(const unsigned char* const * im);
int m_im_width;
int m_im_height;
// RANSAC and refinement parameters:
int m_homography_type;
int m_max_iterations;
double m_scale;
int m_nr_samples;
int m_chunk_size;
double m_outlier_t2;
// Whether to fit a linear model to just the inliers at the end
bool m_linear_polish;
double m_polish_C[36];
double m_polish_D[6];
// local state
bool m_current_is_reference;
bool m_initialized;
// inspection to reference homography:
double m_H_ref_to_ins[9];
double m_H_dref_to_ref[9];
// feature extraction and matching:
db_CornerDetector_u m_cd;
db_Matcher_u m_cm;
// length of corner arrays:
unsigned long m_max_nr_corners;
// corner locations of reference image features:
double * m_x_corners_ref;
double * m_y_corners_ref;
int m_nr_corners_ref;
// corner locations of inspection image features:
double * m_x_corners_ins;
double * m_y_corners_ins;
int m_nr_corners_ins;
// length of match index arrays:
unsigned long m_max_nr_matches;
// match indices:
int * m_match_index_ref;
int * m_match_index_ins;
int m_nr_matches;
// pointer to internal copy of the reference image:
unsigned char ** m_reference_image;
// pointer to internal copy of last aligned inspection image:
unsigned char ** m_aligned_ins_image;
// pointer to quarter resolution image, if used.
unsigned char** m_quarter_res_image;
// temporary storage for the quarter resolution image processing
unsigned char** m_horz_smooth_subsample_image;
// temporary space for homography computation:
double * m_temp_double;
int * m_temp_int;
// homogenous image point arrays:
double * m_corners_ref;
double * m_corners_ins;
// Indices of the points within the match lists
int * m_inlier_indices;
int m_num_inlier_indices;
//void ComputeInliers(double H[9], std::vector<int> &inlier_indices);
void ComputeInliers(double H[9]);
// cost arrays:
void ComputeCostArray();
bool m_sq_cost_computed;
double * m_sq_cost;
// cost histogram:
void ComputeCostHistogram();
int *m_cost_histogram;
void SetOutlierThreshold();
// utility function for smoothing the motion parameters.
void SmoothMotion(void);
private:
double m_K[9];
const int m_over_allocation;
bool m_reference_set;
// Maximum number of inliers seen until now w.r.t the current reference frame
int m_max_inlier_count;
// Number of cost histogram bins:
int m_nr_bins;
// All costs above this threshold get put into the last bin:
int m_max_cost_pix;
// whether to quarter the image resolution for processing, or not
bool m_quarter_resolution;
// the period (in number of frames) for reference update.
unsigned int m_reference_update_period;
// the number of frames processed so far.
unsigned int m_nr_frames_processed;
// smoother for motion transformations
db_StabilizationSmoother m_stab_smoother;
// boolean to control whether motion smoothing occurs (or not)
bool m_do_motion_smoothing;
// double to set the gain for motion smoothing
double m_motion_smoothing_gain;
};
/*!
Create look-up tables to undistort images. Only Bougeut (Matlab toolkit)
is currently supported. Can be used with db_WarpImageLut_u().
\code
xd = H*xs;
xd = xd/xd(3);
\endcode
\param lut_x pre-allocated float image
\param lut_y pre-allocated float image
\param w width
\param h height
\param H image homography from source to destination
*/
inline void db_GenerateHomographyLut(float ** lut_x,float ** lut_y,int w,int h,const double H[9])
{
assert(lut_x && lut_y);
double x[3] = {0.0,0.0,1.0};
double xb[3];
/*
double xl[3];
// Determine the output coordinate system ROI
double Hinv[9];
db_InvertAffineTransform(Hinv,H);
db_Multiply3x3_3x1(xl, Hinv, x);
xl[0] = db_SafeDivision(xl[0],xl[2]);
xl[1] = db_SafeDivision(xl[1],xl[2]);
*/
for ( int i = 0; i < w; ++i )
for ( int j = 0; j < h; ++j )
{
x[0] = double(i);
x[1] = double(j);
db_Multiply3x3_3x1(xb, H, x);
xb[0] = db_SafeDivision(xb[0],xb[2]);
xb[1] = db_SafeDivision(xb[1],xb[2]);
lut_x[j][i] = float(xb[0]);
lut_y[j][i] = float(xb[1]);
}
}
/*!
* Perform a look-up table warp for packed RGB ([rgbrgbrgb...]) images.
* The LUTs must be float images of the same size as source image.
* The source value x_s is determined from destination (x_d,y_d) through lut_x
* and y_s is determined from lut_y:
\code
x_s = lut_x[y_d][x_d];
y_s = lut_y[y_d][x_d];
\endcode
* \param src source image (w*3 by h)
* \param dst destination image (w*3 by h)
* \param w width
* \param h height
* \param lut_x LUT for x
* \param lut_y LUT for y
*/
inline void db_WarpImageLutFast_rgb(const unsigned char * const * src, unsigned char ** dst, int w, int h,
const float * const * lut_x, const float * const * lut_y)
{
assert(src && dst);
int xd=0, yd=0;
for ( int i = 0; i < w; ++i )
for ( int j = 0; j < h; ++j )
{
xd = static_cast<unsigned int>(lut_x[j][i]);
yd = static_cast<unsigned int>(lut_y[j][i]);
if ( xd >= w || yd >= h ||
xd < 0 || yd < 0)
{
dst[j][3*i ] = 0;
dst[j][3*i+1] = 0;
dst[j][3*i+2] = 0;
}
else
{
dst[j][3*i ] = src[yd][3*xd ];
dst[j][3*i+1] = src[yd][3*xd+1];
dst[j][3*i+2] = src[yd][3*xd+2];
}
}
}
inline unsigned char db_BilinearInterpolationRGB(double y, double x, const unsigned char * const * v, int offset)
{
int floor_x=(int) x;
int floor_y=(int) y;
int ceil_x=floor_x+1;
int ceil_y=floor_y+1;
unsigned char f00 = v[floor_y][3*floor_x+offset];
unsigned char f01 = v[floor_y][3*ceil_x+offset];
unsigned char f10 = v[ceil_y][3*floor_x+offset];
unsigned char f11 = v[ceil_y][3*ceil_x+offset];
double xl = x-floor_x;
double yl = y-floor_y;
return (unsigned char)(f00*(1-yl)*(1-xl) + f10*yl*(1-xl) + f01*(1-yl)*xl + f11*yl*xl);
}
inline void db_WarpImageLutBilinear_rgb(const unsigned char * const * src, unsigned char ** dst, int w, int h,
const float * const * lut_x, const float * const * lut_y)
{
assert(src && dst);
double xd=0.0, yd=0.0;
for ( int i = 0; i < w; ++i )
for ( int j = 0; j < h; ++j )
{
xd = static_cast<double>(lut_x[j][i]);
yd = static_cast<double>(lut_y[j][i]);
if ( xd > w-2 || yd > h-2 ||
xd < 0.0 || yd < 0.0)
{
dst[j][3*i ] = 0;
dst[j][3*i+1] = 0;
dst[j][3*i+2] = 0;
}
else
{
dst[j][3*i ] = db_BilinearInterpolationRGB(yd,xd,src,0);
dst[j][3*i+1] = db_BilinearInterpolationRGB(yd,xd,src,1);
dst[j][3*i+2] = db_BilinearInterpolationRGB(yd,xd,src,2);
}
}
}
inline double SquaredInhomogenousHomographyError(double y[3],double H[9],double x[3]){
double x0,x1,x2,mult;
double sd;
x0=H[0]*x[0]+H[1]*x[1]+H[2];
x1=H[3]*x[0]+H[4]*x[1]+H[5];
x2=H[6]*x[0]+H[7]*x[1]+H[8];
mult=1.0/((x2!=0.0)?x2:1.0);
sd=(y[0]-x0*mult)*(y[0]-x0*mult)+(y[1]-x1*mult)*(y[1]-x1*mult);
return(sd);
}
// functions related to profiling
#if PROFILE
/* return current time in milliseconds */
static double
now_ms(void)
{
//struct timespec res;
struct timeval res;
//clock_gettime(CLOCK_REALTIME, &res);
gettimeofday(&res, NULL);
return 1000.0*res.tv_sec + (double)res.tv_usec/1e3;
}
#endif
|