一. 标定原理
摄像机在成像的过程中会在边缘产生显著的畸变,对于普通的摄像机拍摄的图像,会产生径向畸变,切向畸变和其他类型的畸变,我们需要对图像进行矫正处理。使用opencv标定图像的过程,先根据一个标定物体计算旋转矩阵和平移向量,寻找标定物的角点cvFindchessboardCorners;再精确角点的位置(也叫亚像素角点)cvfindCornerSubPix;绘制出所有的标定物角点cvDrawChessboardCorners ;使用标定函数计算摄像机的内参数矩阵和畸变系数,旋转变量和平移变量 cvCalibrateCamera2;根据内参数矩阵和畸变系数求出畸变映射cvInitUndistortMap ,再使用重映射转换图片 cvRemap ,也可以使用cvUndistort2完成所有的事项。
二. 函数简介
1. cvFindchessboardCorners 寻找棋盘上的角点位置
2. cvfindCornerSubPix 精确角点位置
3. cvDrawChessboardCorners 绘制检测到的棋盘角点
4. cvCalibrateCamera2 利用定标来计算摄像机的内参数和外参数
5. cvfindExtrinsicCameraParam2 只计算外参数
6. cvRodrigues2 进行旋转矩阵和旋转向量间的转换
7. cvUndistort2 校正图像因相机镜头引起的变形
8. cvInitUndistortMap 计算畸变映射
9. cvRemap 重映射
<寻找棋盘上的角点位置>
函数cvFindChessboardCorners试图确定输入图像是否是棋盘模式,并确定角点的位置。如果所有角点都被检测到切它们都被以一定顺序排布(一行一行地,每行从左到右),函数返回非零值,否则在函数不能发现所有角点或者记录它们地情况下,函数返回0。例如一个正常地棋盘图右8x8个方块和7x7个内角点,内角点是黑色方块相互联通地位置。这个函数检测到地坐标只是一个大约地值,如果要精确地确定它们的位置,可以使用函数cvFindCornerSubPix。
int cvFindChessboardCorners(const void* image, CvSize patternSize, CvPoint2D32f* corners, int* cornerCount=NULL, int flags=CV_CALIB_CB_ADAPTIVE_THRESH )
- image 输入的棋盘图,必须是8位的灰度或者彩色图像。
- pattern_size 棋盘图中每行和每列角点的个数。
- corners 检测到的角点
- corner_count 输出,角点的个数。如果不是NULL,函数将检测到的角点的个数存储于此变量。
- flags 各种操作标志,可以是0或者下面值的组合:
CV_CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图,而不是一个固定的阈值。
CV_CALIB_CB_NORMALIZE_IMAGE - 在利用固定阈值或者自适应的阈值进行二值化之前,先使用cvNormalizeHist来均衡化图像亮度。
CV_CALIB_CB_FILTER_QUADS - 使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。
<精确角点位置>
函数 cvFindCornerSubPix 通过迭代来发现具有子象素精度的角点位置
void cvFindCornerSubPix(const CvArr* image, CvPoint2D32f* corners, int count, CvSize winSize, CvSize zeroZone, CvTermCriteria criteria)
- 第一个参数 image 输入图像.
- 第二个参数 corners 输入角点的初始坐标,也存储精确的输出坐标
- 第三个参数 count 角点数目
- 第四个参数 win 搜索窗口的一半尺寸。如果 win=(5,5) 那么使用 5*2+1 × 5*2+1 = 11 × 11 大小的搜索窗口
- 第五个参数 zero_zone 死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现的某些可能的奇异性。当值为 (-1,-1) 表示没有死区。
- 第六个参数 criteria 求角点的迭代过程的终止条件。即角点位置的确定,要么迭代数大于某个设定值,或者是精确度达到某个设定值。 criteria 可以是最大迭代数目,或者是设定的精确度,也可以是它们的组合。
<绘制检测到的棋盘角点>
函数cvDrawChessboardCorners以红色圆圈绘制检测到的棋盘角点;如果整个棋盘都检测到,则用直线连接所有的角点。
void cvDrawChessboardCorners(CvArr* image, CvSize patternSize, CvPoint2D32f* corners, int count, int patternWasFound)
- 第一个参数 image 结果图像,必须是8位彩色图像。
- 第二个参数 pattern_size 每行和每列地内角点数目。
- 第三个参数 corners 检测到地角点数组。
- 第四个参数 count 角点数目。
- 第五个参数 pattern_was_found 指示完整地棋盘被发现(≠0)还是没有发现(=0)。可以传输cvFindChessboardCorners函数的返回值。
<利用定标来计算摄像机的内参数和外参数 >
函数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 定标点的世界坐标,为3xN或者Nx3的矩阵,这里N是所有视图中点的总数。
- 第二个参数 image_points 定标点的图像坐标,为2xN或者Nx2的矩阵,这里N是所有视图中点的总数。
- 第三个参数 point_counts 向量,指定不同视图里点的数目,1xM或者Mx1向量,M是视图数目。
- 第四个参数 image_size 图像大小,只用在初始化内参数时。
- 第五个参数 intrinsic_matrix 输出内参矩阵(A) ,如果指定CV_CALIB_USE_INTRINSIC_GUESS和(或)CV_CALIB_FIX_ASPECT_RATION,fx、 fy、 cx和cy部分或者全部必须被初始化。
⎡⎣⎢fx000fy0cxcy1⎤⎦⎥ - 第六个参数 distortion_coeffs 输出大小为4x1或者1x4的向量,里面为形变参数[k1, k2, p1, p2]。
- 第七个参数 rotation_vectors 输出大小为3xM或者Mx3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数cvRodrigues2)
- 第八个参数 translation_vectors 输出大小为3xM或Mx3的矩阵,里面为平移向量。
- 第九个参数 flags 不同的标志,可以是0,或者下面值的组合:
- CV_CALIB_USE_INTRINSIC_GUESS - 内参数矩阵包含fx,fy,cx和cy的初始值。否则,(cx, cy)被初始化到图像中心(这儿用到图像大小),焦距用最小平方差方式计算得到。注意,如果内部参数已知,没有必要使用这个函数,使用cvFindExtrinsicCameraParams2则可。
- CV_CALIB_FIX_PRINCIPAL_POINT - 主点在全局优化过程中不变,一直在中心位置或者在其他指定的位置(当CV_CALIB_USE_INTRINSIC_GUESS设置的时候)。
- CV_CALIB_FIX_ASPECT_RATIO - 优化过程中认为fx和fy中只有一个独立变量,保持比例fx/fy不变,fx/fy的值跟内参数矩阵初始化时的值一样。在这种情况下, (fx, fy)的实际初始值或者从输入内存矩阵中读取(当CV_CALIB_USE_INTRINSIC_GUESS被指定时),或者采用估计值(后者情况中fx和fy可能被设置为任意值,只有比值被使用)。
- CV_CALIB_ZERO_TANGENT_DIST – 切向形变参数(p1, p2)被设置为0,其值在优化过程中保持为0。
<只计算外参数>
函数cvFindExtrinsicCameraParams2使用已知的内参数和某个视图的外参数来估计相机的外参数。3维物体上的点坐标和相应的2维投影必须被指定。这个函数也可以用来最小化反投影误差。
void cvFindExtrinsicCameraParams2( const CvMat* object_points,const CvMat* image_points,const CvMat* camera_matrix,const CvMat* distortion_coeffs,CvMat* rotation_vector, CvMat* translation_vector,int use_extrinsic_guess CV_DEFAULT(0) );
- 第一个参数 object_points 定标点的坐标,为3xN或者Nx3的矩阵,这里N是视图中的个数。
- 第二个参数 image_points 定标点在图像内的坐标,为2xN或者Nx2的矩阵,这里N是视图中的个数。
- 第三个参数 intrinsic_matrix 内参矩阵(A) 。
⎡⎣⎢fx000fy0cxcy1⎤⎦⎥ - 第四个参数 distortion_coeffs 大小为4x1或者1x4的向量,里面为形变参数[k1,k2,p1,p2]。如果是NULL,所有的形变系数都为0。
- 第五个参数 rotation_vector 输出大小为3x1或者1x3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数cvRodrigues2)。
- 第六个参数 translation_vector 大小为3x1或1x3的矩阵,里面为平移向量。
<进行旋转矩阵和旋转向量间的转换 >
函数转换旋转向量到旋转矩阵,或者相反。旋转向量是旋转矩阵的紧凑表示形式。旋转向量的方向是旋转轴,向量的长度是围绕旋转轴的旋转角。
int cvRodrigues2( const CvMat* src, CvMat* dst, CvMat* jacobian=0 );
- 第一个参数 src 输入的旋转向量(3x1或者1x3)或者旋转矩阵(3x3)。
- 第二个参数 dst 输出的旋转矩阵(3x3)或者旋转向量(3x1或者1x3)
- 第三个参数 jacobian 可选的输出雅可比矩阵(3x9或者9x3),关于输入部分的输出数组的偏导数。
<校正图像因相机镜头引起的变形>
函数cvUndistort2对图像进行变换来抵消径向和切向镜头变形。相机参数和变形参数可以通过函数cvCalibrateCamera2取得。使用本节开始时提到的公式,对每个输出图像像素计算其在输入图像中的位置,然后输出图像的像素值通过双线性插值来计算。如果图像得分辨率跟定标时用得图像分辨率不一样,fx、fy、cx和cy需要相应调整,因为形变并没有变化。
void cvUndistort2(const CvArr* src, CvArr* dst, const CvMat* cameraMatrix, const CvMat* distCoeffs, const CvMat* newCameraMatrix=NULL )
- 第提个参数 src 原始图像(已经变形的图像)。
- 第二个参数 dst 结果图像(已经校正的图像)。
- 第三个参数 intrinsic_matrix 相机内参数矩阵,格式为
⎡⎣⎢fx000fy0cxcy1⎤⎦⎥ - 第四个参数 distortion_coeffs 四个变形系数组成的向量,大小为4x1或者1x4,格式为[k1,k2,p1,p2]。
<计算畸变映射>
函数cvInitUndistortMap预先计算非形变对应-正确图像的每个像素在形变图像里的坐标。这个对应可以传递给cvRemap函数(跟输入和输出图像一起)。
void cvInitUndistortMap(const CvMat* cameraMatrix, const CvMat* distCoeffs, CvArr* map1, CvArr* map2)
- 第一个参数 intrinsic_matrix 摄像机内参数矩阵(A) [fx 0 cx; 0 fy cy; 0 0 1].
- 第二个参数 distortion_coeffs 形变系数向量[k1, k2, p1, p2],大小为4x1或者1x4。
- 第三个参数 mapx x坐标的对应矩阵。
- 第四个参数 mapy y坐标的对应矩阵。
<对图像进行普通几何变换 (重映射)>
函数 cvRemap 利用下面指定的矩阵变换输入图像:
dst(x,y)<-src(mapx(x,y),mapy(x,y))
与其它几何变换类似,可以使用一些插值方法(由用户指定,译者注:同cvResize)来计算非整数坐标的像素值。
cvRemap(const CvArr* src, CvArr* dst, const CvArr* mapx, const CvArr* mapy, int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0) )
- 第一个参数 src 输入图像.
- 第二个参数 dst 输出图像.
- 第三个参数 mapx x坐标的映射 (32fC1 image).
- 第四个参数 mapy y坐标的映射 (32fC1 image).
- 第五个参数 flags 插值方法和以下开关选项的组合:
- 第六个参数 CV_WARP_FILL_OUTLIERS - 填充边界外的像素. 如果输出图像的部分象素落在变换后的边界外,那么它们的值设定为 fillval。
- 第七个参数 fillval 用来填充边界外面的值.
三. 标定程序代码
下图为本程序在vs中的配置,三个命令参数对应main中的argv【1】,argv【2】,argv【3】
#include <cv.h>
#include <highgui.h>
#include <stdio.h>
#include <stdlib.h>
int n_boards = 0; //Will be set by input list
const int board_dt = 10;
int board_w;
int board_h;
void help(){
printf("Calibration from disk. Call convention:\n\n"
" ch11_ex11_1_fromdisk board_w board_h image_list\n\n"
"Where: board_{w,h} are the # of internal corners in the checkerboard\n"
" width (board_w) and height (board_h)\n"
" image_list is space separated list of path/filename of checkerboard\n"
" images\n\n"
"Hit 'p' to pause/unpause, ESC to quit. After calibration, press any other key to step through the images\n\n");
}
int main(int argc, char* argv[]) {
CvCapture* capture;// = cvCreateCameraCapture( 0 );
// assert( capture );
if(argc != 4){
help();
return -1;
}
help();
board_w = atoi(argv[1]);
board_h = atoi(argv[2]);
int board_n = board_w * board_h;
CvSize board_sz = cvSize( board_w, board_h );
FILE *fptr = fopen(argv[3],"r");
char names[2048];
//COUNT THE NUMBER OF IMAGES:
while(fscanf(fptr,"%s",names)==1){
n_boards++;
}
rewind(fptr);
cvNamedWindow( "Calibration" );
//ALLOCATE STORAGE
CvMat* image_points = cvCreateMat(n_boards*board_n,2,CV_32FC1);
CvMat* object_points = cvCreateMat(n_boards*board_n,3,CV_32FC1);
CvMat* point_counts = cvCreateMat(n_boards,1,CV_32SC1);
/// CvMat * image_points = cvCreateMat(1, n_boards*board_n, CV_32FC2);
/// CvMat * object_points = cvCreateMat(1, n_boards*board_n, CV_32FC3);
/// CvMat * point_counts = cvCreateMat(1, n_boards, CV_32SC1);
CvMat* intrinsic_matrix = cvCreateMat(3,3,CV_32FC1);
CvMat* distortion_coeffs = cvCreateMat(4,1,CV_32FC1);
IplImage* image = 0;// = cvQueryFrame( capture );
IplImage* gray_image = 0; //for subpixel
CvPoint2D32f* corners = new CvPoint2D32f[ board_n ];
int corner_count;
int successes = 0;
int step;
for( int frame=0; frame<n_boards; frame++ ) {
fscanf(fptr,"%s ",names);
if(image){
cvReleaseImage(&image);
image = 0;
}
image = cvLoadImage( names);
if(gray_image == 0 && image) //We'll need this for subpixel accurate stuff
gray_image = cvCreateImage(cvGetSize(image),8,1);
if(!image)
printf("null image\n");
int found = cvFindChessboardCorners(
image,
board_sz,
corners,
&corner_count,
CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FILTER_QUADS
);
//Get Subpixel accuracy on those corners
cvCvtColor(image, gray_image, CV_BGR2GRAY);
cvFindCornerSubPix(gray_image, corners, corner_count,
cvSize(11,11),cvSize(-1,-1), cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));
//Draw it
cvDrawChessboardCorners(image, board_sz, corners, corner_count, found);
cvShowImage( "Calibration", image );
// If we got a good board, add it to our data
//
if( corner_count == board_n ) {
step = successes*board_n;
printf("Found = %d for %s\n",found,names);
for( int i=step, j=0; j<board_n; ++i,++j ) {
/// CV_MAT_ELEM(*image_points, CvPoint2D32f,0,i) = cvPoint2D32f(corners[j].x,corners[j].y);
/// CV_MAT_ELEM(*object_points,CvPoint3D32f,0,i) = cvPoint3D32f(j/board_w, j%board_w, 0.0f);
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;
}
// CV_MAT_ELEM(*point_counts, int,0,successes) = board_n;
CV_MAT_ELEM(*point_counts, int,frame,0) = board_n;
successes++;
}else
{
printf("Found = %d for %s ",found,names);
CV_MAT_ELEM(*point_counts, int,frame,0) = board_n;
printf("%d counts be found!\n", corner_count);
}
// if( successes == n_boards ) break;
int c = cvWaitKey(15);
if(c == 'p') {
c = 0;
while(c != 'p' && c != 27){
c = cvWaitKey(250);
}
}
if(c == 27)
return 0;
}
printf("successes = %d, n_boards=%d\n",successes,n_boards);
//ALLOCATE MATRICES ACCORDING TO HOW MANY IMAGES WE FOUND CHESSBOARDS ON
/// CvMat* image_points2 = cvCreateMat(1,successes*board_n,CV_32FC2);
/// CvMat* object_points2 = cvCreateMat(1,successes*board_n,CV_32FC3);
/// CvMat* point_counts2 = cvCreateMat(1,successes,CV_32SC1);
CvMat* object_points2 = cvCreateMat(successes*board_n,3,CV_32FC1);
CvMat* image_points2 = cvCreateMat(successes*board_n,2,CV_32FC1);
CvMat* point_counts2 = cvCreateMat(successes,1,CV_32SC1);
//TRANSFER THE POINTS INTO THE CORRECT SIZE MATRICES
for(int i = 0; i<successes*board_n; ++i){
/// CV_MAT_ELEM(*image_points2, CvPoint2D32f,0,i) = CV_MAT_ELEM(*image_points, CvPoint2D32f,0,i);
/// CV_MAT_ELEM(*object_points2,CvPoint3D32f,0,i) = CV_MAT_ELEM(*object_points,CvPoint3D32f,0,i);
CV_MAT_ELEM(*image_points2, float,i,0) = CV_MAT_ELEM(*image_points, float,i,0);
CV_MAT_ELEM(*image_points2, float,i,1) = CV_MAT_ELEM(*image_points, float,i,1);
CV_MAT_ELEM(*object_points2,float,i,0) = CV_MAT_ELEM(*object_points,float,i,0) ;
CV_MAT_ELEM(*object_points2,float,i,1) = CV_MAT_ELEM(*object_points,float,i,1) ;
CV_MAT_ELEM(*object_points2,float,i,2) = CV_MAT_ELEM(*object_points,float,i,2) ;
}
for(int i=0; i<successes; ++i){
/// CV_MAT_ELEM(*point_counts2,int,0, i) = CV_MAT_ELEM(*point_counts, int,0,i);
CV_MAT_ELEM(*point_counts2,int,i, 0) = CV_MAT_ELEM(*point_counts, int,i,0);
}
// cvReleaseMat(&object_points);
// cvReleaseMat(&image_points);
// cvReleaseMat(&point_counts);
// cvWaitKey();//Now we have to reallocate the matrices
// return 0;
// At this point we have all of the chessboard corners we need.
//
// Initialize the intrinsic matrix such that the two focal
// lengths have a ratio of 1.0
//
CV_MAT_ELEM( *intrinsic_matrix, float, 0, 0 ) = 1.0f;
CV_MAT_ELEM( *intrinsic_matrix, float, 1, 1 ) = 1.0f;
//printf("cvCalibrateCamera2\n");
cvCalibrateCamera2(
object_points2,
image_points2,
point_counts2,
cvGetSize( image ),
intrinsic_matrix,
distortion_coeffs,
NULL,
NULL,
0//CV_CALIB_FIX_ASPECT_RATIO
);
// Save our work
cvSave("Intrinsics.xml",intrinsic_matrix);
cvSave("Distortion.xml",distortion_coeffs);
// Load test
CvMat *intrinsic = (CvMat*)cvLoad("Intrinsics.xml");
CvMat *distortion = (CvMat*)cvLoad("Distortion.xml");
// Build the undistort map which we will use for all
// subsequent frames.
//
IplImage* mapx = cvCreateImage( cvGetSize(image), IPL_DEPTH_32F, 1 );
IplImage* mapy = cvCreateImage( cvGetSize(image), IPL_DEPTH_32F, 1 );
// printf("cvInitUndistortMap\n");
cvInitUndistortMap(
intrinsic,
distortion,
mapx,
mapy
);
// Just run the camera to the screen, now only showing the undistorted
// image.
//
rewind(fptr);
cvNamedWindow( "Undistort" );
printf("\n\nPress any key to step through the images, ESC to quit\n\n");
while(fscanf(fptr,"%s ",names)==1){
if(image){
cvReleaseImage(&image);
image = 0;
}
image = cvLoadImage( names);
IplImage *t = cvCloneImage(image);
IplImage *t2 = cvCreateImage(cvSize(t->width/2, t->height/2),t->depth, t->nChannels);
IplImage *t3 = cvCreateImage(cvSize(t->width/2, t->height/2),t->depth, t->nChannels);
cvResize(image, t2);
cvShowImage( "Calibration", t2 );
cvRemap( t, image, mapx, mapy );
cvReleaseImage(&t);
// cvUndistort2(
cvResize(image, t3);
cvShowImage("Undistort", t3);
if((cvWaitKey()&0x7F) == 27) break;
cvReleaseImage(&t2);
cvReleaseImage(&t3);
}
return 0;
}
来源:CSDN
作者:我不是popy
链接:https://blog.csdn.net/u014773418/article/details/50528575