SLProject  4.3.020
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
SENSCalibrationEstimator.cpp
Go to the documentation of this file.
1 /**
2  * \file SENSCalibrationEstimator.cpp
3  * \authors Michael Goettlicher, Marcus Hudritsch
4  * \date Winter 2016
5  * \remarks Please use clangformat to format the code. See more code style on
6  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
7  * \copyright http://opensource.org/licenses/GPL-3.0
8 */
9 
11 #include <SENSCalibration.h>
12 #include <Utils.h>
13 #include <opencv2/imgproc.hpp>
14 #include <opencv2/imgcodecs.hpp>
15 
16 using namespace cv;
17 using namespace std;
18 
19 //-----------------------------------------------------------------------------
21  int camSizeIndex,
22  bool mirroredH,
23  bool mirroredV,
24  SENSCameraType camType,
25  std::string computerInfos,
26  std::string calibDataPath,
27  std::string imageOutputPath,
28  std::string exePath)
29  : _params(params),
30  _camSizeIndex(camSizeIndex),
31  _mirroredH(mirroredH),
32  _mirroredV(mirroredV),
33  _camType(camType),
34  _calibParamsFileName("calib_in_params.yml"),
35  _exception("Undefined error", 0, __FILE__),
36  _computerInfos(computerInfos),
37  _calibDataPath(calibDataPath),
38  _exePath(exePath)
39 {
40  if (!loadCalibParams())
41  {
42  throw SENSCalibrationEstimatorException("Could not load calibration parameter!",
43  __LINE__,
44  __FILE__);
45  }
46 
48  {
49  if (!Utils::dirExists(imageOutputPath))
50  {
51  std::stringstream ss;
52  ss << "Image output directory does not exist: " << imageOutputPath;
53  throw SENSCalibrationEstimatorException(ss.str(),
54  __LINE__,
55  __FILE__);
56  }
57  else
58  {
59  //make subdirectory where images are stored to
60  _calibImgOutputDir = Utils::unifySlashes(imageOutputPath) + "calibimages/";
63  {
64  std::stringstream ss;
65  ss << "Could not create image output directory: " << _calibImgOutputDir;
66  throw SENSCalibrationEstimatorException(ss.str(),
67  __LINE__,
68  __FILE__);
69  }
70  }
71  }
72 }
73 //-----------------------------------------------------------------------------
75 {
76  //wait for the async task to finish
77  if (_calibrationTask.valid())
78  _calibrationTask.wait();
79 }
80 //-----------------------------------------------------------------------------
81 //! Initiates the final calculation
83 {
84  bool calibrationSuccessful = false;
85  if (!_calibrationTask.valid())
86  {
87  _calibrationTask = std::async(std::launch::async, &SENSCalibrationEstimator::calibrateAsync, this);
88  }
89  else if (_calibrationTask.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready)
90  {
93  {
94  Utils::log("SLProject", "Calibration succeeded.");
95  Utils::log("SLProject", "Reproj. error: %f", _reprojectionError);
96  }
97  else
98  {
99  Utils::log("SLProject", "Calibration failed.");
100  }
101  }
102 
103  return calibrationSuccessful;
104 }
105 //-----------------------------------------------------------------------------
107 {
108  if (_imageSize.width == 0 && _imageSize.height == 0)
110  else if (_imageSize.width != _currentImgToExtract.size().width || _imageSize.height != _currentImgToExtract.size().height)
111  {
112  _hasAsyncError = true;
113  _exception = SENSCalibrationEstimatorException("Image size changed during capturing process!",
114  __LINE__,
115  __FILE__);
116  return false;
117  }
118 
119  bool foundPrecisely = false;
120  try
121  {
122  std::vector<cv::Point2f> preciseCorners2D;
123  int flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
124  bool foundPrecisely = cv::findChessboardCorners(_currentImgToExtract,
125  _boardSize,
126  preciseCorners2D,
127  flags);
128 
129  if (foundPrecisely)
130  {
131  cv::cornerSubPix(_currentImgToExtract,
132  preciseCorners2D,
133  cv::Size(11, 11),
134  cv::Size(-1, -1),
135  TermCriteria(TermCriteria::EPS + TermCriteria::COUNT,
136  30,
137  0.0001));
138 
139  //add detected points
140  _imagePoints.push_back(preciseCorners2D);
141  _numCaptured++;
142  }
143  }
144  catch (std::exception& e)
145  {
146  _hasAsyncError = true;
147  _exception = SENSCalibrationEstimatorException(e.what(), __LINE__, __FILE__);
148  return false;
149  }
150  catch (...)
151  {
152  _hasAsyncError = true;
153  _exception = SENSCalibrationEstimatorException("Unknown exception during calibration!", __LINE__, __FILE__);
154  return false;
155  }
156 
157  return foundPrecisely;
158 }
159 //-----------------------------------------------------------------------------
161 {
162  bool ok = false;
163  try
164  {
165  _numCaptured = 0;
166  std::vector<cv::Mat> rvecs, tvecs;
167  vector<float> reprojErrs;
168  cv::Mat cameraMat;
169  cv::Mat distortion;
170 
172  cameraMat,
173  distortion,
174  _imagePoints,
175  rvecs,
176  tvecs,
177  reprojErrs,
179  _boardSize,
183  //correct number of caputured, extraction may have failed
184  if (!rvecs.empty() || !reprojErrs.empty())
185  _numCaptured = (int)std::max(rvecs.size(), reprojErrs.size());
186  else
187  _numCaptured = 0;
188 
189  if (ok)
190  {
191  //instantiate calibration
192  _calibration = std::make_unique<SENSCalibration>(cameraMat,
193  distortion,
194  _imageSize,
195  _boardSize,
198  _numCaptured,
201  _mirroredH,
202  _mirroredV,
203  _camType,
206  true);
207  }
208  }
209  catch (std::exception& e)
210  {
211  _hasAsyncError = true;
212  _exception = SENSCalibrationEstimatorException(e.what(), __LINE__, __FILE__);
213  return false;
214  }
215  catch (...)
216  {
217  _hasAsyncError = true;
218  _exception = SENSCalibrationEstimatorException("Unknown exception during calibration!", __LINE__, __FILE__);
219  return false;
220  }
221 
222  return ok;
223 }
224 //-----------------------------------------------------------------------------
225 //! Calculates the calibration with the given set of image points
227  cv::Mat& cameraMatrix,
228  cv::Mat& distCoeffs,
229  const vector<vector<cv::Point2f>>& imagePoints,
230  std::vector<cv::Mat>& rvecs,
231  std::vector<cv::Mat>& tvecs,
232  vector<float>& reprojErrs,
233  float& totalAvgErr,
234  cv::Size& boardSize,
235  float squareSize,
236  int flag,
237  bool useReleaseObjectMethod)
238 {
239  // Init camera matrix with the eye setter
240  cameraMatrix = cv::Mat::eye(3, 3, CV_64F);
241 
242  // We need to set eleme at 0,0 to 1 if we want a fix aspect ratio
243  if (flag & CALIB_FIX_ASPECT_RATIO)
244  cameraMatrix.at<double>(0, 0) = 1.0;
245 
246  // init the distortion coeffitients to zero
247  distCoeffs = cv::Mat::zeros(8, 1, CV_64F);
248 
249  vector<vector<cv::Point3f>> objectPoints(1);
250 
252  squareSize,
253  objectPoints[0]);
254 
255  objectPoints.resize(imagePoints.size(), objectPoints[0]);
256 
257  ////////////////////////////////////////////////
258  //Find intrinsic and extrinsic camera parameters
259  int iFixedPoint = -1;
260  if (useReleaseObjectMethod)
261  iFixedPoint = boardSize.width - 1;
262 #if 0
263  double rms = cv::calibrateCameraRO(objectPoints,
264  imagePoints,
265  imageSize,
266  iFixedPoint,
267  cameraMatrix,
268  distCoeffs,
269  rvecs,
270  tvecs,
271  cv::noArray(),
272  flag);
273 #else
274  double rms = cv::calibrateCamera(objectPoints,
275  imagePoints,
276  imageSize,
277  //iFixedPoint,
278  cameraMatrix,
279  distCoeffs,
280  rvecs,
281  tvecs,
282  //cv::noArray(),
283  flag);
284 #endif
285  ////////////////////////////////////////////////
286 
287  Utils::log("SLProject", "Re-projection error reported by calibrateCamera: %f", rms);
288 
289  bool ok = cv::checkRange(cameraMatrix) && cv::checkRange(distCoeffs);
290 
291  totalAvgErr = (float)calcReprojectionErrors(objectPoints,
292  imagePoints,
293  rvecs,
294  tvecs,
295  cameraMatrix,
296  distCoeffs,
297  reprojErrs);
298  return ok;
299 }
300 //-----------------------------------------------------------------------------
301 //! Calculates the reprojection error of the calibration
302 double SENSCalibrationEstimator::calcReprojectionErrors(const vector<vector<cv::Point3f>>& objectPoints,
303  const vector<vector<cv::Point2f>>& imagePoints,
304  const std::vector<cv::Mat>& rvecs,
305  const std::vector<cv::Mat>& tvecs,
306  const cv::Mat& cameraMatrix,
307  const cv::Mat& distCoeffs,
308  vector<float>& perViewErrors)
309 {
310  std::vector<cv::Point2f> imagePoints2;
311  size_t totalPoints = 0;
312  double totalErr = 0, err;
313  perViewErrors.resize(objectPoints.size());
314 
315  for (size_t i = 0; i < objectPoints.size(); ++i)
316  {
317  cv::projectPoints(objectPoints[i],
318  rvecs[i],
319  tvecs[i],
320  cameraMatrix,
321  distCoeffs,
322  imagePoints2);
323 
324  err = norm(imagePoints[i], imagePoints2, NORM_L2);
325 
326  size_t n = objectPoints[i].size();
327  perViewErrors[i] = (float)std::sqrt(err * err / n);
328  totalErr += err * err;
329  totalPoints += n;
330  }
331 
332  return std::sqrt(totalErr / totalPoints);
333 }
334 //-----------------------------------------------------------------------------
335 //! Loads the chessboard calibration pattern parameters
337 {
338  FileStorage fs;
339  string fullCalibIniFile = Utils::findFile(_calibParamsFileName,
341  fs.open(fullCalibIniFile, FileStorage::READ);
342  if (!fs.isOpened())
343  {
344  Utils::log("SLProject", "Could not open the calibration parameter file: %s", fullCalibIniFile.c_str());
345  return false;
346  }
347 
348  //assign paramters
349  fs["numInnerCornersWidth"] >> _boardSize.width;
350  fs["numInnerCornersHeight"] >> _boardSize.height;
351  fs["squareSizeMM"] >> _boardSquareMM;
352  fs["numOfImgsToCapture"] >> _numOfImgsToCapture;
353 
354  return true;
355 }
356 //-----------------------------------------------------------------------------
357 void SENSCalibrationEstimator::saveImage(cv::Mat imageGray)
358 {
359  std::stringstream ss;
360  ss << _calibImgOutputDir << "CalibImge_" << Utils::getDateTime2String() << ".jpg";
361  cv::imwrite(ss.str(), imageGray);
362 }
363 //-----------------------------------------------------------------------------
364 void SENSCalibrationEstimator::updateExtractAndCalc(bool found, bool grabFrame, cv::Mat imageGray)
365 {
366  switch (_state)
367  {
368  case State::Streaming:
369  {
370  if (grabFrame && found)
371  {
372  _currentImgToExtract = imageGray.clone();
373  //start async extraction
374  if (!_calibrationTask.valid())
375  {
376  _calibrationTask = std::async(std::launch::async, &SENSCalibrationEstimator::extractAsync, this);
377  }
378 
380  }
381  break;
382  }
384  {
385  //check if async task is ready
386  if (_calibrationTask.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready)
387  {
388  bool extractionSuccessful = _calibrationTask.get();
389 
390  if (_hasAsyncError)
391  {
393  throw _exception;
394  }
395  else if (_numCaptured >= _numOfImgsToCapture)
396  {
397  //if ready and number of capturings exceed number of required start calculation
398  _calibrationTask = std::async(std::launch::async, &SENSCalibrationEstimator::calibrateAsync, this);
400  }
401  else
402  {
404  }
405  }
406  break;
407  }
408  case State::Calculating:
409  {
410  if (_calibrationTask.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready)
411  {
413 
415  {
417  Utils::log("SLProject", "Calibration succeeded.");
418  Utils::log("SLProject", "Reproj. error: %f", _reprojectionError);
419  }
420  else
421  {
422  Utils::log("SLProject", "Calibration failed.");
423  if (_hasAsyncError)
424  {
426  throw _exception;
427  }
428  else
430  }
431  }
432  break;
433  }
434  default: break;
435  }
436 }
437 //-----------------------------------------------------------------------------
438 void SENSCalibrationEstimator::updateOnlyCapture(bool found, bool grabFrame, cv::Mat imageGray)
439 {
440  switch (_state)
441  {
442  case State::Streaming:
443  {
444  if (grabFrame && found)
445  {
446  saveImage(imageGray);
447  _numCaptured++;
448  }
449 
451  {
453  _calibrationSuccessful = true;
454  }
455  break;
456  }
457  default: break;
458  }
459 }
460 //-----------------------------------------------------------------------------
461 //!< Finds the inner chessboard corners in the given image
463  const cv::Mat& imageGray,
464  bool grabFrame,
465  bool drawCorners)
466 {
467  assert(!imageGray.empty() &&
468  "SENSCalibrationEstimator::findChessboard: imageGray is empty!");
469  assert(!imageColor.empty() &&
470  "SENSCalibrationEstimator::findChessboard: imageColor is empty!");
471  assert(_boardSize.width && _boardSize.height &&
472  "SENSCalibrationEstimator::findChessboard: _boardSize is not set!");
473 
474  cv::Size imageSize = imageColor.size();
475 
476  cv::Mat imageGrayExtract = imageGray;
477  //resize image so that we get fluent caputure workflow for high resolutions
478  double scale = 1.0;
479  bool doScale = false;
480  int targetExtractWidth = 640;
481  if (imageSize.width > targetExtractWidth)
482  {
483  doScale = true;
484  scale = (double)imageSize.width / (double)targetExtractWidth;
485  cv::resize(imageGray, imageGrayExtract, cv::Size(), 1 / scale, 1 / scale);
486  }
487 
488  std::vector<cv::Point2f> corners2D;
489  bool found = cv::findChessboardCorners(imageGrayExtract,
490  _boardSize,
491  corners2D,
492  cv::CALIB_CB_FAST_CHECK);
493 
494  if (found)
495  {
496  if (grabFrame && _state == State::Streaming)
497  {
498  //simulate a snapshot
499  cv::bitwise_not(imageColor, imageColor);
500  }
501 
502  if (drawCorners)
503  {
504  if (doScale)
505  {
506  //scale corners into original image size
507  for (cv::Point2f& pt : corners2D)
508  {
509  pt *= scale;
510  }
511  }
512 
513  cv::drawChessboardCorners(imageColor,
514  _boardSize,
515  cv::Mat(corners2D),
516  found);
517  }
518  }
519 
521  {
522  //update state machine for extraction and calculation
523  updateExtractAndCalc(found, grabFrame, imageGray);
524  }
525  else // SENSCalibrationEstimatorParams::EstimatorMode::OnlyCaptureAndSave
526  {
527  updateOnlyCapture(found, grabFrame, imageGray);
528  }
529 
530  return found;
531 }
532 //-----------------------------------------------------------------------------
533 //! Calculates the 3D positions of the chessboard corners
534 void SENSCalibrationEstimator::calcBoardCorners3D(const cv::Size& boardSize,
535  float squareSize,
536  std::vector<cv::Point3f>& objectPoints3D)
537 {
538  // Because OpenCV image coords are top-left we define the according
539  // 3D coords also top-left.
540  objectPoints3D.clear();
541  for (int y = boardSize.height - 1; y >= 0; --y)
542  for (int x = 0; x < boardSize.width; ++x)
543  objectPoints3D.push_back(cv::Point3f((float)x * squareSize,
544  (float)y * squareSize,
545  0));
546 }
special exception that informs about errors during calibration process
std::future< bool > _calibrationTask
future object for calculation of calibration in async task
void saveImage(cv::Mat imageGray)
float _boardSquareMM
Size of chessboard square in mm.
std::unique_ptr< SENSCalibration > _calibration
estimated calibration
bool calculate()
Initiates the final calculation.
void updateOnlyCapture(bool found, bool grabFrame, cv::Mat imageGray)
SENSCalibrationEstimatorException _exception
int _numOfImgsToCapture
NO. of images to capture.
@ BusyExtracting
Estimator is busy extracting the corners of a frame.
@ DoneCaptureAndSave
All images are captured in.
@ Streaming
Estimator waits for new frames.
@ Done
Estimator finished.
@ Calculating
Estimator is currently calculating the calibration.
std::string _calibParamsFileName
name of calibration paramters file
cv::Size _imageSize
Input image size in pixels (after cropping)
float _reprojectionError
Reprojection error after calibration.
SENSCalibrationEstimator(SENSCalibrationEstimatorParams params, int camSizeIndex, bool mirroredH, bool mirroredV, SENSCameraType camType, std::string computerInfos, std::string calibDataPath, std::string imageOutputPath, std::string exePath)
void updateExtractAndCalc(bool found, bool grabFrame, cv::Mat imageGray)
SENSCalibrationEstimatorParams _params
cv::Size _boardSize
NO. of inner chessboard corners.
static double calcReprojectionErrors(const vector< vector< cv::Point3f >> &objectPoints, const vector< vector< cv::Point2f >> &imagePoints, const std::vector< cv::Mat > &rvecs, const std::vector< cv::Mat > &tvecs, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, vector< float > &perViewErrors)
Calculates the reprojection error of the calibration.
bool updateAndDecorate(cv::Mat imageColor, const cv::Mat &imageGray, bool grabFrame, bool drawCorners=true)
< Finds the inner chessboard corners in the given image
bool loadCalibParams()
Loads the chessboard calibration pattern parameters.
int _numCaptured
NO. of images captured.
static void calcBoardCorners3D(const cv::Size &boardSize, float squareSize, std::vector< cv::Point3f > &objectPoints3D)
Calculates the 3D positions of the chessboard corners.
vector< vector< cv::Point2f > > _imagePoints
2D vector of corner points in chessboard
static bool calcCalibration(cv::Size &imageSize, cv::Mat &cameraMatrix, cv::Mat &distCoeffs, const vector< vector< cv::Point2f >> &imagePoints, std::vector< cv::Mat > &rvecs, std::vector< cv::Mat > &tvecs, vector< float > &reprojErrs, float &totalAvgErr, cv::Size &boardSize, float squareSize, int flag, bool useReleaseObjectMethod)
Calculates the calibration with the given set of image points.
Parameterset for the SENSCalibrationEstimator.
string findFile(const string &filename, const vector< string > &pathsToCheck)
Tries to find a filename on various paths to check.
Definition: Utils.cpp:1074
string getDateTime2String()
Returns local time as string like "20190213-154611".
Definition: Utils.cpp:289
string unifySlashes(const string &inputDir, bool withTrailingSlash)
Returns the inputDir string with unified forward slashes, e.g.: "dirA/dirB/".
Definition: Utils.cpp:367
bool dirExists(const string &path)
Returns true if a directory exists.
Definition: Utils.cpp:789
bool makeDir(const string &path)
Creates a directory with given path.
Definition: Utils.cpp:809
void log(const char *tag, const char *format,...)
logs a formatted string platform independently
Definition: Utils.cpp:1100