update:upload

This commit is contained in:
2026-10-14 13:21:06 +08:00
commit 6de0b97d80
3 changed files with 559 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# Xmake cache
.xmake/
build/
images/
output/
.vscode/
# MacOS Cache
.DS_Store
+467
View File
@@ -0,0 +1,467 @@
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include <vector>
namespace fs = std::filesystem;
struct Options {
std::string inputDir = "占位符字段";
std::string outputFile = "占位符字段.yml";
std::string diagnosticsDir = "占位符字段";
// 内角点数量,不是黑白方格数量
int cols = 占位符字段;
int rows = 占位符字段;
// 每个方格的实际边长,单位 mm
double squareSize = 占位符字段;
int minImages = 10;
bool visualize = false;
bool saveDiagnostics = true;
};
static void printUsage(const char* program)
{
std::cout
<< "OpenCV C++ Camera Calibrator\n\n"
<< "Usage:\n"
<< " " << program << " [options]\n\n"
<< "Options:\n"
<< " --input DIR Image directory (default: images)\n"
<< " --output FILE Output YAML (default: "
"output/camera_intrinsics.yml)\n"
<< " --cols N Inner corners per row (default: 9)\n"
<< " --rows N Inner corners per column (default: 6)\n"
<< " --square MM Square side length in mm (default: 25)\n"
<< " --min-images N Minimum valid images (default: 10)\n"
<< " --visualize Show detected corners interactively\n"
<< " --no-diagnostics Do not save diagnostic images\n"
<< " --help Show this help\n\n"
<< "Example for a 10x7-square board with 25 mm squares:\n"
<< " " << program
<< " --input images --cols 9 --rows 6 --square 25\n";
}
static bool parseInt(const std::string& s, int& value)
{
try {
size_t pos = 0;
int v = std::stoi(s, &pos);
if (pos != s.size())
return false;
value = v;
return true;
} catch (...) {
return false;
}
}
static bool parseDouble(const std::string& s, double& value)
{
try {
size_t pos = 0;
double v = std::stod(s, &pos);
if (pos != s.size() || !std::isfinite(v))
return false;
value = v;
return true;
} catch (...) {
return false;
}
}
static bool parseArgs(int argc, char** argv, Options& opt)
{
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
if (arg == "--help") {
printUsage(argv[0]);
return false;
} else if (arg == "--visualize") {
opt.visualize = true;
} else if (arg == "--no-diagnostics") {
opt.saveDiagnostics = false;
} else if (arg == "--input" ||
arg == "--output" ||
arg == "--cols" ||
arg == "--rows" ||
arg == "--square" ||
arg == "--min-images") {
if (i + 1 >= argc) {
std::cerr << "Missing value for " << arg << '\n';
return false;
}
const std::string value = argv[++i];
if (arg == "--input") {
opt.inputDir = value;
} else if (arg == "--output") {
opt.outputFile = value;
} else if (arg == "--cols") {
if (!parseInt(value, opt.cols)) {
std::cerr << "Invalid --cols value\n";
return false;
}
} else if (arg == "--rows") {
if (!parseInt(value, opt.rows)) {
std::cerr << "Invalid --rows value\n";
return false;
}
} else if (arg == "--square") {
if (!parseDouble(value, opt.squareSize)) {
std::cerr << "Invalid --square value\n";
return false;
}
} else if (arg == "--min-images") {
if (!parseInt(value, opt.minImages)) {
std::cerr << "Invalid --min-images value\n";
return false;
}
}
} else {
std::cerr << "Unknown option: " << arg << '\n';
return false;
}
}
if (opt.cols < 2 || opt.rows < 2 ||
opt.squareSize <= 0 || opt.minImages < 3) {
std::cerr << "Invalid parameters. Check cols, rows, square "
"and min-images.\n";
return false;
}
return true;
}
static std::string lower(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return s;
}
static bool isImageFile(const fs::path& path)
{
const std::string ext = lower(path.extension().string());
static const std::set<std::string> extensions = {
".png", ".jpg", ".jpeg", ".bmp", ".tif", ".tiff"
};
return extensions.count(ext) > 0;
}
struct ViewData {
std::string filename;
std::vector<cv::Point2f> corners;
cv::Mat image;
};
int main(int argc, char** argv)
{
Options opt;
if (!parseArgs(argc, argv, opt)) {
return 1;
}
const cv::Size boardSize(opt.cols, opt.rows);
std::cout << "=== Camera Calibration ===\n"
<< "Input directory : " << opt.inputDir << '\n'
<< "Output file : " << opt.outputFile << '\n'
<< "Inner corners : " << opt.cols
<< " x " << opt.rows << '\n'
<< "Square size : " << opt.squareSize << " mm\n"
<< "Minimum images : " << opt.minImages << "\n\n";
if (!fs::exists(opt.inputDir) ||
!fs::is_directory(opt.inputDir)) {
std::cerr << "Input directory does not exist: "
<< opt.inputDir << '\n';
return 2;
}
std::vector<fs::path> files;
for (const auto& entry : fs::directory_iterator(opt.inputDir)) {
if (entry.is_regular_file() && isImageFile(entry.path())) {
files.push_back(entry.path());
}
}
std::sort(files.begin(), files.end());
if (files.empty()) {
std::cerr << "No supported images found in "
<< opt.inputDir << '\n';
return 2;
}
std::cout << "Found " << files.size() << " candidate images.\n";
std::vector<ViewData> views;
cv::Size imageSize;
int readFailures = 0;
int cornerFailures = 0;
int sizeFailures = 0;
const cv::TermCriteria subpixCriteria(
cv::TermCriteria::EPS | cv::TermCriteria::MAX_ITER,
40, 0.001
);
for (const auto& path : files) {
cv::Mat image = cv::imread(path.string(), cv::IMREAD_COLOR);
if (image.empty()) {
std::cout << "[READ FAIL] " << path.filename().string()
<< '\n';
++readFailures;
continue;
}
if (imageSize.empty()) {
imageSize = image.size();
} else if (image.size() != imageSize) {
std::cout << "[SIZE MISMATCH] "
<< path.filename().string() << " is "
<< image.cols << "x" << image.rows
<< "; expected " << imageSize.width
<< "x" << imageSize.height << '\n';
++sizeFailures;
continue;
}
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
std::vector<cv::Point2f> corners;
const bool found = cv::findChessboardCorners(
gray,
boardSize,
corners,
cv::CALIB_CB_ADAPTIVE_THRESH |
cv::CALIB_CB_NORMALIZE_IMAGE
);
if (!found || corners.size() !=
static_cast<size_t>(opt.cols * opt.rows)) {
std::cout << "[NO CORNERS] "
<< path.filename().string() << '\n';
++cornerFailures;
continue;
}
cv::cornerSubPix(
gray,
corners,
cv::Size(11, 11),
cv::Size(-1, -1),
subpixCriteria
);
ViewData view;
view.filename = path.filename().string();
view.corners = corners;
view.image = image;
views.push_back(view);
cv::Mat display = image.clone();
cv::drawChessboardCorners(
display, boardSize, corners, true
);
std::cout << "[OK] " << view.filename << '\n';
if (opt.saveDiagnostics) {
fs::create_directories(opt.diagnosticsDir);
fs::path outPath =
fs::path(opt.diagnosticsDir) /
(path.stem().string() + "_corners.png");
cv::imwrite(outPath.string(), display);
}
if (opt.visualize) {
cv::imshow("Detected checkerboard", display);
const int key = cv::waitKey(0);
if (key == 27) {
cv::destroyAllWindows();
break;
}
}
}
if (opt.visualize)
cv::destroyAllWindows();
std::cout << "\n=== Detection Summary ===\n"
<< "Total files : " << files.size() << '\n'
<< "Valid views : " << views.size() << '\n'
<< "Read failures : " << readFailures << '\n'
<< "Corner failures : " << cornerFailures << '\n'
<< "Size mismatches : " << sizeFailures << '\n';
if (views.size() < static_cast<size_t>(opt.minImages)) {
std::cerr << "\nNot enough valid images. Need at least "
<< opt.minImages << ", got " << views.size()
<< ".\n";
return 3;
}
// 棋盘格位于 Z=0 平面,所有点使用同一单位。
// squareSize 以 mm 输入,因此 tvecs 的单位也是 mm。
std::vector<cv::Point3f> objectTemplate;
for (int row = 0; row < opt.rows; ++row) {
for (int col = 0; col < opt.cols; ++col) {
objectTemplate.emplace_back(
static_cast<float>(col * opt.squareSize),
static_cast<float>(row * opt.squareSize),
0.0f
);
}
}
std::vector<std::vector<cv::Point3f>> objectPoints;
std::vector<std::vector<cv::Point2f>> imagePoints;
for (const auto& view : views) {
objectPoints.push_back(objectTemplate);
imagePoints.push_back(view.corners);
}
cv::Mat cameraMatrix = cv::initCameraMatrix2D(
objectPoints, imagePoints, imageSize
);
cv::Mat distCoeffs = cv::Mat::zeros(8, 1, CV_64F);
std::vector<cv::Mat> rvecs, tvecs;
const double rms = cv::calibrateCamera(
objectPoints,
imagePoints,
imageSize,
cameraMatrix,
distCoeffs,
rvecs,
tvecs,
0,
cv::TermCriteria(
cv::TermCriteria::EPS | cv::TermCriteria::MAX_ITER,
100, 1e-9
)
);
double totalSquaredError = 0.0;
size_t totalPoints = 0;
std::vector<double> perViewErrors;
std::cout << "\n=== Per-image Reprojection Error ===\n";
std::cout << std::fixed << std::setprecision(4);
for (size_t i = 0; i < views.size(); ++i) {
std::vector<cv::Point2f> projected;
cv::projectPoints(
objectPoints[i],
rvecs[i],
tvecs[i],
cameraMatrix,
distCoeffs,
projected
);
double squaredError = 0.0;
for (size_t j = 0; j < projected.size(); ++j) {
const cv::Point2f delta =
imagePoints[i][j] - projected[j];
squaredError += delta.dot(delta);
}
const double viewRms = std::sqrt(
squaredError / projected.size()
);
perViewErrors.push_back(viewRms);
totalSquaredError += squaredError;
totalPoints += projected.size();
std::cout << views[i].filename << " : "
<< viewRms << " px\n";
}
const double globalRms = std::sqrt(
totalSquaredError / totalPoints
);
fs::create_directories(
fs::path(opt.outputFile).parent_path().empty()
? fs::path(".")
: fs::path(opt.outputFile).parent_path()
);
cv::FileStorage out(
opt.outputFile, cv::FileStorage::WRITE
);
if (!out.isOpened()) {
std::cerr << "Cannot write output file: "
<< opt.outputFile << '\n';
return 4;
}
out << "image_width" << imageSize.width;
out << "image_height" << imageSize.height;
out << "board_inner_corners_cols" << opt.cols;
out << "board_inner_corners_rows" << opt.rows;
out << "square_size_mm" << opt.squareSize;
out << "camera_matrix" << cameraMatrix;
out << "distortion_coefficients" << distCoeffs;
out << "opencv_rms" << rms;
out << "global_reprojection_error_px" << globalRms;
out << "per_view_errors_px" << "[";
for (size_t i = 0; i < views.size(); ++i) {
out << "{"
<< "filename" << views[i].filename
<< "error_px" << perViewErrors[i]
<< "}";
}
out << "]";
out.release();
std::cout << "\n=== Calibration Result ===\n"
<< "Image size: " << imageSize.width
<< " x " << imageSize.height << '\n'
<< "Camera matrix:\n" << cameraMatrix << '\n'
<< "Distortion coefficients:\n"
<< distCoeffs << '\n'
<< "OpenCV RMS: " << rms << " px\n"
<< "Global reprojection error: "
<< globalRms << " px\n"
<< "Saved to: " << opt.outputFile << '\n';
return 0;
}
+81
View File
@@ -0,0 +1,81 @@
add_rules("mode.debug", "mode.release")
set_languages("c++17")
set_policy("generator.compile_commands", true)
add_requires("cmake::OpenCV", {alias = "opencv", system = true})
target("camera_calibrator")
set_kind("binary")
add_packages("opencv")
add_files("src/*.cpp")
--
-- If you want to known more usage about xmake, please see https://xmake.io
--
-- ## FAQ
--
-- You can enter the project directory firstly before building project.
--
-- $ cd projectdir
--
-- 1. How to build project?
--
-- $ xmake
--
-- 2. How to configure project?
--
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
--
-- 3. Where is the build output directory?
--
-- The default output directory is `./build` and you can configure the output directory.
--
-- $ xmake f -o outputdir
-- $ xmake
--
-- 4. How to run and debug target after building project?
--
-- $ xmake run [targetname]
-- $ xmake run -d [targetname]
--
-- 5. How to install target to the system directory or other output directory?
--
-- $ xmake install
-- $ xmake install -o installdir
--
-- 6. Add some frequently-used compilation flags in xmake.lua
--
-- @code
-- -- add debug and release modes
-- add_rules("mode.debug", "mode.release")
--
-- -- add macro definition
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
--
-- -- set warning all as error
-- set_warnings("all", "error")
--
-- -- set language: c99, c++11
-- set_languages("c99", "c++11")
--
-- -- set optimization: none, faster, fastest, smallest
-- set_optimize("fastest")
--
-- -- add include search directories
-- add_includedirs("/usr/include", "/usr/local/include")
--
-- -- add link libraries and search directories
-- add_links("tbox")
-- add_linkdirs("/usr/local/lib", "/usr/lib")
--
-- -- add system link libraries
-- add_syslinks("z", "pthread")
--
-- -- add compilation and link flags
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
--
-- @endcode
--