利用二维视野内的图像,求出三维图像在场景中的位姿,这是一个三维透视投影的反向求解问题。常用方法是PNP方法,需要已知三维点集的原始模型。

本文做了大量修改,如有不适,请移步原文:  文章:张正友相机标定&OpenCV实现&程序评价&矫正流程解析

文章:相机标定原理介绍----相机标定---

相机模型

根据光学成像的基本原理,针孔相机在定焦时候有固定的投射关系,这个投射关系是相机参数的大致决定因素。但是对于现实中的相机来说,相机参数会与理想模型有些偏差,涉及到几个因素:第一、相机安装导致光轴不准;第二、使用光学透镜导致成像畸变;第三、若实现变焦功能则整个模型参数都会发生改变;第四、若是双目相机,相机安装导致光轴不平行。

且对于工业制品,相机的焦距长度只能精确到其标称值的4%,只有对焦到无穷远处时相机时焦距长度才是固定的。

所以直接根据投射关系来推算相机参数几乎是不准确的,因此对相机需要参数进行更细致地描述,由此引发了相机标定过程,使用外在的标准几何模型,来直接推算相机的内部参数。

相机标定的基本原理仍然是透视法则即小孔成像原理,标准几何模型一般使用平面点集根据所谓的单应性来推算相机的单应矩阵,进而解算出内参。

相机标定

可惜了,只标定过一次之后便弃之不用,且只是标定过一个相机,所以标定方法将近生疏。

拿来主义就是方便,直接抄,省得想逻辑完备性!看知乎:机器视觉的相机标定到底在标定什么

所以相机标定得到的内参仅仅是对相机物理特性的【近似】,这一点有些人可能一辈子都没办法意识到。

传统相机标定假设相机是小孔成像模型,一般使用两种畸变来模拟镜片的物理畸变。但实际相机的物理特性很可能没办法通过上述假设来得到完全的拟合。所以需要意识到,每一次相机标定仅仅只是对物理相机模型的一次近似,再具体一点来说,每一次标定仅仅是对相机物理模型在采样空间范围内的一次近似。

所以当成像物体所在的空间跟相机标定时的采样空间不一样的时候,你可能永远都没办法得到足够的精度,当你大幅改变相机与成像物体的距离的时候,你最好重新标定相机。如果你想在一个空间里得到更高的精度,你可以在空间里分层多次标定,实际计算的时候通过其他方式得到成像距离,从而选择合适的标定参数。
        传统标定方法需要相机拍摄一个三维标定靶,但仍然估计不出相机畸变。


张正友标定法 

              
       文章:张正友相机标定&OpenCV实现&程序评价&矫正流程解析        
       文章:张正友相机标定的原理与实现               

       1999年,微软研究院的张正友提出了基于移动平面模板的相机标定方法。此方法是介于传统标定方法和自标定方法之间的一种方法,传统标定方法虽然精度高设备有较高的要求,其操作过程也比较繁琐,自标定方法的精度不高,张正友标定算法克服了这两者的缺点同时又兼备二者的优点,因此对办公、家庭的场合使用的桌面视觉系统(DVS)很适合。
       张的文章题目叫做 A Flexible New Technique for Camera Calibration,如果使用高精度的测量仪器制作一个精度极高的参照物,然后把每个点的坐标输入程序进去,这当然是可以的,更经典的标定方法需要制作一个三维的标定物体以避免退化的情况,但是这些没有办法体现flexible。

标定方法使用了立体视觉平面单应性的情景,一般图像标定使用三张图像实现平面就可以,即需要对一个标定平面在一个空间位置进行至少三个角度的采样。

平面单应性应用
      
        使用相机从不同角度观测平面上的点集合,满足了重建的单应性场景。根据单应性的约束,可以求取相机的单应矩阵。用于标定过程,可以求取相机的本质矩阵。

       

约束关系为:

    


平面的使用

        标定方法建议使用标定板,保持标定板的平面性质是重要的。一般方法使用一个工业标准标定板,现阶段合适的方法是使用一个LCD显示器,工业级的显示器一般能保证标定图片的平面性。

标定输出
         2. 外参矩阵:现实世界点P( 世界坐标 )是怎样经过旋转R和平移t,然后落到另一个现实世界点( 摄像机坐标系 )上。
         1. 内参矩阵:点P在2的基础上,是如何继续经过摄像机的镜头、并通过针孔成像透视变换成为像素点(图像坐标系)。

3. 畸变矩阵:上面那个像素点并没有落在理论计算该落在的位置上,还tm产生了一定的偏移和变形!!!

好了,到这里是不是明白了一点?上述3点的每一个转换,都有已经有成熟的数学描述,通过计算,我们完全可以精确地重现现实世界的任意一个点到其数字图像上对应像素点的投影过程。

对于双目视觉系统,通过立体标定还能进一步得到下面的参数:

4.结构参数。告诉你右摄像机是怎样相对于左摄像机经过旋转和平移达到现在的位置。通过结构参数,便能把左右摄像机获取的图像的每一个像素点之间的关系用数学语言定量描述,保证两个相机都处于我们“可求”的状态。

标定流程

标定流程按照这张图里面的原理

文章:张正友相机标定的原理与实现

标定程序的完整流程:

1、准备输入棋盘格图片:对一个标定板在不同位置、不同角度、不同姿态下拍摄,至少三张。一般使用黑白棋盘格。

2、对棋盘格图片,提取角点信息:对棋盘格图像检测角点,使用findChessboardCorners检测函数,专门检测棋盘格内角点。

3、进行再次计算,提取亚像素级角点信息:提高精度,使用亚像素级角点寻找方法。

4、对相机进行标定-使用标定流程:使用启动标定流程,可是直接使用OpenCV的calibrateCamera函数,输出内参外参........................................

5、对标定结果进行评价

6、查看标定结果,利用标定结果对棋盘格图进行矫正

标定的具体流程是:

1)把被取的十个点的世界坐标(齐次坐标)进行转置。

2)对单应性矩阵求解并优化。

3)把六幅图的单应矩阵求解出来后求解出6向量(B矩阵)。因为每个单应矩阵可以得到两个方程,通过循环对矩阵y 赋值后,再对y进行正交分解即可得到6向量。进而得到相机的内参矩阵。

4)先求解出相机的外参,然后对畸变系数进行求解,得到相机坐标(Xc, Yc, Zc)。

5)调用函数对内参和畸变系数进行优化,并显示优化后的结果。然后根据优化后的结果求解外参矩阵。

6)从旋转矩阵中分解出独立变量(三个坐标的转角),再得到平移矩阵,最后把它们和内参、畸变系数一起优化进行最终优化。

OpenCV代码:

    #include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <fstream> using namespace cv;
using namespace std; double coff = 0.0; ifstream fin("calibdata.txt"); /* 标定所用图像文件的路径 */
ofstream fout("caliberation_result.txt"); /* 保存标定结果的文件 */
//读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化
cout << "开始提取角点………………";
int image_count = 0; /* 图像数量 */
Size image_size; /* 图像的尺寸 */
Size board_size = Size(4, 6); /* 标定板上每行、列的角点数 */
std::vector<Point2f> image_points_buf; /* 缓存每幅图像上检测到的角点 */
std::vector<std::vector<Point2f>> image_points_seq; /* 保存检测到的所有角点 */
string filename;
int count = -1;//用于存储角点个数。
while (getline(fin, filename))
{
image_count++;
// 用于观察检验输出
cout << "image_count = " << image_count << endl;
/* 输出检验*/
cout << "-->count = " << count;
cv::Mat imageInput = imread(filename);
if (image_count == 1) //读入第一张图片时获取图像宽高信息
{
image_size.width = imageInput.cols;
image_size.height = imageInput.rows;
cout << "image_size.width = " << image_size.width << endl;
cout << "image_size.height = " << image_size.height << endl;
} /* 提取角点 */
if (0 == findChessboardCorners(imageInput, board_size, image_points_buf))
{
cout << "can not find chessboard corners!\n"; //找不到角点
exit(1);
}
else
{
cv::Mat view_gray;
cvtColor(imageInput, view_gray, CV_RGB2GRAY);
/* 亚像素精确化 */
find4QuadCornerSubpix(view_gray, image_points_buf, Size(5, 5)); //对粗提取的角点进行精确化
//cornerSubPix(view_gray,image_points_buf,Size(5,5),Size(-1,-1),TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));
image_points_seq.push_back(image_points_buf); //保存亚像素角点
/* 在图像上显示角点位置 */
drawChessboardCorners(view_gray, board_size, image_points_buf, false); //用于在图片中标记角点
imshow("Camera Calibration", view_gray);//显示图片
waitKey(500);//暂停0.5S
}
}
int total = image_points_seq.size();
cout << "total = " << total << endl;
int CornerNum = board_size.width*board_size.height; //每张图片上总的角点数
for (int ii = 0; ii < total; ii++)
{
if (0 == ii%CornerNum)// 24 是每幅图片的角点个数。此判断语句是为了输出 图片号,便于控制台观看
{
int i = -1;
i = ii / CornerNum;
int j = i + 1;
cout << "--> 第 " << j << "图片的数据 --> : " << endl;
}
if (0 == ii % 3) // 此判断语句,格式化输出,便于控制台查看
{
cout << endl;
}
else
{
cout.width(10);
}
//输出所有的角点
cout << " -->" << image_points_seq[ii][0].x;
cout << " -->" << image_points_seq[ii][0].y;
}
cout << "角点提取完成!\n"; //以下是摄像机标定
cout << "开始标定………………";
/*棋盘三维信息*/
Size square_size = Size(10, 10); /* 实际测量得到的标定板上每个棋盘格的大小 */
std::vector<std::vector<Point3f>> object_points; /* 保存标定板上角点的三维坐标 */
/*内外参数*/
cv::Mat cameraMatrix = cv::Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 摄像机内参数矩阵 */
std::vector<int> point_counts; // 每幅图像中角点的数量
cv::Mat distCoeffs = cv::Mat(1, 5, CV_32FC1, Scalar::all(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
std::vector<cv::Mat> tvecsMat; /* 每幅图像的旋转向量 */
std::vector<cv::Mat> rvecsMat; /* 每幅图像的平移向量 */
/* 初始化标定板上角点的三维坐标 */
int i, j, t;
for (t = 0; t < image_count; t++)
{
std::vector<Point3f> tempPointSet;
for (i = 0; i < board_size.height; i++)
{
for (j = 0; j < board_size.width; j++)
{
Point3f realPoint;
/* 假设标定板放在世界坐标系中z=0的平面上 */
realPoint.x = i*square_size.width;
realPoint.y = j*square_size.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
object_points.push_back(tempPointSet);
}
/* 初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板 */
for (i = 0; i < image_count; i++)
{
point_counts.push_back(board_size.width*board_size.height);
} /* 开始标定 */
calibrateCamera(object_points, image_points_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);
cout << "标定完成!\n"; //对标定结果进行评价
cout << "开始评价标定结果………………\n";
double total_err = 0.0; /* 所有图像的平均误差的总和 */
double err = 0.0; /* 每幅图像的平均误差 */
std::vector<Point2f> image_points2; /* 保存重新计算得到的投影点 */
cout << "\t每幅图像的标定误差:\n";
fout << "每幅图像的标定误差:\n";
for (i = 0; i < image_count; i++)
{
std::vector<Point3f> tempPointSet = object_points[i];
/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, image_points2);
/* 计算新的投影点和旧的投影点之间的误差*/
std::vector<Point2f> tempImagePoint = image_points_seq[i];
cv::Mat tempImagePointMat = cv::Mat(1, tempImagePoint.size(), CV_32FC2);
cv::Mat image_points2Mat = cv::Mat(1, image_points2.size(), CV_32FC2);
for (int j = 0; j < tempImagePoint.size(); j++)
{
image_points2Mat.at<Vec2f>(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
tempImagePointMat.at<Vec2f>(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
total_err += err /= point_counts[i];
std::cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
fout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
}
std::cout << "总体平均误差:" << total_err / image_count << "像素" << endl;
fout << "总体平均误差:" << total_err / image_count << "像素" << endl << endl;
std::cout << "评价完成!" << endl;
//保存定标结果
std::cout << "开始保存定标结果………………" << endl;
cv::Mat rotation_Matrix = cv::Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
fout << "相机内参数矩阵:" << endl;
fout << cameraMatrix << endl << endl;
fout << "畸变系数:\n";
fout << distCoeffs << endl << endl << endl;
for (int i = 0; i < image_count; i++)
{
fout << "第" << i + 1 << "幅图像的旋转向量:" << endl;
fout << tvecsMat[i] << endl;
/* 将旋转向量转换为相对应的旋转矩阵 */
Rodrigues(tvecsMat[i], rotation_cv::Matrix);
fout << "第" << i + 1 << "幅图像的旋转矩阵:" << endl;
fout << rotation_Matrix << endl;
fout << "第" << i + 1 << "幅图像的平移向量:" << endl;
fout << rvecsMat[i] << endl << endl;
}
std::cout << "完成保存" << endl;
fout << endl;
/************************************************************************
显示定标结果
*************************************************************************/
cv::Mat mapx = cv::Mat(image_size, CV_32FC1);
cv::Mat mapy = cv::Mat(image_size, CV_32FC1);
cv::Mat R = cv::Mat::eye(3, 3, CV_32F);
std::cout << "保存矫正图像" << endl;
string imageFileName;
std::stringstream StrStm;
for (int i = 0; i != image_count; i++)
{
std::cout << "Frame #" << i + 1 << "..." << endl;
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, image_size, CV_32FC1, mapx, mapy);
StrStm.clear();
imageFileName.clear();
string filePath = "chess";
StrStm << i + 1;
StrStm >> imageFileName;
filePath += imageFileName;
filePath += ".bmp";
cv::Mat imageSource = imread(filePath);
cv::Mat newimage = imageSource.clone();
//另一种不需要转换矩阵的方式
//undistort(imageSource,newimage,cameracv::Matrix,distCoeffs);
remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
StrStm.clear();
filePath.clear();
StrStm << i + 1;
StrStm >> imageFileName;
imageFileName += "_d.jpg";
imwrite(imageFileName, newimage);
}
std::cout << "保存结束" << endl;
return coff;
}

后记

相机标定其实是一个体力活,所以标定一次之后几乎再也没有自行标定过..............................

此后,建议使用平板电脑,不过大多数平板电脑不适特别的平。

相机标定:PNP基于单应面解决多点透视问题的更多相关文章

  1. Halocn双目相机标定

    [Halcon]Halcon双目标定 相机标定(4)---基于halcon的双目立体视觉标定 双目立体视觉:四(双目标定matlab,图像校正,图像匹配,计算视差,disparity详解,) 双目测距 ...

  2. 相机标定 和 单应性矩阵H

    求解相机参数的过程就称之为相机标定. 1.相机模型中的四个平面坐标系: 1.1图像像素坐标系(u,v) 以像素为单位,是以图像的左上方为原点的图像坐标系: 1.2图像物理坐标系(也叫像平面坐标系)(x ...

  3. halcon相机标定及图像矫正

    https://blog.csdn.net/humanking7/article/details/44756073 相机标定内容详解:转载自 祥的博客 预备知识 标定中的四个坐标系 1.1.平面旋转 ...

  4. 相机标定:关于用Levenberg-Marquardt算法在相机标定中应用

    LM算法在相机标定的应用共有三处. (1)单目标定或双目标定中,在内参固定的情况下,计算最佳外参.OpenCV中对应的函数为findExtrinsicCameraParams2. (2)单目标定中,在 ...

  5. 相机标定 matlab opencv ROS三种方法标定步骤(1)

    一 . 理解摄像机模型,网上有很多讲解的十分详细,在这里我只是记录我的整合出来的资料和我的部分理解 计算机视觉领域中常见的三个坐标系:图像坐标系,相机坐标系,世界坐标系,实际上就是要用矩阵来表 示各个 ...

  6. SLAM入门之视觉里程计(6):相机标定 张正友经典标定法详解

    想要从二维图像中获取到场景的三维信息,相机的内参数是必须的,在SLAM中,相机通常是提前标定好的.张正友于1998年在论文:"A Flexible New Technique fro Cam ...

  7. OpenCV相机标定

    标签(空格分隔): Opencv 相机标定是图像处理的基础,虽然相机使用的是小孔成像模型,但是由于小孔的透光非常有限,所以需要使用透镜聚焦足够多的光线.在使用的过程中,需要知道相机的焦距.成像中心以及 ...

  8. 相机标定问题-Matlab & Py-Opencv

    一.相机标定基本理论 1.相机成像系统介绍 图中总共有4个坐标系: 图像坐标系:Op    坐标表示方法(u,v)                 Unit:Dots(个) 成像坐标系:Oi      ...

  9. ECCV 2018 | 旷视科技提出GridFace:通过学习局部单应变换实现人脸校正

    全球计算机视觉三大顶会之一 ECCV 2018(European Conference on Computer Vision)即将于 9 月 8 -14 日在德国慕尼黑拉开帷幕,旷视科技有多篇论文被此 ...

随机推荐

  1. RestEasy+用户指南----第5章.@PathParam

    转载说明出处:http://blog.csdn.net/nndtdx/article/details/6870391 原文地址 http://docs.jboss.org/resteasy/docs/ ...

  2. 对SHH的公钥和私钥的简单理解

    SSH是在应用层和传输层基础上的安全协议 SSH提供了两种级别的安全验证: 第一基于密码的安全验证:账号.密码,但可能有别的服务器冒充真正的服务器,无法避免被“中间人”攻击(man-in-the-mi ...

  3. hdu_1232_畅通工程_201403091018

    畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  4. memcached集群

    借鉴:http://www.cnblogs.com/happyday56/p/3461113.html 首先说明下memcached存在如下问题 本身没有内置分布式功能,无法实现使用多台Memcach ...

  5. redo allocation latch redo copy latch

    这两个latch 是干什么的一直有点迷糊,刚才上网查了一下,总结如下: redo allocation latch 在Log Buffer中分配内存空间时需要获取Redo allocation lat ...

  6. keras与sklearn的结合使用

    keras与sklearn的结合使用 新建 模板 Fly Time: 2017-4-14 引言 代码 引言 众所周知,keras目前没有提供交叉验证的功能,我们要向使用交叉验证,就需要与sklearn ...

  7. 本地项目上传虚拟机的gitlab

    前提:在虚拟机安装了gitlab服务,并且本机可以访问到虚拟机的gitlab 自己本机项目上传到gitlab 1.先在gitlab上建立项目 拷贝项目地址: http://192.168.1.105/ ...

  8. crmjs区分窗口是否是高速编辑(2)

    随着crm的版本号不同,有些功能不能使用,这里提供了第二种写法: function loadFrom() {     var formType = Xrm.Page.ui.getFormType(); ...

  9. spring与springboot中,如何在static方法里使用自动注入的属性

    第一步:写注解@Component 使当前类成为一个bean对象.(@Controller,@service都行) 第二步:写个static的变量 第三步:写个@PostConstruct注解注解注释 ...

  10. SuperSocketClientEngine

    https://github.com/kerryjiang/SuperSocket.ClientEngine TcpClientSession的用法 https://github.com/kerryj ...