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