兼职网站项目建设报告(完整版),适合做书籍资源的WordPress主题,用别人家网站做跳转,建网站的网络公司的名称以及服务文章目录 1. 问题引入2. 准备工作2.1 标定场2.2 相机拍摄 3. 基本原理3.1 成像原理3.2 畸变校正 4. 标定解算4.1 代码实现4.2 详细解析4.2.1 解算实现4.2.2 提取点位 4.3 解算结果 5. 问题补充 1. 问题引入
不得不说#xff0c;现在的计算机视觉技术已经发展到足够成熟的阶段… 文章目录 1. 问题引入2. 准备工作2.1 标定场2.2 相机拍摄 3. 基本原理3.1 成像原理3.2 畸变校正 4. 标定解算4.1 代码实现4.2 详细解析4.2.1 解算实现4.2.2 提取点位 4.3 解算结果 5. 问题补充 1. 问题引入
不得不说现在的计算机视觉技术已经发展到足够成熟的阶段了还记得笔者刚工作的时候相机标定还是个很神秘的技术只有少数专业人员能够做网上也找不到什么相关的资料。但是现在相机标定已经是一个非常普遍的技术了也有不少的资料的可以参考因此笔者突发奇想既然那些大部头的相机可以标定那么我们使用的手机摄像头一定也可以标定。因此笔者就记录一下给自己手机摄像头的具体实践算是弥补下当年没有学习到该技术的遗憾毕竟要学习一项技术最好的办法就是亲自实践一下。
2. 准备工作
2.1 标定场
笔者见过不少正规的标定场有的标定场很大有很多带有标志物的竖条还带有载动相机设备的轨道。不过目前比较流行且成本最低的办法就是使用棋盘格标定板了也就是所谓的张正友标定法。
那么棋盘格标定板哪里来呢打印到纸上倒是一个办法不过可能有两个问题一个是打印后每个格子的尺寸需要换算一下由像素换成米制单位这可能不是一个整数另一个就是得找一面墙来贴上去要贴的光滑平整还是挺难的。因此笔者没有选择这个办法最后还是通过网上购物找的标定板。由于是给手机摄像头传感器尺寸都不是太大标定板也不用选择太大笔者最终选用的标定板尺寸如下所示 每个格子是5毫米一共12X9个格子整体尺寸还是比较小巧的大概就一个手掌心大小。材质是玻璃基板成本大概是50元左右。这个尺寸笔者实际体验还是有点偏小的不过再大成本就上来了建议有财力的同学可以适当选择大一点。
2.2 相机拍摄
接下来就是用手机摄像头对棋盘格标定板进行拍摄了。理论上进行标定解算只需要6组控制点就可以了但是因为识别的控制点都是有误差的需要多组点位来进行求解以提升精度。只拍摄一张照片获得的控制点也不太够通常还需要获取多张照片上的控制点避免局部最优的问题提高解算过程的可靠性。如果可以的话要使用多个视角、多个不同距离的标定板照片同时最好保证标定板覆盖整个图像平面的不同区域这样可以更好地估计畸变和其他参数。
在这里笔者拍摄了6张棋盘格标定板的图片分别是前、后、左、右、上、下6个不同的位置和视角如下所示 可以看到拍摄的标定板区域都太靠中间了不过也是没办法使用的标定板尺寸确实有点偏小。拍摄的时候一旦靠的很近手机拍照程序就会自动切换成近景拍摄。笔者不太确定切换成近景拍摄之后会不会修改相机的参数所以都没有靠的很近。但是太远了拍照又有点糊只能使用目前这样的效果。
现在很多手机拍照的功能会自动修正照片比如滤镜广角矫正等等这些功能都尽量关了或者不使用。另外拍照过程不要进行调焦具体来说相机上会有0.6x、1x、2x、3x这样的参数这代表变焦倍率使用原始倍率1x进行标定即可。自动对焦功能当然也要关闭保持镜头和焦平面的位置不变。
还有个问题是保持手机不动移动标定板来拍摄照片还是保持标定板不动移动手机来拍摄照片应该来说两者原理上都可以实现但是标定板不动相机移动更常见一点因为实现起来更见简单。笔者就是将棋盘格标定板通过双面胶粘在墙上实现的也算是组成了一个成本最低的微型标定场了。
其实笔者也试过将标定板放在桌面上来拍摄不过在室内拍摄很容易在照片上有影子还是固定在墙上比较好一点。而且最好放采光比较好的墙面上在白天日照充足的时候进行拍摄以便获得最好的拍摄效果。
3. 基本原理
3.1 成像原理
相机标定虽然解算的是内参但是其实连外参也解算了因为相机标定解算使用的相机成像原理这个过程中内参和外参会一起参与解算。在不考虑畸变的情况下相机的成像原理可用下式(1)来表示 s [ u v 1 ] K [ R ∣ t ] [ X w Y w Z w 1 ] (1) s \begin{bmatrix} u\\ v\\ 1\\ \end{bmatrix} K \begin{bmatrix} R|t\\ \end{bmatrix} \begin{bmatrix} X_w\\ Y_w\\ Z_w\\ 1\\ \end{bmatrix} \tag{1} s uv1 K[R∣t] XwYwZw1 (1)
在这个式子中 [ X w Y w Z w ] T {\begin{bmatrix}X_w Y_w Z_w\\\end{bmatrix}}^T [XwYwZw]T表示世界空间中的三维点也称为物方点。 [ u v ] T {\begin{bmatrix}u v\\\end{bmatrix}}^T [uv]T表示图像平面上的像素坐标也称为像点。 [ R ∣ t ] \begin{bmatrix}R|t\\\end{bmatrix} [R∣t]是相机的外参矩阵。具体来说就是旋转变换和平移变换的组合 R R R就是3X3的旋转矩阵 t t t则是一个3列维向量。由于旋转变换可以用欧拉角来表示因而也可以表示成3维向量。3个旋转量3个平移量这就是相机的6个外参的由来。 K K K是相机的内参矩阵通常表示为下式(2): K [ f x 0 c x 0 f y c y 0 0 1 ] (2) K \begin{bmatrix} f_x 0 c_x\\ 0 f_y c_y\\ 0 0 1\\ \end{bmatrix} \tag{2} K fx000fy0cxcy1 (2) f x f_x fx和 f y f_y fy分别是水平方向和垂直方向的焦距单位为像素。 c x c_x cx和 c y c_y cy是像主点即成像平面的光轴交点坐标单位为像素。 s s s是比例因子这个参数是为了实现齐次坐标的转换将其次三维坐标需要转为二维坐标。
以笔者的见识来说上述相机成像原理其实与其他学科的一些知识有类似的地方 计算机图形学。图形渲染中的几何变换包含模型model变换、视图view变换和投影projection变换 合起来就是通常所说的MVP矩阵。模型变换包括旋转变换和平移变换视图变换又是模型变换的逆变换对应的就是式(1)的外参矩阵 [ R ∣ t ] \begin{bmatrix}R|t\\\end{bmatrix} [R∣t]。不过投影矩阵有所不同式(1)的内参矩阵 K K K是将点从相机坐标系转换为图像坐标系图形渲染中的投影矩阵则是将点从将点从相机坐标系转换为裁剪坐标系。 摄影测量学。在摄影测量学中这一套成像原理的公式被总结为共线方程除了表示的形式不同最显著的不同是内参只有三个焦距和像主点二维坐标。这个公式个人认为并不太直观但是比较容易进行平差计算。
如果有以上两者经验的读者可以对照着进行理解虽然它们看起来有点差异但是笔者确定它们的原理都是一样的都是基于空间的几何变换只不过是应对于不同情况有不同的描述。
3.2 畸变校正
以上成像原理没有考虑到畸变的影响。为什么会产生畸变呢很简单相机镜头不是完美的平面光学系统光线在传输时发生复杂的弯曲这会导致图像中的直线在图像边缘发生扭曲。常见的畸变有径向畸变和切向畸变。
畸变校正看起来很玄乎其实说穿了也非常简单我们只需要理解一点畸变校采用的有理函数的模型。所谓有理函数的模型就是将校正前的位置x与校正后的位置y使用一个高阶多项式形如 y a x 3 b x 2 c x d yax^3bx^2cxd yax3bx2cxd来进行表示没有什么物理上的原理就是纯采用数学方式进行拟合最后得到了每个高阶项的系数abcd。
鉴于畸变校正会增加对标定解算的复杂度这里就不进行进一步论述了。对于初学者来说理解成像原理的公式(1)更为关键一点。
4. 标定解算
4.1 代码实现
使用上述介绍的基本原理就可以进行标定解算了不过解算方法比较复杂我们还是结合具体的实现来解释代码如下所示这里主要使用了OpenCV库
#include filesystem
#include iostream
#include opencv2/opencv.hpp
#include vector#ifdef _WIN32
#include Windows.h
#endifusing namespace cv;
using namespace std;int main() {
#ifdef _WIN32SetConsoleOutputCP(65001);
#endifvectorstd::filesystem::path imgPaths {C:/Work/CalibrateCamera/Data/front.jpg,C:/Work/CalibrateCamera/Data/left.jpg,C:/Work/CalibrateCamera/Data/right.jpg,C:/Work/CalibrateCamera/Data/up.jpg,C:/Work/CalibrateCamera/Data/down.jpg,C:/Work/CalibrateCamera/Data/back.jpg};size_t imageNum imgPaths.size();// 定义棋盘格尺寸 (内角点数)int boardWidth 11; // 列数int boardHeight 8; // 行数cv::Size boardSize(boardWidth, boardHeight);double cellSize 0.005;Size imageSize(3072, 4096); // 图像尺寸// 准备标定所需的物方点和像方点vectorvectorPoint3f objectPoints(imageNum); // 多张图像的3D物方点vectorvectorPoint2f imagePoints(imageNum); // 多张图像的2D像方点for (size_t ii 0; ii imageNum; ii) {// 加载棋盘格图像cv::Mat image cv::imread(imgPaths[ii].string().c_str());if (image.empty()) {std::cerr Error: Could not load image! std::endl;return -1;}// 存储角点坐标std::vectorcv::Point2f corners;// 转换图像为灰度cv::Mat grayImage;cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);// 寻找棋盘格角点// cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGEbool found cv::findChessboardCorners(grayImage, boardSize, corners,cv::CALIB_CB_FAST_CHECK);// 如果找到角点进行进一步处理if (found) {std::cout Chessboard corners found! std::endl;// 增加角点的精度cv::cornerSubPix(grayImage, corners, cv::Size(11, 11), cv::Size(-1, -1),cv::TermCriteria(cv::TermCriteria::EPS cv::TermCriteria::MAX_ITER,30, 0.001));// 绘制角点std::string cornerImgPath imgPaths[ii].parent_path().generic_string() /corner/ imgPaths[ii].stem().string() _corner imgPaths[ii].extension().string();cv::drawChessboardCorners(image, boardSize, corners, found);cv::imwrite(cornerImgPath.c_str(), image);cout corners.size() endl;imagePoints[ii].resize(corners.size());for (size_t ci 0; ci corners.size(); ci) {imagePoints[ii][ci] corners[ci];}objectPoints[ii].resize(corners.size());for (int hi 0; hi boardHeight; hi) {for (int wi 0; wi boardWidth; wi) {int ci hi * boardWidth wi;objectPoints[ii][ci].x cellSize * wi;objectPoints[ii][ci].y cellSize * hi;objectPoints[ii][ci].z 0;}}} else {std::cerr Chessboard corners not found! std::endl;}}// 内参矩阵和畸变系数Mat cameraMatrix Mat::eye(3, 3, CV_64F); // 初始化为单位矩阵Mat distCoeffs Mat::zeros(8, 1, CV_64F); // 初始化为零// 外参的旋转和位移向量vectorMat rvecs, tvecs;// 执行标定double reprojectionError calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix,distCoeffs, rvecs, tvecs);cout u8重投影误差 reprojectionError endl;cout u8内参矩阵 cameraMatrix endl;cout u8畸变系数 distCoeffs endl;return 0;
}4.2 详细解析
4.2.1 解算实现
代码实现的步骤很简单就是通过函数findChessboardCorners提取棋盘格图片的角点将其传入calibrateCamera函数中就得到了最终的解算成果也就是内参矩阵。这其中的关键就在于calibrateCamera这个函数我们可以看一下它的函数原型
CV_EXPORTS_W double calibrateCamera( InputArrayOfArrays objectPoints,InputArrayOfArrays imagePoints, Size imageSize,InputOutputArray cameraMatrix, InputOutputArray distCoeffs,OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,int flags 0, TermCriteria criteria TermCriteria(TermCriteria::COUNT TermCriteria::EPS, 30, DBL_EPSILON) );其参数详解如下
objectPoints3D空间中的物方点坐标集合也就是公式(1)中的 [ X w Y w Z w ] T {\begin{bmatrix}X_w Y_w Z_w\\\end{bmatrix}}^T [XwYwZw]T。由于是多张图片的多组点的集合所以它的类型实际是std::vectorstd::vectorcv::Point3f。imagePoints图像中的像素坐标集合对应公式(1)中的 [ u v ] T {\begin{bmatrix}u v\\\end{bmatrix}}^T [uv]T类型同样应该也是双重数组std::vectorstd::vectorcv::Point2f。imageSize输入图像的尺寸宽度和高度单位为像素。cameraMatrix输出的摄像机内参矩阵也就是公式(1)中的 K K K为3X3矩阵。distCoeffs输出的摄像机的畸变系数通常为1X5或1X8的向量包含径向和切向畸变系数。rvecs输出的旋转向量集合可以转换成公式(1)中的 R R R。每个旋转向量对应一个图像所以类型是std::vectorcv::Mat。tvecs输出的平移向量集合对应公式(1)中的 t t t。每个平移向量对应一个图像类型也是std::vectorcv::Mat。返回值标定的重投影误差用于衡量标定结果的精确度。误差越小标定结果越准确。
通过对calibrateCamera函数的解析相信读者就很容易明白为什么笔者要先讲公式(1)的成像原理。这个解算参数的输入输出都是根据公式(1)来的不过另一个问题来了输入的物方点和像方点是怎么来的呢
4.2.2 提取点位
答案很简单就是棋盘格上的角点。棋盘格由黑白相间的格子组成所以它的角点是很容易提取的另外一方面棋盘格也是规整的只要每个格子的尺寸都是一样就很容易知道物方坐标。理论上只要对图像提取角点然后剔除掉非棋盘的角点就可以作为相机标定的像点了。不过OpenCV提供了更进一步的接口findChessboardCorners直接输入棋盘格的内角点个数就可以自动检测出像点。如下图所示在一张图片上笔者提取的像点 正如上图所示findChessboardCorners提取的是内角点例如12X9的棋盘格提取的内角点是11X8个并且结果是按照从左到右从上往下进行排序的。为什么要这么排序呢因为很容易帮我们算出物方点。在相机标定这个应用中相机的外参是不重要的因此我们可以就以棋盘格标定板的左上角作为世界坐标系的原点第1个点的坐标是(0,0,0)第2个点的坐标是(0.005,0,0)第3个点坐标是(0.010,0,0)…第12个点坐标是(0,0.005,0)第13个点坐标是(0.005,0.005,0)…就这么依次类推得到所有角点对应的世界空间坐标系坐标。
另外一点要提醒读者的是findChessboardCorners这里我配置的是参数是cv::CALIB_CB_FAST_CHECK是一种快速算法cv::CALIB_CB_ADAPTIVE_THRESH和cv::CALIB_CB_NORMALIZE_IMAGE会对图像作预处理能够增加提取棋盘格角点的稳健性。但是我实际使用发现程序卡住了不知道是效率很低还是OpenCV的问题就没有使用这两个选项。
4.3 解算结果
最终笔者的结算结果如下所示
重投影误差0.166339
内参矩阵[2885.695162446343, 0, 1535.720945173723;0, 2885.371543143629, 2053.122840953737;0, 0, 1]
畸变系数[0.181362004467736;-3.970106972775221;0.0005157812878172198;0.0004644406171824815;23.559069196518]解算的结果重投影误差是0.166339表示每个物体点在重新投影到图像上时与实际检测到的角点位置的误差为0.166339像素。通常来说这样的误差已经算是非常小表明标定结果较为精确。
不过笔者还考虑一个问题误差为0.166339像素那么具体是多少米呢以前做测绘软件的时候平差的结果也是以像素为单位总会有客户对我发起灵魂拷问那具体是多少米呢这次笔者也关注了一下这个问题个人认为在相机标定这样的应用场景确实无法直接使用物理单位表示精度因为这个算法的结果就在于重投影到图像上的像素差为量度这一点与相机外参的定向的误差量度有所不同。
对照内参矩阵可得解算的焦距是 f x 2885.695 f_x2885.695 fx2885.695和 f y 2885.372 f_y2885.372 fy2885.372单位也是像素。那么这个焦距换算成物理单位是多少米呢根据笔者查找的资料显示焦距在像素和毫米之间的转换公式如下所示 焦距毫米 焦距像素 × 传感器尺寸毫米 图像分辨率像素 焦距毫米 \frac{焦距像素×传感器尺寸毫米}{图像分辨率像素} 焦距毫米图像分辨率像素焦距像素×传感器尺寸毫米
也就是说与相机传感器尺寸有关不过关于传感器尺寸的描述有点蛋疼比如网上显示我手机摄像头的传感器是1/1.49英寸这通常表示传感器的对角线长度。可以根据对角线长度加上宽高比例如4:3还是16:9算出相机传感器的物理尺寸进而知道具体物理单位的焦距值大小。不过传感器的对角线长度标称值和真实物理尺寸之间会因为行业惯例和历史标准有所差异所以算出来的也不一定正确最好还是联系官方来确定。不过标定出像素单位的焦距已经足够后续满足后续的使用场景了笔者这里也就是寻根究底一下。
5. 问题补充
最后补充一些没搞定或者暂时没理解的问题吧
关于成像原理列出的公式(1)的内参矩阵部分其实笔者也没弄清楚为什么将焦距分成X方向上的 f x f_x fx和y方向上的 f y f_y fy有些资料上的内参矩阵并不是这么列的《摄影测量学》教材上列出的共线方程更是只有一个焦距值 f f f。笔者记得似乎有个操作“相机重标定”可以将使用固定焦距 f f f调整像主点到图像中心以及消除畸变的重投影可以简化后续的空间计算使得计算更为便捷。时间关系就留待后续研究了。本文笔者并没有具体解释解算的算法原理因为这不是一两句话就能说清楚的在测绘学中有个过程有个专门的名词叫做平差或者叫做状态估计、最大似然估计、非线性优化等等至少我们需要知道最小二乘法原理才能继续论述这个就留待后续的文章中进行讨论吧。
列出一些文章以供参考
相机标定从入门到实战相机系列——相机标定简述相机标定之张正友标定法数学原理详解计算机视觉----相机标定
本文源代码和数据地址