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
65namespace
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
328namespace 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
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
#define float
Definition 16_Level.h:22
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.
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.