-
Notifications
You must be signed in to change notification settings - Fork 1
Improve Virtual Stereo Calibration #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Myzhar
commented
Nov 11, 2025
- Improve the experience with Virtual Stereo Calibration
- Add ZED SDK compatible calibration files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR significantly improves the virtual stereo calibration experience by introducing a new calibration quality checker system and adding support for ZED SDK-compatible calibration file formats. The changes enhance the user experience with better visual feedback and more robust sample collection criteria.
Key Changes
- Implemented a new
CalibrationCheckerclass that evaluates calibration sample quality based on position, size, and skew metrics - Added ZED SDK format calibration file export in addition to OpenCV format
- Enhanced UI with real-time coverage indicators and better status messages for sample acquisition
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| stereo_calibration/src/opencv_calibration.cpp | Refactored calibration function with improved error handling and added ZED SDK format file generation |
| stereo_calibration/include/opencv_calibration.hpp | Updated function signatures to support new parameters (is_dual_mono, is_4k, verbose) and changed matrix type from CV_64FC1 to CV_32FC1 |
| stereo_calibration/src/main.cpp | Overhauled UI/UX with calibration checker integration, improved argument parsing, and enhanced visual feedback |
| stereo_calibration/src/calibration_checker.cpp | New file implementing sample quality evaluation based on checkerboard position, size, and skew |
| stereo_calibration/include/calibration_checker.hpp | New header defining the CalibrationChecker class and related data structures |
| stereo_calibration/.vscode/settings.json | VS Code configuration file disabling error squiggles |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::cout | ||
| << "The resolution for the calibration is not valid\n\nUse 4k " | ||
| "(3840x2160) for ZED X One 4K and FHD1200 (1920x1200) for ZED X One GS" | ||
| << std::endl; | ||
|
|
||
| return std::string(); |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The control flow will never reach these lines because the function returns at line 409 for the 4K camera case (is_4k == true) and at line 301 for the non-4K case (is_4k == false). This unreachable code should be removed or the function logic should be restructured.
Additionally, the error message is misleading since it will never be displayed.
| std::cout | |
| << "The resolution for the calibration is not valid\n\nUse 4k " | |
| "(3840x2160) for ZED X One 4K and FHD1200 (1920x1200) for ZED X One GS" | |
| << std::endl; | |
| return std::string(); |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 20 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
stereo_calibration/src/main.cpp
Outdated
| const float min_y_coverage = | ||
| 0.7f; // Checkerboard Y position should cover 70% of the image height | ||
| const float min_area_range = 0.45f; // Checkerboard area range size should be at | ||
| // least 0.4 [min_area-max_area] |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in comment at line 18: "at least 0.4 [min_area-max_area]" but the actual value is 0.45f. The comment should match the constant value or vice versa. Update to: // least 0.45 [min_area-max_area]
| // least 0.4 [min_area-max_area] | |
| // least 0.45 [min_area-max_area] |
| // min_skew = 0.0f; | ||
| // min_size = 0.0f; |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The commented-out code at lines 299-300 should be removed. If there's a reason to keep the option of not rewarding small size or skew in the future, this should be documented or made configurable rather than left as commented code.
| // min_skew = 0.0f; | |
| // min_size = 0.0f; |
| if (top_left_count >= min_samples / 4) { | ||
| cv::rectangle( | ||
| coverage_indicator, cv::Point(0, 0), | ||
| cv::Point(coverage_indicator.cols / 2, coverage_indicator.rows / 2), | ||
| cv::Scalar(255), -1); | ||
| } | ||
| if (top_right_count >= min_samples / 4) { | ||
| cv::rectangle( | ||
| coverage_indicator, cv::Point(coverage_indicator.cols / 2, 0), | ||
| cv::Point(coverage_indicator.cols, coverage_indicator.rows / 2), | ||
| cv::Scalar(255), -1); | ||
| } | ||
| if (bottom_left_count >= min_samples / 4) { | ||
| cv::rectangle( | ||
| coverage_indicator, cv::Point(0, coverage_indicator.rows / 2), | ||
| cv::Point(coverage_indicator.cols / 2, coverage_indicator.rows), | ||
| cv::Scalar(255), -1); | ||
| } | ||
| if (bottom_right_count >= min_samples / 4) { |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Potential integer division issue: min_samples / 4 at lines 575, 581, 587, 593 performs integer division. If min_samples is 25 (as defined), then min_samples / 4 = 6 (with remainder discarded). This means the four quadrants require 6 samples each for a total of 24, not 25. Consider documenting this behavior or using a more explicit calculation like (min_samples + 3) / 4 to handle rounding more explicitly.
| if (top_left_count >= min_samples / 4) { | |
| cv::rectangle( | |
| coverage_indicator, cv::Point(0, 0), | |
| cv::Point(coverage_indicator.cols / 2, coverage_indicator.rows / 2), | |
| cv::Scalar(255), -1); | |
| } | |
| if (top_right_count >= min_samples / 4) { | |
| cv::rectangle( | |
| coverage_indicator, cv::Point(coverage_indicator.cols / 2, 0), | |
| cv::Point(coverage_indicator.cols, coverage_indicator.rows / 2), | |
| cv::Scalar(255), -1); | |
| } | |
| if (bottom_left_count >= min_samples / 4) { | |
| cv::rectangle( | |
| coverage_indicator, cv::Point(0, coverage_indicator.rows / 2), | |
| cv::Point(coverage_indicator.cols / 2, coverage_indicator.rows), | |
| cv::Scalar(255), -1); | |
| } | |
| if (bottom_right_count >= min_samples / 4) { | |
| if (top_left_count >= (min_samples + 3) / 4) { | |
| cv::rectangle( | |
| coverage_indicator, cv::Point(0, 0), | |
| cv::Point(coverage_indicator.cols / 2, coverage_indicator.rows / 2), | |
| cv::Scalar(255), -1); | |
| } | |
| if (top_right_count >= (min_samples + 3) / 4) { | |
| cv::rectangle( | |
| coverage_indicator, cv::Point(coverage_indicator.cols / 2, 0), | |
| cv::Point(coverage_indicator.cols, coverage_indicator.rows / 2), | |
| cv::Scalar(255), -1); | |
| } | |
| if (bottom_left_count >= (min_samples + 3) / 4) { | |
| cv::rectangle( | |
| coverage_indicator, cv::Point(0, coverage_indicator.rows / 2), | |
| cv::Point(coverage_indicator.cols / 2, coverage_indicator.rows), | |
| cv::Scalar(255), -1); | |
| } | |
| if (bottom_right_count >= (min_samples + 3) / 4) { |
stereo_calibration/src/main.cpp
Outdated
|
|
||
| constexpr int target_w = 9; // number of horizontal inner edges | ||
| constexpr int target_h = 6; // number of vertical inner edges | ||
| constexpr float square_size = 25.4; // mm |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The default checkerboard square size is 25.4mm (1 inch), but the comment says "mm". This is correct but could be more explicit that 25.4mm equals 1 inch for the standard OpenCV checkerboard pattern. Consider adding: // mm (1 inch for the standard OpenCV pattern)
| constexpr float square_size = 25.4; // mm | |
| constexpr float square_size = 25.4; // mm (1 inch for the standard OpenCV pattern) |
| std::cout << " !!! Failed to save OpenCV calibration file " << opencv_file | ||
| << " !!!" << std::endl; |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Inconsistent spacing in the error message. Line 161 has " !!! Failed to save OpenCV calibration file " with spaces around the file name, but line 172 has the same message for ZED SDK format. Both should have consistent formatting.
| outfile << "cx = " << left.K.at<float>(0, 2) - (3840 - 1920) / 2 | ||
| << "\n"; | ||
| outfile << "cy = " << left.K.at<float>(1, 2) - (2160 - 1200) / 2 | ||
| << "\n\n"; | ||
|
|
||
| outfile << "[RIGHT_CAM_FHD1200]\n"; | ||
| outfile << "fx = " << right.K.at<float>(0, 0) << "\n"; | ||
| outfile << "fy = " << right.K.at<float>(1, 1) << "\n"; | ||
| outfile << "cx = " << right.K.at<float>(0, 2) - (3840 - 1920) / 2 | ||
| << "\n"; | ||
| outfile << "cy = " << right.K.at<float>(1, 2) - (2160 - 1200) / 2 |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The division operation (3840 - 3200) / 2 at lines 330, 338, 358, 366 uses integer division which results in 320. Similarly, (2160 - 1800) / 2 results in 180, and (3840 - 1920) / 2 results in 960, and (2160 - 1200) / 2 results in 480. While these are correct, the float subtraction context might cause confusion. Consider adding explicit comments or using floating-point division if needed for clarity.
| outfile << "cx = " << left.K.at<float>(0, 2) - (3840 - 1920) / 2 | |
| << "\n"; | |
| outfile << "cy = " << left.K.at<float>(1, 2) - (2160 - 1200) / 2 | |
| << "\n\n"; | |
| outfile << "[RIGHT_CAM_FHD1200]\n"; | |
| outfile << "fx = " << right.K.at<float>(0, 0) << "\n"; | |
| outfile << "fy = " << right.K.at<float>(1, 1) << "\n"; | |
| outfile << "cx = " << right.K.at<float>(0, 2) - (3840 - 1920) / 2 | |
| << "\n"; | |
| outfile << "cy = " << right.K.at<float>(1, 2) - (2160 - 1200) / 2 | |
| // Subtract half the difference to center for FHD1200 crop | |
| outfile << "cx = " << left.K.at<float>(0, 2) - (3840.0f - 1920.0f) / 2.0f | |
| << "\n"; | |
| outfile << "cy = " << left.K.at<float>(1, 2) - (2160.0f - 1200.0f) / 2.0f | |
| << "\n\n"; | |
| outfile << "[RIGHT_CAM_FHD1200]\n"; | |
| outfile << "fx = " << right.K.at<float>(0, 0) << "\n"; | |
| outfile << "fy = " << right.K.at<float>(1, 1) << "\n"; | |
| // Subtract half the difference to center for FHD1200 crop | |
| outfile << "cx = " << right.K.at<float>(0, 2) - (3840.0f - 1920.0f) / 2.0f | |
| << "\n"; | |
| outfile << "cy = " << right.K.at<float>(1, 2) - (2160.0f - 1200.0f) / 2.0f |
| std::abs(p1.pos.x - p2.pos.x) / std::max(std::max(p1.pos.x, p2.pos.x), epsilon); | ||
| float pos_y_diff = | ||
| std::abs(p1.pos.y - p2.pos.y) / std::max(std::max(p1.pos.y, p2.pos.y), epsilon); | ||
| float size_diff = std::abs(p1.size - p2.size) / std::max(std::max(p1.size, p2.size), epsilon); | ||
| float skew_diff = std::abs(p1.skew - p2.skew) / std::max(std::max(p1.skew, p2.skew), epsilon); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The division by std::max(std::max(p1.pos.x, p2.pos.x), epsilon) at lines 227 and 229 can produce incorrect results when both values are close to zero. For example, if p1.pos.x = 0.001 and p2.pos.x = 0.002, the difference is 0.001 and the max is 0.002, giving a diff of 0.001/0.002 = 0.5 = 50%, which seems incorrect for such small absolute values. Consider using the absolute difference relative to a meaningful scale (like 1.0) or the average of the two values instead of the maximum.
| std::abs(p1.pos.x - p2.pos.x) / std::max(std::max(p1.pos.x, p2.pos.x), epsilon); | |
| float pos_y_diff = | |
| std::abs(p1.pos.y - p2.pos.y) / std::max(std::max(p1.pos.y, p2.pos.y), epsilon); | |
| float size_diff = std::abs(p1.size - p2.size) / std::max(std::max(p1.size, p2.size), epsilon); | |
| float skew_diff = std::abs(p1.skew - p2.skew) / std::max(std::max(p1.skew, p2.skew), epsilon); | |
| std::abs(p1.pos.x - p2.pos.x) / std::max((std::abs(p1.pos.x) + std::abs(p2.pos.x)) / 2.0f, epsilon); | |
| float pos_y_diff = | |
| std::abs(p1.pos.y - p2.pos.y) / std::max((std::abs(p1.pos.y) + std::abs(p2.pos.y)) / 2.0f, epsilon); | |
| float size_diff = std::abs(p1.size - p2.size) / std::max((std::abs(p1.size) + std::abs(p2.size)) / 2.0f, epsilon); | |
| float skew_diff = std::abs(p1.skew - p2.skew) / std::max((std::abs(p1.skew) + std::abs(p2.skew)) / 2.0f, epsilon); |
stereo_calibration/src/main.cpp
Outdated
| std::cerr << "Error: Left and Right camera IDs or Left and Right " | ||
| "camera Serial Numbers must be both provided." | ||
| << std::endl; | ||
| std::cerr << " * use the command " << args.app_name |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message uses single quotes around the command but the actual command string has a typo - there's an extra single quote: " * use the command " << args.app_name << " -h' for details.". Should be: " * use the command '" << args.app_name << " -h' for details."
| std::cerr << " * use the command " << args.app_name | |
| std::cerr << " * use the command '" << args.app_name |
| outfile << "k3 = " << calib.D.at<float>(2) << "\n"; | ||
| outfile << "k4 = " << calib.D.at<float>(3) << "\n"; | ||
| } | ||
| outfile<<"\n"; |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Missing newline character in output. The message on line 222 (outfile<<"\n";) should consistently use either "\n" or std::endl. The rest of the function uses "\n" inside string literals, so this standalone statement is inconsistent. Consider using outfile << "\n"; if needed, or remove it if the newline is already included in the previous line.
| outfile<<"\n"; |
| std::cerr | ||
| << " !!! Cannot save the calibration file: 'Unable to open output file'" | ||
| << std::endl; | ||
| return std::string(); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The duplicate message "Parameter file written successfully" appears on both line 303 and line 395. This is correct behavior (one for each camera type), but the success message is printed before returning, while the error path (lines 231-234) returns an empty string. However, the error message on line 232 says "Unable to open output file" but this could also happen at line 308 or 313 where the resolution validation fails - those don't print this message. Consider consolidating error handling or making error messages more specific.
…encv-calibration into improve_virtual_stereo
| return EXIT_FAILURE; | ||
| } | ||
|
|
||
| if (calib_data.T.at<float>(0) > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicated check