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