CalibrationCreator2.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * ArmarX is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * ArmarX is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * @package VisionX::ArmarXObjects::CalibrationCreator2
17  * @author Christian R. G. Dreher <c.dreher@kit.edu>
18  * @date 2020
19  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20  * GNU General Public License
21  */
22 
23 #include "CalibrationCreator2.h"
24 
25 // VisionX
26 //#include <VisionX/tools/TypeMapping.h>
29 
30 // IVT
31 #include <cstdio>
32 #include <ctime>
33 #include <iostream>
34 #include <sstream>
35 #include <string>
36 
37 #include <sys/time.h>
38 
39 #include <opencv2/calib3d.hpp>
40 #include <opencv2/core.hpp>
41 #include <opencv2/core/utility.hpp>
42 #include <opencv2/highgui.hpp>
43 #include <opencv2/imgcodecs.hpp>
44 #include <opencv2/imgproc.hpp>
45 #include <opencv2/opencv.hpp>
46 #include <opencv2/videoio.hpp>
47 
48 #include "Image/ByteImage.h"
49 #include "Image/ImageProcessor.h"
50 #include "Image/IplImageAdaptor.h"
51 
52 //static inline void read(const FileNode& node, Settings& x, const Settings& default_value = Settings())
53 //{
54 // if(node.empty())
55 // x = default_value;
56 // else
57 // x.read(node);
58 //}
59 
60 //static inline void write(FileStorage& fs, const String&, const Settings& s )
61 //{
62 // s.write(fs);
63 //}
64 
65 namespace
66 {
67 
68 
69  static double
70  computeReprojectionErrors(const std::vector<std::vector<cv::Point3f>>& objectPoints,
71  const std::vector<std::vector<cv::Point2f>>& imagePoints,
72  const std::vector<cv::Mat>& rvecs,
73  const std::vector<cv::Mat>& tvecs,
74  const cv::Mat& cameraMatrix,
75  const cv::Mat& distCoeffs,
76  std::vector<float>& perViewErrors)
77  {
78  std::vector<cv::Point2f> imagePoints2;
79  size_t totalPoints = 0;
80  double totalErr = 0, err;
81  perViewErrors.resize(objectPoints.size());
82 
83  for (size_t i = 0; i < objectPoints.size(); ++i)
84  {
85  cv::projectPoints(
86  objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);
87  err = cv::norm(imagePoints[i], imagePoints2, cv::NORM_L2);
88 
89  size_t n = objectPoints[i].size();
90  perViewErrors[i] = (float)std::sqrt(err * err / n);
91  totalErr += err * err;
92  totalPoints += n;
93  }
94 
95  return std::sqrt(totalErr / totalPoints);
96  }
97 
98  static void
99  calcBoardCornerPositions(cv::Size boardSize,
100  float squareSize,
101  std::vector<cv::Point3f>& corners,
102  visionx::Settings::Pattern patternType /*= Settings::CHESSBOARD*/)
103  {
104  corners.clear();
105 
106  switch (patternType)
107  {
110  for (int i = 0; i < boardSize.height; ++i)
111  for (int j = 0; j < boardSize.width; ++j)
112  {
113  corners.push_back(cv::Point3f(j * squareSize, i * squareSize, 0));
114  }
115  break;
116 
118  for (int i = 0; i < boardSize.height; i++)
119  for (int j = 0; j < boardSize.width; j++)
120  {
121  corners.push_back(
122  cv::Point3f((2 * j + i % 2) * squareSize, i * squareSize, 0));
123  }
124  break;
125  default:
126  break;
127  }
128  }
129 
130  static bool
131  runCalibration(visionx::Settings& s,
132  cv::Size& imageSize,
133  cv::Mat& cameraMatrix,
134  cv::Mat& distCoeffs,
135  std::vector<std::vector<cv::Point2f>> imagePoints,
136  std::vector<cv::Mat>& rvecs,
137  std::vector<cv::Mat>& tvecs,
138  std::vector<float>& reprojErrs,
139  double& totalAvgErr)
140  {
141  //! [fixed_aspect]
142  cameraMatrix = cv::Mat::eye(3, 3, CV_64F);
143  if (s.flag & cv::CALIB_FIX_ASPECT_RATIO)
144  {
145  cameraMatrix.at<double>(0, 0) = s.aspectRatio;
146  }
147  //! [fixed_aspect]
148  distCoeffs = cv::Mat::zeros(8, 1, CV_64F);
149 
150  std::vector<std::vector<cv::Point3f>> objectPoints(1);
151  calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);
152 
153  objectPoints.resize(imagePoints.size(), objectPoints[0]);
154 
155  //Find intrinsic and extrinsic camera parameters
156  double rms;
157 
158  rms = cv::calibrateCamera(
159  objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, s.flag);
160 
161  ARMARX_INFO << "Re-projection error reported by calibrateCamera: " << rms;
162 
163  bool ok = cv::checkRange(cameraMatrix) && cv::checkRange(distCoeffs);
164 
165  totalAvgErr = computeReprojectionErrors(
166  objectPoints, imagePoints, rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs);
167 
168  return ok;
169  }
170 
171  static void
172  saveCameraParams(visionx::Settings& s,
173  cv::Size& imageSize,
174  cv::Mat& cameraMatrix,
175  cv::Mat& distCoeffs,
176  const std::vector<cv::Mat>& rvecs,
177  const std::vector<cv::Mat>& tvecs,
178  const std::vector<float>& reprojErrs,
179  const std::vector<std::vector<cv::Point2f>>& imagePoints,
180  double totalAvgErr)
181  {
182  cv::FileStorage fs(s.outputFileName, cv::FileStorage::WRITE);
183 
184  time_t tm;
185  time(&tm);
186  struct tm* t2 = localtime(&tm);
187  char buf[1024];
188  strftime(buf, sizeof(buf), "%c", t2);
189 
190  fs << "calibration_time" << buf;
191 
192  if (!rvecs.empty() || !reprojErrs.empty())
193  {
194  fs << "nr_of_frames" << (int)std::max(rvecs.size(), reprojErrs.size());
195  }
196  fs << "image_width" << imageSize.width;
197  fs << "image_height" << imageSize.height;
198  fs << "board_width" << s.boardSize.width;
199  fs << "board_height" << s.boardSize.height;
200  fs << "square_size" << s.squareSize;
201 
202  if (s.flag & cv::CALIB_FIX_ASPECT_RATIO)
203  {
204  fs << "fix_aspect_ratio" << s.aspectRatio;
205  }
206 
207  if (s.flag)
208  {
209  std::stringstream flagsStringStream;
210 
211  flagsStringStream << "flags:"
212  << (s.flag & cv::CALIB_USE_INTRINSIC_GUESS ? " +use_intrinsic_guess"
213  : "")
214  << (s.flag & cv::CALIB_FIX_ASPECT_RATIO ? " +fix_aspectRatio" : "")
215  << (s.flag & cv::CALIB_FIX_PRINCIPAL_POINT ? " +fix_principal_point"
216  : "")
217  << (s.flag & cv::CALIB_ZERO_TANGENT_DIST ? " +zero_tangent_dist" : "")
218  << (s.flag & cv::CALIB_FIX_K1 ? " +fix_k1" : "")
219  << (s.flag & cv::CALIB_FIX_K2 ? " +fix_k2" : "")
220  << (s.flag & cv::CALIB_FIX_K3 ? " +fix_k3" : "")
221  << (s.flag & cv::CALIB_FIX_K4 ? " +fix_k4" : "")
222  << (s.flag & cv::CALIB_FIX_K5 ? " +fix_k5" : "")
223  << (s.flag & cv::CALIB_FIX_K6 ? " +fix_k6" : "");
224 
225  fs.writeComment(flagsStringStream.str());
226  }
227 
228  fs << "flags" << s.flag;
229 
230  fs << "camera_matrix" << cameraMatrix;
231  fs << "distortion_coefficients" << distCoeffs;
232 
233  fs << "avg_reprojection_error" << totalAvgErr;
234  if (s.writeExtrinsics && !reprojErrs.empty())
235  {
236  fs << "per_view_reprojection_errors" << cv::Mat(reprojErrs);
237  }
238 
239  if (s.writeExtrinsics && !rvecs.empty() && !tvecs.empty())
240  {
241  CV_Assert(rvecs[0].type() == tvecs[0].type());
242  cv::Mat bigmat((int)rvecs.size(), 6, CV_MAKETYPE(rvecs[0].type(), 1));
243  bool needReshapeR = rvecs[0].depth() != 1 ? true : false;
244  bool needReshapeT = tvecs[0].depth() != 1 ? true : false;
245 
246  for (size_t i = 0; i < rvecs.size(); i++)
247  {
248  cv::Mat r = bigmat(cv::Range(int(i), int(i + 1)), cv::Range(0, 3));
249  cv::Mat t = bigmat(cv::Range(int(i), int(i + 1)), cv::Range(3, 6));
250 
251  if (needReshapeR)
252  {
253  rvecs[i].reshape(1, 1).copyTo(r);
254  }
255  else
256  {
257  //*.t() is MatExpr (not Mat) so we can use assignment operator
258  CV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1);
259  r = rvecs[i].t();
260  }
261 
262  if (needReshapeT)
263  {
264  tvecs[i].reshape(1, 1).copyTo(t);
265  }
266  else
267  {
268  CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1);
269  t = tvecs[i].t();
270  }
271  }
272  fs.writeComment(
273  "a set of 6-tuples (rotation vector + translation vector) for each view");
274  fs << "extrinsic_parameters" << bigmat;
275  }
276 
277  if (s.writePoints && !imagePoints.empty())
278  {
279  cv::Mat imagePtMat((int)imagePoints.size(), (int)imagePoints[0].size(), CV_32FC2);
280  for (size_t i = 0; i < imagePoints.size(); i++)
281  {
282  cv::Mat r = imagePtMat.row(int(i)).reshape(2, imagePtMat.cols);
283  cv::Mat imgpti(imagePoints[i]);
284  imgpti.copyTo(r);
285  }
286  fs << "image_points" << imagePtMat;
287  }
288  }
289 
290  bool
291  runCalibrationAndSave(visionx::Settings& s,
292  cv::Size imageSize,
293  cv::Mat& cameraMatrix,
294  cv::Mat& distCoeffs,
295  std::vector<std::vector<cv::Point2f>> imagePoints)
296  {
297  std::vector<cv::Mat> rvecs, tvecs;
298  std::vector<float> reprojErrs;
299  double totalAvgErr = 0;
300 
301  bool ok = runCalibration(s,
302  imageSize,
303  cameraMatrix,
304  distCoeffs,
305  imagePoints,
306  rvecs,
307  tvecs,
308  reprojErrs,
309  totalAvgErr);
310  ARMARX_INFO << (ok ? "Calibration succeeded" : "Calibration failed")
311  << ". avg re projection error = " << totalAvgErr;
312 
313  if (ok)
314  saveCameraParams(s,
315  imageSize,
316  cameraMatrix,
317  distCoeffs,
318  rvecs,
319  tvecs,
320  reprojErrs,
321  imagePoints,
322  totalAvgErr);
323  return ok;
324  }
325 
326 } // namespace
327 
328 namespace visionx
329 {
330 
331  const std::string CalibrationCreator2::default_name = "CalibrationCreator2";
332 
333  void
335  {
336  providerName = getProperty<std::string>("ImageProviderAdapterName").getValue();
337  usingImageProvider(providerName);
338 
339  getProperty(m_outputFileName, "OutputFileName");
340  waitingTimeBetweenCaptures =
341  IceUtil::Time::milliSeconds(getProperty<int>("WaitingIntervalBetweenImages"));
342  std::string image_side = getProperty<std::string>("ImageToUse").getValue();
343  if (image_side == "left")
344  {
345  imageID = 0;
346  }
347  else if (image_side == "right")
348  {
349  imageID = 1;
350  }
351  else
352  {
353  throw std::runtime_error("ImageToUse must be either left or right");
354  }
355  }
356 
357  void
359  {
360  visionx::ImageProviderInfo imageProviderInfo = getImageProvider(providerName);
361  imageProviderPrx = getProxy<ImageProviderInterfacePrx>(providerName);
362  StereoCalibrationInterfacePrx calibrationProvider =
363  StereoCalibrationInterfacePrx::checkedCast(imageProviderInfo.proxy);
364  auto cam = calibrationProvider->getStereoCalibration();
365  auto calib = imageID == 0 ? cam.calibrationLeft : cam.calibrationRight;
366  auto focal_lengths = calib.cameraParam.focalLength;
367 
368  cameraImages = new CByteImage*[2];
369  cameraImages[0] = tools::createByteImage(imageProviderInfo);
370  cameraImages[1] = tools::createByteImage(imageProviderInfo);
371 
372  // cv::Size ; The size of the board -> Number of items by width and height
373  getProperty(m_settings.boardSize.width, "NumberOfColumns");
374  getProperty(m_settings.boardSize.height, "NumberOfRows");
375  // Pattern ; One of the Chessboard, circles, or asymmetric circle pattern
376  m_settings.calibrationPattern = Settings::Pattern::CHESSBOARD;
377  // float ; The size of a square in your defined unit (point, millimeter,etc).
378  getProperty(m_settings.squareSize, "PatternSquareSize");
379  // int ; The number of frames to use from the input for calibration
380  getProperty(m_settings.nrFrames, "NumberOfImages");
381  //float ; The aspect ratio
382  if (getProperty<bool>("EnableFixAspectRatio"))
383  {
384  m_settings.aspectRatio = focal_lengths.at(0) / focal_lengths.at(1);
385  }
386  else
387  {
388  m_settings.aspectRatio = 0;
389  }
390  //bool ; Write detected feature points
391  m_settings.writePoints = true;
392  //bool ; Write extrinsic parameters
393  m_settings.writeExtrinsics = false;
394  //bool ; Flip the captured images around the horizontal axis
395  m_settings.flipVertical = false;
396  //std::string ; The name of the file where to write
397  m_settings.outputFileName = m_outputFileName;
398  //bool ;
399  getProperty(m_settings.useK3ToK6, "UseAdditionalParams");
400 
401  m_settings.validate();
402  if (not m_settings.goodInput)
403  {
404  throw std::runtime_error("Invalid input detected. Application stopping.");
405  }
406 
407  ARMARX_INFO << "Board info: " << m_settings.boardSize.width << "x"
408  << m_settings.boardSize.height << " squares with square size of "
409  << m_settings.squareSize;
410  }
411 
412  void
414  {
415  delete cameraImages[0];
416  delete cameraImages[1];
417  delete[] cameraImages;
418  }
419 
420  void
422  {
423  }
424 
425  void
427  {
428  cv::Mat view;
429 
430  if (not waitForImages(1000))
431  {
432  return;
433  }
434 
435  if (IceUtil::Time::now() - timeOfLastCapture < waitingTimeBetweenCaptures)
436  {
437  return;
438  }
439 
440  ARMARX_INFO << "Capturing image for calibration #" << imagePoints.size();
441  getImages(cameraImages);
442  timeOfLastCapture = IceUtil::Time::now();
443  visionx::imrec::convert(*cameraImages[imageID], view);
444 
445  //----- If no more image, or got enough, then stop calibration and show result -------------
446  if (m_mode == CAPTURING && imagePoints.size() >= (size_t)m_settings.nrFrames)
447  {
448  ARMARX_IMPORTANT << "Running calibration now...";
449  if (runCalibrationAndSave(m_settings, imageSize, cameraMatrix, distCoeffs, imagePoints))
450  {
451  m_mode = CALIBRATED;
452  }
453  ARMARX_IMPORTANT << "Done writing calibration file.";
454  }
455 
456  imageSize = view.size(); // Format input image.
457  if (m_settings.flipVertical)
458  {
459  cv::flip(view, view, 0);
460  }
461 
462  //! [find_pattern]
463  std::vector<cv::Point2f> pointBuf;
464 
465  bool found;
466 
467  int chessBoardFlags =
468  cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_FAST_CHECK;
469 
470  switch (m_settings.calibrationPattern) // Find feature points on the input format
471  {
473  ARMARX_DEBUG << "Searching chessboard corners...";
474  found = cv::findChessboardCorners(
475  view, m_settings.boardSize, pointBuf, chessBoardFlags);
476  break;
478  found = cv::findCirclesGrid(view, m_settings.boardSize, pointBuf);
479  break;
481  found = cv::findCirclesGrid(
482  view, m_settings.boardSize, pointBuf, cv::CALIB_CB_ASYMMETRIC_GRID);
483  break;
484  default:
485  found = false;
486  break;
487  }
488  //! [find_pattern]
489 
490  //! [pattern_found]
491  if (found) // If done with success,
492  {
493  // improve the found corners' coordinate accuracy for chessboard
494  if (m_settings.calibrationPattern == Settings::CHESSBOARD)
495  {
496  cv::Mat viewGray;
497  cv::cvtColor(view, viewGray, cv::COLOR_BGR2GRAY);
498  cv::cornerSubPix(
499  viewGray,
500  pointBuf,
501  cv::Size(11, 11),
502  cv::Size(-1, -1),
503  cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.1));
504  }
505 
506  if (m_mode == CAPTURING)
507  {
508  imagePoints.push_back(pointBuf);
509  }
510 
511  // Draw the corners.
512  cv::drawChessboardCorners(view, m_settings.boardSize, cv::Mat(pointBuf), found);
513  }
514  //! [pattern_found]
515 
516  //----------------------------- Output Text ------------------------------------------------
517  //! [output_text]
518 
519  // //------------------------- Video capture output undistorted ------------------------------
520  // //! [output_undistorted]
521  // if (m_mode == CALIBRATED && m_settings.showUndistorsed)
522  // {
523  // cv::Mat temp = view.clone();
524  // cv::undistort(temp, view, cameraMatrix, distCoeffs);
525  // }
526  // //! [output_undistorted]
527 
528  //------------------------------ Show image and check for input commands -------------------
529  //! [await_input]
530  }
531 
532 } // namespace visionx
visionx::Settings::Pattern
Pattern
Definition: CalibrationCreator2.h:57
visionx::Settings::validate
void validate()
Definition: CalibrationCreator2.h:66
visionx::Settings::calibrationPattern
Pattern calibrationPattern
Definition: CalibrationCreator2.h:118
ARMARX_IMPORTANT
#define ARMARX_IMPORTANT
Definition: Logging.h:190
visionx::Settings::writePoints
bool writePoints
Definition: CalibrationCreator2.h:122
visionx
ArmarX headers.
Definition: OpenPoseStressTest.h:38
CalibrationCreator2.h
visionx::Settings::useK3ToK6
bool useK3ToK6
Definition: CalibrationCreator2.h:126
visionx::Settings::flipVertical
bool flipVertical
Definition: CalibrationCreator2.h:124
visionx::Settings::CIRCLES_GRID
@ CIRCLES_GRID
Definition: CalibrationCreator2.h:61
visionx::ImageProcessor::getImageProvider
ImageProviderInfo getImageProvider(std::string name, ImageType destinationImageType=eRgb, bool waitForProxy=false)
Select an ImageProvider.
Definition: ImageProcessor.cpp:167
visionx::Settings::goodInput
bool goodInput
Definition: CalibrationCreator2.h:128
visionx::imrecman::ok
@ ok
Definition: ImageRecordingManagerInterface.ice:45
visionx::Settings::squareSize
double squareSize
Definition: CalibrationCreator2.h:119
visionx::tools::createByteImage
CByteImage * createByteImage(const ImageFormatInfo &imageFormat, const ImageType imageType)
Creates a ByteImage for the destination type specified in the given imageProviderInfo.
visionx::Settings
Definition: CalibrationCreator2.h:50
visionx::ImageProviderInfo
Definition: ImageProcessor.h:479
visionx::CalibrationCreator2::onConnectImageProcessor
void onConnectImageProcessor() override
Implement this method in the ImageProcessor in order execute parts when the component is fully initia...
Definition: CalibrationCreator2.cpp:358
visionx::imrec::convert
void convert(const CByteImage &in, cv::Mat &out)
Converts an IVT CByteImage to OpenCV's BGR Mat.
Definition: helper.cpp:40
visionx::Settings::ASYMMETRIC_CIRCLES_GRID
@ ASYMMETRIC_CIRCLES_GRID
Definition: CalibrationCreator2.h:62
visionx::Settings::nrFrames
int nrFrames
Definition: CalibrationCreator2.h:120
visionx::Settings::aspectRatio
float aspectRatio
Definition: CalibrationCreator2.h:121
visionx::CalibrationCreator2::process
void process() override
Process the vision component.
Definition: CalibrationCreator2.cpp:426
visionx::Settings::outputFileName
std::string outputFileName
Definition: CalibrationCreator2.h:125
ARMARX_DEBUG
#define ARMARX_DEBUG
Definition: Logging.h:184
visionx::ImageProcessor::usingImageProvider
void usingImageProvider(std::string name)
Registers a delayed topic subscription and a delayed provider proxy retrieval which all will be avail...
Definition: ImageProcessor.cpp:128
EPS
#define EPS
Definition: gdiam.cpp:1945
max
T max(T t1, T t2)
Definition: gdiam.h:51
visionx::ImageProcessor::getImages
int getImages(CByteImage **ppImages)
Poll images from provider.
Definition: ImageProcessor.cpp:395
visionx::Settings::CHESSBOARD
@ CHESSBOARD
Definition: CalibrationCreator2.h:60
GfxTL::sqrt
VectorXD< D, T > sqrt(const VectorXD< D, T > &a)
Definition: VectorXD.h:704
visionx::CalibrationCreator2::onExitImageProcessor
void onExitImageProcessor() override
Exit the ImapeProcessor component.
Definition: CalibrationCreator2.cpp:421
visionx::ImageProviderInfo::proxy
ImageProviderInterfacePrx proxy
proxy to image provider
Definition: ImageProcessor.h:485
float
#define float
Definition: 16_Level.h:22
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
visionx::CalibrationCreator2::default_name
static const std::string default_name
Definition: CalibrationCreator2.h:135
armarx::PropertyUser::getProperty
Property< PropertyType > getProperty(const std::string &name)
Property creation and retrieval.
Definition: PropertyUser.h:180
visionx::Settings::boardSize
cv::Size boardSize
Definition: CalibrationCreator2.h:117
visionx::Settings::writeExtrinsics
bool writeExtrinsics
Definition: CalibrationCreator2.h:123
ImageUtil.h
helper.h
visionx::CalibrationCreator2::onDisconnectImageProcessor
void onDisconnectImageProcessor() override
Implement this method in the ImageProcessor in order execute parts when the component looses network ...
Definition: CalibrationCreator2.cpp:413
visionx::CalibrationCreator2::onInitImageProcessor
void onInitImageProcessor() override
Setup the vision component.
Definition: CalibrationCreator2.cpp:334
armarx::ctrlutil::s
double s(double t, double s0, double v0, double a0, double j)
Definition: CtrlUtil.h:33
visionx::ImageProcessor::waitForImages
bool waitForImages(int milliseconds=1000)
Wait for new images.
Definition: ImageProcessor.cpp:309
norm
double norm(const Point &a)
Definition: point.hpp:102