diff --git a/applications/calibration/CMakeLists.txt b/applications/calibration/CMakeLists.txt index 70b8aa449a2e8b5aa97a9e70cac574ba626e8ba7..0efa6e95c7c66e4480e2965e0cccffae39d5cb09 100644 --- a/applications/calibration/CMakeLists.txt +++ b/applications/calibration/CMakeLists.txt @@ -3,6 +3,7 @@ set(CALIBSRC src/lens.cpp src/stereo.cpp src/align.cpp + src/common.cpp ) add_executable(ftl-calibrate ${CALIBSRC}) diff --git a/applications/calibration/src/common.cpp b/applications/calibration/src/common.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a29bb8f76a05793526854b9f4958f9a04c4b1add --- /dev/null +++ b/applications/calibration/src/common.cpp @@ -0,0 +1,111 @@ +#include <loguru.hpp> +#include <ftl/config.h> + +#include <opencv2/calib3d.hpp> +#include <opencv2/imgproc.hpp> +#include <opencv2/videoio.hpp> +#include <opencv2/highgui.hpp> + +#include "common.hpp" + +using std::vector; +using std::map; +using std::string; + +using cv::Mat; +using cv::Vec2f, cv::Vec3f; +using cv::Size; + +using cv::stereoCalibrate; + +namespace ftl { +namespace calibration { + +// Options + +string getOption(map<string, string> &options, const string &opt) { + auto str = options[opt]; + return str.substr(1,str.size()-2); +} + +bool hasOption(const map<string, string> &options, const string &opt) { + return options.find(opt) != options.end(); +} + +// Save/load files + +bool saveExtrinsics(const string &ofile, Mat &R, Mat &T, Mat &R1, Mat &R2, Mat &P1, Mat &P2, Mat &Q) { + cv::FileStorage fs; + fs.open(ofile, cv::FileStorage::WRITE); + if (fs.isOpened()) { + fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" + << P1 << "P2" << P2 << "Q" << Q; + fs.release(); + return true; + } else { + LOG(ERROR) << "Error: can not save the extrinsic parameters"; + } + return false; +} + +bool saveIntrinsics(const string &ofile, const Mat &M, const Mat& D) { + cv::FileStorage fs(ofile, cv::FileStorage::WRITE); + if (fs.isOpened()) { + fs << "M" << M << "D" << D; + fs.release(); + return true; + } + else { + LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << ofile << "'"; + } + return false; +} + +bool loadIntrinsics(const string &ifile, Mat &M1, Mat &D1) { + using namespace cv; + + FileStorage fs; + + // reading intrinsic parameters + fs.open((ifile).c_str(), FileStorage::READ); + if (!fs.isOpened()) { + LOG(WARNING) << "Could not open intrinsics file : " << ifile; + return false; + } + + LOG(INFO) << "Intrinsics from: " << ifile; + + fs["M"] >> M1; + fs["D"] >> D1; + + return true; +} + +// Calibration classes for different patterns + +CalibrationChessboard::CalibrationChessboard(const map<string, string> &opt) { + pattern_size_ = Size(9, 6); + image_size_ = Size(1280, 720); + pattern_square_size_ = 36.0;//0.036; + // CALIB_CB_NORMALIZE_IMAfE | CALIB_CB_EXHAUSTIVE | CALIB_CB_ACCURACY + chessboard_flags_ = cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_ACCURACY; +} + +void CalibrationChessboard::objectPoints(vector<Vec3f> &out) { + out.reserve(pattern_size_.width * pattern_size_.height); + for (int row = 0; row < pattern_size_.height; ++row) { + for (int col = 0; col < pattern_size_.width; ++col) { + out.push_back(Vec3f(col * pattern_square_size_, row * pattern_square_size_, 0)); + }} +} + +bool CalibrationChessboard::findPoints(Mat &img, vector<Vec2f> &points) { + return cv::findChessboardCornersSB(img, pattern_size_, points, chessboard_flags_); +} + +void CalibrationChessboard::drawPoints(Mat &img, const vector<Vec2f> &points) { + cv::drawChessboardCorners(img, pattern_size_, points, true); +} + +} +} diff --git a/applications/calibration/src/common.hpp b/applications/calibration/src/common.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4bc3d1e4b8c5e7f734821cce9b5af7b4f05a2605 --- /dev/null +++ b/applications/calibration/src/common.hpp @@ -0,0 +1,82 @@ +#ifndef _FTL_CALIBRATION_COMMON_HPP_ +#define _FTL_CALIBRATION_COMMON_HPP_ + +#include <map> +#include <string> + +#include <opencv2/core.hpp> + +namespace ftl { +namespace calibration { + +std::string getOption(std::map<std::string, std::string> &options, const std::string &opt); +bool hasOption(const std::map<std::string, std::string> &options, const std::string &opt); + +bool loadIntrinsics(const std::string &ifile, cv::Mat &M1, cv::Mat &D1); +bool saveIntrinsics(const std::string &ofile, const cv::Mat &M1, const cv::Mat &D1); + +// TODO loadExtrinsics() +bool saveExtrinsics(const std::string &ofile, cv::Mat &R, cv::Mat &T, cv::Mat &R1, cv::Mat &R2, cv::Mat &P1, cv::Mat &P2, cv::Mat &Q); + +/** + * @brief Wrapper for OpenCV's calibration methods. Paramters depend on + * implementation (different types of patterns). + * + * Calibration objects may store state; eg. from previous views of calibration + * images. + */ +class Calibration { +public: + /** + * @brief Calculate reference points for given pattern + * @param Output parameter + */ + void objectPoints(std::vector<cv::Vec3f> &out); + + /** + * @brief Try to find calibration pattern in input image + * @param Input image + * @param Output parameter for found point image coordinates + * @returns true if pattern found, otherwise false + */ + bool findPoints(cv::Mat &in, std::vector<cv::Vec2f> &out); + + /** + * @brief Draw points to image + * @param Image to draw to + * @param Pattern points (in image coordinates) + */ + void drawPoints(cv::Mat &img, const std::vector<cv::Vec2f> &points); +}; + +/** + * @brief Chessboard calibration pattern. Uses OpenCV's + * findChessboardCornersSB function. + * @todo Parameters hardcoded in constructor + * + * All parameters: + * - pattern size (inner corners) + * - square size, millimeters (TODO: meters) + * - image size, pixels + * - flags, see ChessboardCornersSB documentation + */ +class CalibrationChessboard : Calibration { +public: + CalibrationChessboard(const std::map<std::string, std::string> &opt); + void objectPoints(std::vector<cv::Vec3f> &out); + bool findPoints(cv::Mat &in, std::vector<cv::Vec2f> &out); + void drawPoints(cv::Mat &img, const std::vector<cv::Vec2f> &points); + +private: + int chessboard_flags_ = 0; + float pattern_square_size_; + cv::Size pattern_size_; + cv::Size image_size_; +}; + +// TODO other patterns, circles ... + +} +} + +#endif // _FTL_CALIBRATION_COMMON_HPP_ diff --git a/applications/calibration/src/lens.cpp b/applications/calibration/src/lens.cpp index e7fe47b758e62267f0932594cd563ad2bc3ff4e0..afc54fd96def762cb61ff4f3ea9da5e0c087e5d7 100644 --- a/applications/calibration/src/lens.cpp +++ b/applications/calibration/src/lens.cpp @@ -1,3 +1,4 @@ +#include "common.hpp" #include "lens.hpp" #include <loguru.hpp> @@ -17,324 +18,104 @@ using std::string; using std::vector; using cv::Mat; -using cv::Point2f; -using cv::Point3f; +using cv::Vec2f; +using cv::Vec3f; using cv::Size; -struct Settings { - float squareSize; - cv::Size boardSize; - int nrFrames; - float delay; - std::string outputfile; - int device; - bool useFisheye; - double aspectRatio; - int flag; -}; +using namespace ftl::calibration; -//! [compute_errors] -static double computeReprojectionErrors( - const vector<vector<Point3f> >& objectPoints, - const vector<vector<Point2f> >& imagePoints, - const vector<Mat>& rvecs, const vector<Mat>& tvecs, - const Mat& cameraMatrix , const Mat& distCoeffs, - vector<float>& perViewErrors, bool fisheye) { - using namespace cv; - - vector<Point2f> imagePoints2; - size_t totalPoints = 0; - double totalErr = 0; - perViewErrors.resize(objectPoints.size()); - - for (size_t i = 0; i < objectPoints.size(); ++i) { - if (fisheye) { - cv::fisheye::projectPoints(objectPoints[i], imagePoints2, rvecs[i], - tvecs[i], cameraMatrix, distCoeffs); - } else { - projectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, - distCoeffs, imagePoints2); - } - double err = norm(imagePoints[i], imagePoints2, NORM_L2); - - size_t n = objectPoints[i].size(); - perViewErrors[i] = static_cast<float>(std::sqrt(err*err/n)); - totalErr += err*err; - totalPoints += n; - } - - return std::sqrt(totalErr/totalPoints); -} - -//! [board_corners] -static void calcBoardCornerPositions(Size boardSize, float squareSize, - vector<Point3f>& corners) { - corners.clear(); - - for (int i = 0; i < boardSize.height; ++i) - for (int j = 0; j < boardSize.width; ++j) - corners.push_back(Point3f(j*squareSize, i*squareSize, 0)); - -} - -//! [board_corners] -static bool _runCalibration(const Settings& s, Size& imageSize, - Mat& cameraMatrix, Mat& distCoeffs, - vector<vector<Point2f> > imagePoints, vector<Mat>& rvecs, - vector<Mat>& tvecs, vector<float>& reprojErrs, double& totalAvgErr, - vector<Point3f>& newObjPoints, float grid_width, bool release_object) { - - using namespace cv; - - cameraMatrix = Mat::eye(3, 3, CV_64F); - if (s.flag & CALIB_FIX_ASPECT_RATIO) - cameraMatrix.at<double>(0, 0) = s.aspectRatio; - - //if (s.useFisheye) { - // distCoeffs = Mat::zeros(4, 1, CV_64F); - //} else { - distCoeffs = Mat::zeros(8, 1, CV_64F); - //} - - vector<vector<Point3f> > objectPoints(1); - calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0]); - objectPoints[0][s.boardSize.width - 1].x = objectPoints[0][0].x + - grid_width; - newObjPoints = objectPoints[0]; - - objectPoints.resize(imagePoints.size(), objectPoints[0]); - - // Find intrinsic and extrinsic camera parameters - double rms; - - if (s.useFisheye) { - Mat _rvecs, _tvecs; - rms = cv::fisheye::calibrate(objectPoints, imagePoints, imageSize, - cameraMatrix, distCoeffs, _rvecs, _tvecs, s.flag); - - rvecs.reserve(_rvecs.rows); - tvecs.reserve(_tvecs.rows); - for (int i = 0; i < static_cast<int>(objectPoints.size()); i++) { - rvecs.push_back(_rvecs.row(i)); - tvecs.push_back(_tvecs.row(i)); - } - } else { - rms = calibrateCamera(objectPoints, imagePoints, imageSize, - cameraMatrix, distCoeffs, rvecs, tvecs, - s.flag | CALIB_USE_LU); - } - - LOG(INFO) << "Re-projection error reported by calibrateCamera: "<< rms; - - bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs); - - objectPoints.clear(); - objectPoints.resize(imagePoints.size(), newObjPoints); - totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints, rvecs, - tvecs, cameraMatrix, distCoeffs, reprojErrs, s.useFisheye); - - return ok; -} - -//! [run_and_save] -bool runCalibration(const Settings& s, Size imageSize, - Mat& cameraMatrix, Mat& distCoeffs, - vector<vector<Point2f> > imagePoints, float grid_width, - bool release_object) { - vector<Mat> rvecs, tvecs; - vector<float> reprojErrs; - double totalAvgErr = 0; - vector<Point3f> newObjPoints; - - bool ok = _runCalibration(s, imageSize, cameraMatrix, distCoeffs, - imagePoints, rvecs, tvecs, reprojErrs, totalAvgErr, newObjPoints, - grid_width, release_object); - LOG(INFO) << (ok ? "Calibration succeeded" : "Calibration failed") - << ". avg re projection error = " << totalAvgErr; - - return ok; -} - -static bool recalibrate(const Settings &settings, vector<vector<Point2f>> &imagePoints, - Mat &cameraMatrix, Mat &distCoeffs, Size &imageSize) { - using namespace cv; - - VideoCapture camera(settings.device); - if (!camera.isOpened()) { - LOG(ERROR) << "Could not open camera device"; - return false; - } +void ftl::calibration::intrinsic(map<string, string> &opt) { + LOG(INFO) << "Begin intrinsic calibration"; - camera.set(cv::CAP_PROP_FRAME_WIDTH, 1280); // TODO Use settings - camera.set(cv::CAP_PROP_FRAME_HEIGHT, 720); + // TODO PARAMETERS TO CONFIG FILE + Size image_size = Size(1280, 720); + int iter = 60; + string filename_intrinsics = (hasOption(opt, "profile")) ? getOption(opt, "profile") : "./panasonic.yml"; + CalibrationChessboard calib(opt); // TODO paramters hardcoded in constructor + - float grid_width = settings.squareSize * (settings.boardSize.width - 1); - bool release_object = false; - double prevTimestamp = 0.0; - const Scalar RED(0, 0, 255), GREEN(0, 255, 0); + int calibrate_flags = cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_ASPECT_RATIO; + // PARAMETERS -#if CV_VERSION_MAJOR >= 4 - int chessBoardFlags = CALIB_CB_NORMALIZE_IMAGE; -#else - int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE; + cv::VideoCapture camera = cv::VideoCapture(0); - if (!settings.useFisheye) { - // fast check erroneously fails with high distortions like fisheye - chessBoardFlags |= CALIB_CB_FAST_CHECK; + if (!camera.isOpened()) { + LOG(ERROR) << "Could not open camera device"; + return; } -#endif - - auto timestamp = std::chrono::high_resolution_clock::now(); - - for (;;) { - Mat view; - LOG(INFO) << "Grabbing calibration image..."; - camera.grab(); - camera.retrieve(view); - - if (imagePoints.size() >= (size_t)settings.nrFrames) { - bool r = runCalibration(settings, imageSize, - cameraMatrix, distCoeffs, imagePoints, - grid_width, release_object); - - if (r) { - LOG(INFO) << "Calibration successful"; - break; - } + camera.set(cv::CAP_PROP_FRAME_WIDTH, image_size.width); + camera.set(cv::CAP_PROP_FRAME_HEIGHT, image_size.height); + + vector<vector<Vec2f>> image_points; + vector<vector<Vec3f>> object_points; + + while (iter > 0) { + Mat img; + vector<Vec2f> points; + + camera.grab(); + camera.retrieve(img); + + bool found = calib.findPoints(img, points); + if (found) { calib.drawPoints(img, points); } + + cv::imshow("Camera", img); + cv::waitKey(750); + + if (!found) { continue; } + + vector<Vec3f> points_ref; + calib.objectPoints(points_ref); + + Mat camera_matrix, dist_coeffs; + vector<Mat> rvecs, tvecs; + + /* slow + double rms = cv::calibrateCamera( + vector<vector<Vec3f>> { points_ref }, + vector<vector<Vec2f>> { points }, + image_size, camera_matrix, dist_coeffs, + rvecs, tvecs, calibrate_flags + ); + + LOG(INFO) << "rms for pattern: " << rms; + if (rms > max_error) { + LOG(WARNING) << "RMS reprojection error too high, maximum allowed error: " << max_error; + continue; } + */ + + image_points.push_back(points); + object_points.push_back(points_ref); - imageSize = view.size(); // Format input image. - - vector<Point2f> pointBuf; - bool found; - -#if CV_VERSION_MAJOR >= 4 - LOG(INFO) << "Using findChessboardCornersSB()"; - found = findChessboardCornersSB(view, settings.boardSize, - pointBuf, chessBoardFlags); -#else - LOG(INFO) << "Using findChessboardCorners()"; - found = findChessboardCorners(view, settings_.boardSize, - pointBuf, chessBoardFlags); -#endif - - auto now = std::chrono::high_resolution_clock::now(); - std::chrono::duration<double> elapsed = now - timestamp; - - if (found && elapsed.count() > settings.delay) { - timestamp = now; - -#if CV_VERSION_MAJOR >=4 - // findChessboardCornersSB() does not require subpixel step. -#else - // improve the found corners' coordinate accuracy for chessboard. - Mat viewGray; - cvtColor(view, viewGray, COLOR_BGR2GRAY); - cornerSubPix(viewGray, pointBuf, Size(winSize, winSize), - Size(-1, -1), TermCriteria( - TermCriteria::EPS+TermCriteria::COUNT, 30, 0.0001)); -#endif - imagePoints.push_back(pointBuf); - } - - if (found) { - // Draw the corners. - drawChessboardCorners(view, settings.boardSize, - Mat(pointBuf), found); - } else { - LOG(WARNING) << "No calibration pattern found"; - } - - imshow("Left", view); - char key = static_cast<char>(waitKey(10)); - - if (key == 27) - break; + iter--; } - - return true; -} - -inline bool hasOption(const map<string, string> &options, const std::string &opt) { - return options.find(opt) != options.end(); -} - -inline std::string getOption(map<string, string> &options, const std::string &opt) { - auto str = options[opt]; - return str.substr(1,str.size()-2); -} - -void ftl::calibration::intrinsic(map<string, string> &opt) { - using namespace cv; - - Settings settings; - vector<vector<Point2f>> imagePoints; - Mat cameraMatrix; - Mat distCoeffs; - Size imageSize; - - settings.squareSize = 36; - settings.boardSize = Size(9,6); - settings.nrFrames = 30; - settings.delay = 0.5f; - settings.outputfile = "./panasonic.yml"; - settings.device = 0; - settings.useFisheye = false; - settings.aspectRatio = 1.0; - - if (hasOption(opt, "output")) settings.outputfile = getOption(opt,"output"); - if (hasOption(opt, "device")) settings.device = std::stoi(opt["device"]); - - int flag = 0; - flag |= CALIB_FIX_PRINCIPAL_POINT; - flag |= CALIB_ZERO_TANGENT_DIST; - flag |= CALIB_FIX_ASPECT_RATIO; - //flag |= CALIB_FIX_K1; - //flag |= CALIB_FIX_K2; - //flag |= CALIB_FIX_K3; - flag |= CALIB_FIX_K4; - flag |= CALIB_FIX_K5; - - settings.flag = flag; - - if (recalibrate(settings, imagePoints, cameraMatrix, distCoeffs, imageSize)) { - // Yay, calibrated. Write out... - // save intrinsic parameters - FileStorage fs(settings.outputfile, FileStorage::WRITE); - if (fs.isOpened()) { - fs << "M" << cameraMatrix << "D" << distCoeffs; - fs.release(); - } else { - LOG(ERROR) << "Error: can not save the intrinsic parameters to '" << settings.outputfile << "'"; - } - } else { - // Nay, failed - } - + + Mat camera_matrix, dist_coeffs; + vector<Mat> rvecs, tvecs; + + double rms = cv::calibrateCamera( + object_points, image_points, + image_size, camera_matrix, dist_coeffs, + rvecs, tvecs, calibrate_flags + ); + + LOG(INFO) << "final reprojection RMS error: " << rms; + + saveIntrinsics(filename_intrinsics, camera_matrix, dist_coeffs); + LOG(INFO) << "intrinsic paramaters saved to: " << filename_intrinsics; + Mat map1, map2; - initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat::eye(3,3, CV_64F), cameraMatrix, imageSize, CV_16SC2, - map1, map2); + cv::initUndistortRectifyMap(camera_matrix, dist_coeffs, Mat::eye(3,3, CV_64F), camera_matrix, + image_size, CV_16SC2, map1, map2); - VideoCapture camera(settings.device); - if (!camera.isOpened()) { - LOG(ERROR) << "Could not open camera device"; - return; - } - - camera.set(cv::CAP_PROP_FRAME_WIDTH, 1280); // TODO Use settings - camera.set(cv::CAP_PROP_FRAME_HEIGHT, 720); - - while (true) { - Mat img, img2; + while (cv::waitKey(25) != 27) { + Mat img, img_out; + camera.grab(); camera.retrieve(img); - remap(img, img2, map1, map2, INTER_LINEAR); - - imshow("Left", img2); - - char key = static_cast<char>(waitKey(20)); + cv::remap(img, img_out, map1, map2, cv::INTER_CUBIC); - if (key == 27) - break; + cv::imshow("Camera", img_out); } } diff --git a/applications/calibration/src/main.cpp b/applications/calibration/src/main.cpp index 395257f75d712b5190d459692da7d4beeea11954..698c7c9027513d8cea532f0b324f329adf5a6a08 100644 --- a/applications/calibration/src/main.cpp +++ b/applications/calibration/src/main.cpp @@ -1,29 +1,30 @@ #include <loguru.hpp> #include <ftl/configuration.hpp> + #include "lens.hpp" #include "stereo.hpp" #include "align.hpp" int main(int argc, char **argv) { - loguru::g_preamble_date = false; + loguru::g_preamble_date = false; loguru::g_preamble_uptime = false; loguru::g_preamble_thread = false; loguru::init(argc, argv, "--verbosity"); - argc--; + argc--; argv++; - // Process Arguments + // Process Arguments auto options = ftl::config::read_options(&argv, &argc); - if (options.find("intrinsic") != options.end()) { - ftl::calibration::intrinsic(options); - } else if (options.find("stereo") != options.end()) { - ftl::calibration::stereo(options); - } else if (options.find("align") != options.end()) { - ftl::calibration::align(options); - } else { - LOG(ERROR) << "Must have one of: --intrinsic --stereo or --align"; - } + if (options.find("intrinsic") != options.end()) { + ftl::calibration::intrinsic(options); + } else if (options.find("stereo") != options.end()) { + ftl::calibration::stereo(options); + } else if (options.find("align") != options.end()) { + ftl::calibration::align(options); + } else { + LOG(ERROR) << "Must have one of: --intrinsic --stereo or --align"; + } - return 0; -} \ No newline at end of file + return 0; +} diff --git a/applications/calibration/src/stereo.cpp b/applications/calibration/src/stereo.cpp index d43fa61b5aafaa70788909b1310fe40138b1bdc8..65b93e18d306ec00d10298755a4ef6accbab01ff 100644 --- a/applications/calibration/src/stereo.cpp +++ b/applications/calibration/src/stereo.cpp @@ -5,6 +5,7 @@ #include <opencv2/videoio.hpp> #include <opencv2/highgui.hpp> +#include "common.hpp" #include "stereo.hpp" using std::vector; @@ -19,71 +20,24 @@ using cv::stereoCalibrate; using namespace ftl::calibration; -inline std::string getOption(map<string, string> &options, const std::string &opt) { - auto str = options[opt]; - return str.substr(1,str.size()-2); -} - -inline bool hasOption(const map<string, string> &options, const std::string &opt) { - return options.find(opt) != options.end(); -} - -bool loadIntrinsics(const string &ifile, Mat &M1, Mat &D1) { - using namespace cv; - - FileStorage fs; - - // reading intrinsic parameters - fs.open((ifile).c_str(), FileStorage::READ); - if (!fs.isOpened()) { - LOG(WARNING) << "Could not open intrinsics file : " << ifile; - return false; - } - - LOG(INFO) << "Intrinsics from: " << ifile; - - fs["M"] >> M1; - fs["D"] >> D1; - - return true; -} - -CalibrationChessboard::CalibrationChessboard(const map<string, string> &opt) { - pattern_size_ = Size(9, 6); - image_size_ = Size(1280, 720); - pattern_square_size_ = 0.036; - // CALIB_CB_NORMALIZE_IMAGE | CALIB_CB_EXHAUSTIVE | CALIB_CB_ACCURACY - chessboard_flags_ = cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_ACCURACY; -} - -void CalibrationChessboard::objectPoints(vector<Vec3f> &out) { - out.reserve(pattern_size_.width * pattern_size_.height); - for (int row = 0; row < pattern_size_.width; ++row) { - for (int col = 0; col < pattern_size_.height; ++col) { - out.push_back(Vec3f(row * pattern_square_size_, col * pattern_square_size_, 0)); - }} -} - -bool CalibrationChessboard::findPoints(Mat &img, vector<Vec2f> &points) { - return cv::findChessboardCornersSB(img, pattern_size_, points, chessboard_flags_); -} - -void CalibrationChessboard::drawPoints(Mat &img, const vector<Vec2f> &points) { - cv::drawChessboardCorners(img, pattern_size_, points, true); -} - void ftl::calibration::stereo(map<string, string> &opt) { LOG(INFO) << "Begin stereo calibration"; - CalibrationChessboard calib(opt); - - Mat M1, D1; + // TODO PARAMETERS TO CONFIG FILE Size image_size = Size(1280, 720); - int iter = 35; + int iter = 30; + double max_error = 1.0; + float alpha = 0; + string filename_intrinsics = (hasOption(opt, "profile")) ? getOption(opt, "profile") : "./panasonic.yml"; + CalibrationChessboard calib(opt); // TODO paramters hardcoded in constructor + // PARAMETERS + + int stereocalibrate_flags = + cv::CALIB_FIX_INTRINSIC | cv::CALIB_FIX_PRINCIPAL_POINT | cv::CALIB_FIX_ASPECT_RATIO | + cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_SAME_FOCAL_LENGTH | cv::CALIB_RATIONAL_MODEL | + cv::CALIB_FIX_K3 | cv::CALIB_FIX_K4 | cv::CALIB_FIX_K5; vector<cv::VideoCapture> cameras { cv::VideoCapture(0), cv::VideoCapture(1) }; - vector<vector<vector<Vec2f>>> image_points(2); - vector<vector<Vec3f>> object_points; for (auto &camera : cameras ) { if (!camera.isOpened()) { @@ -93,7 +47,18 @@ void ftl::calibration::stereo(map<string, string> &opt) { camera.set(cv::CAP_PROP_FRAME_WIDTH, image_size.width); camera.set(cv::CAP_PROP_FRAME_HEIGHT, image_size.height); } + + vector<vector<vector<Vec2f>>> image_points(2); + vector<vector<Vec3f>> object_points; + vector<Mat> dist_coeffs(2); + vector<Mat> camera_matrices(2); + + // assume identical cameras; load intrinsic parameters + loadIntrinsics(filename_intrinsics, camera_matrices[0], dist_coeffs[0]); + loadIntrinsics(filename_intrinsics, camera_matrices[1], dist_coeffs[1]); + Mat R, T, E, F, per_view_errors; + while (iter > 0) { int res = 0; @@ -103,10 +68,14 @@ void ftl::calibration::stereo(map<string, string> &opt) { for (size_t i = 0; i < 2; i++) { auto &camera = cameras[i]; auto &img = new_img[i]; - auto &points = new_points[i]; camera.grab(); camera.retrieve(img); + } + + for (size_t i = 0; i < 2; i++) { + auto &img = new_img[i]; + auto &points = new_points[i]; if (calib.findPoints(img, points)) { calib.drawPoints(img, points); @@ -115,78 +84,94 @@ void ftl::calibration::stereo(map<string, string> &opt) { cv::imshow("Camera: " + std::to_string(i), img); } - cv::waitKey(500); + cv::waitKey(750); if (res != 2) { LOG(WARNING) << "Input not detected on all inputs"; } else { - for (size_t i = 0; i < 2; i++) { - image_points[i].push_back(new_points[i]); - } vector<Vec3f> points_ref; calib.objectPoints(points_ref); + + // calculate reprojection error with single pair of images + // reject it if RMS reprojection error too high + int flags = stereocalibrate_flags; + + double rms = stereoCalibrate( + vector<vector<Vec3f>> { points_ref }, + vector<vector<Vec2f>> { new_points[0] }, + vector<vector<Vec2f>> { new_points[1] }, + camera_matrices[0], dist_coeffs[0], + camera_matrices[1], dist_coeffs[1], + image_size, R, T, E, F, per_view_errors, + flags); + + LOG(INFO) << "rms for pattern: " << rms; + if (rms > max_error) { + LOG(WARNING) << "RMS reprojection error too high, maximum allowed error: " << max_error; + continue; + } + object_points.push_back(points_ref); + for (size_t i = 0; i < 2; i++) { + image_points[i].push_back(new_points[i]); + } + iter--; } } - vector<Mat> dist_coeffs(2); - vector<Mat> camera_matrices(2); - string filename = (hasOption(opt, "profile")) ? getOption(opt, "profile") : "./panasonic.yml"; - - loadIntrinsics(filename, camera_matrices[0], dist_coeffs[0]); - loadIntrinsics(filename, camera_matrices[1], dist_coeffs[1]); - Mat per_view_errors, R, T, E, F; + // calculate stereoCalibration using all input images (which have low enough + // RMS error in previous step) double rms = stereoCalibrate(object_points, image_points[0], image_points[1], camera_matrices[0], dist_coeffs[0], - camera_matrices[0], dist_coeffs[1], + camera_matrices[1], dist_coeffs[1], image_size, R, T, E, F, per_view_errors, - cv::CALIB_FIX_INTRINSIC + cv::CALIB_FIX_ASPECT_RATIO + - cv::CALIB_ZERO_TANGENT_DIST + cv::CALIB_USE_INTRINSIC_GUESS + - cv::CALIB_SAME_FOCAL_LENGTH + cv::CALIB_RATIONAL_MODEL + - cv::CALIB_FIX_K3 + cv::CALIB_FIX_K4 + cv::CALIB_FIX_K5 - //,cv::TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6) + stereocalibrate_flags, + cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, 120, 1e-6) ); - LOG(INFO) << "extrinsic calibration RMS: " << rms; + LOG(INFO) << "Final extrinsic calibration RMS (reprojection error): " << rms; for (int i = 0; i < per_view_errors.rows * per_view_errors.cols; i++) { - LOG(INFO) << "error: " << ((double*) per_view_errors.data)[i]; + LOG(4) << "error for sample " << i << ": " << ((double*) per_view_errors.data)[i]; } - + Mat R1, R2, P1, P2, Q; cv::Rect validRoi[2]; + // calculate extrinsic parameters stereoRectify( camera_matrices[0], dist_coeffs[0], camera_matrices[1], dist_coeffs[1], image_size, R, T, R1, R2, P1, P2, Q, - cv::CALIB_ZERO_DISPARITY, 1, image_size, - &validRoi[0], &validRoi[1]); - - cv::FileStorage fs; - fs.open(FTL_LOCAL_CONFIG_ROOT "/extrinsics.yml", cv::FileStorage::WRITE); - if (fs.isOpened()) { - fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" - << P1 << "P2" << P2 << "Q" << Q; - fs.release(); - } else { - LOG(ERROR) << "Error: can not save the extrinsic parameters"; - } + cv::CALIB_ZERO_DISPARITY, alpha, image_size, + &validRoi[0], &validRoi[1] + ); + saveExtrinsics(FTL_LOCAL_CONFIG_ROOT "/extrinsics.yml", R, T, R1, R2, P1, P2, Q); + LOG(INFO) << "Stereo camera extrinsics saved to: " << FTL_LOCAL_CONFIG_ROOT "/extrinsics.yml"; + + // display results + vector<Mat> map1(2), map2(2); + cv::initUndistortRectifyMap(camera_matrices[0], dist_coeffs[0], R1, P1, image_size, CV_16SC2, map1[0], map2[0]); + cv::initUndistortRectifyMap(camera_matrices[1], dist_coeffs[1], R2, P2, image_size, CV_16SC2, map1[1], map2[1]); - vector<Mat> mapx(2), mapy(2); - cv::initUndistortRectifyMap(camera_matrices[0], dist_coeffs[0], R1, P1, image_size, CV_32FC2, mapx[0], mapy[0]); - cv::initUndistortRectifyMap(camera_matrices[1], dist_coeffs[1], R2, P2, image_size, CV_32FC2, mapx[1], mapy[1]); vector<Mat> in(2); vector<Mat> out(2); - while(cv::waitKey(50)) { + + while(cv::waitKey(50) == -1) { for(size_t i = 0; i < 2; i++) { auto &camera = cameras[i]; camera.grab(); camera.retrieve(in[i]); cv::imshow("Camera: " + std::to_string(i), in[i]); - cv::remap(in[i], out[i], mapx[i], mapy[i], cv::INTER_CUBIC); + cv::remap(in[i], out[i], map1[i], map2[i], cv::INTER_CUBIC); + + // draw lines + for (int r = 0; r < image_size.height; r = r+50) { + cv::line(out[i], cv::Point(0, r), cv::Point(image_size.width-1, r), cv::Scalar(0,0,255), 1); + } + cv::imshow("Camera " + std::to_string(i) + " (rectified)", out[i]); } } diff --git a/applications/calibration/src/stereo.hpp b/applications/calibration/src/stereo.hpp index 1db23f5d5fbeed7ea1ce7f8b988b0dad4306ba0e..0175ea366251fc9003d4571c97b06c9e38ad5a83 100644 --- a/applications/calibration/src/stereo.hpp +++ b/applications/calibration/src/stereo.hpp @@ -10,34 +10,6 @@ namespace ftl { namespace calibration { -class Calibration { -public: - void objectPoints(std::vector<cv::Vec3f> &out); - bool findPoints(cv::Mat &in, std::vector<cv::Vec2f> &out); - void drawPoints(cv::Mat &img, const std::vector<cv::Vec2f> &points); -}; - -/** - * Parameters: - * - pattern size, inner corners - * - square size, millimeters (TODO: meters) - * - image size, pixels - * - flags, ChessboardCornersSB - */ -class CalibrationChessboard : Calibration { -public: - CalibrationChessboard(const std::map<std::string, std::string> &opt); - void objectPoints(std::vector<cv::Vec3f> &out); - bool findPoints(cv::Mat &in, std::vector<cv::Vec2f> &out); - void drawPoints(cv::Mat &img, const std::vector<cv::Vec2f> &points); - -private: - int chessboard_flags_ = 0; - float pattern_square_size_; - cv::Size pattern_size_; - cv::Size image_size_; -}; - void stereo(std::map<std::string, std::string> &opt); }