相机标定及点云拼接

2023-11-14

前言

在上一篇中已经完成了从图像到点云的转换,但是只针对单个相机,在这一篇中将再进一步,从两个相机拍摄图像,然后做点云的生成及拼接。

一、相机标定原理

相机标定的原理我们上一篇章中也提到了一些(内参矩阵、外参矩阵),而相机标定需要用到的信息包含有内部参数(焦距、光学中心等)、外部参数(旋转、平移矩阵)以及畸变系数。

其中畸变主要指的是径向畸变和切向畸变,径向畸变导致直线呈曲线状,距离图像中心越远,径向失真越大;切向畸变发生是因为摄像透镜没有与成像平面完全平行,图像中的某些区域看起来可能比预期的要近。
在这里插入图片描述
更为详细的原理步骤叙述可以参考:https://blog.csdn.net/lql0716/article/details/71973318。

二、关键代码

2.1 相机标定代码1

因为Azure-Kinect-Sensor-SDK中有提供Kinect标定代码,这里可以直接参考。

这里就简单说一下大致步骤:
(1) 使用相机在近距离、远距离和多个指向角度拍摄标定板图像
运行以下命令以捕获相机0的视频:

k4arecorder --device 0 -c 3072p -d PASSIVE_IR -l 20 ./device0/device0.mkv

运行以下命令以捕获相机1的视频:

k4arecorder --device 1 -c 3072p -d PASSIVE_IR -l 20 ./device1/device1.mkv

(2) 使用ffmpeg获取视频帧:

ffmpeg -i ./device0/device0.mkv -vf fps=1 ./device0/color_%d.jpg
ffmpeg -i ./device1/device1.mkv -vf fps=1 ./device1/color_%d.jpg

(3) 从每个相机拍摄的图像中产生标定文件calib.yaml:

python calibrate.py --dataset-dir ./device0 --template ./PlaneFiles/plane.json
python calibrate.py --dataset-dir ./device1 --template ./PlaneFiles/plane.json

(4) 同时拍摄固定形态的标定板(一个相机一张即可),产生双目标定的信息calibration_blob.json:

python register.py --img-a  <full_path_img_b> --img-b <full_path_img_b> --template ./PlaneFiles/plane.json --calib-a <full_path_calibration_a> --calib-b <full_path_calibration_b> --out-dir <out_dir>

2.2 相机标定代码2

还可以利用Matlab相机标定获取相机的标定信息。

2.3 点云拼接代码

从上一步中可以得到从相机1到相机0的旋转矩阵和平移矩阵(我这里利用的是从Matlab中获取到的标定信息),因此点云根据这两个矩阵进行变换即可,将两个相机的点云数据变换到同一个世界坐标系下。

这里仅供参考的关键代码:

class Reconstruction{
private:
    vector<Eigen::Isometry3d, Eigen::aligned_allocator<Eigen::Isometry3d>> _poses;//相机标定外参
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr _pointcloud; //三维重建拼接点云

    int _camera_num; //相机数目
    double _cx; //光心
    double _cy;
    double _fx; //焦距
    double _fy; 
    double _depthScale;

public:
    //构造函数
    Reconstruction(){
        _pointcloud.reset( new pcl::PointCloud<pcl::PointXYZRGB>() );
        //读取相机内参标定
        ifstream ifs("../parameter1.txt"); //parameter.txt存储相机内参信息
        ifs >> _camera_num >> _cx >> _cy >> _fx >> _fy >> _depthScale;
        ifs.close();
        //读取相机外参标定
        ifstream fin("../poses1.txt"); //poses.txt存储的是两个相机的旋转和平移矩阵信息
        double data[12] = {0};

        for (int i = 0; i < _camera_num; i++) {
            for (auto &d:data){
                fin >> d;
            }
            //平移矩阵![请添加图片描述](https://img-blog.csdnimg.cn/e136bb94f133467d82110251da4c64cd.png)

            Eigen::Vector3d translation( data[0], data[1], data[2]);
            //旋转矩阵
            Eigen::Matrix3d rotation_matrix = Eigen::Matrix3d::Identity();
            rotation_matrix << data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11];
            //构造变换矩阵
            Eigen::Isometry3d T = Eigen::Isometry3d::Identity();
            T.rotate( rotation_matrix );
            T.pretranslate(-rotation_matrix * translation);
            _poses.push_back( T );
            cout << _poses.back().matrix() << endl;
        }
        fin.close();
    }
  	//点云生成
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr ImageToPointcloud(cv::Mat& color, cv::Mat& depth){
	    pcl::PointCloud<pcl::PointXYZRGB>::Ptr pointcloud( new pcl::PointCloud<pcl::PointXYZRGB>() );
	    for (int v = 0; v < depth.rows; v++){
	        for (int u = 0; u < depth.cols; u++){
	            unsigned int d = depth.ptr<unsigned short>(v)[u];
	            pcl::PointXYZRGB point;
	
	            point.z = double(d) / _depthScale;
	            point.x = (u - _cx) * point.z / _fx;  // _cx, _cy是摄像头光学中心
	            point.y = (v - _cy) * point.z / _fy;  // _fx, _fy是摄像头焦距
	            
	            point.b = color.data[v*color.step+u*color.channels()];
	            point.g = color.data[v*color.step+u*color.channels() + 1];
	            point.r = color.data[v*color.step+u*color.channels() + 2];
	
	            pointcloud->points.push_back(point);
	        }
	    }
	    pointcloud->height = 1;
	    pointcloud->width = pointcloud->points.size();
	    pointcloud->is_dense = false;
	
	    return pointcloud;
	}
	//点云拼接
    void pointCloudRegistration(vector<cv::Mat> colorImgs_, vector<cv::Mat> depthImgs_){
        //读取每个相机的color图和depth图
        for (int i = 0; i < _camera_num; i++) {
            cv::Mat color = colorImgs_[i];
            cv::Mat depth = depthImgs_[i];
            Eigen::Isometry3d T = _poses[i];
            
            pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>());
            pcl::PointCloud<pcl::PointXYZRGB>::Ptr pointcloud(new pcl::PointCloud<pcl::PointXYZRGB>());
            cloud = ImageToPointcloud(i, color, depth);
            pcl::transformPointCloud(*cloud, *pointcloud, T.matrix()); //利用变换矩阵转换点云
         
            pcl::io::savePCDFileBinary("../data/camera" + to_string(i) + ".pcd", *pointcloud );
            *_pointcloud += *pointcloud;
        }

        pcl::io::savePCDFileBinary("../data/source_model.pcd", *_pointcloud );
    }
};

三、结果展示

相机0和相机1的color图:

请添加图片描述 请添加图片描述

相机0和相机1产生的点云:

请添加图片描述 请添加图片描述
拼接后的点云:
请添加图片描述 请添加图片描述

总结

这一篇只是完成了简单的点云拼接操作(其实就是两个点云叠加),最关键的部分还是在相机标定那一块,如何得到标定信息。此外,从拼接的结果来看,拼接后的点云之间还是有缝隙,我尝试标定了好几次,缝隙依然存在,也没有改善。观察Kinect所拍摄的深度图发现,人体的边缘部分存在着黑边,感觉是因为边缘误差太大或者是人体的边缘深度没有探测到,后面再看看怎么改进。因为Azure KInect DK拍摄图片形成的点云比较稠密,后续会用PCL进行处理,顺便学习PCL点云库相关的操作和通用算法。

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

相机标定及点云拼接 的相关文章

随机推荐

  • canvas绘制线条、矩形、曲线及填充

    1 绘制线条和填充 1 绘制线段的API是上下文对象的方法 2 beginPath 开始定义一条新的路径 3 moveTo 开始定义一条新的子路径 该方法确定了线段的起点 4 lineTo 将上面定义的线段起点和指定的新的点连接起来 5 到
  • Vue+element ui -- 自定义表单验证:金额

    在实际项目中 表单验证可以说非常普遍 尤其是财务系统项目和商城项目 涉及到金额的输入框更是很多 那么验证用户输入信息的争取与否 就变得至关重要 不单要做到准确还要照顾用户的输入习惯以及舒适度 这边笔记记录了原来我在项目中进行 金额 方面的自
  • xp系统电脑服务器端口,XP的电脑用CMD指令怎么开3389端口

    输入命令 REG ADD HKLM SYSTEM CurrentControlSet Control Terminal Server v fDenyTSConnections t REG DWORD d 00000000 f 如果用CMD命
  • MATLAB与深度学习(一)— Deep Learning Toolbox

    MATLAB与深度学习 一 Deep Learning Toolbox 最近 我在学习基于matlab的深度学习的内容 并整理出如下学习笔记 本文借鉴和引用了网上许多前辈的经验和代码 如有冒犯 请及时与我联系 1 MATLAB与深度学习的简
  • 使用ImageMagick为你的网站减重(转)

    片在网站所占的比重越来越重 更好的优化图片可以提高网站速度 减少宽带流量 1 对用户上传图片进行缩放 对于用户自己上传的图片不能简单的 用css限制大小 因为这样每次加载图片时候还是会加载整幅大图 占用多余的宽带 并且影响页面加载速度 应该
  • 安装程序无法自动安装Virtual Machine Communication Interface Sockets(VSock)驱动程序

    关于 VMware Tools安装时出现的问题的解决办法 安装时出现问题对话框 安装程序无法自动安装Virtual Machine Communication Interface Sockets VSock 驱动程序 必须手动安装此驱动程序
  • 动态场景下基于实例分割的SLAM(毕业设计开题及语义分割部分)

    动态场景下基于实例分割的SLAM 毕业论文设计思路及流水 前言 今年选了个比较难的毕设题目 这里记录一下自己思路和流程 为之后的学弟学妹 划掉 铺个方向 会按日期不定期的更新 一 开题 2019 12 24 考研前选择课题是 利用深度学习对
  • Linux ixgbe网卡(光模块)兼容性问题(没有网卡配置文件)

    Linux ixgbe网卡 光模块 兼容性问题 ixgbe光纤网卡的驱动在默认情况下不支持第三方兼容光模块 会导致网卡驱动加载失败 表现为 执行lspci grep 82599能看到网卡在pci设备中 06 00 0 Ethernet co
  • 剑指 Offer 15. 二进制中1的个数

    public class Solution you need to treat n as an unsigned value public int hammingWeight int n int div 1 int num 0 for in
  • 李宏毅 机器学习 2016 秋:3、Gradient Descent

    文章目录 三 Gradient Descent 3 1 Tuning your learning rates 3 2 Stochastic Gradient Descent 3 3 Feature Scaling 3 4 理论支持 三 Gr
  • 免费html5代码,HTML5(示例代码)

    一 什么是HTML5 1 1 HTML5 简介 万维网的核心语言 标准通用标记语言下的一个应用超文本标记语言 HTML 的第五次重大修改 作为新HTML语言 具有新的元素 属性和行为 XHTML可扩展超文本标记语言 是一种增强了的HTML
  • 微信小程序:动画效果集合

    Life is like riding a bicycle To keep your balance you must keep moving 生活就像骑自行车 为了保持平衡 你必须不断前进 微信小程序 心跳动画 https blog cs
  • 使用 appium 进行微信小程序的自动化测试

    目录 前言 微信小程序结构 自动化用例的调整 示例代码 后记 前言 微信小程序是一种流行的移动应用程序 它在移动设备上提供了丰富的功能和用户体验 为了确保微信小程序的质量和稳定性 自动化测试是必不可少的一环 Appium是一个强大的自动化测
  • assert断言(没有返回值,不需要console.log,断言未通过会抛出错误,通过不会抛出错误)

    1 assert value message 保证value是true就不会抛出错误 2 assert deepEqual actual expected message 表达式 1 表示测试 actual 参数与 expected 参数是
  • Custom numeric format strings

    string Multiplier Multiplier 0 Console WriteLine string Format Multiplier 1000000 string LiteralChar LiteralChar 0 000 C
  • vue3 图片路径转base64 base64转file(二进制一般后台需要格式) file转base64

    1 图片路径转64 function imageUrlToBase64 url let homeImage new Image 解决跨域问题 homeImage setAttribute crossOrigin anonymous home
  • 为什么前端监控要用GIF打点

    1背景 我们知道 目前主流的前端监控 百度统计 友盟 谷歌统计 都在用GIF进行打点 但是 为什么这些系统都会使用GIF 难道是因为没有其他的解决方案吗 这得从前端监控的原理说起 2前端监控的原理 所谓的前端监控 其实是在满足一定条件后 由
  • DataTable筛选出现异常

    异常详细信息 System ArgumentException 在 Range 对象中 Min 37 必须小于或等于 max 1 解决方法 转化字段类型为int 再次出现问题 HH gt 0 and HH lt 35 出来的数据序列为 1
  • Android Jetpack 之 DataStore

    1 概述 Google 推出了 JetPack 的新成员 DataStore DataStore 是一种新的数据存储方案 DataStore 以异步 一致的事务方式存储数据 克服了 SharedPreferences 的一些缺点 Jetpa
  • 相机标定及点云拼接

    文章目录 前言 一 相机标定原理 二 关键代码 2 1 相机标定代码1 2 2 相机标定代码2 2 3 点云拼接代码 三 结果展示 总结 前言 在上一篇中已经完成了从图像到点云的转换 但是只针对单个相机 在这一篇中将再进一步 从两个相机拍摄