深度相机和彩色相机对齐(d2c)

2023-10-23 21:59
文章标签 深度 彩色 相机 对齐 d2c

本文主要是介绍深度相机和彩色相机对齐(d2c),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一般商用的rgbd相机的sdk自带d2c的api,但是LZ还是想利用空闲时间理解下其原理。
第一步:标定彩色相机和深度相机。
分别采集若干张彩色摄像头和红外摄像头(对于带有红外摄像头进行深度测量的深度摄像头,红外摄像头和深度摄像头其实是同一个)的标定板成像图片。LZ这里各采集了9张,如下所示:
在这里插入图片描述
在这里插入图片描述

这里除了第一组图片(color.bmp和ir.bmp)需要在同一场景下拍摄,其他的color图片和ir图片不需要一一对应,可以分开采集。注意,红外摄像头直接拍摄标定板可能成像不清楚,会存在大量噪声影响标定板角点提取。此时可以遮挡住红外发射器,并使用红外光源照射标定板(参考深度图与彩色图的配准与对齐)。

标定程序:

#include <iostream>  
#include <fstream>
#include <opencv2/opencv.hpp>#define BOARD_SCALE 30//棋盘格边长(mm)
#define BOARD_HEIGHT 5//棋盘格高度方向角点个数
#define BOARD_WIDTH 8 //棋盘格宽度方向角点个数/*** @brief CamraCalibration  相机标定* @param files             文件名* @param cameraMatrix      内参矩阵* @param distCoeffs        畸变系数* @param tvecsMat          平移矩阵* @param rvecsMat          旋转矩阵*/
void CamraCalibration(std::vector<std::string>& files, cv::Mat& cameraMatrix, cv::Mat& distCoeffs,std::vector<cv::Mat>& tvecsMat, std::vector<cv::Mat>& rvecsMat)
{//读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化   int image_count = 0;  /* 图像数量 */cv::Size image_size;  /* 图像的尺寸 */cv::Size board_size = cv::Size(BOARD_HEIGHT, BOARD_WIDTH);    /* 标定板上每行、列的角点数 */std::vector<cv::Point2f> image_points_buf;  /* 缓存每幅图像上检测到的角点 */std::vector<std::vector<cv::Point2f>> image_points_seq; /* 保存检测到的所有角点 */for (int i = 0; i < files.size(); i++){std::cout << files[i] << std::endl;cv::Mat imageInput = cv::imread(files[i]);/* 提取角点 */if (0 == findChessboardCorners(imageInput, board_size, image_points_buf)){std::cout << "can not find chessboard corners!\n"; //找不到角点  continue;}else{//找到一幅有效的图片image_count++;if (image_count == 1)  //读入第一张图片时获取图像宽高信息  {image_size.width = imageInput.cols;image_size.height = imageInput.rows;}cv::Mat view_gray;cvtColor(imageInput, view_gray, cv::COLOR_RGB2GRAY);/* 亚像素精确化 */cornerSubPix(view_gray, image_points_buf, cv::Size(5, 5), cv::Size(-1, -1),cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 30, 0.1));image_points_seq.push_back(image_points_buf);  //保存亚像素角点}}int total = image_points_seq.size();std::cout << "共使用了" << total << "幅图片" << std::endl;std::cout << "角点提取完成!\n";/*棋盘三维信息*/cv::Size square_size = cv::Size(BOARD_SCALE, BOARD_SCALE);  /* 实际测量得到的标定板上每个棋盘格的大小 */std::vector<std::vector<cv::Point3f>> object_points; /* 保存标定板上角点的三维坐标 */cameraMatrix = cv::Mat(3, 3, CV_32FC1, cv::Scalar::all(0)); /* 摄像机内参数矩阵 */std::vector<int> point_counts;  // 每幅图像中角点的数量  distCoeffs = cv::Mat(1, 4, CV_32FC1, cv::Scalar::all(0)); /* 摄像机的畸变系数:k1,k2,p1,p2 *//* 初始化标定板上角点的三维坐标 */int i, j, t;for (t = 0; t < image_count; t++){std::vector<cv::Point3f> tempPointSet;for (i = 0; i < board_size.height; i++){for (j = 0; j < board_size.width; j++){cv::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);}/* 开始标定 */cv::calibrateCamera(object_points, image_points_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, cv::CALIB_FIX_K3);std::cout << "标定完成!\n" << std::endl;//对标定结果进行评价  std::ofstream fout("calibration_result.txt");  /* 保存标定结果的文件 */double total_err = 0.0; /* 所有图像的平均误差的总和 */double err = 0.0; /* 每幅图像的平均误差 */std::vector<cv::Point2f> image_points2; /* 保存重新计算得到的投影点 */std::cout << "每幅图像的标定误差:\n";fout << "每幅图像的标定误差:\n";for (i = 0; i < image_count; i++){std::vector<cv::Point3f> tempPointSet = object_points[i];/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */projectPoints(tempPointSet, rvecsMat[i], tvecsMat[i], cameraMatrix, distCoeffs, image_points2);/* 计算新的投影点和旧的投影点之间的误差*/std::vector<cv::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<cv::Vec2f>(0, j) = cv::Vec2f(image_points2[j].x, image_points2[j].y);tempImagePointMat.at<cv::Vec2f>(0, j) = cv::Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);}err = norm(image_points2Mat, tempImagePointMat, cv::NORM_L2);total_err += err /= point_counts[i];std::cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << std::endl;fout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << std::endl;}std::cout << "总体平均误差:" << total_err / image_count << "像素\n" << std::endl;fout << "总体平均误差:" << total_err / image_count << "像素" << std::endl << std::endl;//保存定标结果      cv::Mat rotation_matrix = cv::Mat(3, 3, CV_32FC1, cv::Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */std::cout << "相机内参数矩阵:" << std::endl;std::cout << cameraMatrix << std::endl << std::endl;std::cout << "畸变系数:" << std::endl;std::cout << distCoeffs << std::endl << std::endl;fout << "相机内参数矩阵:" << std::endl;fout << cameraMatrix << std::endl << std::endl;fout << "畸变系数:" << std::endl;fout << distCoeffs << std::endl << std::endl;for (int i = 0; i < image_count; i++){fout << "第" << i + 1 << "幅图像的平移向量:" << std::endl;fout << tvecsMat[i].t() << std::endl;/* 将旋转向量转换为相对应的旋转矩阵 */Rodrigues(rvecsMat[i], rotation_matrix);fout << "第" << i + 1 << "幅图像的旋转矩阵:" << std::endl;fout << rotation_matrix << std::endl << std::endl;}fout << std::endl;
}int main(int argc, char* argv[])
{std::vector<std::string> files;std::vector<cv::Mat> tvecsMat, rvecsMat;cv::Mat cameraMatrix, distCoeffs;cv::glob("./images", files, true);CamraCalibration(files, cameraMatrix, distCoeffs, tvecsMat, rvecsMat);return EXIT_SUCCESS;
}

根据自己标定板参数设置BOARD_SCALE、BOARD_HEIGHT和BOARD_WIDTH的值。标定彩色相机和红外相机的内参如下:
在这里插入图片描述
在这里插入图片描述

第二步:求解深度相机到彩色相机的变换矩阵

第一步中的程序计算出color.bmp和ir.bmp这两张图片对应的相机外参分别为:
在这里插入图片描述
在这里插入图片描述

求解深度相机到彩色相机的变换矩阵的原理:(参考深度图与彩色图的配准与对齐,其实就是求解一个4*4刚性变换矩阵)在这里插入图片描述

使用下面程序计算深度相机到彩色相机的变换矩阵:

#include <iostream>
#include <Eigen/Dense>int main()
{Eigen::Matrix3f Rir, Rrgb, R;Rir << 0.9388621034681646, 0.04409712279151825, 0.341457749118165,0.01647972263398413, -0.9963828535584762, 0.0833644281247999,0.3438987778594756, -0.07264057334697833, -0.9361928101040838;Rrgb << 0.9430537754163527, 0.04244549558711521, 0.3299211369059707,0.01733461460525522, -0.9967487635113179, 0.0786855359970694,0.332188331838208, -0.06848563603430294, -0.940723460878661;Eigen::Vector3f Tir, Trgb, T;Tir << -167.0131975581419, -2.579261897319864, 405.3398579424809;Trgb << -124.6158422573672, 4.649293541474578, 407.7379454786609;Eigen::Matrix4f trans_ir, trans_rgb, trans;trans_ir << Rir, Tir, 0, 0, 0, 1;trans_rgb << Rrgb, Trgb, 0, 0, 0, 1;R = Rrgb * Rir.inverse();T = Trgb - R * Tir;std::cout << " R=\n" << R << std::endl << "T=\n" << T << std::endl;trans = trans_rgb * trans_ir.inverse();std::cout << "transformation matrix=\n" << trans << std::endl;return 0;
}

求解结果如下:
在这里插入图片描述

第三步:rgbd对齐(配准)
在这里插入图片描述

原理参考:RGB 图像与深度图像对齐
将第一步、第二步标定得到的彩色相机和深度相机的内参以及深度相机到彩色相机的变换矩阵填入下面程序的对应位置:

#include <iostream>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>// 相机内参
double color_cx = 319.4576960224705;
double color_cy = 248.6773903752145;
double color_fx = 577.636768616958;
double color_fy = 579.6956673812326;double depth_cx = 331.2627696745718;
double depth_cy = 250.5639330076735;
double depth_fx = 579.632784579308;
double depth_fy = 582.4003748731251;int main(int argc, char* argv[])
{cv::Mat color = cv::imread("color_1.png", 1);cv::Mat depth = cv::imread("depth_1.png", -1);pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);std::ofstream outfile("cloud_1.txt");int point_nums = 0;for (int i = 0; i < depth.rows; ++i){for (int j = 0; j < depth.cols; ++j){short d = depth.ptr<short>(i)[j];if (d == 0)continue;point_nums++;pcl::PointXYZRGB p;p.z = double(d);p.x = (j - depth_cx) * p.z / depth_fx;p.y = (i - depth_cy) * p.z / depth_fy;Eigen::Matrix3f R; //d2c旋转矩阵R << 0.999923, 0.000752991, 0.012362,-0.000811154, 0.999989, 0.00470092,-0.0123583, -0.0047106, 0.999913;Eigen::Vector3f T(37.3757, 5.18758, 0.357422); //d2c平移向量Eigen::Vector3f Pd = p.getArray3fMap();Eigen::Vector3f Pc = R * Pd + T;int n = Pc.x() * color_fx / Pc.z() + color_cx;if (n < 0)n = 0;if (n >= color.cols)n = color.cols - 1;int m = Pc.y() * color_fy / Pc.z() + color_cy;if (m < 0)m = 0;if (m >= color.rows)m = color.rows - 1;p.b = color.at<cv::Vec3b>(m, n)[0];p.g = color.at<cv::Vec3b>(m, n)[1];p.r = color.at<cv::Vec3b>(m, n)[2];cloud->points.push_back(p);outfile << p.x << " " << p.y << " " << p.z << " " << (int)p.r << " " << (int)p.g << " " << (int)p.b << std::endl;}}cloud->width = point_nums;cloud->height = 1;//pcl::io::savePCDFile("cloud_1.pcd", *cloud);outfile.close();return 0;
}

rgbd对齐前后点云可视化结果:
在这里插入图片描述
在这里插入图片描述
注意包装盒的右侧、水壶的把手以及右侧和地面接近部分的点云在rgbd对齐前后可以看出较大区别。
本文所有的代码和图片已更新放在下载连接:深度相机和彩色相机对齐(d2c)

这篇关于深度相机和彩色相机对齐(d2c)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/270713

相关文章

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

最新Spring Security实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)

《最新SpringSecurity实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)》本章节介绍了如何通过SpringSecurity实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟... 目录前言改造准备开始登录页改造自定义用户名密码登陆成功失败跳转问题自定义登出前后端分离适配方案结语前言

Redis 内存淘汰策略深度解析(最新推荐)

《Redis内存淘汰策略深度解析(最新推荐)》本文详细探讨了Redis的内存淘汰策略、实现原理、适用场景及最佳实践,介绍了八种内存淘汰策略,包括noeviction、LRU、LFU、TTL、Rand... 目录一、 内存淘汰策略概述二、内存淘汰策略详解2.1 ​noeviction(不淘汰)​2.2 ​LR

Python中如何控制小数点精度与对齐方式

《Python中如何控制小数点精度与对齐方式》在Python编程中,数据输出格式化是一个常见的需求,尤其是在涉及到小数点精度和对齐方式时,下面小编就来为大家介绍一下如何在Python中实现这些功能吧... 目录一、控制小数点精度1. 使用 round() 函数2. 使用字符串格式化二、控制对齐方式1. 使用

Python与DeepSeek的深度融合实战

《Python与DeepSeek的深度融合实战》Python作为最受欢迎的编程语言之一,以其简洁易读的语法、丰富的库和广泛的应用场景,成为了无数开发者的首选,而DeepSeek,作为人工智能领域的新星... 目录一、python与DeepSeek的结合优势二、模型训练1. 数据准备2. 模型架构与参数设置3

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动