张正友算法的原理:
这里假定模板平面在世界坐标系Z=0的平面上:
如下图,其中,K为摄像机的内参数矩阵,[X Y 1]T为模板平面上点的齐次坐标,[u v 1]T为模板平面上点投影到图象平面上对应点的齐次坐标,[r1 r2 r3]和t 分别是摄像机坐标系相对于世界坐标系的旋转矩阵和平移向量。
根据旋转矩阵的性质,即r1Tr2=0和||r1||=||r2||=1,每幅图象可以获得以下两个对内参数矩阵的基本约束
由于摄像机有5个未知内参数,所以当所摄取得的图像数目大于等于3时,就可以线性唯一求解出K。
OK,开始动手:
1.首先需要准备一张标定板(在一张A4纸上打印一幅黑白相间的类似国际象棋的棋盘图):注意:打印的应该是平的,不能用下图,下图只是告诉你长什么样。具体图片网上很easy就可以下载到。
2.将需要标定的摄像头固定好(注意是固定,就是说拍照过程中摄像头不要动)
3.将棋盘图放在摄像头的拍摄范围内,不断改变A4纸的方向和倾角,拍摄至少10张以上照片(理论上多一点会更准确一点)
注意:摄像头与图片之间的夹角不要太小,夹角太小误差就大。
4.附上源码:
/**
* 程序实现功能:标定摄像头参数
* 输出:内参数矩阵、畸变系数、旋转向量、旋转矩阵、平移向量
* 运行环境:Linux + opencv + eclipse (windows下也可以照常运行)
*/
#include <cv.h>
#include <highgui.h>
#include <iostream>
using namespace std;
int image_width = 640;//待标定图片的宽度
int image_height = 480;//待标定图片的高度
const int ChessBoardSize_w = 5;//图片中可标定的行角点数
const int ChessBoardSize_h = 5;//图片中可标定的列角点数
const CvSize ChessBoardSize = cvSize(ChessBoardSize_w,ChessBoardSize_h);
const int NPoints = ChessBoardSize_w*ChessBoardSize_h;//每张图片中的总角点数
const int NImages=7;//待标定的图片数
int corner_count[NImages] = {0};
float SquareWidth = 19; //棋盘格子的边长19毫米。
CvMat *intrinsics;
CvMat *distortion_coeff;
CvMat *rotation_vectors;
CvMat *translation_vectors;
CvMat *object_points;
CvMat *point_counts;
CvMat *image_points;
//计算旋转矩阵需要
double R_matrix[9];
CvMat pr_vec;
CvMat pR_matrix;
void InitCorners3D(CvMat *Corners3D, CvSize ChessBoardSize, int NImages, float SquareSize);//得到定标点三维坐标矩阵
int main()
{
IplImage *current_frame_rgb; //彩色图像头
IplImage *current_frame_gray;//灰色图像头
IplImage *chessBoard_Img;//棋盘格图像头
CvPoint2D32f corners[NPoints*NImages];//corners数组存放所有图片角点的坐标
chessBoard_Img =cvCreateImage(cvSize(image_width, image_height), IPL_DEPTH_8U, 3);
current_frame_gray = cvCreateImage(cvSize(image_width, image_height), IPL_DEPTH_8U, 1);
current_frame_rgb = cvCreateImage(cvSize(image_width, image_height), IPL_DEPTH_8U, 3);
int captured_frames=0;
for(captured_frames=0;captured_frames<NImages;captured_frames++)
{
char filename[]="00.jpg"; //01.jpg, 02.jpg, 03.jpg, 04.jpg,……
if(captured_frames<9)
filename[1]=(char)(captured_frames+49);
else
if(captured_frames>=9&&captured_frames<=98)
{//两位数的时候,需要拆分个位和十位然后加到filename的下标为0和1单元
int j,jj;
jj=(captured_frames+1)/10;
j=(captured_frames+1)%10;
filename[0]=jj+48;
filename[1]=j+48;
}
else cout<<"error, too many images......."<<endl; //最多99张图片
chessBoard_Img=cvLoadImage( filename, CV_LOAD_IMAGE_COLOR );//加载图片
cvCvtColor(chessBoard_Img, current_frame_gray, CV_BGR2GRAY);//将输入的彩色图像转换为灰度图像
cvCopy(chessBoard_Img,current_frame_rgb);
int find_corners_result;
//寻找棋盘图的内角点位置,不能发现所有角点或者记录它们地情况下,函数返回0
find_corners_result = cvFindChessboardCorners(current_frame_gray,
ChessBoardSize,
&corners[captured_frames*NPoints],
&corner_count[captured_frames],
0);
cout<<find_corners_result<<endl;//打印一下检测结果
//检测亚像素级角点,获取精确角点坐标
cvFindCornerSubPix( current_frame_gray,
&corners[captured_frames*NPoints],
NPoints, cvSize(2,2),cvSize(-1,-1),
cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,20,0.03) );
//画到图片上
cvDrawChessboardCorners(current_frame_rgb, ChessBoardSize,
&corners[captured_frames*NPoints],
NPoints,
find_corners_result);
cvNamedWindow( "源图像", 0);
cvNamedWindow( "标定后图像", 0);
cvShowImage("源图像",chessBoard_Img);
cvShowImage("标定后图像",current_frame_rgb);
cvWaitKey(100);
}//end for
//初始化系列矩阵
intrinsics=cvCreateMat(3,3,CV_32FC1);//输出内参矩阵3*3
distortion_coeff=cvCreateMat(1,4,CV_32FC1);//输出大小为4x1或者1x4的向量,里面为形变参数[k1, k2, p1, p2]
rotation_vectors=cvCreateMat(NImages,3,CV_32FC1);//输出大小为3xM或者Mx3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数cvRodrigues2)
translation_vectors=cvCreateMat(NImages,3,CV_32FC1);//输出大小为3xM或Mx3的矩阵,里面为平移向量
point_counts=cvCreateMat(NImages,1,CV_32SC1);//向量,指定不同视图里点的数目
object_points=cvCreateMat(NImages*NPoints,3,CV_32FC1);//初始化定标点的世界坐标矩阵
image_points=cvCreateMat(NImages*NPoints,2,CV_32FC1);//初始化定标点的图像坐标矩阵
InitCorners3D(object_points, ChessBoardSize, NImages, SquareWidth);
cvSetData( image_points, corners, sizeof(CvPoint2D32f));
cvSetData( point_counts, &corner_count, sizeof(int));
//利用定标来计算摄像机的内参数和外参数函数形式
cvCalibrateCamera2( object_points,image_points,point_counts,cvSize(image_width,image_height),
intrinsics,//内参数矩阵
distortion_coeff,//畸变参数
rotation_vectors,//旋转向量
translation_vectors,//平移向量
CV_CALIB_FIX_PRINCIPAL_POINT);
cout<<"------------------------------------------"<<endl;
cout<<"内参数矩阵:"<<endl;//摄像机内参数矩阵
for ( int o = 0; o < 3; o++)
{
cout<<"[ ";
for ( int p = 0; p < 3; p++)
{
cout<<CV_MAT_ELEM(*intrinsics,float,o,p)<<" ";
}
cout<<"]"<<endl;
}
float dist[4] = {0.0};
float tranv[3] = {0.0};
float rotv[3] = {0.0};
for ( int i = 0; i < 3; i++)
{
dist[i] = ((float*)(distortion_coeff->data.ptr))[i];
tranv[i] = ((float*)(translation_vectors->data.ptr))[i];
rotv[i] = ((float*)(rotation_vectors->data.ptr))[i];
}
dist[3] = ((float*)(distortion_coeff->data.ptr))[3];
//外参数中的R就是旋转矩阵,t就是平移向量/
cout<<"----------------------------------------- "<<endl;
cout<<"畸变系数: \n";//畸变系数
cout<<"[ "<< dist[0]<<" "<< dist[1]<<" "<< dist[2]<<" "<< dist[3]<<" ]"<<endl;
cout<<"----------------------------------------- "<<endl;
cout<<"旋转向量: \n";//旋转向量
cout<<"[ "<<rotv[0]<<" "<< rotv[1]<<" "<< rotv[2]<<" ]"<<endl;
//由旋转向量计算旋转矩阵
cvInitMatHeader(&pr_vec,1,3,CV_64FC1,rotv,CV_AUTOSTEP);
cvInitMatHeader(&pR_matrix,3,3,CV_64FC1,R_matrix,CV_AUTOSTEP);
cvRodrigues2(&pr_vec, &pR_matrix,0);
cout<<"----------------------------------------- "<<endl;
cout<<"旋转矩阵: \n";//旋转矩阵
for(int i=1; i<=9; i++)
{
cout<<R_matrix[i]<<" ";
if(i%3==0)cout<<endl;
}
cout<<"----------------------------------------- "<<endl;
cout<<"平移向量: \n";//平移向量
cout<<"[ "<<tranv[0]<<" "<< tranv[1]<<" "<< tranv[2]<<" ]"<<endl;
cout<<"----------------------------------------- "<<endl;
cvReleaseMat(&intrinsics);
cvReleaseMat(&distortion_coeff);
cvReleaseMat(&rotation_vectors);
cvReleaseMat(&translation_vectors);
cvReleaseMat(&point_counts);
cvReleaseMat(&object_points);
cvReleaseMat(&image_points);
cvDestroyAllWindows();
return 0;
}
void InitCorners3D(CvMat *Corners3D, CvSize ChessBoardSize, int NImages, float SquareSize)
{//得到定标点三维坐标矩阵
int NPoints = ChessBoardSize.height*ChessBoardSize.width;
float * temppoints = new float[NImages*NPoints*3];//所有角点的坐标存在一个一维数组
// 行扫描
for (int CurrentImage = 0 ; CurrentImage < NImages ; CurrentImage++)
{
for (int CurrentRow = 0; CurrentRow < ChessBoardSize.height; CurrentRow++)
{//行
for (int CurrentColumn = 0; CurrentColumn < ChessBoardSize.width; CurrentColumn++)
{//列
temppoints[(CurrentImage*NPoints*3)+(CurrentRow*ChessBoardSize.width + CurrentColumn)*3]=(float)CurrentRow*SquareSize;//x
temppoints[(CurrentImage*NPoints*3)+(CurrentRow*ChessBoardSize.width + CurrentColumn)*3+1]=(float)CurrentColumn*SquareSize;//y
temppoints[(CurrentImage*NPoints*3)+(CurrentRow*ChessBoardSize.width + CurrentColumn)*3+2]=0.f;//z
}
}
}
(*Corners3D) = cvMat(NImages*NPoints,3,CV_32FC1, temppoints);//这里理解起来可能比较晦涩,
//具体可以查看type_c.h中的CV_INLINE CvMat cvMat( int rows, int cols, int type, void* data CV_DEFAULT(NULL))
}