“示例代码——通过 Intel RealSense 设备获取2D图像、3D点云等原始数据”
先放github链接:https://github.com/denkywu/intel-realsense-camera-data-collection-demo
说明如下:
- 基于Intel RealSense 设备(相机)获取2D图像、3D点云等原始数据。这里对原始数据的定义为:直接通过Intel RealSense SDK 2.0从设备中获取的数据,基本没有经过其他数据后处理。
- 已经测试(使用)过的设备,如下:
- Intel RealSense depth camera D435i
- Intel RealSense depth camera D435
- Intel RealSense depth camera D415
- 理论上来说,只要支持Intel RealSense SDK 2.0的设备都可以通过该示例代码获取数据。只是要注意,某些设备可能拥有或没有某些接口。
- 可以获取的原始数据如下:
- color image,2D彩色图像
- 点云 xyz
- texture image,点云纹理彩色图像,与点云xyz(或者说深度图像)是对齐的
- 点云 xyzrgb,将点云 xyz和texture image组合得到
- ir left image,左侧ir灰度图像
- ir right image,右侧ir灰度图像
- 由于本人并不需要imu数据,所以这方面的内容目前没有涉及
- 示例代码中,主要是实现了一个自定义类”class RealSenseData“(详见文件"realSenseData.h" 和"realSenseData.cpp"),使用该自定义类的demo(main函数)见文件"mainDemo.cpp"。
- 示例代码中,通过配置文件"ConfigCameraRealSense.yaml"可对不同相机(比如color,depth)设置分辨率和帧率等。所以在生成了可执行文件后,需要将该文件拷贝至对应目录下,即可运行程序。
- 数据采集自定义类class RealSenseData中的一些接口示例如下(详细内容请参见github):
public:
// 设备状态
bool devStart();// 设备启动
bool devStop();// 设备关闭
bool isDevConnected() const;// 设备是否连接
bool isDevReady() const;// 设备是否准备好采集数据
// 数据
cv::Mat getColorImage_2dCamera();// 2D彩色相机获取的2D彩色图像
cv::Mat getPointCloud_3dCamera();// 3D相机获取的点云xyz数据
cv::Mat getTextureImage_3dCamera();// 3D相机获取的点云纹理彩色图像
cv::Mat getIrLeftImage_3dCamera();// 3D相机获取的 IR left 图像
cv::Mat getIrRightImage_3dCamera();// 3D相机获取的 IR right 图像
// 相机内参和畸变参数
// 2D相机
cv::Matx33f getIntrinsics_2dCamera();// 2D相机-内参
cv::Matx<float,5,1> getDistortion_2dCamera();// 2D相机-畸变参数
// 3D相机
cv::Matx33f getIntrinsics_3dCamera();// 3D相机-内参
cv::Matx<float,5,1> getDistortion_3dCamera();// 3D相机-畸变参数
// 外参, get Depth-to-Color Extrinsics
cv::Matx33f getRotation();
cv::Matx31f getTranslation();
- 基于Intel RealSense depth camera D415设备,采集的一组数据示例如下:
1)2D彩色图像
2)点云xyzrgb,在CloudCompare中打开后的截图
3)点云纹理彩色图像(将2D彩色图像配准到3D点云上的结果,与深度图像是对齐的,即分辨率和点云相同)
4)IR left 灰度图像(图中暗纹就是D415在采集深度数据时发射的红外散斑)
5)IR right 灰度图像(D415是基于双目视觉Stereo+红外散斑来获取深度图像或者说点云数据的。红外散斑的红外强度是可以调节和关闭的,在SDK 2.0中有相应接口,各位感兴趣的话可以试试。该right灰度图像和上面的left是配套的)
完。
------------------------------------------- 分割线 -------------------------------------------
1、RealSense D435i的图像空间、相机空间的坐标系定义都与OpenCV一致,这里不再赘述(可参考我在这篇博客中对坐标系内容的描述,https://blog.csdn.net/denkywu/article/details/89850166)。
但是,我在使用其SDK中的函数 export_to_ply() 导出点云ply文件时发现了以下大坑,听我慢慢道来:
1)首先获取点云数据,以下是基本框架
rs2::pointcloud rs2_PointCloud;// Declare pointcloud object, for calculating pointclouds and texture mappings
rs2::points rs2_Points;
auto rs2_Frameset = pipe.wait_for_frames();// Set of time synchronized frames, one from each active stream
auto rs2_ColorFrame = rs2_Frameset.get_color_frame();// get video_frame - first found color frame.
auto rs2_DepthFrame = rs2_Frameset.get_depth_frame();// get depth_frame - first found depth frame.
// ---------------------------------------------------------------------------------
rs2_PointCloud.map_to(rs2_ColorFrame);// Tell pointcloud object to map to this frame - as texture.
// 将RGB信息作为纹理与点云(深度图)匹配上
// ! 注意 !
// 要想将深度图(点云)和纹理 .map_to(),必须先进行这一步
// 然后才能进行下面的 .calculate() 计算 rs2::points
// 这是我经过实验发现的,如果将先后顺序反过来,会有问题
// ---------------------------------------------------------------------------------
// Generate the pointcloud and texture mappings
rs2_Points = rs2_PointCloud.calculate(rs2_DepthFrame);// 获取三维点云数据
2)在获取 rs2_Points 后,可直接通过方法 export_to_ply() 导出ply文件到本地路径,例如:
rs2_Points.export_to_ply("pointCloud.ply", rs2_ColorFrame);
下面代码是官方SDK中关于该方法的说明
/**
* Export current point cloud to PLY file
* \param[in] string fname - file name of the PLY to be saved
* \param[in] video_frame texture - the texture for the PLY.
*/
void export_to_ply(const std::string& fname, video_frame texture)
{
rs2_frame* ptr = nullptr;
std::swap(texture.frame_ref, ptr);
rs2_error* e = nullptr;
rs2_export_to_ply(get(), fname.c_str(), ptr, &e);
error::handle(e);
}
/**
* When called on Points frame type, this method creates a ply file of the model with the given file name.
* \param[in] frame Points frame
* \param[in] fname The name for the ply file
* \param[in] texture Texture frame
* \param[out] error If non-null, receives any error that occurs during this call, otherwise, errors are ignored
*/
void rs2_export_to_ply(const rs2_frame* frame, const char* fname, rs2_frame* texture, rs2_error** error);
以上看起来没什么特别的,但是使用该方法导出ply文件并打开后,你会发现:任何一个点的z坐标(深度值)是负数!
经过验证后发现,官方SDK计算的深度图数据以及点云xyz信息都是没问题的(且坐标系定义和OpenCV一致)。如果自行将点云数据导出到ply文件也是没问题的。所以我认为:class points 的方法 export_to_ply() 在导出ply文件时,修改了点云xyz的坐标数值:x不变,y取反,z取反。这使得方法 export_to_ply() 导出的ply文件中点云xyz的数值在 y 和 z 坐标上与原始值相差了一个负号。
官方为何如此设置不得知,我猜测是为了ply文件打开后显示的更直观(比如在CloudCompare中打开,无需旋转,其视角就和“以相机照射方向向前看”是一致的),如果我们自行将点云数据存储并打开,是需要经过旋转才可以的。
这不是什么大问题,但是该函数并未进行说明,我认为挺坑的。一不小心,可能认为(我之前就误以为)RealSense的坐标系定义有某些特别之处。
2、RealSense内存泄漏问题
该问题我在github中找到了一模一样的叙述,“pointcloud processing block causing memory leak #2860”,网址https://github.com/IntelRealSense/librealsense/issues/2860
简单来说,就是把 rs2::pointcloud 放在循环中会导致内存泄漏(时间长了一定会发生。因为在每一次循环中该类生成的实例并未完全释放),解决方法就是将其放在循环以外。Mark
—— 后续再更新