https://blog.csdn.net/shiyuqing457/article/details/106764866
opencv版本4.0
程序思路(最下方会有全部代码,复制可直接用)
1、读取图片
2、提取图像的棋盘格格点
3、进行图像标定
对每个函数进行分开说明
#include "opencv.hpp"
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
void get_file(string &src_dir, string &file_type, vector<string> &file_name)
{
struct _finddata_t FileInfo;
long handle;
string src_tmp = src_dir + "\\*" + file_type;
string src_full_name;
handle = _findfirst(src_tmp.c_str(), &FileInfo);
if (!handle)
cout << "the dir is not exist" << endl;
src_full_name = src_dir + "\\" + FileInfo.name; //将路径连接起来,存入容器
file_name.push_back(src_full_name);
while (!_findnext(handle, &FileInfo))
{
src_full_name = src_dir + "\\"+FileInfo.name;
file_name.push_back(src_full_name);
}
_findclose(handle);
/*decltype(file_name.size()) cnt=0;
for (auto file_num : file_name)
{
cout << "the num " << cnt << " file is:" << file_name[cnt] << endl;
cnt++;
}*/
}
Struct _finddata_t结构体可以用来处理各种文件的文件信息,使用该结构体需要添加头文件io.h
它的定义如下所示
struct _finddata_t
{
unsigned attrib;
time_t time_create;
time_t time_access;
time_t time_write;
_fsize_t size;
char name[_MAX_FNAME];
};
对于该结构体中的各个变量的定义如下所示:
unsigned attrib:无符号整形、位表示。它的作用是表示文件的属性,一般来说,文件的属性有如下几种:
_A_ARCH(存档)、 _A_HIDDEN(隐藏)、_A_NORMAL(正常)、_A_RDONLY(只读)、_A_SUBDIR(文件夹)、_A_SYSTEM(系统)
time_t time_create:表示从1970年1月1日0时0分0秒到现在时刻的秒数。
time_t time_access:文件最后一次被访问的时间
time_t time_write:文件最后一次被修改的时间
_fsize_t size:文件的大小。
char name[_MAX_FNAME]:文件的文件名,这里的_MAX_FNAME是一个常量宏,它在头文件中被定义,表示的是文件名的最大长度。
为了使用这个结构体将文件的信息存储到该结构体的内存空间,需要搭配使用_findfirst()、_findnext()、_findclose()三个函数使用。首先用_findfirst函数查找第一个文件,若成功则用返回的句柄,调用_findnext函数查找其他的文件,当查找文件完成之后,就会用_findclose函数结束查找。
long _findfirst( char *filespec, struct _finddata_t *fileinfo );
返回值:如果查找成功,那么就返回一个long型的唯一查找用的句柄。这个句柄会在_findnext函数中被使用。失败的话就会返回0.
参数:filespec:标明文件的字符串。例如:*.c
fileinfo:这里就是用来存放文件信息的结构体的指针。函数成功后,函数会把找到的文件的信息放入这个结构体所分配的内存空间中。
int _findnext( long handle, struct _finddata_t *fileinfo );
返回值:若成功返回0,否则返回-1。
参数:handle:即由_findfirst函数返回回来的句柄。
int _findclose( long handle );
返回值:成功返回0,失败返回-1。
指针与句柄的区别
指针:指针通俗来着就是地址,他是内存的编号,通过指针我们可以直接对内存进行操作,只要地址不变,我们每次操作的物理位置是绝对不变的。
句柄:一般是指向系统的资源的位置,可以说也是地址。但是这些资源的位置真的不变,我们都知道window支持虚拟内存的技术,同一时间内可能有些资源被换出内存,一些被换回来,这就是说同一资源在系统的不同时刻,他在内存的物理位置是不确定的,那么window是如何解决这个问题呢,就是通过句柄来处理资源的物理位置不断变化的这个问题的。window会在物理位置固定的区域存储一张对应表,表中记录了所有的资源实时地址,句柄其实没有直接指向资源的物理地址,而是指向了这个对应表中的一项,这样无论资源怎样的换进换出,通过句柄都可以找到他的实时位置。
1、**cv::findChessboardCorners()**棋盘格角点检测
该函数的具体调用形式如下:
bool cv::findChessboardCorners( // 如果找到角点则返回true
cv::InputArray image, // 输入的棋盘格图像(8UC1或8UC3)
cv::Size patternSize, // 棋盘格内部角点的行、列数
cv::OutputArray corners, // 输出的棋盘格角点
int flags = cv::CALIB_CB_ADAPTIVE_THRESH
| cv::CALIB_CB_NORMALIZE_IMAGE
);
第一个参数是输入的棋盘格图像(可以是8位单通道或三通道图像)。
第二个参数是棋盘格内部的角点的行列数(注意:不是棋盘格的行列数,如下图棋盘格的行列数分别为4、8,而内部角点的行列数分别是3、7,因此这里应该指定为cv::Size(3, 7))。
第三个参数是检测到的棋盘格角点,类型为std::vectorcv::Point2f。
第四个参数flag,用于指定在检测棋盘格角点的过程中所应用的一种或多种过滤方法,可以使用下面的一种或多种,如果都是用则使用OR:
cv::CALIB_CB_ADAPTIVE_THRESH:cv::findChessboardCorners()默认的阈值化处理基于平均亮度,如果该标志指定,则使用自适应滤波(自适应滤波见
OpenCV3中的阈值化操作——cv::threshold()与cv::adaptiveThreshold()详解 )。
cv::CALIB_CB_NORMALIZE_IMAGE:阈值化前使用cv::equalizeHist()进行直方图均衡化处理。
cv::CALIB_CB_FILTER_QUADS:
cv::CALIB_CV_FAST_CHECK:
2,**cv::cornerSubPix()**对角点进行亚像素精度优化。
使用亚像素级别角点检测,返回角点的浮点数值,它的精度比整数像素更准确。可以用cornerSubPix()函数将角点定位到子像素,从而取得亚像素级别的角点检测效果。
使用函数:
void cv::cornerSubPix ( InputArray image,//输入图像
InputOutputArray corners,//检测到的角点的位置
Size winSize,//搜索窗口
Size zeroZone,
TermCriteria criteria//终止条件
)
参考:https://blog.csdn.net/holybin/article/details/41122493
函数参数说明如下:
image:输入图像
corners:输入角点的初始坐标以及精准化后的坐标用于输出。
winSize:搜索窗口边长的一半,例如如果winSize=Size(5,5),则一个大小为(5x2+1)(5x2+1)=1111的搜索窗口将被使用。
zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。
3、**cv::drawChessboardCorners()**棋盘格角点的绘制
cv::drawChessboardCorners()的具体调用形式如下:
void cv::drawChessboardCorners(
cv::InputOutputArray image, // 棋盘格图像(8UC3)即是输入也是输出
cv::Size patternSize, // 棋盘格内部角点的行、列数
cv::InputArray corners, // findChessboardCorners()输出的角点
bool patternWasFound // findChessboardCorners()的返回值
);
第一个参数是棋盘格图像(8UC3)。
第二个参数是棋盘格内部角点的行、列,和cv::findChessboardCorners()指定的相同。
第三个参数是检测到的棋盘格角点。
第四个参数是cv::findChessboardCorners()的返回值。
4, **cv::calibrateCamera()**相机标定程序
double cv::calibrateCamera (
InputArrayOfArrays objectPoints, //世界坐标下角点的坐标vector< vector< Point3f > >
InputArrayOfArrays imagePoints, //提取出的角点的坐标vector< vector< Point2f > >
Size imageSize, //图像的大小Size(图像高rows,图像宽cols)
InputOutputArray cameraMatrix, //内参矩阵Mat
InputOutputArray distCoeffs, //畸变系数,顺序为k1,k2,p1,p2,k3
OutputArrayOfArrays rvecs, //旋转矩阵
OutputArrayOfArrays tvecs, //平移向量
OutputArray stdDeviationsIntrinsics,//可以不用(感兴趣自己看下面)
OutputArray stdDeviationsExtrinsics,//可以不用(感兴趣自己看下面)
OutputArray perViewErrors, //可以不用(感兴趣自己看下面)
int flags = 0, //标定方式
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) //可以不写
)
objectPoints :世界坐标系中的点。在使用时,应该输入vector< vector< Point3f > >。
imagePoints :其对应的图像点。和objectPoints一样,应该输入vector< vector< Point2f > >型的变量。
imageSize :图像的大小,在计算相机的内参数和畸变矩阵需要用到;
cameraMatrix :内参数矩阵。输入一个Mat cameraMatrix即可。
distCoeffs :畸变矩阵。输入一个Mat distCoeffs即可。
rvecs :旋转向量;应该输入一个Mat的vector,即vector< Mat > rvecs因为每个vector< Point3f >会得到一个rvecs。
tvecs :位移向量;和rvecs一样,也应该为vector tvecs。
stdDeviationsIntrinsics :内参数的输出向量。输出顺序为: (fx,fy,cx,cy,k1,k2,p1,p2,k3,k4,k5,k6,s1,s2,s3,s4,τx,τy) ,如果不估计其中某一个参数,值等于0
stdDeviationsExtrinsics :外参数的输出向量。输出顺序: (R1,T1,…,RM,TM) ,M是标定图片的个数, Ri,Ti 是1x3的向量 。
perViewErrors 每个标定图片的重投影均方根误差的输出向量。
criteria: 迭代优化算法的终止准则
flags :标定函数是所采用的模型(重点)”。
可输入如下某个或者某几个参数:
CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,将包含有效的fx,fy,cx,cy的估计值的内参矩阵cameraMatrix,作为初始值输入,然后函数对其做进一步优化。如果不使用这个参数,用图像的中心点初始化光轴点坐标(cx, cy),使用最小二乘估算出fx,fy(这种求法好像和张正友的论文不一样,不知道为何要这样处理)。注意,如果已知内部参数(内参矩阵和畸变系数),就不需要使用这个函数来估计外参,可以使用solvepnp()函数计算外参数矩阵。
CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点,光轴点将保持为图像的中心点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,保持为输入的值。
CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy的实际输入值将会被忽略,只有fx/fy的比值被计算和使用。
CV_CALIB_ZERO_TANGENT_DIST:切向畸变系数(P1,P2)被设置为零并保持为零。
CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变系数在优化中保持不变。如果设置了CV_CALIB_USE_INTRINSIC_GUESS参数,就从提供的畸变系数矩阵中得到。否则,设置为0。
CV_CALIB_RATIONAL_MODEL(理想模型):启用畸变k4,k5,k6三个畸变参数。使标定函数使用有理模型,返回8个系数。如果没有设置,则只计算其它5个畸变参数。
CALIB_THIN_PRISM_MODEL (薄棱镜畸变模型):启用畸变系数S1、S2、S3和S4。使标定函数使用薄棱柱模型并返回12个系数。如果不设置标志,则函数计算并返回只有5个失真系数。
CALIB_FIX_S1_S2_S3_S4 :优化过程中不改变薄棱镜畸变系数S1、S2、S3、S4。如果cv_calib_use_intrinsic_guess设置,使用提供的畸变系数矩阵中的值。否则,设置为0。
CALIB_TILTED_MODEL (倾斜模型):启用畸变系数tauX and tauY。标定函数使用倾斜传感器模型并返回14个系数。如果不设置标志,则函数计算并返回只有5个失真系数。
CALIB_FIX_TAUX_TAUY :在优化过程中,倾斜传感器模型的系数不被改变。如果cv_calib_use_intrinsic_guess设置,从提供的畸变系数矩阵中得到。否则,设置为0。
重投影的总的均方根误差。
#include "opencv.hpp"
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
void get_file(string &src_dir, string &file_type, vector<string> &file_src)
{
struct _finddata_t FileInfo;
long handle;
string src_tmp = src_dir + "\\*" + file_type;
string src_full_name;
handle = _findfirst(src_tmp.c_str(), &FileInfo);
if (!handle)
cout << "the dir is not exist" << endl;
src_full_name = src_dir + "\\" + FileInfo.name; //将路径连接起来,存入容器vector
file_src.push_back(src_full_name);
while (!_findnext(handle, &FileInfo))
{
src_full_name = src_dir + "\\"+FileInfo.name;
file_src.push_back(src_full_name);
}
_findclose(handle);
/*decltype(file_name.size()) cnt=0;
for (auto file_num : file_name)
{
cout << "the num " << cnt << " file is:" << file_name[cnt] << endl;
cnt++;
}*/
}
bool calibration(vector<string> &file_src, Size &board_num,Size &square_size, Mat & cameraMatrix, Mat & distCoeffs, vector<Mat> & rvecs, vector<Mat> & tvecs)
{
Mat file_data;
vector<Mat> all_pic_data;
vector<vector<Point2f>> all_pattern_size;
Size img_size;
ofstream fout("calibration.txt");//用于保存标定结果
int img_num = 0;
cout << "开始读取图像..." << endl;
//将所有图像存入all_pic_data中
for (decltype(file_src.size()) i = 0; i >= 0 && i != file_src.size(); i++)
{
file_data = imread(file_src[i]);
all_pic_data.push_back(file_data);
}
//提取棋盘格中的角点
cout << "开始提取角点..." << endl;
for (decltype(all_pic_data.size()) cnt = 0; cnt >= 0 && cnt != all_pic_data.size(); cnt++)
{
img_size.height = all_pic_data[cnt].rows;
img_size.width = all_pic_data[cnt].cols; //提取图像尺寸并保存,之后标定函数需要
vector<Point2f> pattern_size;//输出检测到的角点坐标
Mat imgInput;
int corner_cnt=0;
imgInput = all_pic_data[cnt];
if (!findChessboardCorners(imgInput, board_num, pattern_size))
{
cout << "第 " << cnt << "幅图的角点检测失败" << endl;
cout << "跳过第" << cnt << "幅图" << endl;
continue;
}
img_num++;
//亚像素化精确
TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.01);
cvtColor(imgInput, imgInput, COLOR_RGB2GRAY);
cornerSubPix(imgInput, pattern_size, Size(5, 5), Size(-1, -1), tc);
all_pattern_size.push_back(pattern_size);
bool patternWasFound = true;
drawChessboardCorners(imgInput, board_num, pattern_size, patternWasFound);//将角点绘制到图上
imshow("Camera Calibration", imgInput);//显示图片
waitKey(100);
}
cout << "角点提取完成..." << endl;
//取z=0为世界坐标系原点位置
/*注意定义各point的位置,一定不能改,
不然会导致存世界坐标的容器无法及时清零*/
vector<vector<Point3f>> all_real_point; //标定板放在世界坐标系下角点的位置
for (int num = 0; num < img_num; num++)
{
vector<Point3f> tmp_point;
for(int i=0;i<board_num.height;i++)
for (int j = 0; j < board_num.width; j++)
{
Point3f real_point;
real_point.x = i * square_size.width;
real_point.y = j * square_size.height;
real_point.z = 0;
tmp_point.push_back(real_point);
}
all_real_point.push_back(tmp_point);
}
cout << "开始标定..." << endl;
double err_first = calibrateCamera(all_real_point, all_pattern_size, img_size, cameraMatrix, distCoeffs, rvecs, tvecs, 0);
cout << "标定完成..." << endl;
cout << "....开始保存数据...." << endl;
fout << "内参矩阵" << endl;
fout << cameraMatrix << endl<<endl;
fout << "相机畸变量" << endl;
fout << distCoeffs << endl<<endl;
for (int i = 0; i < img_num; i++)
{
Mat rotationMatrix;
Rodrigues(rvecs[i], rotationMatrix); //标定结果得到的是旋转向量,将旋转向量转为旋转矩阵
fout << "第" << i + 1 << "副图的旋转矩阵为" << endl;
fout << rotationMatrix << endl;
fout << "第" << i + 1 << "副图的平移向量为" << endl;
fout << tvecs[i] << endl<<endl;
}
cout << "结果保存完成" << endl;
fout.close();
return 1;
}
int main()
{
string src_dir = "D:\\calibra\\ZZY\\photo";
string file_type = ".jpg";
vector<string> file_src;
Size board_num(7, 4);
Size square_size(50, 50);
Mat cameraMatrix(3, 3, CV_32FC1,Scalar::all(0));//内参矩阵
Mat distCoeffs(1, 5, CV_32FC1, Scalar::all(0));//畸变系数[k1,k2,p1,p1,k3]
vector<Mat> rvecs;
vector<Mat> tvecs;
get_file(src_dir, file_type, file_src);
calibration(file_src, board_num, square_size, cameraMatrix, distCoeffs, rvecs, tvecs);
return 0;
}
https://blog.csdn.net/shiyuqing457/article/details/106790847