深度图和RGB图对齐

坐标系间的转换_坐标系转换-CSDN博客

深度图与彩色图的配准与对齐_彩色 深度 配准-CSDN博客

kinect 2.0 SDK学习笔记(四)--深度图与彩色图对齐_mapdepthframetocolorspace-CSDN博客

相机标定(三)-相机成像模型_相机小孔成像模型-CSDN博客

1.原理部分

立体视觉往往会设计到:世界坐标系、相机坐标系、图像坐标系和像素坐标系。

        世界坐标系是为了更好的描述相机的位置,双目立体视觉中一般将世界坐标系原点定在左相机或者右相机又或者两者x轴方向的中点。

①首先从世界坐标系到相机坐标系

世界坐标系中的某一个点通过平移旋转可以获得相机坐标系的位置。

详细推理请看第一篇博客。

②从相机坐标系到图像坐标系,透视变换,从3d到2d,投影点的单位是mm,将它转换到像素坐标系单位才会变成pixel。

        图像坐标系的原点为相机光轴与成像平面的交点,通常情况下是成像平面的中点或者叫principal point。图像坐标系的单位是mm,属于物理单位,而像素坐标系的单位是pixel。

        一个三维中的坐标点,的确可以在图像中找到一个对应的像素点,但是反过来,通过图像中的一个点找到它在三维中对应的点就很成了一个问题,因为我们并不知道等式左边的Zc的值。 

2.深度图和彩色图的配准

        深度图和彩色图的配准又叫对齐,之所以需要进行配准,是因为深度图中的坐标点是在深度相机坐标系下获得,彩色图的坐标点是在RGB相机坐标系下获得的。

        需要先使用张正友标定等标定法,对深度相机和RGB相机分别进行标定,获得相机的内外参矩阵。

       设P_ir为在深度摄像头坐标下某点的空间坐标p_ir为该点在像平面上的投影坐标(x、y单位为像素,z等于深度值,单位为毫米)H_ir为深度摄像头的内参矩阵,由小孔成像模型可知:(公式一)

        小孔成像模型实际上是透视投影(perspective projection)的一种最简单的形式。假设我们要将真实世界中的三维点( X , Y , Z )  投影为二维图像上的点p = ( x , y ) 则有:

        设P_rgb为在RGB摄像头坐标下同一点的空间坐标,p_rgb为该点在RGB像平面上的投影坐标H_rgb为RGB摄像头的内参矩阵。由于深度摄像头的坐标和RGB摄像头的坐标不同,他们之间可以用一个旋转平移变换联系起来,即:(公式二)

       其中R为旋转矩阵,T为平移向量。最后再用H_rgb对P_rgb投影,即可得到该点对应的RGB坐标(空间坐标到像素坐标的关系):(公式三)

         p_ir和p_rgb使用的都是齐次坐标,在构造p_ir时,应将原始的像素坐标(x,y)乘以深度值,而最终的RGB像素坐标必须将p_rgb除以z分量,即(x/z,y/z),且z分量的值即为该点到RGB摄像头的距离(单位为毫米)。

       如何求联系两个坐标系的旋转矩阵和平移向量。这就要用到摄像头的外参了。

        外参矩阵实际上也是由一个旋转矩阵R_ir(R_rgb)和平移向量T_ir(T_rgb)构成的,它表示将一个世界坐标系下的点P变换到摄像头坐标系下,分别对深度摄像头和RGB摄像头进行变换,有以下关系:(公式四)   世界坐标系与相机坐标系的关系,用到外参矩阵。

        上式中先将P用P_ir、R_ir和T_ir的表达式表示,再代入第二个公式,可得:

        上式可以看出,这是在将P_ir变换为P_rgb,对比之前的式子:

可以求出:

        这就是最终两个深度相机坐标系和RGB坐标系之间的坐标转换关系,就是博客三中获得的关系式,但是当时看他的博客,没看懂它是怎么出来的公式,现在才明白。建议大家看第二篇博客更好懂一点,但是每个人理解能力不一样,说不定有的人看第三篇更好懂,个人看法。

小孔成像模型:

        因此,我们只需在同一场景下,得到棋盘相对于深度摄像头和RGB摄像头的外参矩阵,即可算出联系两摄像头坐标系的变换矩阵(注意,所有旋转矩阵都是正交阵,因此可用转置运算代替求逆运算)。虽然不同场景下得到的外参矩阵都不同,计算得到的R和T也有一些变化。

import cv2
import numpy as np
import oppenni as opp
from openni import openni2
    
dframe_data = cv2.imread('depth.png',)
frame = cv2.imread('frame.png')#
#dframe_data = np.array(frame.get_buffer_as_triplet()).reshape([480, 640, 2])
dpt1 = np.asarray(dframe_data[:, :, 0], dtype='float32')
dpt2 = np.asarray(dframe_data[:, :, 1], dtype='float32')

dpt2 *= 255
#对于为什么要乘于255的解答
#深度图像的深度值 是按照16位长度(两字节)的数据格式存储的,也可以认为前八位是高字节,后八位是低字节。
#因此一张深度图像如果是 640480分辨率的话,那么图像字节大小 就是 640480*2,其中一个字节是8位(255)
# 乘以 255 是为了将低字节部分从0-255的范围转换到0-65535的范围,这样就可以与高字节部分相加得到完整的16位深度值
dpt = dpt1 + dpt2
#cv2里面的函数,就是类似于一种筛选
'假设我们需要让我们的深度摄像头感兴趣的距离范围有差别地显示,那么我们就需要确定一个合适的alpha值,公式为:有效距离*alpha=255,' \
'假设我们想让深度摄像头8m距离内的深度被显示,>8m的与8m的颜色显示相同,那么alpha=255/(8*10^3)≈0.03,' \
'假设我们想让深度摄像头6m距离内的深度被显示,>6m的与6m的颜色显示相同,那么alpha=255/(6*10^3)≈0.0425'
cv2.imshow('depth_src', dpt)
dim_gray = cv2.convertScaleAbs(dpt, alpha=0.17)
#对深度图像进行一种图像的渲染,目前有11种渲染方式,大家可以逐一去试下
depth_colormap = cv2.applyColorMap(dim_gray, 2)  # 有0~11种渲染的模式
cv2.imshow('depth', depth_colormap)
ret, frame = cap.read()
cv2.imshow('color', frame)

key = cv2.waitKey(1)
if int(key) == ord('q'):
    break            

# rgb相机的内参矩阵(旋转矩阵 平移矩阵)     相机坐标系到图像坐标系
RK_rgb = np.array([(0.999993, 0.00372933, -0.000414306), (-0.00372927, 0.999993, 0.000135122), (0.000414807, -0.000133576, 1)])
#TK_rgb = np.array([-0.0148581, -8.0544e-05, 2.60393e-05])

# 深度相机的内参矩阵
RK_dep = np.array([(0.999993, 0.00372933, -0.000414306), (-0.00372927, 0.999993, 0.000135122), (0.000414807, -0.000133576, 1)])
#TK_dep = np.array([-0.0148581, -8.0544e-05, 2.60393e-05])

# rgb相机的外参矩阵(旋转矩阵 平移矩阵)     世界坐标系到相机坐标系
R_rgb = np.array([(0.999993, 0.00372933, -0.000414306), (-0.00372927, 0.999993, 0.000135122), (0.000414807, -0.000133576, 1)])
T_rgb = np.array([-0.0148581, -8.0544e-05, 2.60393e-05])

# 深度相机的外参矩阵
R_dep = np.array([(0.999993, 0.00372933, -0.000414306), (-0.00372927, 0.999993, 0.000135122), (0.000414807, -0.000133576, 1)])
T_dep = np.array([-0.0148581, -8.0544e-05, 2.60393e-05])


Rir_rgb = np.dot(R_rgb @ np.linalg.inv(R_dep))             # 两个相机坐标系之间的变换矩阵       
Tir_rgb = T_rgb-R_rgb@np.linalg.inv(R_dep)@T_dep 
R = RK_rgb@Rir_rgb@np.linalg.inv(RK_dep)                   # 像素点之间的变换关系矩阵
T = RK_rgb@Tir_rgb

# 形状
result = np.zeros((dpt.shape[0], dpt.shape[1], 3), dtype=np.uint8)
i = 0 
# 遍历的深度图    深度图在rgb图上对齐  找到对应的像素点
for row in range(dpt.shape[0]):
    for col in range(dpt.shape[1]):
        depth_value = dpt[row, col]                 # 获取深度值
        if depth_value != 0 and depth_value != 65535:
            # 投影到彩色坐标系上的坐标
            uv_depth = np.array([col, row, 1.0])      # 齐次坐标    
            uv_color = depth_value / 1000.0 * np.dot(R, uv_depth) + T / 1000.0          # Z_rgb*p_rgb=R*Z_ir*p_ir+T;; (除以1000,是为了从毫米变米)   深度坐标系下的其次标点加上旋转平移获得egb坐标系下的点
            print("uv_color is:", uv_color, '\n', uv_color.shape)
            X = int(uv_color[0] / uv_color[2])                                          # 除以齐次坐标的最后一个元素获得像素值
            Y = int(uv_color[1] / uv_color[2])                                          # // Z_rgb*p_rgb -> p_rgb
            if 0 <= X < frame.shape[0] and 0 <= Y < frame.shape[1]:
                result[row, col] = frame[Y, X]
            else:
                result[row, col] = [0, 0, 0]
    i += 1
cv2.imwrite(save_path+'registrationResult.png', result)
cv2.imwrite(save_path+'src_depth.png', dpt)
cv2.imwrite(save_path+'color_depth.png', depth_colormap)
cv2.imwrite(save_path+'rgb.png', frame)   

代码是根据其他博客和相关技术写的,还未进行测试,如果有什么问题,大家可以提出来,马上修改。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

天润融通突破AI客服局限,三大关键提升文本机器人问答效果

近期&#xff0c;AI客服再次登上热搜&#xff0c;引发网友集体吐槽&#xff0c;比如AI客服虽然态度客气&#xff0c;但听不懂客户诉求&#xff0c;回答问题驴唇不对马嘴&#xff0c;解决不了问题...... 更有网友将这些问题升级到&#xff0c;企业就是不想解决问题才交给AI客服…

微服务之间调用,OpenFeign传递用户(RequestInterceptor接口)

场景&#xff1a;微服务之黑马商城项目-登录拦截器在网关完成用户的校验&#xff0c;并将用户信息&#xff08;用户id&#xff09;存入请求头&#xff0c;假设将购物车里面的商品进行结算就会生成订单并清空购物车&#xff0c;这里涉及到了交易服务模块远程调用购物车模块&…

Java避坑案例 - “激进”的线程池扩容策略及实现

文章目录 问题思路线程池的默认行为自定义线程池扩容策略Code实现小结 问题 Java 线程池是先用工作队列来存放来不及处理的任务&#xff0c;满了之后再扩容线程池。当我们的工作队列设置得很大时&#xff0c;最大线程数这个参数显得没有意义&#xff0c;因为队列很难满&#x…

OpenAI低调发布多智能体工具Swarm:让多个智能体协同工作!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

C++设计模式结构型模式———适配器模式

文章目录 一、引言二、适配器模式三、类适配器四、总结 一、引言 适配器模式是一种结构型设计模式&#xff0c;它在日常生活中有着广泛的应用&#xff0c;比如各种转换接头和电源适配器&#xff0c;它们的主要作用是解决接口不兼容的问题。就像使用电源适配器将220V的市电转换…

【Clickhouse】客户端连接工具配置

ClickHouse 是什么 ClickHouse 是一个分布式实时分析型列式存储数据库。具备高性能&#xff0c;支撑PB级数据&#xff0c;提供实时分析&#xff0c;稳定可扩展等特性。适用于数据仓库、BI报表、监控系统、互联网用户行为分析、广告投放业务以及工业、物联网等分析和时序应用场…

巴西电商市场神仙打架,美客多多月蝉联访问量榜首,9月Temu位居巴西APP下载量榜首

巴西电商市场近年来呈现出强劲的增长趋势&#xff0c;预计2024年巴西电子商务市场的销售额将达到2043亿雷亚尔&#xff08;约合373亿美元&#xff09;&#xff0c;同比增长约10%。作为拉美地区最大的经济体&#xff0c;巴西吸引了众多电商平台和商家&#xff0c;巴西电商市场竞…

Remix中struct入参

Remix中struct入参 // SPDX-License-Identifier: MIT pragma solidity 0.8.28;contract StructDemo {struct Student {uint256 id;string name;}// 初始化一个结构体Student public student;function initStudent5(Student memory _stu) public {student _stu;} }结构体最终…

网络请求自定义header导致跨域问题

我记得我的项目之前已经解决了跨域问题。 后来在功能开发着&#xff0c;需要添加一个自定义的header&#xff0c;发现又出现跨域报错。 于是又开始一通摸索折腾。 我的项目前面端是用axios网络请求&#xff0c;通过拦截器添加header&#xff0c;代码如下&#xff1a; //添加请…

leetcode344. Reverse String

Write a function that reverses a string. The input string is given as an array of characters s. You must do this by modifying the input array in-place with O(1) extra memory. Example 1: Input: s [“h”,“e”,“l”,“l”,“o”] Output: [“o”,“l”,“l”…

郎酒不做酱香“凤尾”,白酒首富汪俊林要做兼香“鸡头”

前两天&#xff0c;《2024胡润百富榜》发布&#xff0c;郎酒集团董事长汪俊林以590亿元财富位列榜单第65位&#xff0c;虽仍是白酒行业首富&#xff0c;但排名较去年下降18位&#xff0c;财富缩水17%。 个人财富的缩水&#xff0c;或许和身后郎酒的困境息息相关。发展40年来&am…

【力扣】[Java版] 刷题笔记-104. 二叉树的最大深度

题目&#xff1a;104. 二叉树的最大深度 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 解题思路 有关二叉树的题&#xff0c;最先想到的就是利用递归方法遍历。 解题过程 分别计算左右子树的最大…

HCIP-HarmonyOS Application Developer 习题(十七)

&#xff08;判断&#xff09;1、对于用户创建的一些临时卡片在遇到卡片服务框架死亡重启&#xff0c;此时临时卡片数据在卡片管理服务中已经删除&#xff0c;且对应的卡片ID不会通知到提供方&#xff0c;所以卡片使用方需要自己负责清理长时间未刚除的临时卡片数据。 答案&…

2024下半年软考全国计算机软考高级考试,带你一文读懂软考!

一、软考是什么&#xff1f; 全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff0c;简称“软考”&#xff0c;分为初级、中级、高级三个级别&#xff0c;国家级考试&#xff0c;证书含金量很高。 作为IT人&#xff0c;有哪些科目可以报考? 可参考202…

Vue3 学习笔记(十三)Vue组件详解

1、组件&#xff08;Component&#xff09; 介绍 组件&#xff08;Component&#xff09;是 Vue.js 最强大的功能之一。 组件可以扩展 HTML 元素&#xff0c;封装可重用的代码&#xff0c;可以帮助你将用户界面拆分成独立和可复用的部分。 每个 Vue 组件都是一个独立的 Vue 实…

快速入门kotlin编程(精简但全面版)

注&#xff1a;本文章为个人学习记录&#xff0c;如有错误&#xff0c;欢迎留言指正。 目录 1. 变量 1.1 变量声明 1.2 数据类型 2. 函数 3. 判断语句 3.1 if 3.2 when语句 4. 循环语句 4.1 while 4.2 for-in 5. 类和对象 5.1 类的创建和对象的初始化 5.2 继承 5…

性能之光 年度电竞性能旗舰iQOO 13发布

2024年10月30日&#xff0c;被定义为“性能之光”的年度电竞性能旗舰——iQOO 13正式发布&#xff0c;售价3999元起。iQOO 13作为iQOO 品牌在性能上的又一次深入探索&#xff0c;它像是一束光&#xff0c;引领行业不断拉高性能上限&#xff0c;让用户看到更多的可能性。 iQOO …

ubuntu内核更新导致显卡驱动掉的解决办法

方法1&#xff0c;DKMS指定内核版本 用第一个就行 1&#xff0c;借鉴别人博客解决方法 2&#xff0c;借鉴别人博客解决方法 方法2&#xff0c;删除多于内核的方法 系统版本&#xff1a;ubuntu20.24 这个方法是下下策&#xff0c;如果重装驱动还是不行&#xff0c;就删内核在…

端到端拥塞控制的公平性和稳定性

昨天早上环城河跑步时的两个思考&#xff0c;发了朋友圈&#xff0c;简单总结成文。 拥塞控制算法公平性度量要重新评估&#xff01;仅以带宽公平性做论断是过时且自私的&#xff0c;在全局视角&#xff0c;平衡和稳定一定以某种表现为乘积 “矩” 来保证&#xff0c;比如力矩…

Vue 组件生命周期(四)

Vue 组件生命周期 Vue3 的组件生命周期可以概括为四个阶段&#xff1a;创建、挂载、更新、销毁。每个阶段都包含了一组钩子函数&#xff0c;用于在不同阶段执行特定的操作。 生命周期各阶段对应以下 Hooks 函数&#xff1a; 一、创建阶段 setup() Vue3 引入的新生命周期函数&am…