Skip to content

Commit 69bf682

Browse files
authored
Update saddle point refinement (#90)
1 parent 0db0acf commit 69bf682

File tree

3 files changed

+162
-153
lines changed

3 files changed

+162
-153
lines changed

McCalib/include/point_refinement.h

Lines changed: 141 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
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+
111
#include <cmath>
212
#include <iostream>
313
#include <stdio.h>
@@ -7,172 +17,183 @@
717

818
namespace McCalib {
919

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-
*/
2220
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;
2627
SaddlePoint() {}
2728
SaddlePoint(double x, double y) : x(x), y(y) {}
2829
};
2930

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));
4243
if (*w > 0)
43-
cnt++;
44+
nnz++;
4445
else
4546
*w = 0;
4647
sum += *w;
4748
w++;
4849
}
4950
// scale kernel
50-
saddleKernel /= sum;
51+
smoothingKernel /= sum;
52+
}
5153

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);
5358
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);
5861
for (int y = -half_kernel_size; y <= half_kernel_size; y++)
5962
for (int x = -half_kernel_size; x <= half_kernel_size; x++) {
6063
if (*w > 0) {
61-
*v = 255;
64+
*m = 255;
6265
a[0] = x * x;
6366
a[1] = y * y;
6467
a[2] = x * y;
6568
a[3] = y;
6669
a[4] = x;
6770
a[5] = 1;
6871
a += 6;
69-
} else {
70-
*v = 0;
71-
}
72+
} else
73+
*m = 0;
7274
w++;
73-
v++;
75+
m++;
7476
}
7577
// compute pseudoinverse of AtA
7678
cv::invert(A.t() * A, invAtAAt, cv::DECOMP_SVD);
7779
invAtAAt *= A.t();
7880
}
7981

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+
}
87140

88141
refined.resize(initial.size());
89142
for (size_t idx = 0; idx < initial.size(); idx++)
90143
refined[idx] = SaddlePoint(initial[idx].x, initial[idx].y);
91144

92-
// int diverged = 0, iterations = 0;
93-
int width = smooth_input.cols, height = smooth_input.rows;
94145
for (size_t idx = 0; idx < refined.size(); idx++) {
95146
SaddlePoint &pt = refined[idx];
96-
// printf("initial: %.3f, %.3f: ", pt.x, pt.y);
97-
cv::Mat p;
147+
bool point_diverged = true;
98148
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)) {
129151
// fit quadric to surface by solving LSQ
130-
p = A * b;
152+
cv::Mat p = invAtAAt * b;
131153

132154
// k5, k4, k3, k2, k1, k0
133155
// 0 , 1 , 2 , 3 , 4 , 5
134156
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;
141170
pt.x += dx;
142171
pt.y += dy;
143-
dx = std::fabs(dx);
144-
dy = std::fabs(dy);
145-
// iterations++;
146172

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)) {
150175
double k4mk5 = r[1] - r[0];
151176
pt.s = std::sqrt(r[2] * r[2] + k4mk5 * k4mk5);
152177
pt.a1 = std::atan2(-r[2], k4mk5) / 2.0;
153178
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) {
154187
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-
}
164188
}
165189
} else {
166-
// too close to border...
167-
pt.x = pt.y = std::numeric_limits<double>::infinity();
168-
// diverged++;
169190
break;
170191
}
171192
}
172-
// printf(" [%.3f, %.3f]\n", pt.x, pt.y);
193+
if (point_diverged) {
194+
pt.x = pt.y = std::numeric_limits<double>::infinity();
195+
}
173196
}
174-
// printf("Total: %zd, diverged: %d, iterations: %d\n", initial.size(),
175-
// diverged, iterations);
176197
}
177198

178199
template <typename PointType>
@@ -181,15 +202,20 @@ void saddleSubpixelRefinement(const cv::Mat &input,
181202
std::vector<SaddlePoint> &refined,
182203
const int window_half_size = 2,
183204
const int max_iterations = 20) {
184-
cv::Mat weights, A, valid;
185-
initSaddlePointRefinement(window_half_size, weights, A, valid);
186205

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);
190216

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);
193219
}
194220

195221
} // namespace McCalib

0 commit comments

Comments
 (0)