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

news2024/11/27 6:27:05

一般商用的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对齐前后可以看出较大区别。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/608043.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql ssh隧道连接内网mysql

通过SSH隧道连接MySQL数据库 一.背景 问题所在&#xff1a;MySQL被运用于越来越多的业务中&#xff0c;在关键业务中对数据安全性的要求也更高&#xff0c;数据安全如果只靠MySQL应用层面显然是不够的&#xff0c;虽然说MySQL实现的登录机制基本不存在泄露密码的风险&#xf…

冈萨雷斯DIP第11章知识点

文章目录 11.3 边界特征描述子11.4 区域特征描述子11.4.3 纹理11.4.4 矩不变量 11.6 整体图像特征11.6.1 哈里斯-斯蒂芬斯 角检测器11.6.2 最大稳定极值区域 特征检测&#xff1a;在图像、区域或者边界中发现特征&#xff1b;特征描述&#xff1a;将定量属性分配给检测到的特征…

冈萨雷斯DIP第3章知识点

文章目录 3.1 背景3.2 一些基本的灰度变换函数3.2.1 图像反转3.2.2 对数变换3.2.3 幂律伽马变换3.2.4 分段线性变换函数 3.3 直方图处理3.3.1 直方图均衡化3.3.2 直方图匹配&#xff08;规定化&#xff09;3.3.3 局部直方图处理3.3.4 使用直方图统计量增强图像 3.4 空间滤波基础…

期末sql_server复习枯燥?乏味?一文带你轻松击破sql壁垒!

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…

leetcode197. 上升的温度

【题目】 下面是某公司每天的营业额&#xff0c;表名为“日销”。“日期”这一列的数据类型是日期类型&#xff08;date&#xff09;。 请找出所有比前一天&#xff08;昨天&#xff09;营业额更高的数据。&#xff08;前一天的意思&#xff0c;如果“当天”是1月&#xff0c;“…

在腾讯干软件测试5年,5月无情被辞,想给划水的兄弟提个醒

前段时间&#xff0c;一个认识了好几年在大厂工作做软件测试的朋友&#xff0c;年近30了&#xff0c;却被大厂以“人员优化”的名义无情被辞&#xff0c;据他说&#xff0c;有一个月散伙饭都吃了好几顿…… 在很多企业&#xff0c;都有KPI考核&#xff0c;然后在此基础上还会弄…

[自学记录03|百人计划]移动端GPU的TB(D)R架构基础

一、专有名词解释 1.System on Chip&#xff08;Soc&#xff09; Soc是把CPU、GPU、内存、通信基带、GPS模块等等整合在一起的芯片的称呼。常见有A系Soc&#xff08;苹果&#xff09;&#xff0c;骁龙Soc&#xff08;高通&#xff09;&#xff0c;麒麟Soc&#xff08;华为&am…

【人工智能】— 监督学习、分类问题、决策树、信息增益

【人工智能】— 监督学习、分类问题、决策树、线性分类器、K近邻、回归问题、交叉验证 监督学习 - 正式设置符号表示假设选择学习目标预测 分类Decision Trees 决策树建立决策树分类模型的流程如何建立决策树? 决策树学习表达能力决策树学习信息论在决策树学习中的应用特征选择…

Koa学习2:路由与数据库连接

路由 安装 npm i koa-router基本功能 定义路由&#xff1a;koa-router提供了一种简单的方式来定义路由&#xff0c;我们可以根据请求的方法和路径来定义不同的路由。 处理请求&#xff1a;koa-router可以帮助我们处理请求&#xff0c;当请求匹配到对应的路由时&#xff0c;k…

人工智能轨道交通行业周刊-第47期(2023.5.29-6.4)

本期关键词&#xff1a;郑州智慧地铁、货运安全监控、激光炮、6C系统、越行站、ChatGPT原理 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨…

SparkSQL文件格式和压缩算法是否支持Split

大数据支持Split的目的是为了能并行处理任务&#xff0c;可以将文件拆分成多个文件块处理。如果不支持Split的话&#xff0c;只能用一个任务处理单个文件。 能否支持Split受到文件格式和压缩算法的双重限制&#xff0c;大部分文件的读取都是可以支持Split&#xff0c;极少数压缩…

每日学术速递5.29

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Custom-Edit: Text-Guided Image Editing with Customized Diffusion Models(CVPR 2023) 标题&#xff1a;自定义编辑&#xff1a;使用自定义扩散模型进行文本引导图像编辑 作者&a…

部署rabbitmq3.10.6详细步骤

RabbitMQ简介 RabbitMQ是Erlang开发的&#xff0c;集群非常方便&#xff0c;因为Erlang天生就是分布式语言&#xff0c;但其本身并不支持负载均衡&#xff0c;支持高并发&#xff0c;支持可扩展。支持AJAX&#xff0c;持久化&#xff0c;用于在分布式系统中存储转发消息&#x…

八、Git分支和版本号的简介

1、Git分支介绍 分支在Git中相对较难&#xff0c;分支就是科幻电影里面的平行宇宙&#xff0c;如果两个平行宇宙互不干扰&#xff0c;那对现在的你也没啥影响。不过&#xff0c;在某个时间点&#xff0c;两个平行宇宙合并了&#xff0c;我们就需要处理一些问题了&#xff01; 2…

【AI绘图】一、stable diffusion的发展史

一、stable diffusion的发展史 本文目标&#xff1a;学习交流 对于熟悉SD的同学&#xff0c;一起学习和交流使用过程中的技巧和心得。 帮助新手 帮助没有尝试过SD但又对它感兴趣的同学快速入门&#xff0c;并且能够独立生成以上效果图。 1.发展史介绍&#xff1a; 2015年的时候…

汇编重复计算之使用循环与不循环使用区别

没有使用循环的汇编代码,计算ffff:0-ffff:b的累加之和 assume cs:code 表示code段与CS寄存关联 code segment 表示段开始 ,code ends表示段结束,end表示汇编程序结束 mov ax,0ffffh 表示将ffffh送入ax寄存器 mov ds,ax 表示将ax寄存器值送入ds寄存器 mov …

第二十四章 开发Productions - ObjectScript Productions - 定义业务服务

文章目录 第二十四章 开发Productions - ObjectScript Productions - 定义业务服务介绍关键原则定义业务服务类实施 OnProcessInput() 方法 第二十四章 开发Productions - ObjectScript Productions - 定义业务服务 本页介绍如何定义业务服务类。 提示&#xff1a; IRIS 提供…

高完整性系统 (2):Requirement 与 Design 阶段的风险控制——Hazards, HAZOP, Fault Tree

文章目录 安全性工程流程Hazards反事实推理&#xff08;CounterFactual Reasoning&#xff09;案例1案例2案例3 HAZOP: HAZARDS AND OPERABILITY STUDY案例1HAZOP 工作流程HAZOP 总结 Fault Tree AnalysisFault Tree 定义案例Node Symbolsanalysis outcomes 这节课主要介绍了高…

【Linux集锦01】CentOS的安装

Centos的安装 1.创建新的虚拟机2. 自定义3.下一步4.创建虚拟空白光盘5.安装Linux系统和Centos 7 发行版6.命名虚拟机名称和选择磁盘位置7.处理器配置 主要看自己的电脑的情况8.设置虚拟机内存9.网络设置 nat10.选择IO控制器类型11.选择磁盘类型12.创建新虚拟磁盘13.设置磁盘容量…

【MyBatis】2、MyBatis 的动态 SQL 和增删改操作

目录 一、添加(1) 基本插入(2) 设置新插入记录的主键&#xff08;id&#xff09;★ 二、更新三、删除四、动态 SQL(1) if 标签(2) where 标签(3) foreach 标签 五、起别名六、sql 标签七、在 MyBatis 中集成 druid 连接池 一、添加 (1) 基本插入 <mapper namespace"s…