原图




校准过程




校准后的对比图




源码
main.cpp
cpp
/**
* @file test_camera_calibrator.cpp
* @brief 相机标定与图像拼接测试程序
*
* 功能说明:
* 1. 相机标定 - 使用棋盘格标定板标定相机内参和畸变系数
*
* 标定算法: Brown-Conrady 畸变模型
*
* @author Auto Generated
* @date 2026
*/
#include "camera_calibrator.h"
#include "jpeg_writer.h"
#include "jpeg_reader.h"
#include <iostream>
#include <sys/stat.h>
#include <opencv2/stitching.hpp>
#include <opencv2/features2d.hpp>
/**
* @brief 保存JPEG图像
* @param path 保存路径
* @param img 要保存的图像
* @param desc 描述信息(用于日志输出)
*
* 使用自定义JPEG读写器保存图像,失败时输出错误信息
*/
void saveImage(const std::string& path, const cv::Mat& img, const std::string& desc) {
if (imwrite_jpeg(path, img)) {
std::cout << "[保存] " << desc << std::endl;
} else {
std::cerr << "[错误] 保存失败: " << path << std::endl;
}
}
// ============================================================================
// 相机标定与畸变校正测试程序
// ============================================================================
/**
* @brief main - 相机标定主函数
*
* 标定流程:
* 1. 从标定图像中检测棋盘格角点
* 2. 亚像素精化角点坐标
* 3. 调用OpenCV calibrateCamera进行标定
* 4. 生成畸变校正对比图
* 5. 保存标定参数到JSON文件
*/
int main(int argc, char** argv) {
std::cout << "======================================" << std::endl;
std::cout << " 相机标定与畸变校正测试程序 " << std::endl;
std::cout << "======================================" << std::endl;
mkdir("output", 0755);
// 棋盘格参数:9x6表示9列6行的内角点
cv::Size boardSize(9, 6);
// 每个棋盘格方格的物理尺寸(毫米)
float squareSize = 25.0f;
std::cout << "\n===== 从真实标定图像进行标定 =====" << std::endl;
CameraCalibrator calibrator(boardSize, squareSize);
std::cout << "\n[1] 添加标定图像并保存角点检测结果..." << std::endl;
int addedCount = 0;
// 要处理的标定图像编号
int imgNums[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
// 遍历所有标定图像
for (int imgIdx = 0; imgIdx < sizeof(imgNums)/sizeof(imgNums[0]); imgIdx++) {
int i = imgNums[imgIdx];
char buf[64];
sprintf(buf, "../calib_data/left%02d.jpg", i);
std::string filepath(buf);
std::string filename(buf);
// 读取JPEG图像
cv::Mat image = imread_jpeg(filepath);
if (image.empty()) {
std::cerr << "警告: 无法加载图像 " << filepath << std::endl;
continue;
}
// 复制原图用于绘制显示
cv::Mat display;
if (image.channels() == 1) {
cv::cvtColor(image, display, cv::COLOR_GRAY2BGR);
} else {
display = image.clone();
}
// 存储检测到的角点坐标
std::vector<cv::Point2f> corners;
/**
* 检测棋盘格角点
* cv::CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值
* cv::CALIB_CB_NORMALIZE_IMAGE - 归一化图像对比度
*/
bool found = cv::findChessboardCorners(image, boardSize, corners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
if (found) {
/**
* 亚像素精化
* 提高角点精度,从像素级提升到亚像素级
* 迭代30次,精度阈值0.001
*/
cv::cornerSubPix(image, corners, cv::Size(11, 11), cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.001));
// 在图像上绘制检测到的角点
cv::drawChessboardCorners(display, boardSize, corners, found);
// 将角点添加到标定器
if (calibrator.addCalibrationPoints(image)) {
// 保存角点检测结果图像
char outName[64];
sprintf(outName, "../calib_data/%02d_corners.jpg", i);
saveImage(outName, display, "角点检测");
addedCount++;
std::cout << "成功: " << filepath << " (角点数: " << corners.size() << ")" << std::endl;
}
} else {
std::cerr << "警告: 未检测到角点 " << filepath << std::endl;
}
}
std::cout << std::endl;
// 至少需要3张图像才能进行标定
if (addedCount < 3) {
std::cerr << "错误: 至少需要3张标定图像" << std::endl;
return -1;
}
std::cout << "成功添加 " << addedCount << " 张标定图像" << std::endl;
/**
* 执行相机标定
* 内部使用 OpenCV calibrateCamera 函数
* 计算相机内参矩阵和畸变系数
*/
std::cout << "\n[2] 执行相机标定..." << std::endl;
if (!calibrator.calibrate()) {
std::cerr << "标定失败!" << std::endl;
return -1;
}
// 保存畸变校正对比图
std::cout << "\n[3] 生成畸变校正对比图..." << std::endl;
for (int imgIdx = 0; imgIdx < sizeof(imgNums)/sizeof(imgNums[0]); imgIdx++) {
int i = imgNums[imgIdx];
char filepath[64], outname[64];
sprintf(filepath, "../calib_data/left%02d.jpg", i);
sprintf(outname, "../calib_data/%02d_distortion_compare.jpg", i);
cv::Mat image = imread_jpeg(filepath);
if (image.empty()) continue;
// 使用标定参数进行畸变校正
cv::Mat undistorted;
calibrator.undistort(image, undistorted);
// 将原图和校正后的图横向拼接生成对比图
cv::Mat canvas;
cv::hconcat(image, undistorted, canvas);
// 添加文字标注
cv::putText(canvas, "Original (Distorted)", cv::Point(10, 30),
cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 255, 0), 2);
cv::putText(canvas, "Undistorted", cv::Point(image.cols + 10, 30),
cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 255, 0), 2);
saveImage(outname, canvas, "畸变校正对比");
}
// 保存标定参数到JSON文件
std::cout << "\n[4] 保存标定参数..." << std::endl;
calibrator.saveParametersJSON("../calib_data/camera_calibration_params.json");
std::cout << "\n======================================" << std::endl;
std::cout << " 测试完成! " << std::endl;
std::cout << "======================================" << std::endl;
std::cout << "\n输出文件:" << std::endl;
std::cout << " output/*_corners.jpg - 角点检测结果" << std::endl;
std::cout << " output/*_distortion_compare.jpg - 畸变校正对比" << std::endl;
std::cout << " camera_calibration_params.xml - 标定参数" << std::endl;
return 0;
}
camera_calibrator.cpp
cpp
/**
* @file camera_calibrator.cpp
* @brief 相机标定与畸变校正类实现
*
* @see camera_calibrator.h
*/
#include "camera_calibrator.h"
#include <iostream>
/**
* @brief 构造函数
*
* 初始化棋盘格参数和状态标志
*
* @param boardSize 棋盘格内角点数量(列数 x 行数)
* @param squareSize 每个方格的物理尺寸(毫米)
*/
CameraCalibrator::CameraCalibrator(cv::Size boardSize, float squareSize)
: boardSize_(boardSize), squareSize_(squareSize), isCalibrated_(false), rmsError_(0.0) {}
/**
* @brief 生成3D世界坐标点
*
* 原理:
* 假设棋盘格位于Z=0的平面上,棋盘格的世界坐标是已知的
* 每个角点的坐标 = (列号 * 方格尺寸, 行号 * 方格尺寸, 0)
*
* 例如:9x6的棋盘格,第0行第0列角点 → (0, 0, 0)
* 第0行第1列角点 → (25, 0, 0)
* 第1行第0列角点 → (0, 25, 0)
* ...
*
* 注意:内角点数量是(列数-1) x (行数-1),
* 如果棋盘格有10列7行方格,内角点就是9x6
*/
void CameraCalibrator::generateObjectPoints() {
objectPoints_.clear();
// 遍历每张标定图像
for (size_t i = 0; i < imagePoints_.size(); ++i) {
std::vector<cv::Point3f> points;
// 生成该图像对应的3D世界坐标
for (int row = 0; row < boardSize_.height; ++row) {
for (int col = 0; col < boardSize_.width; ++col) {
/**
* 第(row,col)个角点的世界坐标
*
* 物理含义:
* - col * squareSize: X方向位置(毫米)
* - row * squareSize: Y方向位置(毫米)
* - 0: Z方向位置(棋盘格平面上)
*/
points.emplace_back(col * squareSize_, row * squareSize_, 0.0f);
}
}
objectPoints_.push_back(points);
}
}
/**
* @brief 添加标定图像的角点
*
* 流程:
* 1. 彩色图转灰度图
* 2. 检测棋盘格角点(像素级)
* 3. 亚像素精化(提高精度到亚像素级)
* 4. 保存角点坐标
*
* @param image 输入的标定图像
* @return 是否成功检测到角点
*/
bool CameraCalibrator::addCalibrationPoints(const cv::Mat& image) {
// 转灰度(如果需要)
cv::Mat gray;
if (image.channels() == 3) {
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
} else {
gray = image;
}
// 存储检测到的角点坐标
std::vector<cv::Point2f> corners;
/**
* 检测棋盘格角点
*
* cv::CALIB_CB_ADAPTIVE_THRESH:
* 使用自适应阈值二值化,可以更好处理光照不均匀的图像
*
* cv::CALIB_CB_NORMALIZE_IMAGE:
* 先归一化图像对比度,增强检测稳定性
*/
bool found = cv::findChessboardCorners(gray, boardSize_, corners,
cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE);
if (found) {
/**
* 亚像素精化
*
* findChessboardCorners只能检测到像素级精度
* cornerSubPix使用亚像素级精化,将精度提升到亚像素
*
* 参数说明:
* - cv::Size(11, 11): 搜索窗口大小
* - cv::Size(-1, -1): 死区大小(-1表示默认值)
* - TermCriteria: 迭代终止条件
* EPS + MAX_ITER: 迭代30次或精度达到0.001
*/
cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.001));
// 保存角点和图像尺寸
imagePoints_.push_back(corners);
if (imageSize_.width == 0) {
imageSize_ = image.size();
}
}
return found;
}
/**
* @brief 执行相机标定
*
* 核心函数,使用OpenCV calibrateCamera进行标定
*
* 算法原理:
* 1. 使用已知的3D世界坐标(objectPoints_)和检测到的2D图像坐标(imagePoints_)
* 2. 求解针孔相机模型参数
* 3. 得到相机内参矩阵和畸变系数
*
* 标定选项:
* - CALIB_FIX_PRINCIPAL_POINT: 固定主点(cx,cy)在图像中心
* - CALIB_ZERO_TANGENT_DIST: 假设切向畸变(p1=p2=0),简化模型
*
* @return 是否标定成功
*/
bool CameraCalibrator::calibrate() {
// 需要至少添加了一张图像的角点
if (imagePoints_.empty()) return false;
// 生成3D世界坐标点
generateObjectPoints();
/**
* 初始化内参矩阵
*
* 初始化为单位矩阵,calibrateCamera会迭代优化
*
* 内参矩阵结构:
* [fx 0 cx]
* [ 0 fy cy]
* [ 0 0 1]
*
* fx, fy: 焦距(像素)
* cx, cy: 主点(光轴与图像平面的交点)
*/
cameraMatrix_ = cv::Mat::eye(3, 3, CV_64F);
/**
* 初始化畸变系数
*
* [k1, k2, p1, p2, k3]
*
* k1, k2, k3: 径向畸变系数
* p1, p2: 切向畸变系数
*/
distCoeffs_ = cv::Mat::zeros(5, 1, CV_64F);
/**
* 执行标定
*
* calibrateCamera 是OpenCV的核心标定函数
*
* 输入:
* - objectPoints_: 每张图的3D世界坐标
* - imagePoints_: 每张图检测到的2D角点坐标
* - imageSize_: 图像分辨率
*
* 输出:
* - cameraMatrix_: 相机内参矩阵
* - distCoeffs_: 畸变系数
* - rvecs_: 每张图的旋转向量
* - tvecs_: 每张图的平移向量
*
* 返回值:
* - rmsError_: 重投影均方根误差(像素)
*
* 重投影误差:标定后,将3D点用标定参数投影到2D,
* 与实际检测到的2D点的平均距离
* 误差越小说明标定越准确
*/
rmsError_ = cv::calibrateCamera(objectPoints_, imagePoints_, imageSize_,
cameraMatrix_, distCoeffs_, rvecs_, tvecs_,
cv::CALIB_FIX_PRINCIPAL_POINT | cv::CALIB_ZERO_TANGENT_DIST);
// 标记已标定
isCalibrated_ = true;
// 输出标定结果
std::cout << "\n========== 标定结果 ==========" << std::endl;
std::cout << "内参矩阵:\n" << cameraMatrix_ << std::endl;
std::cout << "\n畸变系数:\n" << distCoeffs_ << std::endl;
std::cout << "重投影误差: " << rmsError_ << " 像素" << std::endl;
std::cout << "================================\n" << std::endl;
return true;
}
/**
* @brief 畸变校正
*
* 使用标定得到的内参和畸变系数对图像进行校正
*
* 校正原理:
* 对于图像中每个畸变点(x_distorted, y_distorted),
* 反向计算校正后的理想点(x_corrected, y_corrected)
*
* 公式(Brown-Conrady模型):
* x_corrected = (x_distorted - cx) / fx
* y_corrected = (y_distorted - cy) / fy
* r² = x_corrected² + y_corrected²
*
* x_ideal = x_corrected * (1 + k1*r² + k2*r⁴ + k3*r⁶)
* + 2*p1*x_corrected*y_corrected + p2*(r² + 2*x_corrected²)
*
* y_ideal = y_corrected * (1 + k1*r² + k2*r⁴ + k3*r⁶)
* + 2*p2*x_corrected*y_corrected + p1*(r² + 2*y_corrected²)
*
* x_final = x_ideal * fx + cx
* y_final = y_ideal * fy + cy
*
* @param input 输入的畸变图像
* @param output 输出的校正后图像
* @param alpha 自由比例因子
* -1: 使用OpenCV默认的优化值
* 0: 裁剪掉校正后超出有效区域的像素
* 1: 保留所有像素,可能有黑边
*/
void CameraCalibrator::undistort(const cv::Mat& input, cv::Mat& output, double alpha) {
if (!isCalibrated_) return;
/**
* 计算优化后的新相机矩阵
*
* getOptimalNewCameraMatrix 根据畸变系数和alpha值,
* 计算一个优化后的相机内参矩阵
*
* 目的:
* 畸变校正会改变图像的有效区域,
* 新相机矩阵可以重新计算主点位置,
* 使输出图像最大化保留有效内容
*/
cv::Mat newCameraMatrix = cv::getOptimalNewCameraMatrix(cameraMatrix_, distCoeffs_, imageSize_, alpha);
/**
* 执行畸变校正
*
* undistort 会:
* 1. 使用内参矩阵和畸变系数
* 2. 对输入图像每个像素进行反向映射
* 3. 生成校正后的输出图像
*/
cv::undistort(input, output, cameraMatrix_, distCoeffs_, newCameraMatrix);
}
/**
* @brief 保存标定参数到文件(OpenCV XML格式)
*
* @param filePath 文件路径
* @return 是否保存成功
*/
bool CameraCalibrator::saveParameters(const std::string& filePath) const {
cv::FileStorage fs(filePath, cv::FileStorage::WRITE);
if (!fs.isOpened()) return false;
// 使用OpenCV的FileStorage格式保存
fs << "camera_matrix" << cameraMatrix_; // 相机内参矩阵
fs << "dist_coeffs" << distCoeffs_; // 畸变系数
fs << "image_width" << imageSize_.width; // 图像宽度
fs << "image_height" << imageSize_.height; // 图像高度
fs << "rms_error" << rmsError_; // 重投影误差
fs.release();
return true;
}
/**
* @brief 从文件加载标定参数
*
* @param filePath 文件路径
* @return 是否加载成功
*/
bool CameraCalibrator::loadParameters(const std::string& filePath) {
cv::FileStorage fs(filePath, cv::FileStorage::READ);
if (!fs.isOpened()) return false;
// 读取各项参数
fs["camera_matrix"] >> cameraMatrix_;
fs["dist_coeffs"] >> distCoeffs_;
fs["image_width"] >> imageSize_.width;
fs["image_height"] >> imageSize_.height;
fs["rms_error"] >> rmsError_;
fs.release();
// 标记已标定
isCalibrated_ = true;
return true;
}
/**
* @brief 重置标定器
*
* 清除所有数据,用于重新开始新的标定流程
*/
void CameraCalibrator::reset() {
imagePoints_.clear();
objectPoints_.clear();
rvecs_.clear();
tvecs_.clear();
isCalibrated_ = false;
rmsError_ = 0.0;
}
/**
* @brief 保存标定参数到JSON文件
*
* 输出格式便于其他语言(如Python、JavaScript)读取使用
*
* JSON结构:
* @code
* {
* "camera_matrix": {
* "fx": ...,
* "fy": ...,
* "cx": ...,
* "cy": ...
* },
* "distortion_coefficients": {
* "k1": ...,
* "k2": ...,
* "p1": ...,
* "p2": ...,
* "k3": ...
* },
* "image_size": {
* "width": ...,
* "height": ...
* },
* "reprojection_error": ...
* }
* @endcode
*
* @param filePath 文件路径
* @return 是否保存成功
*/
bool CameraCalibrator::saveParametersJSON(const std::string& filePath) const {
FILE* fp = fopen(filePath.c_str(), "w");
if (!fp) return false;
/**
* 写入JSON格式的标定参数
*
* 内参矩阵参数提取:
* cameraMatrix_.at<double>(0, 0) → fx
* cameraMatrix_.at<double>(1, 1) → fy
* cameraMatrix_.at<double>(0, 2) → cx
* cameraMatrix_.at<double>(1, 2) → cy
*
* 畸变系数参数提取:
* distCoeffs_.at<double>(0, 0) → k1
* distCoeffs_.at<double>(1, 0) → k2
* distCoeffs_.at<double>(2, 0) → p1
* distCoeffs_.at<double>(3, 0) → p2
* distCoeffs_.at<double>(4, 0) → k3
*/
fprintf(fp, "{\n");
fprintf(fp, " \"camera_matrix\": {\n");
fprintf(fp, " \"fx\": %.6f,\n", cameraMatrix_.at<double>(0, 0));
fprintf(fp, " \"fy\": %.6f,\n", cameraMatrix_.at<double>(1, 1));
fprintf(fp, " \"cx\": %.6f,\n", cameraMatrix_.at<double>(0, 2));
fprintf(fp, " \"cy\": %.6f\n", cameraMatrix_.at<double>(1, 2));
fprintf(fp, " },\n");
fprintf(fp, " \"distortion_coefficients\": {\n");
fprintf(fp, " \"k1\": %.6f,\n", distCoeffs_.at<double>(0, 0));
fprintf(fp, " \"k2\": %.6f,\n", distCoeffs_.at<double>(1, 0));
fprintf(fp, " \"p1\": %.6f,\n", distCoeffs_.at<double>(2, 0));
fprintf(fp, " \"p2\": %.6f,\n", distCoeffs_.at<double>(3, 0));
fprintf(fp, " \"k3\": %.6f\n", distCoeffs_.at<double>(4, 0));
fprintf(fp, " },\n");
fprintf(fp, " \"image_size\": {\n");
fprintf(fp, " \"width\": %d,\n", imageSize_.width);
fprintf(fp, " \"height\": %d\n", imageSize_.height);
fprintf(fp, " },\n");
fprintf(fp, " \"reprojection_error\": %.6f\n", rmsError_);
fprintf(fp, "}\n");
fclose(fp);
return true;
}
camera_calibrator.h
cpp
/**
* @file camera_calibrator.h
* @brief 相机标定与畸变校正类声明
*
* 功能说明:
* - 使用棋盘格标定板进行相机标定
* - 计算相机内参矩阵和畸变系数
* - 支持畸变校正和参数保存/加载
*
* 相机标定原理:
* 通过已知的3D标定物坐标(棋盘格角点世界坐标)和对应的2D图像坐标,
* 利用针孔相机模型求解相机内参(焦距、主点)和畸变系数。
*
* 针孔相机模型:
* s * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T
*
* 其中:
* - K: 相机内参矩阵 (3x3)
* - [R|t]: 旋转矩阵和平移向量 (3x4)
* - [X,Y,Z]: 世界坐标
* - [u,v]: 像素坐标
* - s: 尺度因子
*
* 内参矩阵K:
* [fx 0 cx]
* [ 0 fy cy]
* [ 0 0 1]
*
* 畸变模型(Brown-Conrady):
* x_distorted = x_corrected + x*(k1*r² + k2*r⁴ + k3*r⁶) + 2*p1*x*y + p2*(r² + 2*x²)
* y_distorted = y_corrected + y*(k1*r² + k2*r⁴ + k3*r⁶) + 2*p2*x*y + p1*(r² + 2*y²)
*
* 其中 r² = x² + y²
*
* @author Auto Generated
* @date 2024
*/
#ifndef CAMERA_CALIBRATOR_H
#define CAMERA_CALIBRATOR_H
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/calib3d.hpp>
#include <vector>
#include <string>
/**
* @brief 相机标定与畸变校正类
*
* 相机标定流程:
* 1. 构造对象,指定棋盘格参数(行列数、方格尺寸)
* 2. 添加多张不同角度拍摄的标定图像
* 3. 调用calibrate()进行标定
* 4. 使用undistort()进行畸变校正
* 5. 使用saveParametersJSON()保存标定参数
*
* 示例用法:
* @code
* CameraCalibrator calibrator(cv::Size(9, 6), 25.0f); // 9x6棋盘格,方格25mm
* for (每张标定图) {
* calibrator.addCalibrationPoints(image);
* }
* calibrator.calibrate();
* calibrator.saveParametersJSON("params.json");
* @endcode
*/
class CameraCalibrator {
public:
/**
* @brief 构造函数
* @param boardSize 棋盘格内角点数量,如cv::Size(9,6)表示9列6行
* @param squareSize 每个棋盘格方格的物理尺寸(毫米)
*/
CameraCalibrator(cv::Size boardSize, float squareSize = 1.0f);
/**
* @brief 添加标定图像的角点
*
* 自动检测棋盘格角点并进行亚像素精化
*
* @param image 输入的标定图像
* @return 是否成功检测到角点
*/
bool addCalibrationPoints(const cv::Mat& image);
/**
* @brief 执行相机标定
*
* 使用OpenCV calibrateCamera函数计算相机内参和畸变系数
*
* 标定选项:
* - CALIB_FIX_PRINCIPAL_POINT: 固定主点位置
* - CALIB_ZERO_TANGENT_DIST: 假设切向畸变为零
*
* @return 是否标定成功
*/
bool calibrate();
/**
* @brief 畸变校正
*
* 使用标定得到的内参和畸变系数对图像进行校正
*
* @param input 输入的畸变图像
* @param output 输出的校正后图像
* @param alpha 自由比例因子
* -1: 使用默认优化
* 0: 只裁剪有效像素
* 1: 保留所有像素(可能有黑边)
*/
void undistort(const cv::Mat& input, cv::Mat& output, double alpha = -1);
// =========================================================================
// 参数保存与加载
// =========================================================================
/**
* @brief 从文件加载标定参数(OpenCV XML格式)
* @param filePath 文件路径
* @return 是否加载成功
*/
bool loadParameters(const std::string& filePath);
/**
* @brief 保存标定参数到文件(OpenCV XML格式)
* @param filePath 文件路径
* @return 是否保存成功
*/
bool saveParameters(const std::string& filePath) const;
/**
* @brief 保存标定参数到JSON文件(便于其他语言读取)
* @param filePath 文件路径
* @return 是否保存成功
*/
bool saveParametersJSON(const std::string& filePath) const;
// =========================================================================
// 参数获取
// =========================================================================
/** @brief 获取相机内参矩阵 */
cv::Mat getCameraMatrix() const { return cameraMatrix_; }
/** @brief 获取畸变系数向量 [k1, k2, p1, p2, k3] */
cv::Mat getDistCoeffs() const { return distCoeffs_; }
/** @brief 获取旋转向量列表 */
std::vector<cv::Mat> getRotationVectors() const { return rvecs_; }
/** @brief 获取图像尺寸 */
cv::Size getImageSize() const { return imageSize_; }
/** @brief 获取重投影误差(像素) */
double getReprojectionError() const { return rmsError_; }
/**
* @brief 重置标定器,清除所有数据
*
* 用于重新开始新的标定流程
*/
void reset();
private:
/** @brief 棋盘格内角点尺寸(列数 x 行数) */
cv::Size boardSize_;
/** @brief 每个方格的物理尺寸(毫米) */
float squareSize_;
/** @brief 标定图像的尺寸 */
cv::Size imageSize_;
/** @brief 所有标定图像的角点坐标(2D图像坐标) */
std::vector<std::vector<cv::Point2f>> imagePoints_;
/** @brief 所有标定图像对应的3D世界坐标 */
std::vector<std::vector<cv::Point3f>> objectPoints_;
/** @brief 相机内参矩阵 (3x3) */
cv::Mat cameraMatrix_;
/** @brief 畸变系数 (5x1): [k1, k2, p1, p2, k3] */
cv::Mat distCoeffs_;
/** @brief 旋转向量列表(每张图一个) */
std::vector<cv::Mat> rvecs_;
/** @brief 平移向量列表(每张图一个) */
std::vector<cv::Mat> tvecs_;
/** @brief 是否已完成标定 */
bool isCalibrated_;
/** @brief 重投影均方根误差(像素) */
double rmsError_;
/**
* @brief 生成3D世界坐标点
*
* 假设棋盘格位于Z=0的平面上
* 第(row,col)个角点的世界坐标为 (col*squareSize, row*squareSize, 0)
*/
void generateObjectPoints();
};
#endif
jpeg_reader.h
cpp
#ifndef JPEG_READER_H
#define JPEG_READER_H
#include <opencv2/opencv.hpp>
#include <cstdio>
#include <jpeglib.h>
// 使用 libjpeg 读取 JPEG 文件
static cv::Mat imread_jpeg(const std::string& filepath) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE* infile = fopen(filepath.c_str(), "rb");
if (!infile) {
return cv::Mat();
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return cv::Mat();
}
jpeg_start_decompress(&cinfo);
int width = cinfo.output_width;
int height = cinfo.output_height;
int channels = cinfo.output_components;
cv::Mat img(height, width, channels == 3 ? CV_8UC3 : CV_8UC1);
JSAMPROW row_pointer[1];
while (cinfo.output_scanline < height) {
row_pointer[0] = img.data + cinfo.output_scanline * width * channels;
jpeg_read_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
// libjpeg 返回 RGB,OpenCV 使用 BGR
if (channels == 3) {
cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
}
return img;
}
#endif // JPEG_READER_H
jpeg_writer.h
cpp
#ifndef JPEG_WRITER_H
#define JPEG_WRITER_H
#include <opencv2/opencv.hpp>
#include <cstdio>
#include <jpeglib.h>
// 使用 libjpeg 保存 JPEG 文件 - 强制保存为彩色
static bool imwrite_jpeg(const std::string& filepath, const cv::Mat& img, int quality = 90) {
if (img.empty()) return false;
cv::Mat rgb;
// OpenCV 使用 BGR,libjpeg 使用 RGB
if (img.channels() == 1) {
cv::cvtColor(img, rgb, cv::COLOR_GRAY2RGB);
} else if (img.channels() == 3) {
cv::cvtColor(img, rgb, cv::COLOR_BGR2RGB);
} else if (img.channels() == 4) {
cv::cvtColor(img, rgb, cv::COLOR_BGRA2RGBA);
} else {
img.copyTo(rgb);
}
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE* outfile;
JSAMPROW row_pointer[1];
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
if ((outfile = fopen(filepath.c_str(), "wb")) == NULL) {
return false;
}
jpeg_stdio_dest(&cinfo, outfile);
cinfo.image_width = rgb.cols;
cinfo.image_height = rgb.rows;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
while (cinfo.next_scanline < cinfo.image_height) {
row_pointer[0] = rgb.data + cinfo.next_scanline * rgb.cols * 3;
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
fclose(outfile);
jpeg_destroy_compress(&cinfo);
return true;
}
#endif // JPEG_WRITER_H
测试结果
cpp
======================================
相机标定与畸变校正测试程序
======================================
===== 从真实标定图像进行标定 =====
[1] 添加标定图像并保存角点检测结果...
[保存] 角点检测
成功: ../calib_data/left01.jpg (角点数: 54)
[保存] 角点检测
成功: ../calib_data/left02.jpg (角点数: 54)
[保存] 角点检测
成功: ../calib_data/left03.jpg (角点数: 54)
[保存] 角点检测
成功: ../calib_data/left04.jpg (角点数: 54)
成功添加 4 张标定图像
[2] 执行相机标定...
========== 标定结果 ==========
内参矩阵:
[539.2031279337825, 0, 319.5;
0, 539.7183736104245, 239.5;
0, 0, 1]
畸变系数:
[-0.2889548355664327;
0.07941285022620277;
0;
0;
0.07560162201900809]
重投影误差: 0.496707 像素
================================
[3] 生成畸变校正对比图...
[保存] 畸变校正对比
[保存] 畸变校正对比
[保存] 畸变校正对比
[保存] 畸变校正对比
[4] 保存标定参数...
======================================
测试完成!
======================================
输出文件:
output/*_corners.jpg - 角点检测结果
output/*_distortion_compare.jpg - 畸变校正对比
camera_calibration_params.xml - 标定参数
畸变参数
camera_calibration_params.json
bash
{
"camera_matrix": {
"fx": 539.203128,
"fy": 539.718374,
"cx": 319.500000,
"cy": 239.500000
},
"distortion_coefficients": {
"k1": -0.288955,
"k2": 0.079413,
"p1": 0.000000,
"p2": 0.000000,
"k3": 0.075602
},
"image_size": {
"width": 640,
"height": 480
},
"reprojection_error": 0.496707
}