张正友算法的原理:

这里假定模板平面在世界坐标系Z=0的平面上:

opencv 相机低画质无法标定 opencv相机标定步骤_张正友

如下图,其中,K为摄像机的内参数矩阵,[X Y 1]T为模板平面上点的齐次坐标,[u v 1]T为模板平面上点投影到图象平面上对应点的齐次坐标,[r1 r2 r3]和t 分别是摄像机坐标系相对于世界坐标系的旋转矩阵和平移向量。

opencv 相机低画质无法标定 opencv相机标定步骤_opencv 相机低画质无法标定_02

opencv 相机低画质无法标定 opencv相机标定步骤_摄像头标定_03

根据旋转矩阵的性质,即r1Tr2=0和||r1||=||r2||=1,每幅图象可以获得以下两个对内参数矩阵的基本约束

opencv 相机低画质无法标定 opencv相机标定步骤_摄像头_04

由于摄像机有5个未知内参数,所以当所摄取得的图像数目大于等于3时,就可以线性唯一求解出K。

 

OK,开始动手:

1.首先需要准备一张标定板(在一张A4纸上打印一幅黑白相间的类似国际象棋的棋盘图):注意:打印的应该是平的,不能用下图,下图只是告诉你长什么样。具体图片网上很easy就可以下载到。

opencv 相机低画质无法标定 opencv相机标定步骤_摄像头标定_05

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))
}