相机内参的标定

2023-05-16

       最近刚刚开始学习相机的标定,也是在师兄的帮助下完成的。过程还是值得记录的,于是决定写在自己的第一篇CSDN上,便于之后的复习,同时也希望能够和大家进行交流,相互学习,相互借鉴,达到共同进步的目的!

       由于这是我第一次写文章,故有不足之处,希望大家予以批评指正,感激不尽!

       对于相机内参的标定,笔者认为可以将其看为一个解方程的过程,也就是说在我们知道空间点P ( X,Y,Z ) 以及其在像素坐标系下的坐标  p(u,v),再根据世界坐标系同相机内部各个坐标系之间的转换关系,来求解相机的内参,从而达到相机内参的标定的目的。下面就让我们先来了解一下各个坐标系之间的转换关系。

        上文假设一点的空间坐标为P ( X,Y,Z )(世界坐标系下的坐标),通过相机的外参矩阵 T(其中 T 是由旋转矩阵 R,平移矩阵 t 组成的)将点P ( X,Y,Z )转换到相机坐标系下得到点Pc(Xc,Yc,Zc),公式如下:

Pc_{}=T\cdot P

        设P在物理成像坐标系下的坐标为P'(X',Y',Z'),由相机的成像模型可知

X'=f\cdot \frac{Xc}{Zc}

Y'=f\cdot \frac{Yc}{Zc}

        至此得到了空间点P ( X,Y,Z )在物理成像平面上的坐标P'(X',Y',Z')。因为这些坐标的单位都是米,要想转换到像素坐标系下还需要通过参数\alpha\beta来进行转换,这两个参数的物理意义是单位长度上的像素点的个数,同时考虑到物理成像坐标系和像素坐标系的原点不重合。

        其中物理成像坐标系的原点在区域中心处,而像素坐标系的原点在左上角,所以有一个偏移量。假设在u方向上的像素点的偏移量为c_{x}(单位为像素个数),在v方向上的像素点的偏移量为c_{y}。故可以得到p(u,v)的坐标如下所示

\left\{\begin{matrix} u=\alpha \cdot X'+c_{x} & & \\ v=\beta \cdot Y'+c_{y} & & \end{matrix}\right.

        进一步的可以得到如下公式

\left\{\begin{matrix} u=f_{x}\cdot \frac{Xc}{Zc}+c_{x} & & \\ v=f_{y}\cdot \frac{Yc}{Zc}+c_{y} & & \end{matrix}\right.

        用矩阵的形式可以表示为

\begin{bmatrix} u\\ v\\ 1 \end{bmatrix}=\begin{bmatrix} f_{x} &0 & c_{x}\\ 0&f_{y} &c_{y} \\ 0 &0 &1 \end{bmatrix}\cdot \begin{bmatrix} \frac{Xc}{Zc}\\ \frac{Yc}{Zc}\\ 1 \end{bmatrix}

        有

p=K\cdot T\cdot P

        上式中从左到右参数依次为像素坐标点,相机内参矩阵,变换矩阵(外参矩阵),世界坐标系下的点的坐标。至此理论部分完成,我们所需要做的就是估计出K

        在ROS(机器人操作系统 Roboat Operating System)下进行相机标定,以下部分为代码部分,作出了部分注解。

#include <iostream>
#include <sstream>
#include <time.h>
#include <stdio.h>
#include <fstream>
#include <ros/ros.h>

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/highgui/highgui.hpp>
//首先包含头文件

using namespace std;
using namespace cv;
//对命名空间的使用

string camera_in_path, camera_folder_path, result_path;
int row_number, col_number, width, height;
//从这里我们可以看出ros::param里面应该有参数
//1.camera_in_path 2.camera_folder_path 3.result_path 4.row_number 5.col_number 6.width 7.height

void getParameters() //该函数是从launch文件中读取参数
{
    cout << "Get the parameters from the launch file" << endl;

    if (!ros::param::get("camera_in_path", camera_in_path)) 
	{
        cout << "Can not get the value of camera_in_path" << endl;
        exit(1);
    }
    if (!ros::param::get("camera_folder_path", camera_folder_path)) 
	{
        cout << "Can not get the value of camera_folder_path" << endl;
        exit(1);
    }
    if (!ros::param::get("result_path", result_path)) 
	{
        cout << "Can not get the value of result_path" << endl;
        exit(1);
    }
    if (!ros::param::get("row_number", row_number)) 
	{
        cout << "Can not get the value of row_number" << endl;
        exit(1);
    }
    if (!ros::param::get("col_number", col_number)) 
	{
        cout << "Can not get the value of col_number" << endl;
        exit(1);
    }
    if (!ros::param::get("width", width)) 
	{
        cout << "Can not get the value of width" << endl;
        exit(1);
    }
    if (!ros::param::get("height", height)) 
	{
        cout << "Can not get the value of height" << endl;
        exit(1);
    }

} 

int main(int argc, char **argv) 
{
	ros::init(argc, argv, "cameraCalib");//通过ros::init()进行初始化
	getParameters();//调用函数获取参数值
        ifstream fin(camera_in_path);//相机标定时采用的图像文件的保存路径
        ofstream fout(result_path);//相机标定完成保存输出结果的文件路径
        //读取每幅图像并从中提取出角点,对其进行呀像素精确化
        int image_count = 0;  // 图像数量 
	Size image_size;      // 图像的尺寸    
        Size board_size = Size(row_number, col_number);   // 标定板上每行、列的角点数 
	vector<Point2f> image_points_buf;         // 缓存每幅图像上检测到的角点 
	vector<vector<Point2f>> image_points_seq; // 保存检测到的所有角点 
        string filename;      // 图片名
	vector<string> filenames;
	while (getline(fin, filename) && filename.size() > 1) 
	{
		++image_count;
		filename = camera_folder_path + filename;
		cout << filename << endl;
		Mat imageInput = imread(filename);
                if (imageInput.empty())
                {  
                  //利用文件名称来寻找照片
                  break;
                }
		filenames.push_back(filename); //将filename存入到filenames中去

		// 读入第一张图片时获取图片大小
		if (image_count == 1) 
		{
			image_size.width = imageInput.cols;
			image_size.height = imageInput.rows;
		}

		/* 提取角点 */
		if (0 == findChessboardCorners(imageInput, board_size, image_points_buf)) //利用Opencv中的函数来寻找角点
		{
			cout << "**" << filename << "** can not find chessboard corners!\n";
			exit(1);
		}
		else 
		{
			Mat view_gray;
			cvtColor(imageInput, view_gray, cv::COLOR_RGB2GRAY);  // 转灰度图

			/* 亚像素精确化 */
			// image_points_buf 初始的角点坐标向量,同时作为亚像素坐标位置的输出
			// Size(5,5) 搜索窗口大小
			// (-1,-1)表示没有死区
			// TermCriteria 角点的迭代过程的终止条件, 可以为迭代次数和角点精度两者的组合
			cornerSubPix(view_gray, image_points_buf, Size(5, 5), Size(-1, -1), TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.1));  //Opencv中的函数进行亚像素精确化

			image_points_seq.push_back(image_points_buf);  // 保存亚像素角点
			drawChessboardCorners(view_gray, board_size, image_points_buf, false); // 用于在图片中标记角点

			imshow("Camera Calibration", view_gray);       // 显示图片

			waitKey(1000); //暂停1S      
		}
	}
	// int CornerNum = board_size.width * board_size.height;  // 每张图片上总的角点数

	//-------------以下是摄像机标定------------------

	/*棋盘三维信息*/
	Size square_size = Size(width, height);         /* 实际测量得到的标定板上每个棋盘格的大小 */
	vector<vector<Point3f>> object_points;   /* 保存标定板上角点的三维坐标 */

	/*内外参数*/
	Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));  /* 摄像机内参数矩阵 */
	vector<int> point_counts;   // 每幅图像中角点的数量
	Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));       /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
        vector<Mat> tvecsMat;      /* 每幅图像的旋转向量 */ //R
        vector<Mat> rvecsMat;      /* 每幅图像的平移向量 */ //t

	/* 初始化标定板上角点的三维坐标 */
	int i, j, t;
        for (t = 0; t<image_count; t++) //此时的image count为所有图片的个数
	{
		vector<Point3f> tempPointSet; //存储数据类型为Point3f的点
		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; //空间点的X坐标,所在行与单个棋盘格子宽度的乘积
				realPoint.y = j * square_size.height; //空间点的Y坐标,所在列与单个棋盘格子高度的乘积
				realPoint.z = 0;
				tempPointSet.push_back(realPoint); //把得到的点的坐标存入到temp中去
			} //一列一列的进行遍历
		}
		object_points.push_back(tempPointSet);
	}

	/* 初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板 */
        for (i = 0; i<image_count; i++)
        {
		point_counts.push_back(board_size.width * board_size.height);
	}

	/* 开始标定 */
	// object_points 世界坐标系中的角点的三维坐标
	// image_points_seq 每一个内角点对应的图像坐标点
	// image_size 图像的像素尺寸大小
	// cameraMatrix 输出,内参矩阵
	// distCoeffs 输出,畸变系数
	// rvecsMat 输出,旋转向量
	// tvecsMat 输出,位移向量
	// 0 标定时所采用的算法
	calibrateCamera(object_points, image_points_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);

	//------------------------标定完成------------------------------------

	// -------------------对标定结果进行评价------------------------------

	double total_err = 0.0;         /* 所有图像的平均误差的总和 */
	double err = 0.0;               /* 每幅图像的平均误差 */
	vector<Point2f> image_points2;  /* 保存重新计算得到的投影点 */
	fout << "Average error: \n";

	for (i = 0; i<image_count; i++) 
	{
		vector<Point3f> tempPointSet = object_points[i];

		/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
		projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, image_points2);

		/* 计算新的投影点和旧的投影点之间的误差*/
		vector<Point2f> tempImagePoint = image_points_seq[i];
		Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2);
		Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);

		for (unsigned 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];
		fout << "The error of picture " << i + 1 << " is " << err << " pixel" << endl;
	}
	fout << "Overall average error is: " << total_err / image_count << " pixel" << endl << endl;

	//-------------------------评价完成---------------------------------------------

	//-----------------------保存定标结果------------------------------------------- 
	Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0));  /* 保存每幅图像的旋转矩阵 */
	fout << "Intrinsic: " << endl;
	fout << cameraMatrix << endl << endl;
	fout << "Distortion parameters: " << endl;
	fout << distCoeffs << endl << endl << endl;
	cout << "Get result!" << endl;

	fin.close();
	fout.close();
	return 0;
}

         以下是.launch文件的编写

<launch>
<param name="camera_in_path" value="$(find camera_calibration)/data/camera/in.txt"/>

<param name="camera_folder_path" value="$(find camera_calibration)/data/camera/photos/"/>

<param name="result_path" value="$(find camera_calibration)/data/camera/result.txt"/>

<param name="row_number" type="int" value="9"/>

<param name="col_number" type="int" value="6"/>

<param name="width" type="int" value="50"/>

<param name="height" type="int" value="50"/>

<node pkg="camera_calibration" name="camera_calibration" type="camera_calibration" output="screen"/>
</launch>

        以下是得到的标定结果

Intrinsic: //相机的内参矩阵
[886.2391758072229, 0, 639.496654129007;
 0, 884.5569261594919, 338.8017291870644;
 0, 0, 1]

Distortion parameters: //相机的畸变参数
[0.179050927277567, -0.6498127975110598, -0.0004748475598530125, 0.00124291886207487, 0.6813914669391971]

         以上皆是在师兄的帮助下完成的,所以要感谢我的师兄~,和大家多多交流,希望大家指出问题,我们共同进步!

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

相机内参的标定 的相关文章

随机推荐

  • HTTP认证模式:Basic & Digest

    引言 经常在工作中使用到了各种认证方式 xff0c 但从未考虑过这些认证方式所属的知识范畴 xff0c 同时也解释不清楚它们 曾用到的认证方式 xff08 看看是否您也用过 xff0c 但很难解释清楚他们 xff09 xff1a Basic
  • Could not retrieve

    输入where nvm 找到nvm安装文件夹 在nvm文件夹下找到settings txt 添加以下代码 xff1a xff08 若没有则新建settings txt文件 xff09 node mirror npm taobao org m
  • Jetson Xavier gpio编程 (8)

    GPIO lines are attached to gpiochips Look in sys class gpio and you should see gpiochip240 248 and 288 I haven t yet det
  • curl 函数

    1 curl close 关闭一个cURL会话 语法 xff1a curl close span class hljs variable ch span span class hljs variable ch span 由 curl ini
  • CMake Error at /opt/ros/kinetic/share/catkin/cmake/catkinConfig.cmake:83 (find_package):

    出现了这样的情况的话如 xff1a CMake Error at opt ros kinetic share catkin cmake catkinConfig cmake 83 find package Could not find a
  • Rails Digest认证实现和原理

    优势 Http Digest是一种Http 不仅限于Web页面 认证框架 xff0c 相比通常使用的基本认证 xff0c Digest认证的优点是相对安全 基于网络标准和简单 xff0c 它不需要编写登录表单页面 xff0c 对登录信息进行
  • 【目标检测】修改YOLO标注索引,批量修改txt文件指定内容

    假设需要将原索引 0 xff0c 1 xff0c 2 xff0c 3 都修改为 0 xff1a import os path 61 39 train 39 total txt 61 os listdir path deleteList 61
  • 《C语言中分配了动态内存后一定要释放吗?》

    问 xff1a 比如main函数里有一句 malloc 后面没有free 1 那么当main结束后 xff0c 动态分配的内存不会随之释放吗 xff1f 2 如果程序结束能自动释放 xff0c 那么还加上free xff08 xff09 x
  • 串口通讯的延时问题

    串口编程涉及很多问题 xff0c 对于实时采集系统 xff0c 串口编程必须服从系统定时器采集节拍 xff0c 这样通过事件方式接收串口然后延时就会带来很多问题 串口数据通常不是一次到来 xff0c 对于一个较为长的数据 xff0c 可能分
  • linux下从源代码编译安装软件的一般步骤

    1 下载并解压文件 如果下的压缩文件的后缀是 tar gz 解压用 tar xzvf xxx tar gz tar b2 解压用 tar xjvf xxx tar b2 tar 解压用 tar xvf xxx tar 2 配置安装路径 在
  • 单精度浮点数(float)与双精度浮点数(double)的区别

    单精度浮点数 xff08 float xff09 与双精度浮点数 xff08 double xff09 的区别如下 xff1a xff08 1 xff09 在内存中占有的字节数不同 单精度浮点数在机内占4个字节 双精度浮点数在机内占8个字节
  • /dev/ttyUSB0 permission denied 解决办法:永久有可操作权限

    一般使用USB口 无论USB转什么口 xff0c 串口之类的 xff0c 启动时容易出现 dev ttyUSB0 permission denied 因为一般情况下不是root用户 xff0c 对端口没有权限 xff0e 遇到这种情况 xf
  • 三种继承的方法:public 继承/private继承/protected继承详解及区别

    公有继承 public 私有继承 private 保护继承 protected 是常用的三种继承方式 1 公有继承 public 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时 xff0c 它们都保持原有的状态 xff0c 而基
  • [C/C++] const 详解(修饰变量、输入参数、返回值、成员函数)

    看到const关键字 xff0c 程序员首先想到的可能是const 常量 const 更大的魅力是它可以修饰函数的参数 返回值 xff0c 甚至函数的定义体 const 是constant 的缩写 xff0c 恒定不变 的意思 被const
  • 怎么理解矩阵的秩

    首先来想一个问题 xff0c 最初的那个人为什么为什么要叫他为 秩 xff0c 为什么不叫 猪 牛 马 xff1f 举个例子就很容易理解 xff0c 大家排队买票 如果大家互相不认识 xff0c 那就会一个排一个 xff0c 非常有秩序 然
  • javascript中的sort()方法

    现在在学习javascript中 xff0c 发现sort 函数是有点奇怪的东西 xff08 可能是本人水平的问题 xff01 xff09 xff0c 于是就在这里记录一下自己找到的东西吧 sort 这个方法的参数很奇怪 xff0c 必须是
  • 【ROS读书笔记】--- 7.参数(parameters)

    时间见证一切 xff01 一 简述二 通过命令行操作参数 查看参数列表 查询参数 设置参数 删除参数 创建和加载参数文件 三 在C 43 43 代码中操作参数 设置参数 读取参数 四 在启动文件中操作参数 设置参数 设置私有参数 从yaml
  • 高位字节与低位字节简单介绍

    一般一个16位 xff08 双字节 xff09 的数据 xff0c 比如 FF1A xff08 16进制 xff09 那么高位字节就是FF xff0c 低位是1A 如果是32位的数据 xff0c 比如 3F68415B 高位字 xff08
  • STL常用容器详细解析

    STL容器的实现原理 STL共有六大组件 1 容器 2 算法 3 迭代器 4 仿函数 6 适配器 STL容器的实现原理 STL来管理数据十分方便 省去了我们自己构建数据结构的时间 其实 STL的实现也是基于我们常见的数据结构 序列式容器 x
  • 相机内参的标定

    最近刚刚开始学习相机的标定 xff0c 也是在师兄的帮助下完成的 过程还是值得记录的 xff0c 于是决定写在自己的第一篇CSDN上 xff0c 便于之后的复习 xff0c 同时也希望能够和大家进行交流 xff0c 相互学习 xff0c 相