相机标定
这里我用笔记本电脑自带的摄像头进行相机标定
仅作示例,实际工程中要用对应的摄像头进行标定
同时代码也要相应的修改,不过修改的主要是相机的初始化
粗略的说就是打开相机那部分要修改(依据实际情况相应修改)
最终的结果会输出
重投影误差 | RMS( 越小越好) |
---|---|
相机内参矩阵 | cameraMatrix |
畸变参数 | distCoeffs.t() |
这里用Opencv内置的矩阵转置函数.t() ,将畸变参数从列向量转换为行向量,便于显示,不过它这个只是显示上改变,畸变参数实际存储的还是列向量。
所以下次如果没有用.t( )把它转置,那最后输出的矩阵就会是列向量
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <iomanip>
using namespace cv;
using namespace std;
int main() {
// ========== 参数配置 ==========
const Size boardSize(12, 8); // 内角点数量(12×8)
const float squareSize = 20.0f; // 每个棋盘格实际尺寸(mm)
const int targetSamples = 20; // 需要采集的样本数
const TermCriteria criteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001);
// ========== 准备工作 ==========
// 生成理论角点坐标(世界坐标系)
vector<Point3f> objectCorners;
for (int i = 0; i < boardSize.height; ++i) {
for (int j = 0; j < boardSize.width; ++j) {
objectCorners.push_back(Point3f(j * squareSize, i * squareSize, 0));
}
}
vector<vector<Point3f>> objectPoints;
vector<vector<Point2f>> imagePoints;
// ========== 初始化摄像头 ==========
VideoCapture cap(0);
if (!cap.isOpened()) {
cerr << "ERROR: 无法打开摄像头" << endl;
return -1;
}
// 获取初始帧确定分辨率
Mat initFrame;
cap >> initFrame;
if (initFrame.empty()) {
cerr << "ERROR: 无法获取初始帧" << endl;
return -1;
}
Size imageSize = initFrame.size();
// ========== 数据采集 ==========
cout << "=== 相机标定程序 ===" << endl;
cout << "棋盘规格: " << boardSize.width << "x" << boardSize.height
<< " (每个格子 " << squareSize << "mm)" << endl;
cout << "图像分辨率: " << imageSize.width << "x" << imageSize.height << endl;
cout << "需要采集 " << targetSamples << " 个有效样本" << endl;
cout << "操作说明:\n 空格键 - 保存当前帧\n ESC键 - 提前结束采集\n";
int currentSamples = 0;
Mat frame;
while (currentSamples < targetSamples) {
cap >> frame;
if (frame.empty()) {
cerr << "WARNING: 获取帧失败" << endl;
continue;
}
Mat gray;
cvtColor(frame, gray, COLOR_BGR2GRAY);
// 查找棋盘格角点
vector<Point2f> corners;
bool found = findChessboardCorners(gray, boardSize, corners,
CALIB_CB_ADAPTIVE_THRESH +
CALIB_CB_NORMALIZE_IMAGE +
CALIB_CB_FAST_CHECK);
if (found) {
// 亚像素级精确化
cornerSubPix(gray, corners, Size(11, 11), Size(-1, -1), criteria);
// 可视化结果
drawChessboardCorners(frame, boardSize, corners, found);
string status = format("采集进度: %d/%d | 按空格保存",
currentSamples, targetSamples);
putText(frame, status, Point(20, 30),
FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2);
imshow("Camera Calibration", frame);
// 按键处理 按空格键保存当前图像到示例中
int key = waitKey(1);
if (key == 32) { // 空格键
objectPoints.push_back(objectCorners);
imagePoints.push_back(corners);
currentSamples++;
// 保存确认反馈
Mat feedback;
frame.copyTo(feedback);
string msg = format("样本 %d 已保存!", currentSamples);
putText(feedback, msg, Point(frame.cols/4, frame.rows/2),
FONT_HERSHEY_SIMPLEX, 1.5, Scalar(0, 255, 255), 3);
imshow("Camera Calibration", feedback);
waitKey(300);
} else if (key == 27) { // ESC键
break;
}
} else {
string msg = format("未检测到棋盘格 | 需要 %dx%d 内角点",
boardSize.width, boardSize.height);
putText(frame, msg, Point(20, 30), FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 0, 255), 2);
imshow("Camera Calibration", frame);
if (waitKey(1) == 27) break;
}
}
// ========== 相机标定 ==========
if (objectPoints.size() >= 5) {
Mat cameraMatrix, distCoeffs;
vector<Mat> rvecs, tvecs;
cout << "\n>>> 开始计算相机参数..." << endl;
double rms = calibrateCamera(objectPoints, imagePoints, imageSize,
cameraMatrix, distCoeffs, rvecs, tvecs,
CALIB_FIX_K4 + CALIB_FIX_K5);
// ========== 结果输出 ==========
cout << fixed << setprecision(5);
cout << "\n=== 标定结果 ===" << endl;
cout << "重投影误差(RMS): " << rms << " (值越小越好,建议<0.5)" << endl;
cout << "\n相机内参矩阵:\n" << cameraMatrix << endl;
cout << "\n畸变系数(k1,k2,p1,p2,k3):\n" << distCoeffs.t() << endl;
// ========== 保存结果 ==========
FileStorage fs("camera_calibration.yml", FileStorage::WRITE);
fs << "calibration_date" << "2024-03-20";
fs << "image_width" << imageSize.width;
fs << "image_height" << imageSize.height;
fs << "board_width" << boardSize.width;
fs << "board_height" << boardSize.height;
fs << "square_size" << squareSize;
fs << "camera_matrix" << cameraMatrix;
fs << "distortion_coefficients" << distCoeffs;
fs << "reprojection_error" << rms;
fs.release();
cout << "\n>>> 参数已保存到 camera_calibration.yml" << endl;
// ========== 验证结果 ==========
cout << "\n>>> 按ESC键退出验证..." << endl;
Mat map1, map2;
Mat newCameraMatrix = getOptimalNewCameraMatrix(cameraMatrix, distCoeffs,
imageSize, 1, imageSize, 0);
initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
newCameraMatrix, imageSize,
CV_16SC2, map1, map2);
while (true) {
cap >> frame;
if (frame.empty()) break;
// 去畸变处理
Mat undistorted;
remap(frame, undistorted, map1, map2, INTER_LINEAR);
// 并排显示对比
Mat comparison;
hconcat(frame, undistorted, comparison);
putText(comparison, "原始图像", Point(20, 30),
FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 0, 255), 2);
putText(comparison, "去畸变图像", Point(frame.cols + 20, 30),
FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0, 255, 0), 2);
imshow("Calibration Results", comparison);
if (waitKey(1) == 27) break;
}
} else {
cerr << "ERROR: 有效样本不足 (" << objectPoints.size() << "),至少需要5个" << endl;
}
// ========== 资源释放 ==========
cap.release();
destroyAllWindows();
cout << "\n>>> 程序正常结束" << endl;
return 0;
}
最终代码实现功能
-
完整的错误处理:
- 摄像头初始化检查
- 帧获取失败处理
- 样本数量验证
注意:这里样本数量检测的并不是棋盘格的数量,而是指成功检测到棋盘格角点并保存的有效图像帧数
(你仔细想想,一张图片就直接测出相机的畸变参数,那误差得有多大啊)
-
增强的棋盘格检测:
CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK
在相机标定代码中,增强的棋盘格检测主要通过OpenCV的findChessboardCorners
函数结合特定的标志位(flags)实现。以下是详细解析:
1. 核心代码段
bool found = findChessboardCorners(
gray, // 输入灰度图像
boardSize, // 棋盘格内角点数量(如12x8)
corners, // 输出的角点坐标
CALIB_CB_ADAPTIVE_THRESH +
CALIB_CB_NORMALIZE_IMAGE +
CALIB_CB_FAST_CHECK // 增强检测的标志位组合
);
2. 标志位的作用
通过以下三个标志位的组合,显著提升了棋盘格检测的鲁棒性和效率:
(1) CALIB_CB_ADAPTIVE_THRESH
- 功能:
使用自适应阈值法二值化图像,替代全局阈值。 - 解决的问题:
光照不均时(如部分阴影或反光),全局阈值可能导致棋盘格部分区域无法检测。 - 效果:
对每个局部区域独立计算阈值,确保棋盘格线条清晰。
(2) CALIB_CB_NORMALIZE_IMAGE
- 功能:
在二值化前对图像进行直方图归一化。 - 解决的问题:
低对比度或亮度偏暗/偏亮的图像。 - 效果:
增强图像对比度,使黑白棋盘格更分明。
(3) CALIB_CB_FAST_CHECK
- 功能:
启用快速检查模式,先粗略验证棋盘格是否存在。 - 解决的问题:
避免在无棋盘格的图像上浪费计算资源。 - 效果:
显著提升检测速度(尤其对视频流实时处理)。
3. 为何需要这些增强?
场景 | 无增强的检测 | 增强后的检测 |
---|---|---|
光照不均 | 可能漏检部分角点 | 自适应阈值保证全图检测 |
低对比度图像 | 角点检测不稳定 | 归一化后对比度提升 |
快速视频处理 | 每帧都完整计算,耗时 | 快速检查跳过无棋盘格的帧 |
棋盘格部分遮挡 | 易失败 | 自适应阈值+归一化提高容错性 |
总结
- 核心标志位:
ADAPTIVE_THRESH
+NORMALIZE_IMAGE
+FAST_CHECK
是应对复杂场景的黄金组合。 - 适用场景:
光照变化、低对比度、实时视频流、部分遮挡等情况。 - 性能权衡:
增强检测会略微增加单次计算量,但通过FAST_CHECK
和提前终止机制,整体效率更高。
-
详细的用户引导:
- 实时显示采集进度
- 控制台输出标定参数
- 保存完整的标定结果文件
-
直观的结果验证:
- 并排显示原始/去畸变图像
- 显示重投影误差(RMS)
-
规范的变量管理:
- 使用const定义配置参数
- 合理的作用域控制
- 图像尺寸单独保存
编译命令
终端g++编译示例(但是不太推荐)
g++ camera_calibration.cpp -o calibration `pkg-config --cflags --libs opencv4` -std=c++11
最好用CMake或者一些其他的编译工具,会更方便一点
这里就不多赘述了
输出文件示例(camera_calibration.yml)
%YAML 1.0
calibration_date: "2024-03-20"
image_width: 640
image_height: 480
board_width: 12
board_height: 8
square_size: 20.0
camera_matrix: !!opencv-matrix
rows: 3
cols: 3
dt: d
data: [ 5.12345678e+02, 0., 3.19500000e+02, 0., 5.12345678e+02, 2.39500000e+02, 0., 0., 1. ]
distortion_coefficients: !!opencv-matrix
rows: 5
cols: 1
dt: d
data: [ -2.123456e-01, 8.765432e-02, 1.234567e-03, -2.345678e-04, 0. ]
reprojection_error: 0.12345