当前位置: 首页 > news >正文

【Depth与RGB对齐算法(D2C)】

本文介绍一下RGBD相机中深度图像与RGB图像的对齐算法。大部分RGBD相机是自带RGB与深度图像对齐功能的,开发者可以很方便地调用。但有的开发者使用的RGBD相机是自己用独立的RGB相机与深度相机组建而成的,这时候通常要自己实现RGB与深度对齐的算法。本篇博客的算法主要参考Realsense SDK中的代码。

D2C算法的核心,是解决深度图像从一个相机的图像坐标系到另外一个相机图像坐标系的映射问题,特别是在两个相机分辨率、焦距不同的情况下,如何又快又好地实现深度的映射。

一个简单直接的想法是,首先根据深度相机的内参,将深度图像转化为深度相机坐标系中的点云,然后将点云根据深度相机和RGB相机的相对外参,转换到RGB相机坐标系,最后根据RGB相机的内参,投影转换后的点云到RGB相机的图像坐标系,把点云Z值赋给最近邻的整像素。但是由于RGB相机通常比深度相机有更高的图像分辨率,此方法得到的对齐深度图会存在很多空缺的像素。

本文介绍的算法是此想法的改进。核心思想是把深度图中的像素看成一个1x1大小矩形,而非一个点。对于深度图中所有的像素,计算以其为中心的矩形左上点和右下点在RGB相机图像坐标系中的映射坐标,将映射坐标所构成矩形区域中的所有像素,赋值为矩形左上点和右下点转换后点云Z值的均值。当像素已被赋值时,则取已有值和新赋值的最小值,以正确处理坐标转换带来的遮挡问题。

算法代码如下:

// d2c.h
#pragma once
#include <cstdint>struct Intrinsics { //内参int width, height; //图像长宽float fx, fy; //焦距float cx, cy; //主点
};struct Extrinsics { //外参float R[9]; //旋转矩阵float T[3]; //平移矩阵
};void DepthToColor(const uint16_t* depth, uint16_t* aligned_depth, const Intrinsics& depth_intrinsics, const Intrinsics& color_intrinsics, const Extrinsics& extrinsics);
//d2c.cpp
#include "d2c.h"
#include <algorithm>//将深度值根据深度相机内参转换为点云
static inline void DeprojectPointToPoint(float point[3], const Intrinsics& intrinsics, const float pixel[2], float depth) {point[0] = (pixel[0] - intrinsics.cx) * depth / intrinsics.fx;point[1] = (pixel[1] - intrinsics.cy) * depth / intrinsics.fy;point[2] = depth;
}
//根据外参,将深度相机坐标系中的点云转换到RGB相机坐标系
static inline void TransformPointToPoint(float to[3], const Extrinsics& extrinsics, const float from[3]) {to[0] = extrinsics.R[0] * from[0] + extrinsics.R[1] * from[1] + extrinsics.R[2] * from[2] + extrinsics.T[0];to[1] = extrinsics.R[3] * from[0] + extrinsics.R[4] * from[1] + extrinsics.R[5] * from[2] + extrinsics.T[1];to[2] = extrinsics.R[6] * from[0] + extrinsics.R[7] * from[1] + extrinsics.R[8] * from[2] + extrinsics.T[2];
}
//将RGB相机坐标系的点云投影为图像坐标
static inline void ProjectPointToPixel(float pixel[2], const Intrinsics& intrinsics, const float point[3]) {pixel[0] = (point[0] * intrinsics.fx / point[2]) + intrinsics.cx;pixel[1] = (point[1] * intrinsics.fy / point[2]) + intrinsics.cy;
}
//深度RGB对齐
void DepthToColor(const uint16_t* depth, uint16_t* aligned_depth, const Intrinsics& depth_intrinsics, const Intrinsics& color_intrinsics, const Extrinsics& extrinsics) {for (int depth_y = 0; depth_y < depth_intrinsics.height; ++depth_y) {int depth_pixel_index = depth_y * depth_intrinsics.width;for (int depth_x = 0; depth_x < depth_intrinsics.width; ++depth_x, ++depth_pixel_index) {//跳过深度值为0的像素if (uint16_t z = depth[depth_pixel_index]) {//将左上角点{ depth_x - 0.5f, depth_y - 0.5f }映射到RGB图像坐标float depth_pixel[2] = { depth_x - 0.5f, depth_y - 0.5f }, depth_point[3], other_point0[3], other_point1[3], other_pixel[2];DeprojectPointToPoint(depth_point, depth_intrinsics, depth_pixel, z);TransformPointToPoint(other_point0, extrinsics, depth_point);ProjectPointToPixel(other_pixel, color_intrinsics, other_point0);const int other_x0 = static_cast<int>(other_pixel[0] + 0.5f);//快速四舍五入const int other_y0 = static_cast<int>(other_pixel[1] + 0.5f);//将右下角点{ depth_x + 0.5f, depth_y + 0.5f }映射到RGB图像坐标depth_pixel[0] = depth_x + 0.5f; depth_pixel[1] = depth_y + 0.5f;DeprojectPointToPoint(depth_point, depth_intrinsics, depth_pixel, z);TransformPointToPoint(other_point1, extrinsics, depth_point);ProjectPointToPixel(other_pixel, color_intrinsics, other_point1);const int other_x1 = static_cast<int>(other_pixel[0] + 0.5f);const int other_y1 = static_cast<int>(other_pixel[1] + 0.5f);//映射后的矩形超出RGB图像范围则跳过if (other_x0 < 0 || other_y0 < 0 || other_x1 >= color_intrinsics.width || other_y1 >= color_intrinsics.height)continue;//计算转换点云的Z值均值const uint16_t new_z = (other_point0[2] + other_point1[2]) * 0.5;//对映射后矩阵中的像素进行赋值for (int y = other_y0; y <= other_y1; ++y) {for (int x = other_x0; x <= other_x1; ++x) {const int index = y * color_intrinsics.width + x;const uint16_t aligned_z = aligned_depth[index];aligned_depth[index] = aligned_z ? std::min(aligned_z, new_z) : new_z;//如果已有值,则取两者最小值}}}}}
}

算法运行结果
在这里插入图片描述

http://www.dtcms.com/a/354619.html

相关文章:

  • PPT处理控件Aspose.Slides教程:在 C# 中将 PPTX 转换为 Markdown
  • 项目一系列-第7章 父子组件通信
  • vue3使用axios向spring boot 发送请求,请求响应时间快慢交替问题分析
  • 探索 Vertex AI 与 Elasticsearch
  • Ubuntu 从零到一搭建 Appium+Python 自动化环境(含下厨房真机实战)—2025 版
  • 导出wireshark的FLV RAW数据并进行分析
  • 第13集 当您的USB设备不在已实测支持列表,如何让TOS-WLink支持您的USB设备--答案Wireshark USB抓包
  • [数据结构] ArrayList与顺序表(下)
  • indexDB快速上手
  • 2015考研数学(二)真题
  • 让模糊物体变清晰的视频AI:快速提升画质指南
  • 51c大模型~合集175
  • pcl_案例2 叶片与根茎的分离
  • Redis发布订阅:实时消息系统的极简解决方案
  • MyBatis延迟加载
  • 云计算学习100天-第29天
  • Node.js 的模块化规范是什么?CommonJS 和 ES6 模块有什么区别?
  • Python DELL Logo
  • day1 ———C++———变量和字符串的使用
  • AI驱动企业数字化转型:解码未来三年的智能化变革密码
  • STAGEWISE实战指南:从集成到使用的完整解决方案
  • AI在商业领域的多元应用:从写作助手到精准运营,解锁AI商业工具新价值
  • 流程控制语句(3)
  • 操作系统中的死锁是什么意思
  • 农行广西区分行携手广西专精特新商会共探金融赋能专精特新企业新路径
  • 用KPI导航数字化转型:制造企业如何科学评估系统上线成效
  • 流程控制语句(2)
  • Java网络编程(UDP, TCP, HTTP)
  • 【Linux基础知识系列:第一百一十五篇】使用gzip与bzip2进行压缩
  • 从首次测试到采购40个机器人:Junior kühlk如何自动化协作机械臂矩阵