1. 遍历文件目录
//1. 读取所有照片
vector<cv::String> file_paths;
cv::glob("./data/image_*.jpg", file_paths);
//遍历
for(cv::String& path : filePath)
{
cout<<path<<endl;
}
2. 查找角点
vector<cv::Point2f> corners; //角点存储容器
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 2. 遍历查找每个照片的角点,要求输入灰度图片
bool patternfound = cv::findChessboardCorners(gray, patternSize, corners);
if (patternfound) {
// 3. 通过亚像素优化角点的位置 (绘制角点) (11,11)为滑动窗口大小 TermCriteria终止条件 30 迭代次数 0.1 误差值
cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
}
// 绘制角点
cv::drawChessboardCorners(image, patternSize, corners, patternfound);
cv::imshow("img", image);
3. 存储二维和三维角点坐标
// 每张图片的所有角点
vector<vector<cv::Point2f>> imagesPoints;
vector<vector<cv::Point3f>> objectPoints;
if (patternfound) {
// 4. 创建对应的物体3d坐标
vector<cv::Point3f> objPoints;
calcObjPoints(patternSize, square_size, objPoints);
imagesPoints.push_back(corners);
objectPoints.push_back(objPoints);
}
4. 手动填入角点坐标的真实值
for (int i = 0; i < patternSize.height; ++i) {
for (int j = 0; j < patternSize.width; ++j) {
// 打印每一行的所有内容,6个
// printf("(%f,%f,0)", j * square_size, i * square_size);
objPoints.emplace_back(j * square_size, i * square_size, 0);
}
}
//每一张图片 作为一个整体 放到容器中去
imagesPoints.push_back(corners);
objectPoints.push_back(objPoints);
5. 执行标定
// 每张图片的所有角点
vector<vector<cv::Point2f>> imagesPoints; //角点容器
vector<vector<cv::Point3f>> objectPoints; //真实点容器
cv::Mat cameraMatrix; // 相机内参 fx,fy,cx,cy
cv::Mat distCoeffs; // 畸变参数 distortion coefficients
vector<cv::Mat> rvecs; // 每张图片的旋转矩阵 rotation
vector<cv::Mat> tvecs; // 每张图片的平移向量 transform
cv::calibrateCamera(objectPoints, imagesPoints, imageSize,
cameraMatrix, distCoeffs, rvecs, tvecs);
6. 保存标定结果到文件
cv::FileStorage fs("./calib_chessboard.yml", cv::FileStorage::WRITE);
// 获取当前时间
time_t tm;
time(&tm);
struct tm *t2 = localtime(&tm);
char buf[1024];
strftime(buf, sizeof(buf), "%c", t2);
fs << "calib_time" << buf;
fs << "imageSize" << imageSize;
fs << "cameraMatrix" << cameraMatrix;
fs << "distCoeffs" << distCoeffs;
fs.release();
完整代码如下:
/**
* @BelongsProject: CameraCalibration
* @Author: ty
* @CreateTime: 2019-10-31
* @Description: 使用棋盘格进行相机标定
*
* 直接从已有照片目录中读取图片,进行标定。
*
* 1. 读取所有照片
* 2. 遍历查找每个照片的角点
* 3. 通过亚像素优化角点的位置 (绘制角点)
* 4. 创建对应的物体3d坐标
* 5. 执行标定操作 *
* 6. 保存标定结果到文件
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
/**
* 根据图片,标定板查找角点
* @param image 图片
* @param patternSize 标定板尺寸
* @param corners 输出的角点
* @return patternfound 是否在图像中找到了角点
*/
bool findCorners(const cv::Mat &image, const cv::Size &patternSize, vector<cv::Point2f> &corners) {
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 2. 遍历查找每个照片的角点
bool patternfound = cv::findChessboardCorners(gray, patternSize, corners);
if (patternfound) {
// 3. 通过亚像素优化角点的位置 (绘制角点)
cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
}
// 绘制角点
cv::drawChessboardCorners(image, patternSize, corners, patternfound);
cv::imshow("img", image);
return patternfound;
}
void saveParams(cv::Size imageSize, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs) {
// cv::FileStorage fs("./calib_chessboard.xml", cv::FileStorage::WRITE);
cv::FileStorage fs("./calib_chessboard.yml", cv::FileStorage::WRITE);
// 获取当前时间
time_t tm;
time(&tm);
struct tm *t2 = localtime(&tm);
char buf[1024];
strftime(buf, sizeof(buf), "%c", t2);
fs << "calib_time" << buf;
fs << "imageSize" << imageSize;
fs << "cameraMatrix" << cameraMatrix;
fs << "distCoeffs" << distCoeffs;
fs.release();
}
void calcObjPoints(const cv::Size &patternSize, float square_size, vector<cv::Point3f> &objPoints) {
for (int i = 0; i < patternSize.height; ++i) {
for (int j = 0; j < patternSize.width; ++j) {
// 打印每一行的所有内容,6个
// printf("(%f,%f,0)", j * square_size, i * square_size);
objPoints.emplace_back(j * square_size, i * square_size, 0);
}
}
}
int main(int argc, char **argv) {
std::cout << "Hello" << std::endl;
// 棋盘格尺寸
cv::Size patternSize(6, 9);
float square_size = 20.0;
// 每张图片的所有角点
vector<vector<cv::Point2f>> imagesPoints;
vector<vector<cv::Point3f>> objectPoints;
// 图片的像素尺寸
cv::Size imageSize;
// 1. 读取所有照片
vector<cv::String> file_paths;
cv::glob("./data/image_*.jpg", file_paths);
for (cv::String& path : file_paths) {
// 读取图片,获取图片尺寸
cv::Mat img = cv::imread(path, cv::IMREAD_COLOR);
cv::Mat image;
img.copyTo(image);
imageSize = image.size();
std::cout << path << " size: " << imageSize << std::endl;
// 定义角点坐标的列表
vector<cv::Point2f> corners;
bool patternfound = findCorners(image, patternSize, corners);
// 如果找到了角点, 把角点和对应的3d坐标加到列表中
if (patternfound) {
// 4. 创建对应的物体3d坐标
/**
* [0,0,0] [1,0,0] [2,0,0]
* [0,1,0] [1,1,0] [2,1,0]
* [0,2,0] [1,2,0] [2,2,0]
*/
vector<cv::Point3f> objPoints;
(patternSize, square_size, objPoints);
imagesPoints.push_back(corners);
objectPoints.push_back(objPoints);
}
}
cv::Mat cameraMatrix; // 相机内参 fx,fy,cx,cy
cv::Mat distCoeffs; // 畸变参数 distortion coefficients
vector<cv::Mat> rvecs; // 每张图片的旋转矩阵 rotation
vector<cv::Mat> tvecs; // 每张图片的平移向量 transform
// 5. 执行标定操作 *
cv::calibrateCamera(objectPoints, imagesPoints, imageSize,
cameraMatrix, distCoeffs, rvecs, tvecs);
// 6. 打印并保存标定结果到文件
std::cout << "cameraMatrix: " << std::endl;
std::cout << cameraMatrix << std::endl;
std::cout << "distCoeffs: " << std::endl;
std::cout << distCoeffs << std::endl;
saveParams(imageSize, cameraMatrix, distCoeffs);
while (cv::waitKey(30) != 27) {
}
cv::destroyAllWindows();
return 0;
}
使用摄像头或视频进行标定
/**
* @BelongsProject: CameraCalibration
* @Author: ty
* @CreateTime: 2019-10-31
* @Description: 使用棋盘格进行相机标定
*
* 直接从已有照片目录中读取图片,进行标定。
*
* 1. 开启摄像头,实时获取照片
* 2. 遍历查找每个照片的角点
* 3. 通过亚像素优化角点的位置 (绘制角点)
* 4. 创建对应的物体3d坐标
* 5. 执行标定操作 *
* 6. 保存标定结果到文件
*/
#include <iostream>
#include <opencv2/opencv.hpp>
const int ACTION_ESC = 27;
const int ACTION_SPACE = 32;
void saveParams(cv::String saveFileName, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, double rms);
using namespace std;
/**
* 根据图片,标定板查找角点
* @param image 图片
* @param patternSize 标定板尺寸
* @param corners 输出的角点
* @return patternfound 是否在图像中找到了角点
*/
bool findCorners(const cv::Mat &image, const cv::Size &patternSize, vector<cv::Point2f> &corners) {
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 2. 遍历查找每个圆网格的中心
bool patternfound = cv::findCirclesGrid(gray, patternSize, corners);
// 绘制角点
cv::drawChessboardCorners(image, patternSize, corners, patternfound);
cv::imshow("img", image);
return patternfound;
}
void calcObjPoints(const cv::Size &patternSize, float square_size, vector<cv::Point3f> &objPoints) {
/**
* [0,0,0] [1,0,0] [2,0,0]
* [0,1,0] [1,1,0] [2,1,0]
* [0,2,0] [1,2,0] [2,2,0]
*/
for (int i = 0; i < patternSize.height; ++i) {
for (int j = 0; j < patternSize.width; ++j) {
// 打印每一行的所有内容,6个
// printf("(%f,%f,0)", j * square_size, i * square_size);
objPoints.emplace_back(j * square_size, i * square_size, 0);
}
}
}
int main(int argc, char **argv) {
std::cout << "开始采集图片信息,按[空格键]采集图片。" << std::endl;
// 棋盘格尺寸
cv::Size patternSize(7, 10);
float square_size = 20.0;
double scale_factor = 1.0;
// 采集15张图片
int num_boards = 15;
// 每张图片的所有角点
vector<vector<cv::Point2f>> imagesPoints;
// 图片的像素尺寸
cv::Size imageSize;
cv::VideoCapture capture(0);
// 判断相机是否正确打开
if (!capture.isOpened()) {
std::cerr << "相机无法打开!请检查后重试。" << std::endl;
return -1;
}
while (true) {
cv::Mat image0, image;
capture >> image0;
image0.copyTo(image);
// 水平翻转图片
cv::flip(image, image, 1);
// 执行缩放
if (scale_factor != 1.0) {
cv::resize(image, image, cv::Size(), scale_factor, scale_factor, cv::INTER_LINEAR);
}
imageSize = image.size();
// 定义角点坐标的列表
vector<cv::Point2f> corners;
bool patternfound = findCorners(image, patternSize, corners);
int action = cv::waitKey(30) & 0xFF;
if (action == ACTION_ESC || action == 'q') {
printf("标定程序已终止!\n");
return -1;
} else if(action == ACTION_SPACE){ //按空格保存数据
// 如果找到了角点, 把角点和对应的3d坐标加到列表中
if (patternfound) {
imagesPoints.push_back(corners);
printf("保存照片及参数 %d/%d \n", (int)imagesPoints.size(), num_boards);
if ((int) imagesPoints.size() == num_boards) {
break;
}
}else {
printf("未发现角点\n");
}
}
}
printf("照片及角点参数已采集完毕,开始执行标定!\n");
// 物体对象的3d坐标列表
vector<vector<cv::Point3f>> objectPoints;
// 4. 创建对应的物体3d坐标
vector<cv::Point3f> objPoints;
calcObjPoints(patternSize, square_size, objPoints);
objectPoints.resize(num_boards, objPoints);
cv::Mat cameraMatrix; // 相机内参 fx,fy,cx,cy
cv::Mat distCoeffs; // 畸变参数 distortion coefficients
vector<cv::Mat> rvecs; // 每张图片的旋转矩阵 rotation
vector<cv::Mat> tvecs; // 每张图片的平移向量 transform
// 5. 执行标定操作 * RMS error | RMSE
double rms = cv::calibrateCamera(objectPoints, imagesPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs);
printf("RMS error report by calibrateCamera: %f\n", rms);
// 6. 打印并保存标定结果到文件
std::cout << "cameraMatrix: " << std::endl;
std::cout << cameraMatrix << std::endl;
std::cout << "distCoeffs: " << std::endl;
std::cout << distCoeffs << std::endl;
saveParams("./calib_circle.yml", cameraMatrix, distCoeffs, rms);
cv::destroyAllWindows();
return 0;
}
void saveParams(cv::String saveFileName, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, double rms) {
// cv::FileStorage fs("./calib_chessboard.xml", cv::FileStorage::WRITE);
cv::FileStorage fs(saveFileName, cv::FileStorage::WRITE);
// 获取当前时间
time_t tm;
time(&tm);
struct tm *t2 = localtime(&tm);
char buf[1024];
strftime(buf, sizeof(buf), "%c", t2);
fs << "calib_time" << buf;
fs << "rms" << rms;
fs << "cameraMatrix" << cameraMatrix;
fs << "distCoeffs" << distCoeffs;
fs.release();
}
标定的评价指标,使用cv::calibrateCamera()的返回值rms,值越小误差越小,效果越好
double rms = cv::calibrateCamera(objectPoints, imagesPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs);
圆网格标定
只是将上述寻找角点函数替换为圆网格的寻找角点,且亚像素优化只适用于棋盘格,其他不适用( cv::cornerSubPix() )
bool patternfound = cv::findCirclesGrid(gray, patternSize, corners);
//但是绘制的时候还是使用棋盘格法来绘制
//绘制角点
cv::drawChessboardCorners(image, patternSize, corners, patternfound);
cv::imshow("img", image);
非对称圆网格,要注意真实坐标矩阵的获取
for (int i = 0; i < patternSize.height; ++i) {
for (int j = 0; j < patternSize.width; ++j) {
// 打印每一行的所有内容,6个
objPoints.emplace_back(
float((j * 2 + i % 2) * square_size),
float(i * square_size), 0);
}
}
闪屏的添加,借助bitwise_not()函数
// 添加闪屏,告知用户获取成功
cv::bitwise_not(image, image, cv::noArray());
图片的保存
stringstream ss;
ss << "./calib_acircle_pic/image_" << (int) imagesPoints.size() << ".jpg";
// 保存照片
cv::imwrite(ss.str(), image0, vector<int>{CV_IMWRITE_JPEG_QUALITY, 100});
使用标准命令行封装参数
// ./CameraCalibrationACircleVideo 4 11 20 15 --scale=1.0 -f
// ./CameraCalibrationACircleVideo -h --help
// 解析用户输入的参数
cv::CommandLineParser parser(argc, argv, "{@arg1||}{@arg2||}{@arg3||}{@arg4|15|}"
"{help h||}{scale s|1.0|}{f||}");
if (parser.has("h")) {
print_help();
return 0;
}
int board_width = parser.get<int>(0);
int board_height = parser.get<int>(1);
float square_size = parser.get<float>(2);
int num = parser.get<int>(3);