13std::string formatCalibrationMatrix(
const cv::Mat& matrix)
15 std::ostringstream stream;
20void logCameraCalibration(
const char* cameraName,
23 yInfo() <<
"[CAMERA_CALIBRATION_" << cameraName <<
"]";
25 yInfo() <<
"fx" << camera.
K.at<
double>(0, 0)
26 <<
"fy" << camera.
K.at<
double>(1, 1)
27 <<
"cx" << camera.
K.at<
double>(0, 2)
28 <<
"cy" << camera.
K.at<
double>(1, 2);
29 yInfo() <<
"D = [k1 k2 k3 k4] =" << formatCalibrationMatrix(camera.
D.t());
30 yInfo() <<
"K =" << formatCalibrationMatrix(camera.
K);
31 yInfo() <<
"monocular RMS =" << camera.
rms;
36 yInfo() <<
"========== Fisheye calibration result (not written to disk) ==========";
40 logCameraCalibration(
"LEFT", result.
leftCamera);
49 cv::Mat homogeneousTransform = cv::Mat::eye(4, 4, CV_64F);
50 result.
stereo.
R.copyTo(homogeneousTransform(cv::Rect(0, 0, 3, 3)));
51 result.
stereo.
T.reshape(1, 3).copyTo(homogeneousTransform(cv::Rect(3, 0, 1, 3)));
53 yInfo() <<
"[STEREO_DISPARITY]";
54 yInfo() <<
"Stereo RMS =" << result.
stereo.
rms
55 <<
"baseline norm =" << cv::norm(result.
stereo.
T);
56 yInfo() <<
"R =" << formatCalibrationMatrix(result.
stereo.
R);
57 yInfo() <<
"T =" << formatCalibrationMatrix(result.
stereo.
T.t());
59 yInfo() <<
"HN =" << formatCalibrationMatrix(homogeneousTransform);
68 yInfo() <<
"Q =" << formatCalibrationMatrix(result.
rectification.
Q);
73 yInfo() <<
"Rectification vertical error [mean median RMS p95 max] ="
80 yInfo() <<
"======================================================================";
88 this->maxQueueSize = maxQueueSize;
96 std::lock_guard<std::mutex> lock(_mutex);
105 frame.
image.copy(leftFrame);
106 frame.
stamp = timestamp;
108 leftQueue.push_back(std::move(frame));
117 frame.
image.copy(rightFrame);
118 frame.
stamp = timestamp;
120 rightQueue.push_back(std::move(frame));
125void StereoPairSynchronizer::trimLeftQueue()
127 while(leftQueue.size() > maxQueueSize)
129 leftQueue.pop_front();
130 std::lock_guard<std::mutex> lock(_mutex);
135void StereoPairSynchronizer::trimRightQueue()
137 while(rightQueue.size() > maxQueueSize)
139 rightQueue.pop_front();
140 std::lock_guard<std::mutex> lock(_mutex);
147 while(!leftQueue.empty() && !rightQueue.empty())
149 const double leftStamp = leftQueue.front().stamp.getTime();
150 const double rightStamp = rightQueue.front().stamp.getTime();
152 const double absoluteDelta = std::abs(leftStamp - rightStamp);
153 if(absoluteDelta <= toleranceSeconds)
155 pair.
left = std::move(leftQueue.front().image);
156 pair.
right = std::move(rightQueue.front().image);
157 pair.
leftStamp = leftQueue.front().stamp;
162 leftQueue.pop_front();
163 rightQueue.pop_front();
165 std::lock_guard<std::mutex> lock(_mutex);
173 if(leftStamp < rightStamp)
176 leftQueue.pop_front();
177 std::lock_guard<std::mutex> lock(_mutex);
182 rightQueue.pop_front();
183 std::lock_guard<std::mutex> lock(_mutex);
193 moduleName=rf.check(
"name", Value(
"stereoCalib"),
"module name (string)").asString().c_str();
194 robotName=rf.check(
"robotName",Value(
"icub"),
"module name (string)").asString().c_str();
196 this->inputLeftPortName =
"/"+moduleName;
197 this->inputLeftPortName +=rf.check(
"imgLeft",Value(
"/cam/left:i"),
"Input image port (string)").asString().c_str();
199 this->inputRightPortName =
"/"+moduleName;
200 this->inputRightPortName += rf.check(
"imgRight", Value(
"/cam/right:i"),
"Input image port (string)").asString().c_str();
202 this->outNameRight =
"/"+moduleName;
203 this->outNameRight += rf.check(
"outRight",Value(
"/cam/right:o"),
"Output image port (string)").asString().c_str();
205 this->outNameLeft =
"/"+moduleName;
206 this->outNameLeft +=rf.check(
"outLeft",Value(
"/cam/left:o"),
"Output image port (string)").asString().c_str();
208 Bottle stereoCalibOpts=rf.findGroup(
"STEREO_CALIBRATION_CONFIGURATION");
209 this->boardWidth = stereoCalibOpts.check(
"boardWidth", Value(8)).asInt32();
210 this->boardHeight= stereoCalibOpts.check(
"boardHeight", Value(6)).asInt32();
211 this->numOfPairs= stereoCalibOpts.check(
"numberOfPairs", Value(30)).asInt32();
212 if(this->numOfPairs < 3)
214 yWarning() <<
"numberOfPairs must be at least 3; using 3";
215 this->numOfPairs = 3;
217 this->squareSize= (float)stereoCalibOpts.check(
"boardSize", Value(0.09241)).asFloat64();
218 this->boardType= stereoCalibOpts.check(
"boardType", Value(
"CHESSBOARD")).asString();
219 const double syncToleranceMs = stereoCalibOpts.check(
"syncToleranceMs", Value(20.0)).asFloat64();
220 _syncToleranceSeconds = syncToleranceMs / 1000.0;
221 const int configuredQueueSize = stereoCalibOpts.check(
"syncQueueSize", Value(5)).asInt32();
222 if(configuredQueueSize <= 0)
224 yWarning() <<
"Invalid syncQueueSize; using 5";
229 _syncQueueSize =
static_cast<std::size_t
>(configuredQueueSize);
232 if(_syncToleranceSeconds <= 0.0)
234 yWarning() <<
"Invalid syncToleranceMs; using 20 ms";
235 _syncToleranceSeconds = 0.020;
238 synchronizer.
configure(_syncToleranceSeconds, _syncQueueSize);
240 this->minCaptureIntervalSeconds = stereoCalibOpts.check(
"minCaptureIntervalSeconds", Value(2.0)).asFloat64();
241 this->minimumBoardSpanRatio = stereoCalibOpts.check(
"minimumBoardSpanRatio", Value(0.15)).asFloat64();
243 this->commandPort=commPort;
244 this->imageDir=imageDir;
245 this->collectionResetRequested.store(
false);
246 this->calibrationState.store(CalibrationState::Idle);
247 this->currentPathDir=rf.getHomeContextPath().c_str();
248 const bool legacyMonoRequested =
249 stereoCalibOpts.check(
"MonoCalib", Value(0)).asInt32() != 0;
253 this->camCalibFile=rf.getHomeContextPath().c_str();
254 this->standalone = rf.check(
"standalone");
255 string fileName=
"outputCalib.ini";
257 this->camCalibFile=this->camCalibFile+
"/"+fileName.c_str();
259 _observationsFile = stereoCalibOpts.check(
260 "observationsFile", Value(
"calibrationObservations.yml")).asString();
261 if(!_observationsFile.empty() && _observationsFile.front() !=
'/')
263 _observationsFile = this->imageDir +
"/" + _observationsFile;
267 _chessboardConfiguration.
cornersX = this->boardWidth;
268 _chessboardConfiguration.
cornersY = this->boardHeight;
272 _saveImages = stereoCalibOpts.check(
"saveImages", Value(1)).asInt32() != 0;
273 _drawDiagnosticCorners = stereoCalibOpts.check(
"drawDiagnosticCorners", Value(1)).asInt32() != 0;
275 const std::string configuredMode = stereoCalibOpts.check(
"calibrationMode", Value(
"")).asString();
276 if(configuredMode ==
"MonocularLeft")
280 else if(configuredMode ==
"MonocularRight")
284 else if(configuredMode ==
"MonocularBoth")
288 else if(configuredMode.empty() && legacyMonoRequested)
290 yWarning() <<
"MonoCalib is deprecated; using MonocularLeft with the synchronized observation pipeline.";
293 else if(configuredMode.empty() || configuredMode ==
"StereoFull")
299 yWarning() <<
"Unknown calibrationMode; using StereoFull:" << configuredMode;
303 if(!_chessboardConfiguration.
isValid())
305 yError() <<
"Invalid chessboard configuration";
311 if (!imagePortInLeft.open(inputLeftPortName.c_str())) {
312 cout <<
": unable to open port " << inputLeftPortName << endl;
316 if (!imagePortInRight.open(inputRightPortName.c_str())) {
317 cout <<
": unable to open port " << inputRightPortName << endl;
321 if (!outPortLeft.open(outNameLeft.c_str())) {
322 cout <<
": unable to open port " << outNameLeft << endl;
326 if (!outPortRight.open(outNameRight.c_str())) {
327 cout <<
": unable to open port " << outNameRight << endl;
332 if(!stereo || standalone)
return true;
335 optHead.put(
"device",
"remote_controlboard");
336 optHead.put(
"remote",(
"/"+robotName+
"/head").c_str());
337 optHead.put(
"local",
"/"+moduleName+
"/client/head");
338 if (polyHead.open(optHead))
339 polyHead.view(posHead);
342 cout<<
"Devices not available"<<endl;
347 optTorso.put(
"device",
"remote_controlboard");
348 optTorso.put(
"remote",(
"/"+robotName+
"/torso").c_str());
349 optTorso.put(
"local",
"/"+moduleName+
"/client/torso");
352 if (polyTorso.open(optTorso))
353 polyTorso.view(posTorso);
356 yWarning(
"Unable to connect to torso! Continuing without...");
360 yarp::sig::Vector head_angles(6,0.0);
361 posHead->getEncoders(head_angles.data());
363 yarp::sig::Vector torso_angles(3,0.0);
365 posTorso->getEncoders(torso_angles.data());
367 qL.resize(torso_angles.length()+head_angles.length()-1);
368 for(
size_t i=0; i<torso_angles.length(); i++)
369 qL[i]=torso_angles[torso_angles.length()-i-1];
371 for(
size_t i=0; i<head_angles.length()-2; i++)
372 qL[i+torso_angles.length()]=head_angles[i];
373 qL[7]=head_angles[4]+(0.5-(
LEFT))*head_angles[5];
376 qR.resize(torso_angles.length()+head_angles.length()-1);
377 for(
size_t i=0; i<torso_angles.length(); i++)
378 qR[i]=torso_angles[torso_angles.length()-i-1];
380 for(
size_t i=0; i<head_angles.length()-2; i++)
381 qR[i+torso_angles.length()]=head_angles[i];
382 qR[7]=head_angles[4]+(0.5-(
RIGHT))*head_angles[5];
388 yInfo(
"Running synchronized fisheye calibration pipeline... \n");
392void stereoCalibThread::processSynchronizedPair(
SynchronizedPair& pair, Size boardSize)
396 const double previousProcessedCandidateTime = lastProcessedCandidateTime;
397 if(previousProcessedCandidateTime >= 0.0 && (pairTime - previousProcessedCandidateTime) < minCaptureIntervalSeconds)
399 yDebug() <<
"Skipping candidate pair due to minimum capture interval";
402 if(previousProcessedCandidateTime >= 0.0)
404 yDebug() <<
"Timestamp delta between processed pairs:" << (pairTime - previousProcessedCandidateTime) <<
"seconds";
406 lastProcessedCandidateTime = pairTime;
411 const Size leftSize(pair.
left.width(), pair.
left.height());
412 const Size rightSize(pair.
right.width(), pair.
right.height());
414 if(leftSize != _expectedImageSize || rightSize != _expectedImageSize)
416 if(_expectedImageSize.empty())
418 _expectedImageSize = leftSize;
422 if(leftSize != _expectedImageSize || rightSize != _expectedImageSize)
424 yError() <<
"Left and right images have different sizes:" <<
425 "Left:" << leftSize.width <<
"x" << leftSize.height <<
426 "Right:" << rightSize.width <<
"x" << rightSize.height;
428 std::lock_guard<std::mutex> lock(mtx);
429 _calibrationError =
"Input images do not match the expected calibration resolution.";
432 calibrationState.store(CalibrationState::Error);
437 LeftRgb=yarp::cv::toCvMat(pair.
left);
438 RightRgb=yarp::cv::toCvMat(pair.
right);
441 Mat leftGray, rightGray;
442 cvtColor(LeftRgb,leftGray,CV_RGB2GRAY);
443 cvtColor(RightRgb,rightGray,CV_RGB2GRAY);
445 std::vector<Point2f> leftCorners;
446 std::vector<Point2f> rightCorners;
448 if(boardType ==
"CIRCLES_GRID") {
449 foundL = findCirclesGrid(LeftRgb, boardSize, leftCorners, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
450 foundR = findCirclesGrid(RightRgb, boardSize, rightCorners, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
451 }
else if(boardType ==
"ASYMMETRIC_CIRCLES_GRID") {
452 foundL = findCirclesGrid(LeftRgb, boardSize, leftCorners, CALIB_CB_ASYMMETRIC_GRID | CALIB_CB_CLUSTERING);
453 foundR = findCirclesGrid(RightRgb, boardSize, rightCorners, CALIB_CB_ASYMMETRIC_GRID | CALIB_CB_CLUSTERING);
454 }
else if(boardType ==
"CHESSBOARD_SECTOR_BASED") {
455 foundL = findChessboardCornersSB(leftGray, boardSize, leftCorners);
456 foundR = findChessboardCornersSB(rightGray, boardSize, rightCorners);
458 foundL = findChessboardCorners(leftGray, boardSize, leftCorners, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE | CALIB_CB_FILTER_QUADS);
459 foundR = findChessboardCorners(rightGray, boardSize, rightCorners, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE | CALIB_CB_FILTER_QUADS);
464 yDebug() <<
"Found chessboard corners in both left and right images";
466 const Rect leftBoardBounds = boundingRect(leftCorners);
467 const Rect rightBoardBounds = boundingRect(rightCorners);
468 const bool leftBoardTooSmall =
469 leftBoardBounds.width < leftSize.width * minimumBoardSpanRatio ||
470 leftBoardBounds.height < leftSize.height * minimumBoardSpanRatio;
471 const bool rightBoardTooSmall =
472 rightBoardBounds.width < rightSize.width * minimumBoardSpanRatio ||
473 rightBoardBounds.height < rightSize.height * minimumBoardSpanRatio;
475 if(leftBoardTooSmall || rightBoardTooSmall)
477 yWarning() <<
"Skipping stereo pair: chessboard is too small."
478 <<
"Left board:" << leftBoardBounds.width <<
"x" << leftBoardBounds.height
479 <<
"of" << leftSize.width <<
"x" << leftSize.height <<
";"
480 <<
"right board:" << rightBoardBounds.width <<
"x" << rightBoardBounds.height
481 <<
"of" << rightSize.width <<
"x" << rightSize.height <<
"."
482 <<
"Each board must span at least" << (minimumBoardSpanRatio * 100.0)
483 <<
"% of both image dimensions.";
485 ++_rejectedDetections;
489 TermCriteria criteria = TermCriteria(TermCriteria::EPS+TermCriteria::COUNT, 30, 0.01);
490 cornerSubPix(leftGray, leftCorners, Size(5,5), Size(-1,-1), criteria);
491 cornerSubPix(rightGray, rightCorners, Size(5,5), Size(-1,-1), criteria);
511 yError() <<
"Generated invalid stereo observation";
512 ++_rejectedDetections;
516 std::size_t observationIndex = 0;
517 observationIndex = _observations.size();
523 std::string imageError;
530 yError() <<
"Could not save accepted calibration image pair:" << imageError;
532 std::lock_guard<std::mutex> lock(mtx);
533 _calibrationError = imageError;
536 calibrationState.store(CalibrationState::Error);
541 _observations.push_back(std::move(observation));
545 if(_drawDiagnosticCorners)
547 drawChessboardCorners(LeftRgb, boardSize, leftCorners, foundL);
548 drawChessboardCorners(RightRgb, boardSize, rightCorners, foundR);
551 ImageOf<PixelRgb>& outimL = outPortLeft.prepare();
556 ImageOf<PixelRgb>& outimR = outPortRight.prepare();
559 outPortRight.write();
564 ++_rejectedDetections;
574 std::lock_guard<std::mutex> lock(mtx);
576 switch(calibrationState.load())
578 case CalibrationState::Idle:
579 result.
state =
"Idle";
581 case CalibrationState::Collecting:
582 result.
state =
"Collecting";
584 case CalibrationState::Calibrating:
585 result.
state =
"Calibrating";
587 case CalibrationState::Completed:
588 result.
state =
"Completed";
590 case CalibrationState::Error:
591 result.
state =
"Error";
601 if(syncStats.pairedFrames > 0)
603 result.
meanTimestampDeltaMs = 1000.0 * syncStats.accumulatedTimeStampDelta /
static_cast<double>(syncStats.pairedFrames);
609 if(calibrationState.load() == CalibrationState::Completed && _calibrationResults.
isValid())
612 switch(_calibrationResults.
mode)
655bool stereoCalibThread::shouldQueueFrameForCollection(
const Stamp& timestamp)
const
657 return lastProcessedCandidateTime < 0.0 ||
658 (timestamp.getTime() - lastProcessedCandidateTime) >= minCaptureIntervalSeconds;
661void stereoCalibThread::stereoCalibRun()
663 Size boardSize, imageSize;
664 boardSize.width=this->boardWidth;
665 boardSize.height=this->boardHeight;
668 while (!isStopping())
673 if(calibrationState.load() == CalibrationState::Error)
679 if(collectionResetRequested.exchange(
false))
681 synchronizer.
reset();
682 lastProcessedCandidateTime = -1.0;
683 _observations.clear();
684 _rejectedDetections = 0;
686 std::lock_guard<std::mutex> lock(mtx);
688 _calibrationError.clear();
692 bool areFramesReceived =
false;
693 ImageOf<PixelRgb> *tmpL = imagePortInLeft.read(
false);
697 areFramesReceived =
true;
700 imagePortInLeft.getEnvelope(TSLeft);
703 ImageOf<PixelRgb>& outimL = outPortLeft.prepare();
705 outPortLeft.setEnvelope(TSLeft);
709 if(calibrationState.load() == CalibrationState::Collecting &&
710 shouldQueueFrameForCollection(TSLeft))
712 synchronizer.
pushLeft(*tmpL, TSLeft);
716 ImageOf<PixelRgb> *tmpR = imagePortInRight.read(
false);
719 areFramesReceived =
true;
722 imagePortInRight.getEnvelope(TSRight);
725 ImageOf<PixelRgb>& outimR=outPortRight.prepare();
727 outPortRight.setEnvelope(TSRight);
728 outPortRight.write();
732 if(calibrationState.load() == CalibrationState::Collecting &&
733 shouldQueueFrameForCollection(TSRight))
739 std::vector<stereo_calib::StereoObservation> observationSnapshot;
741 std::lock_guard<std::mutex> lock(mtx);
742 if(calibrationState.load() == CalibrationState::Collecting)
748 processSynchronizedPair(pair, boardSize);
750 if(_observations.size() >=
static_cast<std::size_t
>(numOfPairs))
752 yInfo(
"Collected %zu valid stereo observations. Stopping collection.", _observations.size());
753 observationSnapshot = _observations;
754 calibrationState.store(CalibrationState::Calibrating);
755 yInfo() <<
"Observation collection complete";
762 if(calibrationState.load() == CalibrationState::Calibrating)
764 yInfo() <<
"Starting the calibration process";
765 if(observationSnapshot.empty())
767 observationSnapshot = _observations;
769 _calibrationOptions.
imageSize = observationSnapshot.front().imageSize;
773 std::string calibrationError;
774 const bool success = _calibrationEngine.
calibrate(
784 yError() <<
"Fisheye calibration failed:" << calibrationError;
786 std::lock_guard<std::mutex> lock(mtx);
788 _calibrationError = calibrationError.empty()
789 ?
"Fisheye calibration failed without an error message."
792 calibrationState.store(CalibrationState::Error);
799 std::string persistenceError;
800 if(!_calibrationWriter.
write(camCalibFile, calibrationResult, persistenceError))
802 yError() <<
"Could not save calibration results:" << persistenceError;
804 std::lock_guard<std::mutex> lock(mtx);
805 _calibrationResults = std::move(calibrationResult);
806 _calibrationError = persistenceError;
808 calibrationState.store(CalibrationState::Error);
811 if(!_calibrationWriter.
writeObservations(_observationsFile, observationSnapshot, persistenceError))
813 yError() <<
"Could not save calibration observations:" << persistenceError;
815 std::lock_guard<std::mutex> lock(mtx);
816 _calibrationResults = std::move(calibrationResult);
817 _calibrationError = persistenceError;
819 calibrationState.store(CalibrationState::Error);
823 logCalibrationResult(calibrationResult);
825 std::lock_guard<std::mutex> lock(mtx);
826 _calibrationResults = std::move(calibrationResult);
827 _calibrationError.clear();
830 calibrationState.store(CalibrationState::Completed);
831 yInfo() <<
"Entire calibration process completed";
834 if(!areFramesReceived)
844void stereoCalibThread::monoCalibRun()
846 while(imagePortInLeft.getInputCount()==0 && imagePortInRight.getInputCount()==0)
848 yInfo(
"Connect one camera.. \n");
856 bool left= imagePortInLeft.getInputCount()>0?
true:
false;
865 yInfo(
"CALIBRATING %s CAMERA \n",cameraName.c_str());
869 Size boardSize, imageSize;
870 boardSize.width=this->boardWidth;
871 boardSize.height=this->boardHeight;
875 while (!isStopping()) {
877 imageL = std::move(imagePortInLeft.read(
false));
879 imageL = std::move(imagePortInRight.read(
false));
884 if(calibrationState.load() == CalibrationState::Calibrating) {
886 string pathImg=imageDir;
887 preparePath(pathImg.c_str(),pathL,pathR,count);
889 LeftRgb=yarp::cv::toCvMat(*imageL);
890 std::vector<Point2f> pointbufL;
892 if(boardType ==
"CIRCLES_GRID") {
893 foundL = findCirclesGrid(LeftRgb, boardSize, pointbufL, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
894 }
else if(boardType ==
"ASYMMETRIC_CIRCLES_GRID") {
895 foundL = findCirclesGrid(LeftRgb, boardSize, pointbufL, CALIB_CB_ASYMMETRIC_GRID | CALIB_CB_CLUSTERING);
897 foundL = findChessboardCorners(LeftRgb, boardSize, pointbufL, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);
901 cvtColor(LeftRgb,LeftRgb,CV_RGB2BGR);
902 saveImage(pathImg.c_str(),LeftRgb,count);
903 imageListL.push_back(iml);
905 drawChessboardCorners(LeftRgb, boardSize, cL, foundL);
909 if(count>numOfPairs) {
910 yInfo(
" Running %s Camera Calibration... \n", cameraName.c_str());
911 monoCalibration(imageListL,this->boardWidth,this->boardHeight,this->Kleft,this->DistL,cameraName.c_str());
913 yInfo(
" Saving Calibration Results... \n");
914 updateIntrinsics(LeftRgb.cols,LeftRgb.rows,Kleft.at<
double>(0,0),Kleft.at<
double>(1,1),Kleft.at<
double>(0,2),
915 Kleft.at<
double>(1,2),DistL.at<
double>(0,0),DistL.at<
double>(0,1),DistL.at<
double>(0,2),
916 DistL.at<
double>(0,3),left?
"CAMERA_CALIBRATION_LEFT":
"CAMERA_CALIBRATION_RIGHT");
917 yInfo(
"Calibration Results Saved in %s \n", camCalibFile.c_str());
919 calibrationState.store(CalibrationState::Completed);
925 ImageOf<PixelRgb>& outimL=outPortLeft.prepare();
929 ImageOf<PixelRgb>& outimR=outPortRight.prepare();
931 outPortRight.write();
942 imagePortInRight.close();
943 imagePortInLeft.close();
945 outPortRight.close();
946 commandPort->close();
948 if (polyHead.isValid())
951 if (polyTorso.isValid())
957 calibrationState.store(CalibrationState::Idle);
958 collectionResetRequested.store(
false);
959 imagePortInRight.interrupt();
960 imagePortInLeft.interrupt();
961 outPortLeft.interrupt();
962 outPortRight.interrupt();
963 commandPort->interrupt();
968 std::lock_guard<std::mutex> lock(mtx);
970 const CalibrationState currentState = calibrationState.load();
972 if(currentState == CalibrationState::Collecting || currentState == CalibrationState::Calibrating)
974 yWarning() <<
"Cannot start a new calibration while calibration is already running";
979 _calibrationError.clear();
980 collectionResetRequested.store(
true);
981 calibrationState.store(CalibrationState::Collecting);
983 yInfo() <<
"Calibration collection started";
987 std::lock_guard<std::mutex> lock(mtx);
989 if(calibrationState.load() != CalibrationState::Collecting)
991 yWarning() <<
"Cannot stop calibration collection when it is not running";
994 calibrationState.store(CalibrationState::Idle);
995 collectionResetRequested.store(
true);
997 yInfo() <<
"Calibration collection stopped";
1000void stereoCalibThread::printMatrix(Mat &matrix) {
1001 int row = matrix.rows;
1002 int col = matrix.cols;
1004 for(
int i = 0; i < matrix.rows; i++)
1006 const double* Mi = matrix.ptr<
double>(i);
1007 for(
int j = 0; j < matrix.cols; j++)
1008 cout << Mi[j] <<
" ";
1015bool stereoCalibThread::checkTS(
double TSLeft,
double TSRight,
double th) {
1016 double diff = fabs(TSLeft-TSRight);
1023void stereoCalibThread::preparePath(
const char * imageDir,
char* pathL,
char* pathR,
int count) {
1025 sprintf(num,
"%i", count);
1028 strncpy(pathL,imageDir, strlen(imageDir));
1029 pathL[strlen(imageDir)]=
'\0';
1030 strcat(pathL,
"left");
1032 strcat(pathL,
".png");
1034 strncpy(pathR,imageDir, strlen(imageDir));
1035 pathR[strlen(imageDir)]=
'\0';
1036 strcat(pathR,
"right");
1038 strcat(pathR,
".png");
1043void stereoCalibThread::saveStereoImage(
const char * imageDir,
const Mat& left,
const Mat& right,
int num) {
1046 preparePath(imageDir, pathL,pathR,num);
1048 yInfo(
"Saving stereo images number %d \n",num);
1050 imwrite(pathL,left);
1051 imwrite(pathR,right);
1054void stereoCalibThread::saveImage(
const char * imageDir,
const Mat& left,
int num) {
1056 preparePath(imageDir, pathL,pathR,num);
1058 yInfo(
"Saving images number %d \n",num);
1060 imwrite(pathL,left);
1063bool stereoCalibThread::updateIntrinsics(
int width,
int height,
double fx,
double fy,
double cx,
double cy,
double k1,
double k2,
double k3,
double k4,
const string& groupname){
1065 std::vector<string> lines;
1070 in.open(camCalibFile.c_str());
1075 bool sectionFound =
false;
1076 bool sectionClosed =
false;
1079 while(std::getline(in, line)){
1081 if (sectionFound ==
true && line.find(
"[", 0) != string::npos)
1082 sectionClosed =
true;
1084 if (line.find(
string(
"[") + groupname +
string(
"]"), 0) != string::npos)
1085 sectionFound =
true;
1087 if (groupname ==
"")
1088 sectionFound =
true;
1090 if (sectionFound ==
true && sectionClosed ==
false){
1092 if (line.find(
"w",0) ==0){
1095 line =
"w " + string(ss.str());
1098 if (line.find(
"h",0) ==0){
1101 line =
"h " + string(ss.str());
1104 if (line.find(
"fx",0) != string::npos){
1107 line =
"fx " + string(ss.str());
1110 if (line.find(
"fy",0) != string::npos){
1113 line =
"fy " + string(ss.str());
1116 if (line.find(
"cx",0) != string::npos){
1119 line =
"cx " + string(ss.str());
1122 if (line.find(
"cy",0) != string::npos){
1125 line =
"cy " + string(ss.str());
1128 if (line.find(
"k1",0) != string::npos){
1131 line =
"k1 " + string(ss.str());
1134 if (line.find(
"k2",0) != string::npos){
1137 line =
"k2 " + string(ss.str());
1140 if (line.find(
"k3",0) != string::npos){
1143 line =
"k3 " + string(ss.str());
1146 if (line.find(
"k4",0) != string::npos){
1149 line =
"k4 " + string(ss.str());
1153 lines.push_back(line);
1161 cout <<
"Camera calibration parameter section " + string(
"[") + groupname + string(
"]") +
" not found in file " << camCalibFile <<
". Adding group..." << endl;
1166 out.open(camCalibFile.c_str(), ios::trunc);
1168 for (
int i = 0; i < (int)lines.size(); i++)
1169 out << lines[i] << endl;
1184 out.open(camCalibFile.c_str(), ios::app);
1186 out << string(
"[") + groupname + string(
"]") << endl;
1188 out <<
"w " << width << endl;
1189 out <<
"h " << height << endl;
1190 out <<
"fx " << fx << endl;
1191 out <<
"fy " << fy << endl;
1192 out <<
"cx " << cx << endl;
1193 out <<
"cy " << cy << endl;
1194 out <<
"k1 " << k1 << endl;
1195 out <<
"k2 " << k2 << endl;
1196 out <<
"k3 " << k3 << endl;
1197 out <<
"k4 " << k4 << endl;
1208double stereoCalibThread::monoCalibration(
const vector<string>& imageList,
int boardWidth,
int boardHeight, Mat &K, Mat &Dist,
const char* cameraName)
1210 vector<vector<Point2f> > imagePoints;
1211 Size boardSize, imageSize;
1212 boardSize.width=boardWidth;
1213 boardSize.height=boardHeight;
1217 float squareSize = this->squareSize;
1218 float aspectRatio = 1.f;
1219 if (squareSize <= 0.0f)
1221 yWarning(
"Mono calibration: invalid square size %f, using 1.0", squareSize);
1227 for(i = 0; i<(int)imageList.size();i++)
1229 view = cv::imread(imageList[i], IMREAD_COLOR);
1232 yWarning(
"Mono calibration: could not read image %s", imageList[i].c_str());
1236 if (imageSize == Size())
1237 imageSize =
view.size();
1238 else if (
view.size() != imageSize)
1240 yWarning(
"Mono calibration: image %s has size %dx%d while first image had %dx%d",
1241 imageList[i].c_str(),
view.cols,
view.rows, imageSize.width, imageSize.height);
1245 vector<Point2f> pointbuf;
1246 cvtColor(
view, viewGray, CV_BGR2GRAY);
1249 if(boardType ==
"CIRCLES_GRID") {
1250 found = findCirclesGrid(
view, boardSize, pointbuf, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
1251 }
else if(boardType ==
"ASYMMETRIC_CIRCLES_GRID") {
1252 found = findCirclesGrid(
view, boardSize, pointbuf, CALIB_CB_ASYMMETRIC_GRID | CALIB_CB_CLUSTERING);
1254 found = findChessboardCorners(viewGray, boardSize, pointbuf,
1255 CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);
1260 if (pointbuf.size() !=
static_cast<size_t>(boardWidth * boardHeight))
1262 yWarning(
"Mono calibration: skipping image %s because %zu corners were detected, expected %d",
1263 imageList[i].c_str(), pointbuf.size(), boardWidth * boardHeight);
1267 Rect bbox = boundingRect(pointbuf);
1268 if (bbox.width <
view.cols * 0.15 || bbox.height <
view.rows * 0.15)
1270 yWarning(
"Mono calibration: skipping image %s because the detected board is too small (bbox %dx%d) with respect to the image size (%dx%d)",
1271 imageList[i].c_str(), bbox.width, bbox.height,
view.cols,
view.rows);
1275 TermCriteria subpixCriteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 30, 0.001);
1276 cornerSubPix(viewGray, pointbuf, Size(7, 7), Size(-1, -1), subpixCriteria);
1277 drawChessboardCorners(
view, boardSize, Mat(pointbuf), found);
1278 imagePoints.push_back(pointbuf);
1282 yWarning(
"Mono calibration: no chessboard detected in %s", imageList[i].c_str());
1286 if (imagePoints.size() < 2)
1288 yError(
"Mono calibration: only %zu valid image(s) remain after filtering; aborting", imagePoints.size());
1292 std::vector<Mat> rvecs, tvecs;
1293 std::vector<float> reprojErrs;
1294 double totalAvgErr = 0;
1296 std::vector<std::vector<Point3f> > objectPoints(1);
1297 calcChessboardCorners(boardSize, squareSize, objectPoints[0]);
1298 objectPoints.resize(imagePoints.size(), objectPoints[0]);
1300 K = Mat::eye(3, 3, CV_64F);
1301 Dist = Mat::zeros(4, 1, CV_64F);
1303 bool usedPinholeGuess =
false;
1306 Mat pinholeK = Mat::eye(3, 3, CV_64F);
1307 Mat pinholeDist = Mat::zeros(5, 1, CV_64F);
1308 std::vector<Mat> pinholeRvecs, pinholeTvecs;
1309 int pinholeFlags = cv::CALIB_FIX_K3 | cv::CALIB_FIX_K4 | cv::CALIB_FIX_TANGENT_DIST;
1310 double pinholeRms = cv::calibrateCamera(objectPoints, imagePoints, imageSize, pinholeK, pinholeDist,
1311 pinholeRvecs, pinholeTvecs, pinholeFlags);
1312 yInfo(
"Pinhole initial guess RMS for %s camera: %g", cameraName, pinholeRms);
1313 if (pinholeK.rows == 3 && pinholeK.cols == 3)
1315 K = pinholeK.clone();
1316 usedPinholeGuess =
true;
1319 catch (
const cv::Exception&
e)
1321 yWarning(
"Pinhole initial guess failed for %s camera: %s", cameraName,
e.what());
1324 if (!usedPinholeGuess)
1326 double focal = std::max(imageSize.width, imageSize.height) * 0.8;
1327 K = Mat::eye(3, 3, CV_64F);
1328 K.at<
double>(0,0) = focal;
1329 K.at<
double>(1,1) = focal;
1330 K.at<
double>(0,2) = imageSize.width * 0.5;
1331 K.at<
double>(1,2) = imageSize.height * 0.5;
1333 if( flags & CV_CALIB_FIX_ASPECT_RATIO )
1334 K.at<
double>(0,0) = aspectRatio;
1338 int calFlags = fisheye::CALIB_USE_INTRINSIC_GUESS | fisheye::CALIB_RECOMPUTE_EXTRINSIC | fisheye::CALIB_FIX_SKEW;
1339 double rms = fisheye::calibrate(objectPoints, imagePoints, imageSize, K, Dist, rvecs, tvecs,
1341 TermCriteria(TermCriteria::EPS+TermCriteria::MAX_ITER, 100, 1
e-5));
1343 yInfo(
"Mono calibration RMS for %s camera: %g", cameraName, rms);
1344 yInfo(
"Mono calibration intrinsics for %s camera: fx=%g fy=%g cx=%g cy=%g",
1345 cameraName, K.at<
double>(0,0), K.at<
double>(1,1), K.at<
double>(0,2), K.at<
double>(1,2));
1346 yInfo(
"Mono calibration distortion for %s camera: k1=%g k2=%g k3=%g k4=%g",
1347 cameraName, Dist.at<
double>(0,0), Dist.at<
double>(1,0), Dist.at<
double>(2,0), Dist.at<
double>(3,0));
1351 catch (
const cv::Exception&
e)
1353 yError(
"OpenCV mono calibration raised an exception: %s",
e.what());
1354 yError(
"Mono calibration failed with %zu valid images", imagePoints.size());
1361void logStereoCalibrationDebugInfo(
const std::vector<std::vector<cv::Point2f> >& imagePointsLeft,
1362 const std::vector<std::vector<cv::Point2f> >& imagePointsRight,
1363 const std::vector<std::vector<cv::Point3f> >& objectPoints,
1364 const std::vector<std::string>& imagelist,
1365 const cv::Size& imageSize,
1369 yInfo(
"Stereo calibration debug: %zu image pairs, board %dx%d, image size %dx%d",
1370 imagePointsLeft.size(), boardWidth, boardHeight, imageSize.width, imageSize.height);
1372 for (
size_t i = 0; i < imagePointsLeft.size(); ++i)
1374 yInfo(
"pair[%zu]: left corners=%zu right corners=%zu object points=%zu", i,
1375 imagePointsLeft[i].size(), imagePointsRight[i].size(), objectPoints[i].size());
1376 if (imagePointsLeft[i].size() != imagePointsRight[i].size())
1378 yError(
"pair[%zu] has mismatched corner counts: left=%zu right=%zu", i,
1379 imagePointsLeft[i].size(), imagePointsRight[i].size());
1381 if (imagePointsLeft[i].size() != objectPoints[i].size())
1383 yError(
"pair[%zu] has mismatched corners/object-points: corners=%zu object=%zu", i,
1384 imagePointsLeft[i].size(), objectPoints[i].size());
1386 if (i < imagelist.size() / 2)
1388 yInfo(
"pair[%zu] images: %s / %s", i, imagelist[2 * i].c_str(), imagelist[2 * i + 1].c_str());
1394void stereoCalibThread::stereoCalibration(
const vector<string>& imagelist,
int boardWidth,
int boardHeight,
float sqsize)
1397 boardSize.width=boardWidth;
1398 boardSize.height=boardHeight;
1399 if( imagelist.size() % 2 != 0 )
1401 cout <<
"Error: the image list contains odd (non-even) number of elements\n";
1405 const int maxScale = 2;
1408 std::vector<std::vector<Point2f> > imagePoints[2];
1411 int i, j, k, nimages = (int)imagelist.size()/2;
1413 imagePoints[0].resize(nimages);
1414 imagePoints[1].resize(nimages);
1415 std::vector<string> goodImageList;
1416 bool differentSizes =
false;
1418 for( i = j = 0; i < nimages; i++ )
1420 for( k = 0; k < 2; k++ )
1422 const string& filename = imagelist[i*2+k];
1423 Mat img = cv::imread(filename, IMREAD_GRAYSCALE);
1426 if( imageSize == Size() )
1427 imageSize = img.size();
1428 else if( img.size() != imageSize )
1430 yWarning() <<
"The image " << filename <<
" has the size different from the first image size.\n";
1431 differentSizes =
true;
1434 std::vector<Point2f>& corners = imagePoints[k][j];
1435 for(
int scale = 1; scale <= maxScale; scale++ )
1441 resize(img, timg, Size(), scale, scale);
1443 if(boardType ==
"CIRCLES_GRID") {
1444 found = findCirclesGrid(timg, boardSize, corners, CALIB_CB_SYMMETRIC_GRID | CALIB_CB_CLUSTERING);
1445 }
else if(boardType ==
"ASYMMETRIC_CIRCLES_GRID") {
1446 found = findCirclesGrid(timg, boardSize, corners, CALIB_CB_ASYMMETRIC_GRID | CALIB_CB_CLUSTERING);
1448 found = findChessboardCorners(timg, boardSize, corners,
1449 CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);
1456 Mat cornersMat(corners);
1457 cornersMat *= 1./scale;
1467 goodImageList.push_back(imagelist[i*2]);
1468 goodImageList.push_back(imagelist[i*2+1]);
1472 yInfo(
"%i pairs have been successfully detected.\n",j);
1476 yError(
"Error: too few pairs detected \n");
1480 imagePoints[0].resize(nimages);
1481 imagePoints[1].resize(nimages);
1483 std::vector<std::vector<Point3f> > objectPoints(1);
1484 calcChessboardCorners(boardSize, squareSize, objectPoints[0]);
1485 objectPoints.resize(nimages, objectPoints[0]);
1487 yInfo(
"Running stereo calibration ...\n");
1491 Mat cameraMatrix[2], distCoeffs[2];
1493 int flags = fisheye::CALIB_FIX_INTRINSIC | fisheye::CALIB_RECOMPUTE_EXTRINSIC | fisheye::CALIB_FIX_SKEW;
1494 TermCriteria criteria = TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 100, 1
e-5);
1496 yInfo(
"Stereo calibration intrinsics: left empty=%d right empty=%d", this->Kleft.empty(), this->Kright.empty());
1497 yInfo(
"Stereo calibration distortion: left empty=%d right empty=%d", this->DistL.empty(), this->DistR.empty());
1499 if (this->Kleft.empty() || this->Kright.empty())
1501 if (differentSizes){
1502 yError(
"Images have different sizes. Please make sure to compute intrinsic parameters before running stereo calibration. Quitting...");
1505 yError(
"Stereo calibration: intrinsics are empty; cannot proceed with fixed-intrinsic stereo solve.");
1509 this->R = Mat::eye(3, 3, CV_64F);
1510 this->T = Mat::zeros(3, 1, CV_64F);
1511 this->T.at<
double>(0, 0) = 0.06;
1512 yInfo(
"Stereo calibration initial guess: R=identity, T=(%.4f, %.4f, %.4f)", this->T.at<
double>(0,0), this->T.at<
double>(1,0), this->T.at<
double>(2,0));
1515 this->lastImageSize = imageSize;
1519 yInfo(
"Using precomputed intrinsic parameters with fixed intrinsics and a baseline-based initial translation");
1520 double rms = fisheye::stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1],
1521 this->Kleft, this->DistL,
1522 this->Kright, this->DistR,
1523 imageSize, this->R, this->T,
1525 yInfo(
"done with RMS error= %f\n",rms);
1527 catch (
const cv::Exception&
e)
1529 yError(
"OpenCV stereo calibration raised an exception: %s",
e.what());
1530 yError(
"Stereo calibration failed with %zu left pairs and %zu right pairs", imagePoints[0].size(), imagePoints[1].size());
1535 cameraMatrix[0] = this->Kleft;
1536 cameraMatrix[1] = this->Kright;
1537 distCoeffs[0] = this->DistL;
1538 distCoeffs[1] = this->DistR;
1543 Mat Tx = Mat::zeros(3, 3, CV_64F);
1544 Tx.at<
double>(0, 1) = -
T.at<
double>(2, 0);
1545 Tx.at<
double>(0, 2) =
T.at<
double>(1, 0);
1546 Tx.at<
double>(1, 0) =
T.at<
double>(2, 0);
1547 Tx.at<
double>(1, 2) = -
T.at<
double>(0, 0);
1548 Tx.at<
double>(2, 0) = -
T.at<
double>(1, 0);
1549 Tx.at<
double>(2, 1) =
T.at<
double>(0, 0);
1551 F = cameraMatrix[1].inv().t() * Tx * R * cameraMatrix[0].inv();
1552 yInfo(
"Computed fundamental matrix from stereo extrinsics.");
1556 std::vector<Vec3f> lines[2];
1557 for( i = 0; i < nimages; i++ )
1559 int npt = (int)imagePoints[0][i].size();
1561 for( k = 0; k < 2; k++ )
1563 imgpt[k] = Mat(imagePoints[k][i]);
1564 fisheye::undistortPoints(imgpt[k], imgpt[k], cameraMatrix[k], distCoeffs[k], Mat(), cameraMatrix[k]);
1565 yDebug() <<
"Calculated undistorted points for image " << i <<
" camera " << k;
1566 computeCorrespondEpilines(imgpt[k], k+1,
F, lines[k]);
1568 for( j = 0; j < npt; j++ )
1570 double errij = fabs(imagePoints[0][i][j].
x*lines[1][j][0] +
1571 imagePoints[0][i][j].
y*lines[1][j][1] + lines[1][j][2]) +
1572 fabs(imagePoints[1][i][j].
x*lines[0][j][0] +
1573 imagePoints[1][i][j].
y*lines[0][j][1] + lines[0][j][2]);
1578 yInfo(
"average reprojection err = %f\n",err/npoints);
1583void stereoCalibThread::saveCalibration(
const string& extrinsicFilePath,
const string& intrinsicFilePath){
1585 if( Kleft.empty() || Kright.empty() || DistL.empty() || DistR.empty() || R.empty() ||
T.empty()) {
1586 cout <<
"Error: cameras are not calibrated! Run the calibration or set intrinsic and extrinsic parameters \n";
1590 FileStorage fs(intrinsicFilePath+
".yml", cv::FileStorage::Mode::WRITE);
1593 fs <<
"M1" << Kleft <<
"D1" << DistL <<
"M2" << Kright <<
"D2" << DistR;
1597 cout <<
"Error: can not save the intrinsic parameters\n";
1599 fs.open(extrinsicFilePath+
".yml", cv::FileStorage::Mode::WRITE);
1604 Mat R1, R2, P1, P2, Qr;
1606 fisheye::stereoRectify(Kleft, DistL, Kright, DistR, this->lastImageSize, R, T, R1, R2, P1, P2, Qr, flags, Size());
1607 fs <<
"R" << R <<
"T" <<
T <<
"R1" << R1 <<
"R2" << R2 <<
"P1" << P1 <<
"P2" << P2 <<
"Q" << Qr;
1609 catch (
const cv::Exception &
e) {
1610 yError(
"stereoRectify failed: %s",
e.what());
1611 fs <<
"R" << R <<
"T" <<
T <<
"Q" << Q;
1616 cout <<
"Error: can not save the intrinsic parameters\n";
1620void stereoCalibThread::calcChessboardCorners(Size boardSize,
float squareSize, vector<Point3f>& corners)
1624 if(boardType ==
"ASYMMETRIC_CIRCLES_GRID") {
1625 for(
int i = 0; i < boardSize.height; i++ )
1626 for(
int j = 0; j < boardSize.width; j++ )
1627 corners.push_back(Point3f(
float((2*j + i % 2)*squareSize),
float(i*squareSize), 0));
1629 for(
int i = 0; i < boardSize.height; i++ )
1630 for(
int j = 0; j < boardSize.width; j++ )
1631 corners.push_back(Point3f(
float(j*squareSize),
1632 float(i*squareSize), 0));
1636bool stereoCalibThread::updateExtrinsics(Mat Rot, Mat Tr,
const string& groupname)
1638 std::vector<string> lines;
1642 in.open(camCalibFile.c_str());
1647 bool sectionFound =
false;
1648 bool sectionClosed =
false;
1651 while(std::getline(in, line)){
1653 if (sectionFound ==
true && line.find(
"[", 0) != string::npos)
1654 sectionClosed =
true;
1656 if (line.find(
string(
"[") + groupname +
string(
"]"), 0) != string::npos)
1657 sectionFound =
true;
1659 if (groupname ==
"")
1660 sectionFound =
true;
1662 if (sectionFound ==
true && sectionClosed ==
false){
1664 if (line.find(
"HN",0) != string::npos){
1666 ss <<
" (" << Rot.at<
double>(0,0) <<
" " << Rot.at<
double>(0,1) <<
" " << Rot.at<
double>(0,2) <<
" " << Tr.at<
double>(0,0) <<
" "
1667 << Rot.at<
double>(1,0) <<
" " << Rot.at<
double>(1,1) <<
" " << Rot.at<
double>(1,2) <<
" " << Tr.at<
double>(1,0) <<
" "
1668 << Rot.at<
double>(2,0) <<
" " << Rot.at<
double>(2,1) <<
" " << Rot.at<
double>(2,2) <<
" " << Tr.at<
double>(2,0) <<
" "
1669 << 0.0 <<
" " << 0.0 <<
" " << 0.0 <<
" " << 1.0 <<
")";
1670 line =
"HN" + string(ss.str());
1674 if (line.find(
"QL", 0) != string::npos) {
1675 line =
"QL (" + string(qL.toString().c_str()) +
")";
1677 if (line.find(
"QR", 0) != string::npos) {
1678 line =
"QR (" + string(qR.toString().c_str()) +
")";
1683 lines.push_back(line);
1691 cout <<
"Camera calibration parameter section " + string(
"[") + groupname + string(
"]") +
" not found in file " << camCalibFile <<
". Adding group..." << endl;
1696 out.open(camCalibFile.c_str(), ios::trunc);
1698 for (
int i = 0; i < (int)lines.size(); i++)
1699 out << lines[i] << endl;
1714 out.open(camCalibFile.c_str(), ios::app);
1717 out << string(
"[") + groupname + string(
"]") << endl;
1718 out <<
"HN (" << Rot.at<
double>(0,0) <<
" " << Rot.at<
double>(0,1) <<
" " << Rot.at<
double>(0,2) <<
" " << Tr.at<
double>(0,0) <<
" "
1719 << Rot.at<
double>(1,0) <<
" " << Rot.at<
double>(1,1) <<
" " << Rot.at<
double>(1,2) <<
" " << Tr.at<
double>(1,0) <<
" "
1720 << Rot.at<
double>(2,0) <<
" " << Rot.at<
double>(2,1) <<
" " << Rot.at<
double>(2,2) <<
" " << Tr.at<
double>(2,0) <<
" "
1721 << 0.0 <<
" " << 0.0 <<
" " << 0.0 <<
" " << 1.0 <<
")";
1723 out <<
"QL (" << qL.toString().c_str() <<
")" << endl;
1724 out <<
"QR (" << qR.toString().c_str() <<
")" << endl;
constexpr double tolerance
SynchronizerStatistics getStatistics() const
void configure(double tolerance, std::size_t maxQueueSize)
void pushRight(const ImageOf< PixelRgb > &rightFrame, const Stamp ×tamp)
bool tryPopPair(SynchronizedPair &pair)
void pushLeft(const ImageOf< PixelRgb > &leftFrame, const Stamp ×tamp)
stereoCalibThread(ResourceFinder &rf, Port *commPort, const char *imageDir)
StereoCalibStatus getStatus() const
bool writeImagePair(const std::string &outputDirectory, std::size_t observationIndex, const cv::Mat &leftImg, const cv::Mat &rightImg, std::string &leftFile, std::string &rightFile, std::string &errorMessage) const
bool write(const std::string &outputFile, const CalibrationResult &result, std::string &errorMessage) const
bool writeObservations(const std::string &outputFile, const std::vector< StereoObservation > &observations, std::string &errorMessage) const
bool calibrate(const std::vector< StereoObservation > &observations, const FisheyeCalibrationOptions &options, CalibrationResult &result, std::string &errorMessage) const
void append(const size_t index, shared_ptr< Epsilon_neighbours_t > en)
constexpr double CTRL_DEG2RAD
PI/180.
ImageOf< PixelRgb > image
bool calibrationAvailable
std::string lastCalibrationError
double maxVerticalRectificationErrorPx
std::size_t droppedLeftFrames
double p95VerticalRectificationErrorPx
std::string calibrationMode
double meanTimestampDeltaMs
double medianVerticalRectificationErrorPx
double maxTimestampDeltaMs
std::size_t droppedRightFrames
ImageOf< PixelRgb > right
double accumulatedTimeStampDelta
std::size_t droppedLeftFrames
std::size_t droppedRightFrames
double rmsVerticalRectificationErrorPx
std::size_t synchronizedPairs
double medianVerticalRectificationErrorPx
double maxVerticalRectificationErrorPx
double p95VerticalRectificationErrorPx
double meanVerticalRectificationErrorPx
std::size_t rejectedDetections
RectificationResult rectification
CameraCalibrationResult leftCamera
StereoCalibrationResult stereo
CameraCalibrationResult rightCamera
CalibrationQualityMetrics quality
std::vector< cv::Point3f > createObjectPoints() const
CalibrationMode calibrationMode
double cameraFocalLengthGuess
double timestampDeltaSeconds
int64_t leftSequenceNumber
std::string leftImageFilename
int64_t rightSequenceNumber
std::vector< cv::Point2f > leftImagePoints
std::vector< cv::Point3f > objectPoints
std::string rightImageFilename
double rightTimestampSeconds
double leftTimestampSeconds
std::vector< cv::Point2f > rightImagePoints