From 4ae801d106b37ebe6cce50c208c33f1e96b5ad9d Mon Sep 17 00:00:00 2001 From: Gary Bradski Date: Wed, 21 Jun 2017 05:25:58 +0000 Subject: [PATCH 1/3] example 19-2 --- CMakeLists.txt | 2 + example_19-02.cpp | 209 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 example_19-02.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 40b0480..88a2a84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable( example_17-01 example_17-01.cpp ) add_executable( example_18-01 example_18-01.cpp ) add_executable( example_18-01_from_disk example_18-01_from_disk.cpp ) add_executable( example_19-01 example_19-01.cpp ) +add_executable( example_19-02 example_19-02.cpp ) add_executable( example_20-01 example_20-01.cpp ) add_executable( example_20-02 example_20-02.cpp ) add_executable( example_21-01 example_21-01.cpp ) @@ -126,6 +127,7 @@ target_link_libraries( example_17-01 ${OpenCV_LIBS} ) target_link_libraries( example_18-01 ${OpenCV_LIBS} ) target_link_libraries( example_18-01_from_disk ${OpenCV_LIBS} ) target_link_libraries( example_19-01 ${OpenCV_LIBS} ) +target_link_libraries( example_19-02 ${OpenCV_LIBS} ) target_link_libraries( example_20-01 ${OpenCV_LIBS} ) target_link_libraries( example_20-02 ${OpenCV_LIBS} ) target_link_libraries( example_21-01 ${OpenCV_LIBS} ) diff --git a/example_19-02.cpp b/example_19-02.cpp new file mode 100644 index 0000000..abf865d --- /dev/null +++ b/example_19-02.cpp @@ -0,0 +1,209 @@ +// Example 19-2. Computing the fundamental matrix using RANSAC +#include +#include + +using namespace std; +void help(char *argv[]) { + cout << "\nExample 19-2, Computing the fundamental matrix using RANSAC relating 2 images. Show the camera a checkerboard " + << "\nCall" + << "\n./example_19-2 <1:board_w> <2:board_h> <3:# of boards> <4:delay capture this many ms between frames> <5:scale the images 0-1>" + << "\n\nExample call:" + << "\n./example_19-2 12 12 20 500 0.5" + << "\n" + << endl; +} + + +// args: [board_w] [board_h] [number_of_boards] [delay]? [scale]? +// +int main(int argc, char *argv[]) { + int n_boards = 0; + float image_sf = 0.5f; + float delay = 1.f; + int board_w = 0; + int board_h = 0; + + // Will be set by input list + if (argc < 4 || argc > 6) { + cout << "\nERROR: Wrong number of input parameters, need 5, got " << argc - 1 << "\n"; + help(argv); + return -1; + } + board_w = atoi(argv[1]); + board_h = atoi(argv[2]); + n_boards = atoi(argv[3]); + delay = atof(argv[4]); + image_sf = atof(argv[5]); + int board_n = board_w * board_h; + cv::Size board_sz = cv::Size(board_w, board_h); + cv::VideoCapture capture(0); + + if (!capture.isOpened()) { + cout << "\nCouldn't open the camera\n"; + help(argv); + return -1; + } + // Allocate Storage + // + vector > image_points; + vector > object_points; + // Capture corner views; loop until we've got n_boards number of + // successful captures (meaning: all corners on each + // board are found). + // + double last_captured_timestamp = 0; + cv::Size image_size; + while (image_points.size() < (size_t)n_boards) { + cv::Mat image0, image; + capture >> image0; + image_size = image0.size(); + resize(image0, image, cv::Size(), image_sf, image_sf, cv::INTER_LINEAR); + // Find the board + // + vector corners; + bool found = cv::findChessboardCorners(image, board_sz, corners); + // Draw it + // + cv::drawChessboardCorners(image, board_sz, corners, found); + // If we got a good board, add it to our data + // + double timestamp = (double)clock() / CLOCKS_PER_SEC; + if (found && timestamp - last_captured_timestamp > 1) { + last_captured_timestamp = timestamp; + image ^= cv::Scalar::all(255); + + cv::Mat mcorners(corners); + // do not copy the data + mcorners *= (1. / image_sf); + // scale corner coordinates + image_points.push_back(corners); + object_points.push_back(vector()); + vector &opts = object_points.back(); + opts.resize(board_n); + for (int j = 0; j < board_n; j++) { + opts[j] = cv::Point3f((float)(j / board_w), (float)(j % board_w), 0.f); + } + cout << "Collected our " << (int)image_points.size() << " of " << n_boards + << " needed chessboard images\n" << endl; + } + // in color if we did collect the image + // + cv::imshow("Calibration", image); + if ((cv::waitKey(30) & 255) == 27) + return -1; + } + // end collection while() loop. + cv::destroyWindow("Calibration"); + cout << "\n\n*** CALIBRATING THE CAMERA...\n" << endl; + // Calibrate the camera! + // + cv::Mat intrinsic_matrix, distortion_coeffs; + double err = cv::calibrateCamera( + object_points, // Vector of vectors of points + // from the calibration pattern + image_points, // Vector of vectors of projected + // locations (on images) + image_size, // Size of images used + intrinsic_matrix, // Output camera matrix + distortion_coeffs, // Output distortion coefficients + cv::noArray(), // We'll pass on the rotation vectors... + cv::noArray(), // ...and the translation vectors + cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_PRINCIPAL_POINT); + + // Save the intrinsics and distortions + cout << " *** DONE!\n\nReprojection error is " << err + << "\nStoring Intrinsics.xml and Distortions.xml files\n\n"; + cv::FileStorage fs("intrinsics.xml", cv::FileStorage::WRITE); + fs << "image_width" << image_size.width << "image_height" << image_size.height + << "camera_matrix" << intrinsic_matrix << "distortion_coefficients" + << distortion_coeffs; + fs.release(); + + // Example of loading these matrices back in: + // + fs.open("intrinsics.xml", cv::FileStorage::READ); + cout << "\nimage width: " << (int)fs["image_width"]; + cout << "\nimage height: " << (int)fs["image_height"]; + cv::Mat intrinsic_matrix_loaded, distortion_coeffs_loaded; + fs["camera_matrix"] >> intrinsic_matrix_loaded; + fs["distortion_coefficients"] >> distortion_coeffs_loaded; + cout << "\nintrinsic matrix:" << intrinsic_matrix_loaded; + cout << "\ndistortion coefficients: " << distortion_coeffs_loaded << endl; + + // Compute Fundamental Matrix Between the first + // and the second frames: + // + cv::undistortPoints( + image_points[0], // Observed point coordinates (from frame 0) + image_points[0], // undistorted coordinates (in this case, + // the same array as above) + intrinsic_matrix, // Intrinsics, from cv::calibrateCamera() + distortion_coeffs, // Distortion coefficients, also + // from cv::calibrateCamera() + cv::Mat(), // Rectification transformation (but + // here, we don't need this) + intrinsic_matrix // New camera matrix + ); + + cv::undistortPoints( + image_points[1], // Observed point coordinates (from frame 1) + image_points[1], // undistorted coordinates (in this case, + // the same array as above) + intrinsic_matrix, // Intrinsics, from cv::calibrateCamera() + distortion_coeffs, // Distortion coefficients, also + // from cv::calibrateCamera() + cv::Mat(), // Rectification transformation (but + // here, we don't need this) + intrinsic_matrix // New camera matrix + ); + + // Since all the found chessboard corners are inliers, i.e., they + // must satisfy epipolar constraints, here we are using the + // fastest, and the most accurate (in this case) 8-point algorithm. + // + cv::Mat F = cv::findFundamentalMat( // Return computed matrix + image_points[0], // Points from frame 0 + image_points[1], // Points from frame 1 + cv::FM_8POINT // Use the 8-point algorithm + ); + cout << "Fundamental matrix: " << F << endl; + + // Build the undistort map which we will use for all + // subsequent frames. + // + cv::Mat map1, map2; + cv::initUndistortRectifyMap( + intrinsic_matrix_loaded, // Our camera matrix + distortion_coeffs_loaded, // Our distortion coefficients + cv::Mat(), // (Optional) Rectification, don't + // need. + intrinsic_matrix_loaded, // "New" matrix, here it's the same + // as the first argument. + image_size, // Size of undistorted image we want + CV_16SC2, // Specifies the format of map to use + map1, // Integerized coordinates + map2 // Fixed-point offsets for + // elements of map1 + ); + + // Just run the camera to the screen, now showing the raw and + // the undistorted image. + // + for (;;) { + cv::Mat image, image0; + capture >> image0; + if (image0.empty()) + break; + cv::remap(image0, // Input image + image, // Output image + map1, // Integer part of map + map2, // Fixed point part of map + cv::INTER_LINEAR, cv::BORDER_CONSTANT, + cv::Scalar() // Set border values to black + ); + cv::imshow("Undistorted", image); + if ((cv::waitKey(30) & 255) == 27) + break; + } + return 1; +} From ead6e82b77282963a725ff28341cda4b017515bf Mon Sep 17 00:00:00 2001 From: Gary Bradski Date: Wed, 21 Jun 2017 05:58:38 +0000 Subject: [PATCH 2/3] fix 18-01, 19-2, add checkerboard9x6.png --- example_18-01.cpp | 4 +++- example_19-02.cpp | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/example_18-01.cpp b/example_18-01.cpp index b1f50eb..9e9255c 100644 --- a/example_18-01.cpp +++ b/example_18-01.cpp @@ -15,6 +15,8 @@ void help(char **argv) { << " reading and collecting the requested number of views,\n" << " and calibrating the camera\n\n" << "Call:\n" << argv[0] << " \n\n" + << "Example:\n./example_18-01 9 6 15 500 0.5\n" + << "\n -- use the checkerboard9x6.png provided\n\n" << " * First it reads in checker boards and calibrates itself\n" << " * Then it saves and reloads the calibration matricies\n" << " * Then it creates an undistortion map and finaly\n" @@ -29,7 +31,7 @@ int main(int argc, char *argv[]) { int board_w = 0; int board_h = 0; - if (argc < 4 || argc > 6) { + if (argc != 6) { cout << "\nERROR: Wrong number of input parameters\n"; help(argv); return -1; diff --git a/example_19-02.cpp b/example_19-02.cpp index abf865d..0bb9d94 100644 --- a/example_19-02.cpp +++ b/example_19-02.cpp @@ -8,7 +8,8 @@ void help(char *argv[]) { << "\nCall" << "\n./example_19-2 <1:board_w> <2:board_h> <3:# of boards> <4:delay capture this many ms between frames> <5:scale the images 0-1>" << "\n\nExample call:" - << "\n./example_19-2 12 12 20 500 0.5" + << "\n./example_19-2 9 6 20 500 0.5" + << "\n\n -- use the checkerboard9x6.png provided" << "\n" << endl; } @@ -24,7 +25,7 @@ int main(int argc, char *argv[]) { int board_h = 0; // Will be set by input list - if (argc < 4 || argc > 6) { + if (argc != 6) { cout << "\nERROR: Wrong number of input parameters, need 5, got " << argc - 1 << "\n"; help(argv); return -1; From 70ff5f26b18a8a2083dd7c916ce0aa3de26d3c88 Mon Sep 17 00:00:00 2001 From: Gary Bradski Date: Wed, 21 Jun 2017 05:59:23 +0000 Subject: [PATCH 3/3] adding checkerboard9x6.png --- checkerboard9x6.png | Bin 0 -> 22255 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 checkerboard9x6.png diff --git a/checkerboard9x6.png b/checkerboard9x6.png new file mode 100644 index 0000000000000000000000000000000000000000..5227c918717a0fe9111d2dc302d5c9aa073065e9 GIT binary patch literal 22255 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