计算机双目视觉----摄像机单目标定

一、摄像机单目标定
OpenCV对标定的处理是这样的:
1.打印一幅棋盘图贴到一个平板上,转动该模板,用摄像机拍摄20张(一般多于6张即可,多了结果可以更精确)图片
2.对于每一张图片都用cvFindChessboardCorners提取角点坐标,这个函数提取的仅是一个粗略坐标,然后调用cvFindCornerSubPix()来获取精确角点坐标。提出来后可以用cvDrawChessboardCorners画到图片上。
有几张图片,这个过程就重复多少遍。最终提取的20张图片角点坐标全存到N*2维矩阵指针image_points中。
3.初始化定标点的三维坐标,也是20张图片上的角点坐标全赋值。可以认为每张图上对应点的坐标是一样的(Z坐标为0)。角点的三维坐标都存到N*3维矩阵指针object_points中。
4.调用cvCalibrateCamera2求摄像机的内外参数矩阵。对于外参数,该函数实际得到的是N*3维的旋转矢量和N*3维的平移矢量,而不是矩阵。N行的矢量,第i行就对应着第i张图片的外参数。如果想得到3*3的矩阵形式,需要先把第i行的值取出来,再调用函数cvRodrigues2进行转换。
虽然每张图片的物体坐标我们假设是一样的,但是投影坐标坐标不同,所以获得的外参数是不一样的。个人理解,几张图片的参考点是不一样的。都是以模板物理位置为参考点,得到的外参数是摄像机相对于该位置的旋转和平移。
标定效果(7*7):










 使用的是VS+opencv2.4.5
#include "cv.h"
#include "highgui.h"
#include <string>
#include <iostream>
#include<stdio.h>


using namespace std;


int main()
{
    CvCapture* capture; //摄像头指针
    capture = cvCaptureFromAVI("1.avi");
    IplImage* frame; //图像指针
    cvNamedWindow("摄像机帧截取窗口",1);
    printf("按“C”键截取当前帧并保存为标定图片...\n按“Q”键退出截取帧过程...\n\n");
    int number_image=1; //文件名后的编号,从1开始,也是截取的图像帧数
    char filename[20]=""; //保存文件名的字符串数组
    while(true)
    {
        frame=cvQueryFrame(capture);
        if(!frame)
            break;


        cvShowImage("摄像机帧截取窗口",frame);


        if(cvWaitKey(2000)=='c')
        {
            sprintf_s (filename,"%d.jpg",number_image);
            cvSaveImage(filename,frame);
            cout<<"成功获取当前帧,并以文件名"<<filename<<"保存...\n\n";
            printf("按“C”键截取当前帧并保存为标定图片...\n按“Q”键退出截取帧过程...\n\n");
            number_image++;
        }
        else if(cvWaitKey(2000)=='q')
        {
            printf("截取图像帧过程完成...\n\n");
            cout<<"共成功截取"<<--number_image<<"帧图像!!\n\n";
            break;
        }
    }
    cvReleaseImage(&frame);
    cvDestroyWindow("摄像机帧截取窗口");


    //puts("afsd");


    IplImage * show; //RePlay图像指针
    cvNamedWindow("RePlay",1);
    int number_image_copy=number_image; //复制图像帧数
    CvSize board_size=cvSize(7,7); //标定板角点数
    CvSize2D32f square_size=cvSize2D32f(18.2,18.2); //假设我的每个标定方格长宽都是1.82厘米
    float square_length=square_size.width; //方格长度
    float square_height=square_size.height; //方格高度
    int board_width=board_size.width; //每行角点数
    int board_height=board_size.height; //每列角点数
    int total_per_image=board_width*board_height; //每张图片角点总数
    int count; //存储每帧图像中实际识别的角点数
    int found; //识别标定板角点的标志位
    int step; //存储步长,step=successes*total_per_image;
    int successes=0; //存储成功找到标定板上所有角点的图像帧数
    int a=1; //临时变量,表示在操作第a帧图像


    CvPoint2D32f * image_points_buf = new CvPoint2D32f[total_per_image]; //存储角点图像坐标的数组
    CvMat * image_points=cvCreateMat(number_image*total_per_image,2,CV_32FC1); //存储角点的图像坐标的矩阵
    CvMat * object_points=cvCreateMat(number_image*total_per_image,3,CV_32FC1); //存储角点的三维坐标的矩阵
    CvMat * point_counts=cvCreateMat(number_image,1,CV_32SC1); //存储每帧图像的识别的角点数
    CvMat * intrinsic_matrix=cvCreateMat(3,3,CV_32FC1); //内参数矩阵
    CvMat * distortion_coeffs=cvCreateMat(5,1,CV_32FC1); //畸变系数


    number_image_copy=6;
    while(a<=number_image_copy)
    {
        sprintf_s (filename,"%d.jpg",a);
        show=cvLoadImage(filename,-1);
        found=cvFindChessboardCorners(show,board_size,image_points_buf,&count,
            CV_CALIB_CB_ADAPTIVE_THRESH|CV_CALIB_CB_FILTER_QUADS);
        if(found==0)
        { //如果没找到标定板角点时
            cout<<"第"<<a<<"帧图片无法找到棋盘格所有角点!\n\n";
            cvNamedWindow("RePlay",1);
            cvShowImage("RePlay",show);
            cvWaitKey(1000);


        }
        else
        { //找到标定板角点时
            cout<<"第"<<a<<"帧图像成功获得"<<count<<"个角点...\n";
            cvNamedWindow("RePlay",1);
            IplImage * gray_image= cvCreateImage(cvGetSize(show),8,1);
            cvCvtColor(show,gray_image,CV_BGR2GRAY);
            cout<<"获取源图像灰度图过程完成...\n";


            cvFindCornerSubPix(gray_image,image_points_buf,count,cvSize(11,11),cvSize(-1,-1),
                cvTermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));
            cout<<"灰度图亚像素化过程完成...\n";


            cvDrawChessboardCorners(show,board_size,image_points_buf,count,found);
            cout<<"在源图像上绘制角点过程完成...\n\n";


            cvShowImage("RePlay",show);
            cvWaitKey(1000);
        }


        if(total_per_image==count)
        {
            step=successes*total_per_image; //计算存储相应坐标数据的步长
            for(int i=step,j=0;j<total_per_image;++i,++j)
            {
                CV_MAT_ELEM(*image_points,float,i,0)=image_points_buf[j].x;
                CV_MAT_ELEM(*image_points,float,i,1)=image_points_buf[j].y;
                CV_MAT_ELEM(*object_points,float,i,0)=(float)((j/board_width)*square_length);
                CV_MAT_ELEM(*object_points,float,i,1)=(float)((j%board_width)*square_height);
                CV_MAT_ELEM(*object_points,float,i,2)=0.0f;
            }
            CV_MAT_ELEM(*point_counts,int,successes,0)=total_per_image;
            successes++;
        }
        a++;
    }


    cvReleaseImage(&show);
    cvDestroyWindow("RePlay");


    CvCapture* capture1;
    capture1=cvCaptureFromAVI("1.avi");
    IplImage * show_colie;
    show_colie=cvQueryFrame(capture1);




    CvMat * object_points2=cvCreateMat(successes*total_per_image,3,CV_32FC1);
    CvMat * image_points2=cvCreateMat(successes*total_per_image,2,CV_32FC1);
    CvMat * point_counts2=cvCreateMat(successes,1,CV_32SC1);




    for(int i=0;i<successes*total_per_image;++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,i,0)=CV_MAT_ELEM(*point_counts,int,i,0);
    }




    cvReleaseMat(&object_points);
    cvReleaseMat(&image_points);
    cvReleaseMat(&point_counts);




    CV_MAT_ELEM(*intrinsic_matrix,float,0,0)=1.0f;
    CV_MAT_ELEM(*intrinsic_matrix,float,1,1)=1.0f;




    cvCalibrateCamera2(object_points2,image_points2,point_counts2,cvGetSize(show_colie),
        intrinsic_matrix,distortion_coeffs,NULL,NULL,0);


    cvSave("Intrinsics.xml",intrinsic_matrix);
    cvSave("Distortion.xml",distortion_coeffs);


    cout<<"摄像机矩阵、畸变系数向量已经分别存储在名为Intrinsics.xml、Distortion.xml文档中\n\n";


    CvMat * intrinsic=(CvMat *)cvLoad("Intrinsics.xml");
    CvMat * distortion=(CvMat *)cvLoad("Distortion.xml");


    IplImage * mapx=cvCreateImage(cvGetSize(show_colie),IPL_DEPTH_32F,1);
    IplImage * mapy=cvCreateImage(cvGetSize(show_colie),IPL_DEPTH_32F,1);


    cvInitUndistortMap(intrinsic,distortion,mapx,mapy);


    cvNamedWindow("原始图像",1);
    cvNamedWindow("非畸变图像",1);


    cout<<"按‘E’键退出显示...\n\n";


    while(show_colie)
    {
        puts("212552");
        IplImage * clone=cvCloneImage(show_colie);
        cvShowImage("原始图像",show_colie);
        cvRemap(clone,show_colie,mapx,mapy);
        cvReleaseImage(&clone);
        cvShowImage("非畸变图像",show_colie);


        if(cvWaitKey(2000)=='e')
        {
            break;
        }


        show_colie=cvQueryFrame(capture1);
    }
    cvWaitKey(0);
    return 0;


}
在解释几个关键的函数:
FindChessboardCorners 寻找棋盘图的内角点位置


 


int cvFindChessboardCorners( const void*image, CvSize pattern_size,


CvPoint2D32f* corners,int* corner_count=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 - 使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。


函数cvFindChessboardCorners试图确定输入图像是否是棋盘模式,并确定角点的位置。如果所有角点都被检测到切它们都被以一定顺序排布(一行一行地,每行从左到右),函数返回非零值,否则在函数不能发现所有角点或者记录它们地情况下,函数返回0。例如一个正常地棋盘图右8x8个方块和7x7个内角点,内角点是黑色方块相互联通地位置。这个函数检测到地坐标只是一个大约地值,如果要精确地确定它们的位置,可以使用函数cvFindCornerSubPix。


 


 




FindCornerSubPix 寻找棋盘图的内角点位置


精确角点位置


void cvFindCornerSubPix( const CvArr* image, CvPoint2D32f* corners,


                        int count, CvSize win, CvSize zero_zone,


                         CvTermCriteria criteria );


image


输入图像.


corners


输入角点的初始坐标,也存储精确的输出坐标


count


角点数目


win


搜索窗口的一半尺寸。如果win=(5,5) 那么使用 5*2+1 × 5*2+1 = 11 × 11 大小的搜索窗口


zero_zone


死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现的某些可能的奇异性。当值为 (-1,-1) 表示没有死区。


criteria


求角点的迭代过程的终止条件。即角点位置的确定,要么迭代数大于某个设定值,或者是精确度达到某个设定值。 criteria 可以是最大迭代数目,或者是设定的精确度,也可以是它们的组合。


函数cvFindCornerSubPix通过迭代来发现具有子象素精度的角点位置,或如图所示的放射鞍点(radial saddle points)。


子象素级角点定位的实现是基于对向量正交性的观测而实现的,即从中央点q到其邻域点p 的向量和p点处的图像梯度正交(服从图像和测量噪声)。考虑以下的表达式:


εi=DIpiT·(q-pi)


其中,DIpi表示在q的一个邻域点pi处的图像梯度,q的值通过最小化εi得到。通过将εi设为0,可以建立系统方程如下:


sumi(DIpi·DIpiT)·q - sumi(DIpi·DIpiT·pi) = 0


其中q的邻域(搜索窗)中的梯度被累加。调用第一个梯度参数G和第二个梯度参数b,得到:


q=G-1·b


该算法将搜索窗的中心设为新的中心q,然后迭代,直到找到低于某个阈值点的中心位置。


 




DrawChessBoardCorners 绘制检测到的棋盘角点


 


void cvDrawChessboardCorners( CvArr* image,CvSize pattern_size,CvPoint2D32f*corners,


int count,int pattern_was_found );


 


image


结果图像,必须是8位彩色图像。


pattern_size


每行和每列地内角点数目。


corners


检测到地角点数组。


count


角点数目。


pattern_was_found


指示完整地棋盘被发现(≠0)还是没有发现(=0)。可以传输cvFindChessboardCorners函数的返回值。


当棋盘没有完全检测出时,函数cvDrawChessboardCorners以红色圆圈绘制检测到的棋盘角点;如果整个棋盘都检测到,则用直线连接所有的角点。


 




CalibrateCamera2利用定标来计算摄像机的内参数和外参数


void cvCalibrateCamera2 ( const CvMat* object_points,


               constCvMat* image_points,const CvMat* point_counts,


               CvSizeimage_size, CvMat* intrinsic_matrix,


               CvMat*distortion_coeffs, CvMat* rotation_vectors=NULL,


               CvMat*translation_vectors=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部分或者全部必须被初始化。


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。


函数cvCalibrateCamera2从每个视图中估计相机的内参数和外参数。3维物体上的点和它们对应的在每个视图的2维投影必须被指定。这些可以通过使用一个已知几何形状切具有容易检测的特征点的物体来实现。这样的一个物体被称作定标设备或者定标模式,OpenCV有内建的把棋盘当作定标设备方法(参考cvFindChessboardCorners)。目前,传入初始化的内参数(当CV_CALIB_USE_INTRINSIC_GUESS被设置时)只支持平面定标设备(物体点的Z坐标必须时全0或者全1)。不过3维定标设备依然可以用在提供初始内参数矩阵情况。在内参数和外参数矩阵的初始值都计算出之后,它们会被优化用来减小反投影误差(图像上的实际坐标跟cvProjectPoints2计算出的图像坐标的差的平方和)。


 




cvUndistort2校正图像因相机镜头引起的变形


void cvUndistort2( const CvArr* src, CvArr* dst,


                  const CvMat* intrinsic_matrix,


                  const CvMat* distortion_coeffs );


 


src


原始图像(已经变形的图像)。


dst


结果图像(已经校正的图像)。


intrinsic_matrix


相机内参数矩阵,格式为 。


distortion_coeffs


四个变形系数组成的向量,大小为4x1或者1x4,格式为[k1,k2,p1,p2]。


函数cvUndistort2对图像进行变换来抵消径向和切向镜头变形。相机参数和变形参数可以通过函数cvCalibrateCamera2取得。使用本节开始时提到的公式,对每个输出图像像素计算其在输入图像中的位置,然后输出图像的像素值通过双线性插值来计算。如果图像得分辨率跟定标时用得图像分辨率不一样,fx、fy、cx和cy需要相应调整,因为形变并没有变化。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值