Print corner coodinates in decimal form for more precision.
[gocam.git] / gocam.cc
blobd0f659ab408e18cb06cbb9c7d3534316051890b9
1 #ifndef GOCAM_CC
2 #define GOCAM_CC
3 #include "geom.hh"
4 #include "gocam.hh"
5 #include "im.hh"
6 #include "util.hh"
9 namespace gocam {
11 Analyser::Analyser()
12 : verbose(1),
13 board_size(19),
14 line_peak_filter_width(5),
15 hough_peak_filter_width(5),
16 line_image_sigma(0.2),
17 approx_series_width(10),
18 approx_theta_remove_range(25),
19 median_peak_remove_width(10),
20 num_initial_lines(5),
21 max_initial_lines(10)
25 void
26 Analyser::reset(const CImg<float> &img)
28 image = img;
30 // Convert to gray-scale
31 image.resize(-100, -100, -100, 1);
32 image.normalize(0,1);
34 // Reset intermediate variables
35 line_image = line_image.is_empty();
36 weighted_line_image = weighted_line_image.is_empty();
37 hough_image = hough_image.is_empty();
38 blurred_hough_image = blurred_hough_image.is_empty();
39 for (int s = 0; s < 2; s++) {
40 max_rho[s] = max_rho[s].is_empty();
41 initial_lines[s].clear();
42 lines[s].clear();
46 void
47 Analyser::analyse()
49 compute_line_images();
50 compute_hough_image();
51 compute_initial_grid();
52 grow_grid();
53 if (verbose > 0)
54 fprintf(stderr, "Analysis complete.\n");
57 void
58 Analyser::compute_line_images()
60 if (verbose > 0)
61 fprintf(stderr, "Computing the line images.\n");
63 // Compute the line image using a peak filter
64 CImg<float> filter = im::peak_filter<float>(line_peak_filter_width, -1);
65 line_image = image.get_correlate(filter);
66 im::zero_negatives(line_image);
67 line_image.normalize(0, 1);
69 // Weight the line image with Gaussian
70 weighted_line_image = line_image;
71 im::weight_gaussian(weighted_line_image,
72 weighted_line_image.width / 2.0,
73 weighted_line_image.height / 2.0,
74 util::sqr(line_image.width * line_image_sigma),
75 util::sqr(line_image.height * line_image_sigma));
76 weighted_line_image.normalize(0, 1);
79 void
80 Analyser::compute_hough_image()
82 // Check if the hough image exists already.
83 if (hough_image.dimx() > 0 && hough_image.dimy() > 0) {
84 if (verbose > 0)
85 fprintf(stderr, "Using a pre-computed hough image.\n");
86 return;
89 if (verbose > 0)
90 fprintf(stderr, "Computing the hough image.\n");
92 // Compute the hough image for degrees 0, 1, ..., 179.
93 int max_rho = cimg::max(weighted_line_image.height,
94 weighted_line_image.width) / 2;
95 CImg<float> tmp_hough = im::hough(weighted_line_image,
96 0, 179, 180, max_rho);
98 // Extend the image to degrees 0, 1, ..., 359 by copying the image
99 // upside down to right.
100 hough_image = CImg<float>(tmp_hough.width * 2, tmp_hough.height);
101 im::paste_image(tmp_hough, hough_image, 0, 0);
102 tmp_hough.mirror('y');
103 im::paste_image(tmp_hough, hough_image, tmp_hough.width, 0);
105 // Amplify peaks with a peak filter, remove negatives, and normalize
106 CImg<float> filter = im::peak_filter<float>(hough_peak_filter_width, 1);
107 hough_image.correlate(filter);
108 im::zero_negatives(hough_image);
109 hough_image.normalize(0, 1);
112 void
113 Analyser::compute_initial_grid()
115 if (verbose > 0)
116 fprintf(stderr, "Computing the initial grid.\n");
118 // First we find approximate theta-locations for the two almost
119 // vertical series of local maximums in the Hough image.
121 // Blur the image horizontally and compute the sum of each column.
122 CImg<float> blur_filter(5,1);
123 blur_filter.fill(1);
124 blurred_hough_image = hough_image.get_correlate(blur_filter);
125 blurred_column_sum = im::sum_y(blurred_hough_image);
126 blurred_column_sum.normalize(0, 1);
128 // Find the maximum, remove it, and find another maximum. Note
129 // that the maximum is computed between degrees [90,270), so that
130 // we avoid the edges of the Hough image.
131 approx_theta[0] = im::find_max1(blurred_column_sum, 90, 269);
132 im::set_range1(blurred_column_sum,
133 approx_theta[0] - approx_theta_remove_range,
134 approx_theta[0] + approx_theta_remove_range, (float)0);
135 im::set_range1(blurred_column_sum,
136 approx_theta[0] - approx_theta_remove_range + 180,
137 approx_theta[0] + approx_theta_remove_range + 180,
138 (float)0);
139 im::set_range1(blurred_column_sum,
140 approx_theta[0] - approx_theta_remove_range - 180,
141 approx_theta[0] + approx_theta_remove_range - 180,
142 (float)0);
143 approx_theta[1] = im::find_max1(blurred_column_sum, 90, 269);
145 // Find best lines for both series
146 for (int s = 0; s < 2; s++) {
148 // Select best maximums until the next maximum would be less
149 // than half of the smallest maximum. Select at least
150 // "num_initial_lines" lines, but at most "max_initial_lines" lines.
152 max_rho[s] = im::max_x(hough_image,
153 approx_theta[s] - approx_series_width,
154 approx_theta[s] + approx_series_width);
155 std::vector<float> rho_maxes;
156 while (1) {
158 // Find a maximum
159 int rho = im::find_max1(max_rho[s]);
160 float max = max_rho[s](rho);
162 // Check if we want more lines
163 if ((int)initial_lines[s].size() >= num_initial_lines &&
164 max < 0.5 * rho_maxes.back())
165 break;
166 if ((int)initial_lines[s].size() >= max_initial_lines)
167 break;
169 // Remove the maximum and the line
170 im::median_peak_remove(max_rho[s], rho, median_peak_remove_width);
171 int theta = im::find_max_x(hough_image, rho,
172 approx_theta[s] - approx_series_width,
173 approx_theta[s] + approx_series_width);
174 initial_lines[s].push_back(geom::LineRT(rho, theta));
175 rho_maxes.push_back(max);
179 // Fill possible gaps in the grid
180 for (int s = 0; s < 2; s++) {
182 // Compute median difference between lines
183 std::sort(initial_lines[s].begin(), initial_lines[s].end());
184 std::vector<float> diff = rho_differences(initial_lines[s]);
185 float abs_median_diff = cimg::abs(util::median(diff));
187 // Fill gaps that are greater than 1.5 of the median difference,
188 // by finding maximums within the gap.
189 for (int l = 0; l < (int)initial_lines[s].size()-1; l++) {
190 if (cimg::abs(initial_lines[s][l].rho - initial_lines[s][l+1].rho) <
191 1.5 * abs_median_diff)
192 continue;
194 // // Sanity check
195 // if ((int)initial_lines[s].size() >= board_size) {
196 // fprintf(stderr, "initial guess failed: too many lines\n");
197 // exit(1);
198 // }
200 // Compute and remove the maximum
201 int new_rho = im::find_max1(max_rho[s],
202 (int)initial_lines[s][l].rho,
203 (int)initial_lines[s][l+1].rho);
204 im::median_peak_remove(max_rho[s], new_rho, median_peak_remove_width);
205 float new_theta = (initial_lines[s][l].theta +
206 initial_lines[s][l+1].theta) / 2;
208 // Add the line at the correct position
209 initial_lines[s].insert(initial_lines[s].begin() + l + 1,
210 geom::LineRT(new_rho, new_theta));
211 l--; // Process the same line again
213 // Debug report
214 if (verbose > 1)
215 fprintf(stderr, "Added a line to fill the gap: "
216 "(rho=%d, theta=%.2f)\n", new_rho, new_theta);
219 // Print initial lines if requested
220 if (verbose > 1) {
221 fprintf(stderr, "Initial lines:\n");
222 for (int l = 0; l < (int)initial_lines[s].size(); l++) {
223 fprintf(stderr, "\t%.2f, %.2f\n", initial_lines[s][l].rho,
224 initial_lines[s][l].theta);
229 // Take num_initial_lines from the middle.
230 for (int s = 0; s < 2; s++) {
231 for (int tgt = 0; tgt < num_initial_lines; tgt++) {
232 int src = tgt + (initial_lines[s].size() - num_initial_lines) / 2;
233 initial_lines[s][tgt] = initial_lines[s][src];
235 initial_lines[s].resize(num_initial_lines);
238 // Convert the initial lines in the polar system to Euclidian
239 // space, and move to the center of the image.
240 int rho_max = hough_image.dimy() / 2;
241 for (int s = 0; s < 2; s++) {
242 for (int l = 0; l < (int)initial_lines[s].size(); l++) {
244 // Currently, the rhos of the initial lines are points in the
245 // hough image, i.e., between 0 and hough_image.dimy().
246 // Normalize them between -rho_max and rho_max. Convert also
247 // thetas from degrees to radians.
248 initial_lines[s][l].rho -= rho_max;
249 initial_lines[s][l].theta = geom::rad(initial_lines[s][l].theta);
251 // Convert to Euclidian space.
252 lines[s].push_back(geom::Line(initial_lines[s][l]));
253 lines[s].back().add(geom::Point(line_image.dimx() / 2,
254 line_image.dimy() / 2));
259 void
260 Analyser::fix_end_points()
262 for (int s = 0; s < 2; s++)
263 for (int l = 0; l < (int)lines[s].size(); l++)
264 lines[s][l].cut(lines[1-s].front(), lines[1-s].back());
267 void
268 Analyser::tune_line(geom::Line &line, geom::Line d1, geom::Line d2)
270 // Compute the number of points to test for each end point
271 int num_points_d1 = util::max((int)lrintf(d1.length() * 2), 2);
272 int num_points_d2 = util::max((int)lrintf(d2.length() * 2), 2);
274 // Iterate all orientations and select the best.
275 float best_value = -1;
276 geom::Line best_line;
277 for (int i1 = 0; i1 < num_points_d1; i1++) {
278 for (int i2 = 0; i2 < num_points_d2; i2++) {
280 // Compute the test end points and the sum along the test
281 // line.
282 geom::Line test_line(geom::mean(d1.a, d1.b,
283 i1 / (num_points_d1 - 1.0)),
284 geom::mean(d2.a, d2.b,
285 i2 / (num_points_d2 - 1.0)));
286 float value = im::line_sum(line_image, test_line);
287 if (value > best_value) {
288 best_value = value;
289 best_line = test_line;
293 line = best_line;
296 void
297 Analyser::tune_grid()
299 fix_end_points();
301 // Tune both line series
302 for (int s = 0; s < 2; s++) {
304 // Tune each line of a series at time
305 for (int l = 0; l < (int)lines[s].size(); l++) {
306 geom::Line d1;
307 geom::Line d2;
308 if (l > 0) {
309 d1.a = geom::mean(lines[s][l-1].a, lines[s][l].a, 0.3);
310 d2.a = geom::mean(lines[s][l-1].b, lines[s][l].b, 0.3);
312 else {
313 geom::Line imag_line = lines[s][1].get_mirror(lines[s][0]);
314 imag_line.cut(lines[1-s][0], lines[1-s].back());
315 d1.a = geom::mean(imag_line.a, lines[s][0].a, 0.3);
316 d2.a = geom::mean(imag_line.b, lines[s][0].b, 0.3);
318 if (l < (int)lines[s].size() - 1) {
319 d1.b = geom::mean(lines[s][l+1].a, lines[s][l].a, 0.3);
320 d2.b = geom::mean(lines[s][l+1].b, lines[s][l].b, 0.3);
322 else {
323 geom::Line imag_line = lines[s][l-1].get_mirror(lines[s][l]);
324 imag_line.cut(lines[1-s][0], lines[1-s].back());
325 d1.b = geom::mean(imag_line.a, lines[s][l].a, 0.3);
326 d2.b = geom::mean(imag_line.b, lines[s][l].b, 0.3);
328 tune_line(lines[s][l], d1, d2);
332 fix_end_points();
335 void
336 Analyser::add_best_line(int series)
338 // Short handles for the line series.
339 std::vector<geom::Line> &l1 = lines[series];
340 std::vector<geom::Line> &l2 = lines[1 - series];
341 assert(l1.size() >= 2);
342 assert(l2.size() >= 2);
344 // Do we have a full board already?
345 if ((int)lines[series].size() == board_size)
346 return;
348 // Get line candidates by mirroring the second lines from the
349 // edge with respect to the line at the edge.
350 int last = l1.size() - 1;
351 geom::Line new_line1 = l1[1].get_mirror(l1[0]);
352 geom::Line new_line2 = l1[last - 1].get_mirror(l1[last]);
354 // Choose the better candidate.
355 // Note that these functions also adjust the end points of the new lines.
356 float score1 = score_new_line(new_line1, l1.front(), l2);
357 float score2 = score_new_line(new_line2, l1.back(), l2);
358 if (score1 > score2)
359 l1.insert(l1.begin(), new_line1);
360 else
361 l1.push_back(new_line2);
364 void
365 Analyser::grow_grid(bool only_once)
367 if (verbose > 0)
368 fprintf(stderr, "Growing the grid.\n");
370 while ((int)lines[0].size() < board_size ||
371 (int)lines[1].size() < board_size)
373 tune_grid();
374 if ((int)lines[0].size() < board_size)
375 add_best_line(0);
376 if ((int)lines[1].size() < board_size)
377 add_best_line(1);
378 if (only_once)
379 break;
381 tune_grid();
384 std::vector<float>
385 Analyser::rho_differences(const std::vector<geom::LineRT> &vec)
387 // Check size
388 std::vector<float> result;
389 if (vec.size() == 0)
390 return result;
392 // Compute differences between the rhos of the lines
393 result.resize(vec.size() - 1);
394 for (int i = 0; i < (int)vec.size() - 1; i++)
395 result[i] = vec[i+1].rho - vec[i].rho;
396 return result;
399 float
400 Analyser::score_new_line(const geom::Line &new_line,
401 const geom::Line &next_line,
402 const std::vector<geom::Line> &lines)
404 // Create the intersection points that correspond to the new line
405 // segments parallel to "lines".
406 std::vector<geom::Line> two_lines(1, new_line);
407 two_lines.push_back(next_line);
408 geom::Grid grid(two_lines, lines);
410 // Use only half of the line segments
411 for (int x = 0; x < grid.width; x++) {
412 grid(x, 0).add(grid(x, 1));
413 grid(x, 0).scale(0.5);
416 // Compute the average of the pixel values at the new line segments.
417 im::PixelSum<float> sum(line_image);
418 for (int l = 0; l < (int)lines.size(); l++) {
419 geom::Line segment(grid(l, 0), grid(l, 1));
420 segment.map(sum);
423 return sum.sum / sum.count;
428 #endif /* GOCAM_CC */