关于OpenCV的那些事——相机标定

来源:互联网 发布:淘宝卖stussy的正品店 编辑:程序博客网 时间:2024/04/16 18:06

这一节我们首先介绍下计算机视觉领域中常见的三个坐标系:图像坐标系,相机坐标系,世界坐标系。以及他们之间的关系。然后介绍如何使用张正友相机标定法标定相机。

图像坐标系:


理想的图像坐标系原点O1和真实的O0有一定的偏差,由此我们建立了等式(1)和(2),可以用矩阵形式(3)表示。

相机坐标系(C)和世界坐标系(W):


通过相机与图像的投影关系,我们得到了等式(4)和等式(5),可以用矩阵形式(6)表示。我们又知道相机坐标系和世界坐标的关系可以用等式(7)表示:


由等式(3),等式(6)和等式(7)我们可以推导出图像坐标系和世界坐标系的关系:


其中M1称为相机的内参矩阵,包含内参(fx,fy,u0,v0)。M2称为相机的外参矩阵,包含外参(R:旋转矩阵,T:平移矩阵)。

众所周知,相机镜头存在一些畸变,主要是径向畸变(下图dr),也包括切向畸变(下图dt)等。


上图右侧等式中,k1,k2,k3,k4,k5,k6为径向畸变,p1,p2为切向畸变。在OpenCV中我们使用张正友相机标定法通过10幅不同角度的棋盘图像来标定相机获得相机内参和畸变系数。函数为calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs,flag)

objectPoints: 一组世界坐标系中的3D

imagePoints: 超过10张图片的角点集合

imageSize: 每张图片的大小

cameraMatrix: 内参矩阵

distCoeffs: 畸变矩阵(默认获得5个即便参数k1,k2,p1,p2,k3,可修改)

rvecs: 外参:旋转向量

tvecs: 外参:平移向量

flag: 标定时的一些选项:

CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。

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_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。

首先我们打开摄像头并按下'g'键开始标定:

[cpp] view plain copy
 print?
  1. VideoCapture cap(1);  
  2. cap.set(CV_CAP_PROP_FRAME_WIDTH,640);  
  3. cap.set(CV_CAP_PROP_FRAME_HEIGHT,480);  
  4. if(!cap.isOpened()){  
  5.     std::cout<<"打开摄像头失败,退出";  
  6.     exit(-1);  
  7. }  
  8. namedWindow("Calibration");  
  9.     std::cout<<"Press 'g' to start capturing images!"<<endl;   
[cpp] view plain copy
 print?
  1. if( cap.isOpened() && key == 'g' )  
  2. {  
  3. <span style="white-space:pre">    </span>mode = CAPTURING;  
  4. }  
按下空格键(SPACE)后使用findChessboardCorners函数在当前帧寻找是否存在可用于标定的角点,如果存在将其提取出来后亚像素化并压入角点集合,保存当前图像:

[cpp] view plain copy
 print?
  1. if( (key & 255) == 32 )  
  2. {  
  3.     image_size = frame.size();  
  4.     /* 提取角点 */     
  5.     Mat imageGray;  
  6.     cvtColor(frame, imageGray , CV_RGB2GRAY);  
  7.     bool patternfound = findChessboardCorners(frame, board_size, corners,CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK );  
  8.     if (patternfound)     
  9.     {      
  10.         n++;  
  11.         tempname<<n;  
  12.         tempname>>filename;  
  13.         filename+=".jpg";  
  14.         /* 亚像素精确化 */  
  15.         cornerSubPix(imageGray, corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));  
  16.         count += corners.size();  
  17.         corners_Seq.push_back(corners);  
  18.         imwrite(filename,frame);  
  19.         tempname.clear();  
  20.         filename.clear();  
  21.     }  
  22.     else  
  23.     {  
  24.         std::cout<<"Detect Failed.\n";  
  25.     }  
  26. }     
角点提取完成后开始标定,首先初始化定标板上角点的三维坐标:
[cpp] view plain copy
 print?
  1. for (int t=0;t<image_count;t++)   
  2. {     
  3. <span style="white-space:pre">    </span>vector<Point3f> tempPointSet;  
  4.         for (int i=0;i<board_size.height;i++)   
  5.     {     
  6.         <span style="white-space:pre">    </span>for (int j=0;j<board_size.width;j++)   
  7.         {     
  8.                 /* 假设定标板放在世界坐标系中z=0的平面上 */     
  9.             Point3f tempPoint;  
  10.             tempPoint.x = i*square_size.width;  
  11.             tempPoint.y = j*square_size.height;  
  12.             tempPoint.z = 0;  
  13.             tempPointSet.push_back(tempPoint);  
  14.            <span style="white-space:pre"> </span>}     
  15.         }  
  16.     object_Points.push_back(tempPointSet);  
  17. }  
使用calibrateCamera函数开始标定:
[cpp] view plain copy
 print?
  1. calibrateCamera(object_Points, corners_Seq, image_size,  intrinsic_matrix  ,distortion_coeffs, rotation_vectors, translation_vectors);    
完成定标后对标定进行评价,计算标定误差并写入文件:
[cpp] view plain copy
 print?
  1. std::cout<<"每幅图像的定标误差:"<<endl;     
  2. fout<<"每幅图像的定标误差:"<<endl<<endl;     
  3. for (int i=0;  i<image_count;  i++)   
  4. {  
  5.     vector<Point3f> tempPointSet = object_Points[i];  
  6.         /****    通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点     ****/  
  7.     projectPoints(tempPointSet, rotation_vectors[i], translation_vectors[i], intrinsic_matrix, distortion_coeffs, image_points2);  
  8.         /* 计算新的投影点和旧的投影点之间的误差*/    
  9.     vector<Point2f> tempImagePoint = corners_Seq[i];  
  10.     Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);  
  11.     Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);  
  12.     for (int j = 0 ; j < tempImagePoint.size(); j++)  
  13.     {  
  14.         image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);  
  15.         tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);  
  16.     }  
  17.     err = norm(image_points2Mat, tempImagePointMat, NORM_L2);  
  18.         total_err += err/=  point_counts[i];     
  19.         std::cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;     
  20.         fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;     
  21. }     
  22. std::cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;     
  23. fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;     
  24. std::cout<<"评价完成!"<<endl;  
显示标定结果并写入文件:
[cpp] view plain copy
 print?
  1. std::cout<<"开始保存定标结果………………"<<endl;         
  2. Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */     
  3.          
  4. fout<<"相机内参数矩阵:"<<endl;     
  5. fout<<intrinsic_matrix<<endl<<endl;     
  6. fout<<"畸变系数:\n";     
  7. fout<<distortion_coeffs<<endl<<endl<<endl;     
  8. for (int i=0; i<image_count; i++)   
  9. {   
  10.         fout<<"第"<<i+1<<"幅图像的旋转向量:"<<endl;     
  11.         fout<<rotation_vectors[i]<<endl;     
  12.     
  13.         /* 将旋转向量转换为相对应的旋转矩阵 */     
  14.         Rodrigues(rotation_vectors[i],rotation_matrix);     
  15.         fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<<endl;     
  16.         fout<<rotation_matrix<<endl;     
  17.         fout<<"第"<<i+1<<"幅图像的平移向量:"<<endl;     
  18.         fout<<translation_vectors[i]<<endl<<endl;     
  19. }     
  20. std::cout<<"完成保存"<<endl;   
  21. fout<<endl;  

具体的代码实现和工程详见:Calibration

运行截图:



下一节我们将使用RPP相机姿态算法得到相机的外部参数:旋转和平移。


==============================================================================================

2015/11/14补充:

所有分辨率下的畸变(k1,k2,p1,p2)相同,但内参不同(fx,fy,u0,v0),不同分辨率下需要重新标定相机内参。以下是罗技C920在1920*1080下的内参:


0 0
原创粉丝点击