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),
26 Analyser::reset(const CImg
<float> &img
)
30 // Convert to gray-scale
31 image
.resize(-100, -100, -100, 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();
49 compute_line_images();
50 compute_hough_image();
51 compute_initial_grid();
54 fprintf(stderr
, "Analysis complete.\n");
58 Analyser::compute_line_images()
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);
80 Analyser::compute_hough_image()
82 // Check if the hough image exists already.
83 if (hough_image
.dimx() > 0 && hough_image
.dimy() > 0) {
85 fprintf(stderr
, "Using a pre-computed hough image.\n");
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);
113 Analyser::compute_initial_grid()
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);
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,
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,
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
;
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())
166 if ((int)initial_lines
[s
].size() >= max_initial_lines
)
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
)
195 // if ((int)initial_lines[s].size() >= board_size) {
196 // fprintf(stderr, "initial guess failed: too many lines\n");
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
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
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));
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());
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
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
) {
289 best_line
= test_line
;
297 Analyser::tune_grid()
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
++) {
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);
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);
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
);
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
)
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
);
359 l1
.insert(l1
.begin(), new_line1
);
361 l1
.push_back(new_line2
);
365 Analyser::grow_grid(bool only_once
)
368 fprintf(stderr
, "Growing the grid.\n");
370 while ((int)lines
[0].size() < board_size
||
371 (int)lines
[1].size() < board_size
)
374 if ((int)lines
[0].size() < board_size
)
376 if ((int)lines
[1].size() < board_size
)
385 Analyser::rho_differences(const std::vector
<geom::LineRT
> &vec
)
388 std::vector
<float> 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
;
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));
423 return sum
.sum
/ sum
.count
;
428 #endif /* GOCAM_CC */