Skip to content

Conversation

@Myzhar
Copy link
Member

@Myzhar Myzhar commented Nov 11, 2025

  • Improve the experience with Virtual Stereo Calibration
  • Add ZED SDK compatible calibration files

@Myzhar Myzhar self-assigned this Nov 11, 2025
@Myzhar Myzhar requested a review from Copilot November 20, 2025 16:14
Copilot finished reviewing on behalf of Myzhar November 20, 2025 16:16
Copy link

Copilot AI left a 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 CalibrationChecker class 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.

Comment on lines 412 to 417
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();
Copy link

Copilot AI Nov 20, 2025

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.

Suggested change
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 uses AI. Check for mistakes.
@Myzhar Myzhar requested a review from Copilot November 28, 2025 09:10
@P-yver P-yver marked this pull request as ready for review November 28, 2025 09:12
Copilot finished reviewing on behalf of Myzhar November 28, 2025 09:13
Copy link

Copilot AI left a 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.

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]
Copy link

Copilot AI Nov 28, 2025

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]

Suggested change
// least 0.4 [min_area-max_area]
// least 0.45 [min_area-max_area]

Copilot uses AI. Check for mistakes.
Comment on lines +299 to +300
// min_skew = 0.0f;
// min_size = 0.0f;
Copy link

Copilot AI Nov 28, 2025

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.

Suggested change
// min_skew = 0.0f;
// min_size = 0.0f;

Copilot uses AI. Check for mistakes.
Comment on lines +575 to +593
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) {
Copy link

Copilot AI Nov 28, 2025

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.

Suggested change
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) {

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Nov 28, 2025

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)

Suggested change
constexpr float square_size = 25.4; // mm
constexpr float square_size = 25.4; // mm (1 inch for the standard OpenCV pattern)

Copilot uses AI. Check for mistakes.
Comment on lines 161 to 162
std::cout << " !!! Failed to save OpenCV calibration file " << opencv_file
<< " !!!" << std::endl;
Copy link

Copilot AI Nov 28, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines 358 to 368
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
Copy link

Copilot AI Nov 28, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines +227 to +231
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);
Copy link

Copilot AI Nov 28, 2025

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Nov 28, 2025

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."

Suggested change
std::cerr << " * use the command " << args.app_name
std::cerr << " * use the command '" << args.app_name

Copilot uses AI. Check for mistakes.
outfile << "k3 = " << calib.D.at<float>(2) << "\n";
outfile << "k4 = " << calib.D.at<float>(3) << "\n";
}
outfile<<"\n";
Copy link

Copilot AI Nov 28, 2025

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.

Suggested change
outfile<<"\n";

Copilot uses AI. Check for mistakes.
Comment on lines +231 to +234
std::cerr
<< " !!! Cannot save the calibration file: 'Unable to open output file'"
<< std::endl;
return std::string();
Copy link

Copilot AI Nov 28, 2025

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.

Copilot uses AI. Check for mistakes.
return EXIT_FAILURE;
}

if (calib_data.T.at<float>(0) > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants