licornea_tools
checkerboard.cc
Go to the documentation of this file.
1 #include "checkerboard.h"
2 #include "../../lib/common.h"
3 #include "../../lib/misc.h"
4 #include <algorithm>
5 #include <iostream>
6 #include <climits>
7 #include <cassert>
8 
9 // based on
10 // https://github.com/code-iai/iai_kinect2/blob/master/kinect2_calibration/src/kinect2_calibration.cpp
11 
12 
13 namespace tlz {
14 
15 checkerboard::checkerboard(int cols_, int rows_, real square_width_, const std::vector<vec2>& corners_) :
16  cols(cols_),
17  rows(rows_),
18  square_width(square_width_),
19  corners(corners_)
20 {
21  assert(corners_.size() == cols*rows);
22 
23  outer_corners = {
24  corner(0, 0),
25  corner(cols-1, 0),
26  corner(cols-1, rows-1),
27  corner(0, rows-1)
28  };
29 
30  int min_x = INT_MAX, min_y = INT_MAX, max_x = 0, max_y = 0;
31  for(const vec2& pt : outer_corners) {
32  int x = pt[0], y = pt[1];
33  if(x > max_x) max_x = x;
34  if(x < min_x) min_x = x;
35  if(y > max_y) max_y = y;
36  if(y < min_y) min_y = y;
37  }
38  bounding_rect = cv::Rect(min_x, min_y, max_x-min_x, max_y-min_y);
39 }
40 
41 std::vector<cv::Point> checkerboard::outer_corners_i() const {
42  return vec2_to_point(outer_corners);
43 }
44 
45 
46 checkerboard detect_color_checkerboard(cv::Mat_<cv::Vec3b>& img, int cols, int rows, real square_width) {
47  std::vector<cv::Point2f> corners;
48 
49  int flags = cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_ADAPTIVE_THRESH;
50  bool found = cv::findChessboardCorners(img, cv::Size(cols, rows), corners, flags);
51  if(!found || corners.size() != cols*rows) return checkerboard();
52 
53  cv::TermCriteria term(cv::TermCriteria::COUNT | cv::TermCriteria::EPS, 100, DBL_EPSILON);
54  cv::Mat img_mono;
55  cv::cvtColor(img, img_mono, CV_BGR2GRAY);
56  cv::cornerSubPix(img_mono, corners, cv::Size(11, 11), cv::Size(-1, -1), term);
57 
58  return checkerboard(cols, rows, square_width, point2f_to_vec2(corners));
59 }
60 
61 
62 checkerboard detect_ir_checkerboard(cv::Mat_<uchar>& img, int cols, int rows, real square_width) {
63  static cv::Ptr<cv::CLAHE> clahe; // TODO zero-initialize?
64  if(! clahe) clahe = cv::createCLAHE(1.5, cv::Size(32, 32));
65 
66  cv::Mat_<uchar> larger_img;
67  real scale = 2.0;
68  cv::resize(img, larger_img, cv::Size(0,0), scale, scale, cv::INTER_CUBIC);
69 
70  clahe->apply(larger_img, larger_img);
71 
72  std::vector<cv::Point2f> corners;
73 
74  int flags = cv::CALIB_CB_ADAPTIVE_THRESH;
75  bool found = cv::findChessboardCorners(larger_img, cv::Size(cols, rows), corners, flags);
76  if(!found || corners.size() != cols*rows) return checkerboard();
77 
78  cv::TermCriteria term(cv::TermCriteria::EPS, 100, DBL_EPSILON);
79  cv::cornerSubPix(larger_img, corners, cv::Size(11, 11), cv::Size(-1, -1), term);
80 
81  for(cv::Point2f& corner : corners) corner = cv::Point2f(corner.x/scale, corner.y/scale);
82 
83  return checkerboard(cols, rows, square_width, point2f_to_vec2(corners));
84 }
85 
86 
87 checkerboard detect_ir_checkerboard(cv::Mat_<ushort>& ir_orig, int cols, int rows, real square_width) {
88  ushort max_ir = 0xffff;
89  ushort min_ir = 0;
90  float alpha = 255.0f / (max_ir - min_ir);
91  float beta = -alpha * min_ir;
92  cv::Mat_<uchar> ir;
93  cv::convertScaleAbs(ir_orig, ir, alpha, beta);
94  //ir.setTo(0, (ir_orig < min_ir));
95  //ir.setTo(255, (ir_orig > max_ir));
96  ir.setTo(0, (ir_orig == 0));
97  return detect_ir_checkerboard(ir, cols, rows, square_width);
98 }
99 
100 
101 std::vector<vec3> checkerboard_world_corners(int cols, int rows, real square_width) {
102  std::vector<vec3> world_corners(cols * rows);
103  for(int row = 0, idx = 0; row < rows; ++row) for(int col = 0; col < cols; ++col, ++idx)
104  world_corners[idx] = vec3((col - cols/2)*square_width, (row - rows/2)*square_width, 0.0);
105  return world_corners;
106 }
107 
108 
109 std::vector<vec2> checkerboard_image_corners(const checkerboard& chk) {
110  return chk.corners;
111 }
112 
113 
114 cv::Mat_<cv::Vec3b> visualize_checkerboard(const cv::Mat_<cv::Vec3b>& img, const checkerboard& chk, const checkerboard_visualization_parameters& param) {
115  cv::Mat_<cv::Vec3b> out_img;
116  img.copyTo(out_img);
117  if(! chk) return out_img;
118 
119  if(param.line) {
120  std::vector<std::vector<cv::Point>> polylines { chk.outer_corners_i() };
121  cv::polylines(out_img, polylines, true, cv::Scalar(param.lines_color), param.line_thickness);
122  }
123 
124  if(param.cv_visualization) {
125  cv::drawChessboardCorners(out_img, cv::Size(chk.cols, chk.rows), vec2_to_point2f(chk.corners), true);
126  }
127 
128  return out_img;
129 }
130 
131 
132 cv::Mat_<cv::Vec3b> visualize_checkerboard(const cv::Mat_<uchar>& img, const checkerboard& chk, const checkerboard_visualization_parameters& param) {
133  cv::Mat_<cv::Vec3b> conv_img;
134  cv::cvtColor(img, conv_img, CV_GRAY2BGR);
135  return visualize_checkerboard(conv_img, chk, param);
136 }
137 cv::Mat_<cv::Vec3b> visualize_checkerboard(const cv::Mat_<ushort>& img, const checkerboard& chk, const checkerboard_visualization_parameters& param) {
138  cv::Mat_<uchar> img_8bit(img);
139  ushort max_ir = 0xffff;
140  ushort min_ir = 0;
141  float alpha = 255.0f / (max_ir - min_ir);
142  float beta = -alpha * min_ir;
143  cv::Mat_<uchar> ir;
144  cv::convertScaleAbs(img, img_8bit, alpha, beta);
145  return visualize_checkerboard(img_8bit, chk, param);
146 }
147 
148 
149 cv::Mat_<cv::Vec3b> visualize_checkerboard_pixel_samples(const cv::Mat_<cv::Vec3b>& img, const std::vector<checkerboard_pixel_depth_sample>& pixels, int rad) {
150  cv::Mat_<cv::Vec3b> out_img;
151  img.copyTo(out_img);
152  for(const auto& pix : pixels) {
153  int x = pix.coordinates[0], y = pix.coordinates[1];
154  if(x < 0 || x >= img.cols || y < 0 || y >= img.rows) continue;
155 
156  cv::Vec3b col(100, 100, 255);
157  if(rad == 1) out_img(y, x) = col;
158  else cv::circle(out_img, cv::Point(x, y), rad, cv::Scalar(col), 2);
159  }
160  return out_img;
161 }
162 
163 
164 cv::Mat_<cv::Vec3b> visualize_checkerboard_pixel_samples(const cv::Mat_<uchar>& img, const std::vector<checkerboard_pixel_depth_sample>& pixels, int rad) {
165  cv::Mat_<cv::Vec3b> conv_img;
166  cv::cvtColor(img, conv_img, CV_GRAY2BGR);
167  return visualize_checkerboard_pixel_samples(conv_img, pixels, rad);
168 }
169 
170 
171 
172 
175  std::vector<vec3> world_corners = checkerboard_world_corners(chk.cols, chk.rows, chk.square_width);
176  std::vector<vec2> image_corners = checkerboard_image_corners(chk);
177  for(int i = 0; i < chk.rows*chk.cols; ++i) {
179  cor.object_coordinates[0] = world_corners[i];
180  cor.image_coordinates[0] = image_corners[i];
181  cors.push_back(cor);
182  }
183  return cors;
184 }
185 
186 
188  assert(chk1.rows == chk2.rows && chk1.cols == chk2.cols && chk1.square_width == chk2.square_width);
190  std::vector<vec3> world_corners = checkerboard_world_corners(chk1.cols, chk1.rows, chk1.square_width);
191  std::vector<vec2> image1_corners = checkerboard_image_corners(chk1);
192  std::vector<vec2> image2_corners = checkerboard_image_corners(chk2);
193  for(int i = 0; i < chk1.rows*chk1.cols; ++i) {
195  cor.object_coordinates[0] = world_corners[i];
196  cor.image_coordinates[0] = image1_corners[i];
197  cor.image_coordinates[1] = image2_corners[i];
198  cors.push_back(cor);
199  }
200  return cors;
201 }
202 
203 
205  std::vector<vec3> object_points = checkerboard_world_corners(chk.cols, chk.rows, chk.square_width);
206  std::vector<vec2> image_points = checkerboard_image_corners(chk);
207 
208  vec3 rotation_vec, translation;
209  mat33 rotation;
210  const bool use_ransac = false;
211  if(use_ransac) {
212  std::vector<cv::Vec3f> object_points_(chk.corners_count());
213  std::vector<cv::Vec2f> image_points_(chk.corners_count());
214  for(int idx = 0; idx < chk.corners_count(); ++idx) {
215  object_points_[idx] = cv::Vec3f( object_points[idx][0], object_points[idx][1], object_points[idx][2] );
216  image_points_[idx] = cv::Vec2f( image_points[idx][0], image_points[idx][1] );
217  }
218 
219  cv::solvePnPRansac(
220  object_points_,
221  image_points_,
222  intr.K,
223  intr.distortion.cv_coeffs(),
224  rotation_vec,
225  translation,
226  false,
227  300,
228  0.05,
229  chk.corners_count(),
230  cv::noArray(),
231  CV_ITERATIVE
232  );
233  } else {
234  cv::solvePnP(
235  object_points,
236  image_points,
237  intr.K,
238  intr.distortion.cv_coeffs(),
239  rotation_vec,
240  translation,
241  false
242  );
243  }
244  cv::Rodrigues(rotation_vec, rotation);
245 
246  return checkerboard_extrinsics {
247  translation,
248  rotation,
249  rotation_vec
250  };
251 }
252 
253 
255  std::vector<vec3> object_points = checkerboard_world_corners(chk.cols, chk.rows, chk.square_width);
256  std::vector<vec2> image_points = checkerboard_image_corners(chk);
257 
258  std::vector<vec2> reprojected_image_points;
259  cv::projectPoints(
260  object_points,
261  ext.rotation_vec,
262  ext.translation,
263  intr.K,
264  intr.distortion.cv_coeffs(),
265  reprojected_image_points
266  );
267  real err = 0.0;
268  for(int idx = 0; idx < chk.corners_count(); ++idx) {
269  const vec2& i_orig = image_points[idx];
270  const vec2& i_reproj = reprojected_image_points[idx];
271  vec2 diff = i_reproj - i_orig;
272  err += sq(diff[0]) + sq(diff[1]);
273  }
274  err /= chk.corners_count();
275  return std::sqrt(err);
276 }
277 
278 
280  auto direction_deviation = [](const std::vector<vec2>& vecs) {
281  std::vector<vec2> nvecs;
282  for(const vec2& vec : vecs)
283  nvecs.push_back(vec / std::sqrt(sq(vec[0]) + sq(vec[1])));
284 
285  vec2 mean(0.0, 0.0);
286  for(const vec2& nvec : nvecs) mean += nvec;
287  mean *= 1.0/vecs.size();
288 
289  real rms = 0.0;
290  for(const vec2 nvec : nvecs) {
291  vec2 diff = nvec - mean;
292  rms += sq(diff[0]) + sq(diff[1]);
293  }
294  rms /= vecs.size();
295  return std::sqrt(rms);
296  };
297 
298  std::vector<vec2> horizontal_vectors, vertical_vectors;
299  for(int row = 0; row < chk.rows; ++row)
300  horizontal_vectors.push_back(chk.corner(chk.cols-1, row) - chk.corner(0, row));
301 
302  for(int col = 0; col < chk.cols; ++col)
303  vertical_vectors.push_back(chk.corner(col, chk.rows-1) - chk.corner(col, 0));
304 
305  return vec2(
306  direction_deviation(horizontal_vectors),
307  direction_deviation(vertical_vectors)
308  );
309 }
310 
311 
313  // not taking distortion into account
314 
315  real fx = intr.K(0, 0), fy = intr.K(1, 1);
316 
317  real w = (chk.cols - 1) * chk.square_width;
318  real h = (chk.rows - 1) * chk.square_width;
319 
320  real iw = 0.0;
321  for(int row = 0; row < chk.rows; ++row) {
322  vec2 diff = chk.corner(chk.cols-1, row) - chk.corner(0, row);
323  iw += sq(diff[0]) + sq(diff[1]);
324  }
325  iw = std::sqrt(iw / chk.rows);
326 
327  real ih = 0.0;
328  for(int col = 0; col < chk.cols; ++col) {
329  vec2 diff = chk.corner(col, chk.rows-1) - chk.corner(col, 0);
330  ih += sq(diff[0]) + sq(diff[1]);
331  }
332  ih = std::sqrt(ih / chk.cols);
333 
334  real dx = fx * w / iw;
335  real dy = fy * h / ih;
336 
337  //std::cout << "dx: " << dx << "\ndy: " << dy << "\n\n\n";
338 
339  return (dx + dy) / 2.0;
340 }
341 
342 
343 
344 std::vector<checkerboard_pixel_depth_sample> checkerboard_pixel_depth_samples(const checkerboard& chk, const cv::Mat_<float>& depth_image, int granularity) {
345  // region of interest: bounding box + mask
346  cv::Rect bounding_rect = chk.bounding_rect;
347  cv::Mat_<uchar> mask(depth_image.size());
348  mask.setTo(0);
349  std::vector<std::vector<cv::Point>> polylines { chk.outer_corners_i() };
350  cv::fillConvexPoly(mask, chk.outer_corners_i().data(), 4, 255);
351 
352  // get measured depths for each pixel
353  std::vector<checkerboard_pixel_depth_sample> depth_samples;
354  for(int ry = 0; ry < bounding_rect.height; ry += granularity)
355  for(int rx = 0; rx < bounding_rect.width; rx += granularity) {
356  int x = bounding_rect.x + rx, y = bounding_rect.y + ry;
357 
358  if(! mask(y, x)) continue;
359  real d = depth_image(y, x);
360  if(d <= 100.0) continue;
361 
363  samp.coordinates = vec2(x, y);
364  samp.measured_depth = d;
365  samp.calculated_depth = NAN;
366  depth_samples.push_back(samp);
367  }
368 
369  return depth_samples;
370 }
371 
372 
373 
374 void calculate_checkerboard_pixel_depths(const intrinsics& intr, const checkerboard_extrinsics& ext, std::vector<checkerboard_pixel_depth_sample>& inout_samples) {
375  if(inout_samples.size() == 0) return;
376 
377  // get image points
378  std::vector<vec2> image_points;
379  for(const checkerboard_pixel_depth_sample& sample : inout_samples)
380  image_points.push_back(sample.coordinates);
381 
382  // normal vector and distance of checkerboard in camera view space
383  vec3 normal(0.0, 0.0, 1.0);
384  normal = ext.rotation * normal;
385  real plane_distance = normal.dot(ext.translation);
386 
387  // undistort image points, and get normalized view space points with depth 1.0
388  std::vector<vec2> undistorted_normalized_points;
389  cv::undistortPoints(
390  image_points,
391  undistorted_normalized_points,
392  intr.K,
393  intr.distortion.cv_coeffs(),
394  cv::noArray(),
395  cv::noArray()
396  );
397 
398  // calculate distances
399  for(int idx = 0; idx < image_points.size(); ++idx) {
400  vec3 v;
401  v[0] = undistorted_normalized_points[idx][0];
402  v[1] = undistorted_normalized_points[idx][1];
403  v[2] = 1.0;
404 
405  real t = plane_distance / normal.dot(v);
406  v = v * t;
407 
408  inout_samples[idx].calculated_depth = v[2];
409  }
410 }
411 
412 }
Numeric sq(Numeric n)
Compute square of a number.
Definition: misc.h:17
std::array< vec3, Obj_count > object_coordinates
void calculate_checkerboard_pixel_depths(const intrinsics &intr, const checkerboard_extrinsics &ext, std::vector< checkerboard_pixel_depth_sample > &inout_samples)
obj_img_correspondences< 1, 1 > checkerboard_obj_img_correspondences(const checkerboard &chk)
std::vector< vec2 > checkerboard_image_corners(const checkerboard &chk)
int rows
checkerboard_extrinsics estimate_checkerboard_extrinsics(const checkerboard &chk, const intrinsics &intr)
cv::Mat_< cv::Vec3b > visualize_checkerboard(const cv::Mat_< cv::Vec3b > &img, const checkerboard &chk, const checkerboard_visualization_parameters &param)
std::vector< vec2 > outer_corners
Definition: checkerboard.h:16
cv::Vec< real, 2 > vec2
Definition: common.h:22
checkerboard detect_color_checkerboard(cv::Mat_< cv::Vec3b > &img, int cols, int rows, real square_width)
Definition: checkerboard.cc:46
std::vector< vec2 > corners
Definition: checkerboard.h:15
vec2 checkerboard_parallel_measures(const checkerboard &chk)
std::size_t corners_count() const
Definition: checkerboard.h:27
real calculate_parallel_checkerboard_depth(const checkerboard &chk, const intrinsics &intr)
cv::Mat_< cv::Vec3b > visualize_checkerboard_pixel_samples(const cv::Mat_< cv::Vec3b > &img, const std::vector< checkerboard_pixel_depth_sample > &pixels, int rad)
distortion_parameters distortion
Definition: intrinsics.h:30
cv::Matx< real, 3, 3 > mat33
Definition: common.h:26
double real
Definition: common.h:16
cv::Rect bounding_rect
Definition: checkerboard.h:17
std::vector< obj_img_correspondence< Obj_count, Img_count >> obj_img_correspondences
vec2 corner(int col, int row) const
Definition: checkerboard.h:22
std::array< vec2, Img_count > image_coordinates
cv::Vec< real, 3 > vec3
Definition: common.h:23
real square_width
std::vector< vec3 > checkerboard_world_corners(int cols, int rows, real square_width)
int cols
std::vector< real > cv_coeffs() const
Definition: intrinsics.h:17
real checkerboard_reprojection_error(const checkerboard &chk, const intrinsics &intr, const checkerboard_extrinsics &ext)
obj_img_correspondences< 1, 2 > checkerboard_obj_2img_correspondences(const checkerboard &chk1, const checkerboard &chk2)
checkerboard detect_ir_checkerboard(cv::Mat_< uchar > &img, int cols, int rows, real square_width)
Definition: checkerboard.cc:62
std::vector< cv::Point > outer_corners_i() const
Definition: checkerboard.cc:41
std::vector< checkerboard_pixel_depth_sample > checkerboard_pixel_depth_samples(const checkerboard &chk, const cv::Mat_< float > &depth_image, int granularity)
checkerboard()=default