1
+ /* *
2
+ * @brief Saddle Point Refinement
3
+ *
4
+ * Adapted from
5
+ * https://github.com/facebookarchive/deltille/blob/master/include/deltille/PolynomialFit.h
6
+ *
7
+ * Reference: "Deltille Grids for Geometric Camera Calibration", H Ha, M
8
+ * Perdoch, H Alismail, I So Kweon, Y Sheikh (ICCV 2017)
9
+ */
10
+
1
11
#include < cmath>
2
12
#include < iostream>
3
13
#include < stdio.h>
7
17
8
18
namespace McCalib {
9
19
10
- /* ***** ******/
11
-
12
- double step_threshold = 0.001 ;
13
-
14
- /* *
15
- * @struct SaddlePoint
16
- *
17
- * @brief Saddle Point Refinement
18
- *
19
- * Reference: "Deltille Grids for Geometric Camera Calibration", H Ha, M
20
- * Perdoch, H Alismail, I So Kweon, Y Sheikh (ICCV 2017)
21
- */
22
20
struct SaddlePoint {
23
- double x, y;
24
- double a1, a2;
25
- double s, det;
21
+ double x;
22
+ double y;
23
+ double a1;
24
+ double a2;
25
+ double s;
26
+ double det;
26
27
SaddlePoint () {}
27
28
SaddlePoint (double x, double y) : x(x), y(y) {}
28
29
};
29
30
30
- void initSaddlePointRefinement (const int half_kernel_size ,
31
- cv::Mat &saddleKernel , cv::Mat &invAtAAt ,
32
- cv::Mat &valid ) {
33
- int window_size = half_kernel_size * 2 + 1 ;
34
- saddleKernel .create (window_size, window_size, CV_64FC1 );
35
- double maxVal = half_kernel_size + 1 , sum = 0 ;
36
- double *w = saddleKernel. ptr < double >( 0 ) ;
37
- // circular kernel
38
- int cnt = 0 ;
39
- for (int y = -half_kernel_size ; y <= half_kernel_size ; y++)
40
- for (int x = -half_kernel_size ; x <= half_kernel_size ; x++) {
41
- *w = maxVal - std::sqrt (x * x + y * y);
31
+ void initConeSmoothingKernel (const int window_half_size ,
32
+ cv::Mat &smoothingKernel , cv::Mat &mask ,
33
+ int &nnz ) {
34
+ int window_size = window_half_size * 2 + 1 ;
35
+ smoothingKernel .create (window_size, window_size, cv::DataType< float >::depth );
36
+ mask. create (window_size, window_size, CV_8UC1) ;
37
+ double maxVal = window_half_size + 1 , sum = 0 ;
38
+ float *w = smoothingKernel. ptr < float >( 0 );
39
+ // cone kernel
40
+ for (int y = -window_half_size ; y <= window_half_size ; y++)
41
+ for (int x = -window_half_size ; x <= window_half_size ; x++) {
42
+ *w = float ( maxVal - std::sqrt (x * x + y * y) );
42
43
if (*w > 0 )
43
- cnt ++;
44
+ nnz ++;
44
45
else
45
46
*w = 0 ;
46
47
sum += *w;
47
48
w++;
48
49
}
49
50
// scale kernel
50
- saddleKernel /= sum;
51
+ smoothingKernel /= sum;
52
+ }
51
53
52
- cv::Mat A (cnt, 6 , CV_64FC1);
54
+ void initSaddleFitting (const int half_kernel_size, const int nnz,
55
+ cv::Mat &smoothingKernel, cv::Mat &mask,
56
+ cv::Mat &invAtAAt) {
57
+ cv::Mat A (nnz, 6 , CV_64FC1);
53
58
double *a = A.ptr <double >(0 );
54
- w = saddleKernel.ptr <double >(0 );
55
- valid.create (window_size, window_size, CV_8UC1);
56
- uint8_t *v = valid.ptr <uint8_t >(0 );
57
-
59
+ float *w = smoothingKernel.ptr <float >(0 );
60
+ uint8_t *m = mask.ptr <uint8_t >(0 );
58
61
for (int y = -half_kernel_size; y <= half_kernel_size; y++)
59
62
for (int x = -half_kernel_size; x <= half_kernel_size; x++) {
60
63
if (*w > 0 ) {
61
- *v = 255 ;
64
+ *m = 255 ;
62
65
a[0 ] = x * x;
63
66
a[1 ] = y * y;
64
67
a[2 ] = x * y;
65
68
a[3 ] = y;
66
69
a[4 ] = x;
67
70
a[5 ] = 1 ;
68
71
a += 6 ;
69
- } else {
70
- *v = 0 ;
71
- }
72
+ } else
73
+ *m = 0 ;
72
74
w++;
73
- v ++;
75
+ m ++;
74
76
}
75
77
// compute pseudoinverse of AtA
76
78
cv::invert (A.t () * A, invAtAAt, cv::DECOMP_SVD);
77
79
invAtAAt *= A.t ();
78
80
}
79
81
80
- template <typename PointType>
81
- void saddleSubpixelRefinement (const cv::Mat &smooth_input,
82
- const std::vector<PointType> &initial, cv::Mat &A,
83
- cv::Mat &valid, std::vector<SaddlePoint> &refined,
84
- const int window_half_size = 3 ,
85
- const int max_iterations = 3 ) {
86
- cv::Mat b (A.cols , 1 , CV_64FC1);
82
+ template <typename ImageType>
83
+ bool interpolatePatch (double x, double y, int window_half_size,
84
+ const cv::Mat &input, const cv::Mat &mask,
85
+ cv::Mat &b_vec) {
86
+ if (x > window_half_size + 1 && x < input.cols - (window_half_size + 2 ) &&
87
+ y > window_half_size + 1 && y < input.rows - (window_half_size + 2 )) {
88
+ int x0 = int (x);
89
+ int y0 = int (y);
90
+ double xw = x - x0;
91
+ double yw = y - y0;
92
+
93
+ // precompute bilinear interpolation weights
94
+ double w00 = (1.0 - xw) * (1.0 - yw), w01 = xw * (1.0 - yw),
95
+ w10 = (1.0 - xw) * yw, w11 = xw * yw;
96
+
97
+ // fit to local neighborhood = b vector...
98
+ const uint8_t *v = mask.ptr <const uint8_t >(0 );
99
+ double *m = b_vec.ptr <double >(0 );
100
+ double mn = DBL_MAX;
101
+ double mx = DBL_MIN;
102
+ for (int wy = -window_half_size; wy <= window_half_size; wy++) {
103
+ const ImageType *im00 = input.ptr <ImageType>(y0 + wy),
104
+ *im10 = input.ptr <ImageType>(y0 + wy + 1 );
105
+ for (int wx = -window_half_size; wx <= window_half_size; wx++) {
106
+ if (*v > 0 ) {
107
+ const int col0 = x0 + wx;
108
+ const int col1 = col0 + 1 ;
109
+ double val = im00[col0] * w00 + im00[col1] * w01 + im10[col0] * w10 +
110
+ im10[col1] * w11;
111
+ *(m++) = val;
112
+ if (mn > val)
113
+ mn = val;
114
+ if (mx < val)
115
+ mx = val;
116
+ }
117
+ v++;
118
+ }
119
+ }
120
+ if (mx - mn > 1.0 / 255 )
121
+ return true ;
122
+ }
123
+ return false ;
124
+ }
125
+
126
+ template <typename LocationsPointType, typename SmoothedImageType = double >
127
+ void saddleSubpixelRefinement (
128
+ const cv::Mat &smoothed_input,
129
+ const std::vector<LocationsPointType> &initial, const cv::Mat &invAtAAt,
130
+ const cv::Mat &mask, const int window_half_size,
131
+ std::vector<SaddlePoint> &refined, const int max_iterations = 3 ,
132
+ const bool tight_convergence = true ,
133
+ const double spatial_convergence_threshold = 0.001 ) {
134
+ cv::Mat b (invAtAAt.cols , 1 , CV_64FC1);
135
+
136
+ double convergence_region = window_half_size;
137
+ if (tight_convergence) {
138
+ convergence_region = 1.0 ;
139
+ }
87
140
88
141
refined.resize (initial.size ());
89
142
for (size_t idx = 0 ; idx < initial.size (); idx++)
90
143
refined[idx] = SaddlePoint (initial[idx].x , initial[idx].y );
91
144
92
- // int diverged = 0, iterations = 0;
93
- int width = smooth_input.cols , height = smooth_input.rows ;
94
145
for (size_t idx = 0 ; idx < refined.size (); idx++) {
95
146
SaddlePoint &pt = refined[idx];
96
- // printf("initial: %.3f, %.3f: ", pt.x, pt.y);
97
- cv::Mat p;
147
+ bool point_diverged = true ;
98
148
for (int it = 0 ; it < max_iterations; it++) {
99
- if (pt.x > window_half_size + 1 &&
100
- pt.x < width - (window_half_size + 2 ) &&
101
- pt.y > window_half_size + 1 &&
102
- pt.y < height - (window_half_size + 2 )) {
103
- int x0 = int (pt.x );
104
- int y0 = int (pt.y );
105
- double xw = pt.x - x0;
106
- double yw = pt.y - y0;
107
-
108
- // precompute bilinear interpolation weights
109
- double w00 = (1.0 - xw) * (1.0 - yw), w01 = xw * (1.0 - yw),
110
- w10 = (1.0 - xw) * yw, w11 = xw * yw;
111
-
112
- // fit to local neighborhood = b vector...
113
- double *m = b.ptr <double >(0 );
114
- uint8_t *v = valid.ptr <uint8_t >(0 );
115
-
116
- for (int y = -window_half_size; y <= window_half_size; y++) {
117
- const double *im00 = smooth_input.ptr <double >(y0 + y),
118
- *im10 = smooth_input.ptr <double >(y0 + y + 1 );
119
- for (int x = -window_half_size; x <= window_half_size; x++) {
120
- if (*v > 0 ) {
121
- const int col0 = x0 + x;
122
- const int col1 = col0 + 1 ;
123
- *(m++) = im00[col0] * w00 + im00[col1] * w01 + im10[col0] * w10 +
124
- im10[col1] * w11;
125
- }
126
- v++;
127
- }
128
- }
149
+ if (interpolatePatch<SmoothedImageType>(pt.x , pt.y , window_half_size,
150
+ smoothed_input, mask, b)) {
129
151
// fit quadric to surface by solving LSQ
130
- p = A * b;
152
+ cv::Mat p = invAtAAt * b;
131
153
132
154
// k5, k4, k3, k2, k1, k0
133
155
// 0 , 1 , 2 , 3 , 4 , 5
134
156
double *r = p.ptr <double >(0 );
135
- pt.det = 4.0 * r[0 ] * r[1 ] - r[2 ] * r[2 ]; // 4.0 * k5 * k4 - k3 * k3
136
- // compute the new location
137
- double dx = (-2 * r[1 ] * r[4 ] + r[2 ] * r[3 ]) /
138
- pt.det ; // - 2 * k4 * k1 + k3 * k2
139
- double dy = (r[2 ] * r[4 ] - 2 * r[0 ] * r[3 ]) /
140
- pt.det ; // k3 * k1 - 2 * k5 * k2
157
+ // 4.0 * k5 * k4 - k3 * k3
158
+ pt.det = 4.0 * r[0 ] * r[1 ] - r[2 ] * r[2 ];
159
+
160
+ // check if it is still a saddle point
161
+ if (pt.det > 0 ) {
162
+ break ;
163
+ }
164
+
165
+ // compute the new location
166
+ // - 2 * k4 * k1 + k3 * k2
167
+ double dx = (-2.0 * r[1 ] * r[4 ] + r[2 ] * r[3 ]) / pt.det ;
168
+ // k3 * k1 - 2 * k5 * k2
169
+ double dy = (r[2 ] * r[4 ] - 2.0 * r[0 ] * r[3 ]) / pt.det ;
141
170
pt.x += dx;
142
171
pt.y += dy;
143
- dx = std::fabs (dx);
144
- dy = std::fabs (dy);
145
- // iterations++;
146
172
147
- if (it == max_iterations ||
148
- (step_threshold > dx && step_threshold > dy)) {
149
- // converged
173
+ if (spatial_convergence_threshold > std::fabs (dx) &&
174
+ spatial_convergence_threshold > std::fabs (dy)) {
150
175
double k4mk5 = r[1 ] - r[0 ];
151
176
pt.s = std::sqrt (r[2 ] * r[2 ] + k4mk5 * k4mk5);
152
177
pt.a1 = std::atan2 (-r[2 ], k4mk5) / 2.0 ;
153
178
pt.a2 = std::acos ((r[1 ] + r[0 ]) / pt.s ) / 2.0 ;
179
+ // converged
180
+ point_diverged = false ;
181
+ break ;
182
+ }
183
+ // check for divergence due to departure out of convergence region or
184
+ // point type change
185
+ if (std::fabs (pt.x - initial[idx].x ) > convergence_region ||
186
+ std::fabs (pt.y - initial[idx].y ) > convergence_region) {
154
187
break ;
155
- } else {
156
- // check for divergence
157
- if (pt.det > 0 ||
158
- std::fabs (pt.x - initial[idx].x ) > window_half_size ||
159
- std::fabs (pt.y - initial[idx].y ) > window_half_size) {
160
- pt.x = pt.y = std::numeric_limits<double >::infinity ();
161
- // diverged++;
162
- break ;
163
- }
164
188
}
165
189
} else {
166
- // too close to border...
167
- pt.x = pt.y = std::numeric_limits<double >::infinity ();
168
- // diverged++;
169
190
break ;
170
191
}
171
192
}
172
- // printf(" [%.3f, %.3f]\n", pt.x, pt.y);
193
+ if (point_diverged) {
194
+ pt.x = pt.y = std::numeric_limits<double >::infinity ();
195
+ }
173
196
}
174
- // printf("Total: %zd, diverged: %d, iterations: %d\n", initial.size(),
175
- // diverged, iterations);
176
197
}
177
198
178
199
template <typename PointType>
@@ -181,15 +202,20 @@ void saddleSubpixelRefinement(const cv::Mat &input,
181
202
std::vector<SaddlePoint> &refined,
182
203
const int window_half_size = 2 ,
183
204
const int max_iterations = 20 ) {
184
- cv::Mat weights, A, valid;
185
- initSaddlePointRefinement (window_half_size, weights, A, valid);
186
205
187
- // circular filter smoothing
188
- cv::Mat smooth;
189
- cv::filter2D (input, smooth, CV_64FC1, weights);
206
+ cv::Mat smoothingKernel;
207
+ cv::Mat mask;
208
+ int nnz;
209
+ initConeSmoothingKernel (window_half_size, smoothingKernel, mask, nnz);
210
+
211
+ cv::Mat invAtAAt;
212
+ initSaddleFitting (window_half_size, nnz, smoothingKernel, mask, invAtAAt);
213
+
214
+ cv::Mat smooth_input;
215
+ cv::filter2D (input, smooth_input, CV_64FC1, smoothingKernel);
190
216
191
- saddleSubpixelRefinement (smooth , initial, A, valid, refined, window_half_size ,
192
- max_iterations);
217
+ saddleSubpixelRefinement (smooth_input , initial, invAtAAt, mask ,
218
+ window_half_size, refined, max_iterations);
193
219
}
194
220
195
221
} // namespace McCalib
0 commit comments