单目标定源代码
OpenCV版本4.0.0 Visual studio2017版本
如果遇到任何问题,或者有错误的地方,欢迎评论留言指正
本段代码亲测可用,直接复制即可
注意:有些路径是需要更改的,注释中已有说明
很多文章中的源码,不是收费,就是运行不成功,且注释较少,较难理解。我在这份代码中加了足够多的注释,希望这份代码能对和我一样刚学习标定的同学有所帮助!
#include <opencv2/core/core.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdio.h>
#include <iostream>
using namespace cv;
using namespace std;
vector< vector< Point3f > > object_points; //定义物点
vector< vector< Point2f > > image_points;//定义像点
vector< Point2f > corners;//定义角点(一张图片)
Mat img, gray;
Size im_size;
bool doesExist(const std::string& name) {//判断图像是否存在
struct stat buffer;
return (stat(name.c_str(), &buffer) == 0);
}
void setup_calibration(int board_width, int board_height, int num_imgs,
float square_size, const char* imgs_directory, const char* imgs_filename,
const char* extension) {//将所有的物点坐标写入object_points,所有与之对应的像点都放入image_points中
Size board_size = Size(board_width, board_height);//获得标定板尺寸,用于后续寻找角点,只有标定板的内角点是有效的角点,所以标定板最外侧一圈不参与计算
int board_n = board_width * board_height;//获得格子数目
for (int k = 1; k <= num_imgs; k++) {//遍历传入的每一张图像
char img_file[100]; //定义字符串存储路径的名字
sprintf_s(img_file, "%s%s%d.%s", imgs_directory, imgs_filename, k, extension);//格式化字符串用的,将img_file拼接成一段完整路径
//printf("%s\n", img_file);
if (!doesExist(img_file))//若文件不存在,则结束本次循环
continue;
img = imread(img_file, IMREAD_COLOR);//从路径中读取本次图片文件
//img = imread(img_file);
cv::cvtColor(img, gray, COLOR_BGR2GRAY);//转灰度
bool found = false;
found = cv::findChessboardCorners(img, board_size, corners,
CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);//该函数的功能就是判断图像内是否包含完整的棋盘图,如果能够检测完全,就把他们的角点坐标按顺序(逐行,从左到右)记录下来,并返回非0数true,否则返回false。使用自适应阈值转化为黑白图,CALIB_CB_FILTER_QUADS用于去除检测阶段检测到的错误方块
if (found)
{
cornerSubPix(gray, corners, Size(5, 5), Size(-1, -1),
TermCriteria(TermCriteria::EPS | TermCriteria::MAX_ITER, 30, 0.1));//进行亚像素精度的调整,获得亚像素级别的角点坐标
drawChessboardCorners(gray, board_size, corners, found);//将每一个角点处做标记,此为物点对应的像点坐标值
}
vector< Point3f > obj;
for (int i = 0; i < board_height; i++)//寻找每张图的物点坐标并存储在obj中
for (int j = 0; j < board_width; j++)
obj.push_back(Point3f((float)j * square_size, (float)i * square_size, 0));//把一张图中寻找到的每一个角点坐标依次存入数组obj(vector<Point3f>)中
if (found) {//将每张图片的角点坐标集中放在object_points中,存储所有点的坐标
cout << k << ". Found corners!" << endl;
image_points.push_back(corners);//把每张图提取的角点坐标依次都存入image_points中,此为像点坐标
object_points.push_back(obj);//把存放每张图的所有角点坐标(obj内)依次存放到数组object_points(vetor<vector<3f>>)中,object_points中存放了所有的物点坐标
}
}
}
double computeReprojectionErrors(const vector< vector< Point3f > >& objectPoints,
const vector< vector< Point2f > >& imagePoints,
const vector< Mat >& rvecs, const vector< Mat >& tvecs,
const Mat& cameraMatrix, const Mat& distCoeffs) {//将标定结果代入计算误差
vector< Point2f > imagePoints2;//一张图片上的二维角点坐标,用于存储三维点投影后的结果,与标定的到的像点进行对比
int i, totalPoints = 0;
double totalErr = 0, err;
vector< float > perViewErrors;
perViewErrors.resize(objectPoints.size());//大小为图片数量
for (i = 0; i < (int)objectPoints.size(); ++i) {//遍历传入的每一张图片
projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);//求取三维点投影到二维平面的坐标
err = norm(Mat(imagePoints[i]), Mat(imagePoints2), NORM_L2); //求解对应点的范数,确定误差,传入标定的像点;重投影的像点;求范数类型;所有对应点距离的平方加和开根号
int n = (int)objectPoints[i].size();//第i张图片含有的角点数
perViewErrors[i] = (float)std::sqrt(err * err / n);//第i张标定板测得的平均误差,第i张板的对应点距离平方相加/第i张图片角点个数
totalErr += err * err;//累计误差
totalPoints += n;//参与计算的有效角点坐标
}
return std::sqrt(totalErr / totalPoints);//最终得到所有对应点距离平方加和/总的角点数,为总的平均误差
}
int main(int argc, char const** argv) {
int board_width, board_height, num_imgs;//标定板的宽度、高度、图像数量
float square_size;//标定板格子尺寸
//char* imgs_directory;//文件存储路径
//char* imgs_filename;//文件名
//char* out_file;
char* extension;
setup_calibration(9, 6, 27, 0.02423, "C:/Users/26839/Desktop/right/", "right", "jpg");//传入棋盘格标定板的参数,得到所有的物点坐标和对应的像点坐标,存放在数组object_pointd和image_ponts中。这里需要根据自己的实际图片存储路径,名称格式改
//像点的坐标根据物点的坐标,用角点提取算法即可获得
printf("Starting Calibration\n");
Mat K;//内参矩阵(3*3)
Mat D;//畸变矩阵(1*5)
vector< Mat > rvecs, tvecs;//旋转向量和位移向量,用于存放相机和每一个标定板的外参矩阵(旋转+平移)
int flag = 0;//标定函数中所采用的模型
flag |= CALIB_FIX_K4;
flag |= CALIB_FIX_K5;
//calibrateCamera(object_points, image_points, img.size(), K, D, rvecs, tvecs, flag);
calibrateCamera(object_points, image_points, img.size(), K, D, rvecs, tvecs, flag );//传入物点、像点、图像尺寸、内参矩阵、畸变矩阵、旋转向量、平移向量、flag标志位,进行标定
/*此段为矫正畸变,可以考虑优化
Mat dst;
for (int i = 1; i <= 30; i++) {
char img_file[100]; //定义字符串存储路径的名字
sprintf_s(img_file, "%s%s%d.%s", "C:/Users/26839/Desktop/calibR/", "right", i, "jpg");//格式化字符串用的,将img_file拼接成一段完整路径
if (!doesExist(img_file))//若文件不存在,则结束本次循环
continue;
img = imread(img_file, IMREAD_COLOR);//从路径中读取本次图片文件
undistort(img, dst, K, D);//校正畸变
imwrite(format("C:/Users/26839/Desktop/camera301/right%d.jpg",i), dst);//将矫正畸变后的图像存放到目标路径下
//imshow("img", img);
//imshow("dst", dst);
//waitKey(1000);
}
*/
cout << "Calibration error: " << computeReprojectionErrors(object_points, image_points, rvecs, tvecs, K, D) << endl;//输出投影的误差
//const string out_file = "D:/test_calibration.yml";
FileStorage fs("D:/calib/right_calibration.yml", FileStorage::WRITE);//存储标定结果,这里可以选择自己的存放路径
fs << "K" << K;//存放内参矩阵
fs << "D" << D;//存放畸变矩阵
fs << "board_width" << 9;//存放标定板长度信息
fs << "board_height" << 6;//存放标定板宽度信息
fs << "square_size" << 0.02423;//存放标定板格子尺寸信息
printf("Done Calibration\n");
return 0;
}