一、 标定原理
相机标定的目的就是要获得相机的内参数,得到二维平面像素坐标和三维世界坐标的关系,从而进行三维重建。
1、几个坐标系及其变换
(1)图像坐标系:是一个以像素为单位的坐标系,它的原点在左上方,每个像素点的位置是以像素为单位来表示的,所以这样的坐标系叫图像像素坐标系(u,v),u和v分别表示像素在数字图像中的列数和行数,但是并没有用物理单位表示像素的位置,因此还需建立以物理单位表示的图像坐标系,叫图像物理坐标系(x,y),该坐标系是以光轴与图像平面的交点为原点,该点一般位于图像中心,但是由于制造原因,很多情况下会偏移。以毫米为单位。两个坐标轴分别与图像像素坐标系平行。
若图像物理坐标系的原点在图像像素坐标系中的坐标为,每个像素在图像物理坐标系中的尺寸为dx,dy,则两个坐标系的关系为:
化为齐次坐标和矩阵形式:
(2)相机坐标系:是以相机的光心为原点Oc,Zc轴与光轴重合,与成像平面垂直,Xc和Yc轴与图像坐标系的两个坐标轴平行。OOc为摄像机的焦距,即相机坐标系的原点与图像物理坐标系原点之间的距离。
(3)世界坐标系:是由用户定义的三维坐标系,用于描述三维空间中的物体和相机的位置。用表示XwYwZw。
三者可用如下图表示:
(1)从世界坐标系到相机坐标系的变换:可以由一个旋转矩阵R和一个平移向量t来描述:
化为齐次坐标:
(2)相机坐标系到图像物理坐标系:
其中f是焦距。化成齐次坐标为:
所以可以得到从世界坐标系到图像坐标系的转换:
其中是相机的内参数,是外参数。
2、透镜畸变
实际的摄像机由于镜头制作工艺等原因,使摄像机获取的原始图像存在畸变,畸变有两种,径向畸变和切向畸变。径向畸变来自透镜形状,切向畸变来自于整个摄像机的组装过程。径向畸变可以由三个参数k1,k2,k3确定,切向畸变可由两个参数p1,p2确定。
3、总结
相机标定的目的就是建立摄像机图像像素位置与场景点位置之间的关系,即世界坐标系与图像坐标系之间的关系。方法就是根据摄像机模型,由已知特征点的图像坐标求解摄像机的模型参数,从而可以从图像出发恢复出空间点三维坐标,即三维重建。所以要求解的参数包括4个内参数和5个畸变参数,对于外参数,即旋转矩阵的三个旋转参数和平移向量的三个参数。内参数直接与期盼所在空间的3D几何相关,即外参数。而畸变参数则与点集如何畸变的2D集合相关。对于外参数,需要知道棋盘的位置,对棋盘的6个不同的视场图像,需要这6个参数,总之,在每个视场中,需要计算4个内参数和6个外参数。
4、opencv中要用到的函数
Opencv中使用的求解焦距和偏移的算法是基于zhang的方法,求解畸变参数是基于Brown的方法。Opencv不是使用基于3D构造物体的视场,而是使用平面物体的多个视场,使用黑白方块交替排列的模式能保证在测量上任何一边都没有偏移,并且格线角点也让亚像素定位函数的使用更自然。
1、给定一个棋盘图像,可以使用opencv函数findChessboardCorner()来定位棋盘的角点:
int
cvFindChessboardCorners(const void* image, CvSize patternSize, CvPoint2D32f* corners, nt* cornerCount=NULL, int flags=CV_CALIB_CB_ADAPTIVE_THRESH)
参数的意义为:
image:是一个输入变量,包含棋盘的单幅图像,必须为8位灰度图像。
patternSize:表示棋盘的每行和每列有多少个角点,值应为cvSize(columns,rows).
corners:存储角点位置的数组指针。
注:如果函数成功找到所有的角点,则返回非0,反之,返回0.最后的flags变量可以用来定义额外的滤波步长以有助于寻找棋盘角点。
2、cvFindChessboardCorners()返回的角点仅仅是近似值,实际上位置的精度受限于图像设备的精度,即小于一个像素,必须单独使用另外的函数来计算角点的精确位置以达到亚像素精度,即函数cvFindCornerSubPix().
3、在图像上绘制找到的棋盘角点,这样就可以看到投影的角点与观察的角点是否匹配,opencv提供了函数cvDrawChessboardCorners()将发现的所有角点绘制到所提供的图像上,如果没有发现所有的角点,那么得到的角点将用红色圆圈绘制,如果都找到就使用不同颜色绘制,并且把角点以一定的顺序用线连接起来。
Void
cvDrawChessboardCorners(CvArr* image, CvSize patternSize, CvPoint2D32f* corners, int count, int patternWasFound)
参数的意义:
image:要绘制的图像。必须是8位的彩色图像。就是findChessboardCorners()所使用的图像的复制品。
patternSize和corners与发现角点函数的参数意义一样。
count:等于角点数目,是整数。
最后一个参数表示是否所有的棋盘模式都成功找到。
4、一旦有多个图像的角点,就可以调用函数函数cvCalibrateCamera2(),可以得到摄像机内参数矩阵、畸变系数、旋转向量和平移向量。
double cvCalibrateCamera2(const
CvMat* objectPoints, const CvMat* imagePoints, const CvMat* pointCounts,CvSize imageSize, CvMat* cameraMatrix, CvMat*distCoeffs, CvMat* rvecs=NULL, CvMat* tvecs=NULL,
int flags=0 )
参数的意义:
object_points:是一个N*3的矩阵,包含物体的每k个点在每M个图像上的物理坐标,N=K*M.这些点位于物体的坐标
平面上。
Image_points:是一个N*2的矩阵,包含object_points所提供的所有点的像素坐标,就是由M此调用cvFindChessboardCorners()的返回值构成。
points_count:表示每个图像的点的个数,以M*1矩阵形式表示。
image_size:是以像素衡量的图像尺寸。
cameraMatrix:3*3的相机内参数矩阵。
distCoeffs:5*1的畸变参数。
rvecs:旋转参数,M*3,代表棋盘围绕相机坐标系统下三维空间的坐标轴的旋转,每个向量的长度表示逆时针旋转的角度,可以通过调用cvRodrigues2()转换为3*3的旋转矩阵。
tvecs():平移参数,M*3。
代码:
#include<opencv2\core\core.hpp> #include<opencv2\highgui\highgui.hpp> #include<opencv2\calib3d\calib3d.hpp> #include<opencv\cv.h> using namespace std; using namespace cv; class CameraCalibrator { int board_w; int board_h; int board_n; int N; CvSize board_sz; CvMat* image_points; CvMat* object_points; CvMat* point_counts; CvMat* intrinsic; CvMat* distortion; CvPoint2D32f* corners; int corner_count; int success; char filename[10]; char windowname[20]; IplImage* image; IplImage* gray_image; public: CameraCalibrator(void); CameraCalibrator(int w,int h,int n); ~CameraCalibrator(void); void FindCorners(); void calibrate(); };
类的实现:
#include "CameraCalibrator.h" CameraCalibrator::CameraCalibrator(int w,int h,int n) { board_w=w; board_h=h; N=n; board_n=w*h; board_sz=cvSize(board_w,board_h); image_points=cvCreateMat(N*board_n,2,CV_32FC1); object_points=cvCreateMat(N*board_n,3,CV_32FC1); point_counts=cvCreateMat(N,1,CV_32SC1); intrinsic=cvCreateMat(3,3,CV_32FC1); distortion=cvCreateMat(5,1,CV_32FC1); corners=new CvPoint2D32f[board_n]; success=0; } void CameraCalibrator::FindCorners() { int index=1; while(index<=N) { sprintf(filename,"chess%d.jpg",index); image=cvLoadImage(filename); gray_image=cvCreateImage(cvGetSize(image),8,1); int found=cvFindChessboardCorners(image,board_sz,corners,&corner_count, CV_CALIB_CB_ADAPTIVE_THRESH|CV_CALIB_CB_FILTER_QUADS); cvCvtColor(image,gray_image,CV_BGR2GRAY); cout<<corner_count<<endl; cvFindCornerSubPix(gray_image,corners,corner_count,cvSize(11,11),cvSize(-1,-1), cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1)); cvDrawChessboardCorners(image,board_sz,corners,corner_count,found); sprintf(windowname,"chesscalib%d",index); cvShowImage(windowname,image); cvWaitKey(0); if(found) { for(int i=success*board_n,j=0;j<board_n;++i,++j) { CV_MAT_ELEM(*image_points,float,i,0)=corners[j].x; CV_MAT_ELEM(*image_points,float,i,1)=corners[j].y; CV_MAT_ELEM(*object_points,float,i,0)=j/board_w; CV_MAT_ELEM(*object_points,float,i,1)=j%board_w; CV_MAT_ELEM(*object_points,float,i,2)=0.0f; }//endfor CV_MAT_ELEM(*point_counts,int,success,0)=board_n; success++; }//endif index++; }//endwhile } void CameraCalibrator::calibrate() { CV_MAT_ELEM(*intrinsic,float,0,0)=1.0f; CV_MAT_ELEM(*intrinsic,float,1,1)=1.0f; //进行标定 计算参数 cvCalibrateCamera2(object_points,image_points,point_counts, cvGetSize(image),intrinsic,distortion,NULL,NULL,0); cvSave("intrinsic.xml",intrinsic); cvSave("distortion.xml",distortion); } CameraCalibrator::~CameraCalibrator(void) { cvReleaseMat(&object_points); cvReleaseMat(&image_points); cvReleaseMat(&point_counts); } //主函数: #include"CameraCalibrator.h" int main() { CameraCalibrator cam1(5,7,7); cam1.FindCorners(); cam1.calibrate(); return 0; }
结果:
内参数矩阵:
畸变参数矩阵:
内参数矩阵中:
F是相机的物理焦距长度,Sx是x方向每个单元像素个数,单位是像素/每单元, 所以fx的单位是像素。
图像的高:240 图像的宽:320
设相机传感器的大小为m*n
Cx,Cy是成像中心相对于光轴与成像平面交点的偏移量:
畸变参数矩阵:
径向畸变参数k1=-1.82442880,k2=16.5068817,k3=-49.8691101
切向畸变参数p1=-0.0106841922,p2=-0.00607223995
图像的高:240 图像的宽:320