Open3d作为免费开源的点云处理库,其在点云可视化、渲染等方面具有独特优势,作为一个还在不断发展拓新的开源库,open3d的版本更新迭代差别很大,目前最新的版本库为0.15.1,也存在一些方面的编译和运行问题,比如目前最新库还不能在MSVC上有效运行,作者也在摸索中,目前使用的是open3d0.10版本,在基本可视化模块部分与最新版本不冲突,文章主要内容包含SDK相机数据结构、open3d读取sdk数据、可视化。
一.RGB_D相机SDK图像数据结构详解
存储RGB_D相机每一帧的图像数据,即数据帧结构为: MV3D_RGBD_FRAME_DATA,其具体结构如下:
typedef struct _MV3D_RGBD_FRAME_DATA_
{
uint32_t nImageCount; // 图像个数,表示stImage数组的有效个数
MV3D_RGBD_IMAGE_DATA tImageData[MV3D_RGBD_MAX_IMAGE_COUNT]; // 图像数组,每一个代表一种类型的图像
uint8_t nReserved[16]; // 保留字节
} MV3D_RGBD_FRAME_DATA;
如结构体中所示,每一帧的图像数据会有nImageCount个图像,这些图像会存储在tImageData数组中,这些图像都是_MV3D_RGBD_IMAGE_DATA_结构体类型,但也根据Mv3dRgbdImageType分为多种类型,该结构体如下所示:
//相机图像
typedef struct _MV3D_RGBD_IMAGE_DATA_
{
Mv3dRgbdImageType enImageType; // 图像格式
uint32_t nWidth; // 图像宽
uint32_t nHeight; // 图像高
uint8_t* pData; // 相机输出的图像数据
uint32_t nDataLen; // 图像数据长度(字节)
uint32_t nFrameNum; // 帧号,代表第几帧图像
int64_t nTimeStamp; // 设备上报的时间戳 (设备上电从0开始,规则详见设备手册)
uint8_t nReserved[16]; // 保留字节
} MV3D_RGBD_IMAGE_DATA;
主要包含图像格式、图像参数(图像宽、高)、图像数据(pData)、图像数据的长度等。
常见图像格式Mv3dRgbdImageType存储的图像数据如下:
图像格式(Mv3dRgbdImageType) | 图像数据(pData) |
---|
ImageType_YUV422 | YUV格式(16bit) |
ImageType_RGB8_Planar
| RGB值(planar存储形式,24bit) |
ImageType_PointCloud
| XYZ直角坐标系位置(96bit) |
ImageType_Depth
| 深度图深度信息(16bit) |
RGB_D相机直接输出数据格式ImageType_Depth(深度图)和ImageType_YUV422(YUV格式图像)比较常用,但没有直接输出点云数据,这就要我们自己转换获取点云数据。(注意,这里并未涉及标定,故显示的是深度图坐标下的点云数据,深度图的深度信息就是XYZ坐标系下的Z值)
有幸的是SDK给我们提供了这个转换接口: MV3D_RGBD_MapDepthToPointCloud()函数可以将通过深度图,获取点云格式的图像数据。
//深度图转点云
MV3D_RGBD_FRAME_DATA stFrameData ;//RGB_D相机图像帧数据
// 获取图像数据
int nRet = MV3D_RGBD_FetchFrame(handle, &stFrameData, 5000);
while (!bExit_Main)
{
if (MV3D_RGBD_OK == nRet)
{
for (int i = 0; i < stFrameData.nImageCount; i++)
{
if (ImageType_Depth == stFrameData.stImageData[i].enImageType)//读取深度图
{
//定义图像结构体
MV3D_RGBD_IMAGE_DATA stPointCloudImage;
//函数:MV3D_RGBD_MapDepthToPointCloud//该语句后,stPointCloudImage的Mv3dRgbdImageType变为ImageType_PointCloud
nRet = MV3D_RGBD_MapDepthToPointCloud(handle, &stFrameData.stImageData[i], &stPointCloudImage);
if (MV3D_RGBD_OK != nRet)
{
cout << "MV3D_RGBD_OK != nRet" << endl;
break;
}
//点云数据处理程序
/*……………………*/
}
}
到了这步,SDK输出的点云数据、深度信息等图像数据,都已经获取。接下来就是将数据转换到open3d中。
总结:
1.获取的数据是存储在SDK中的,每次读取一帧图像,会包含深度图、RGB图等多组图像数组,通过Mv3dRgbdImageType来获取所需类型的图像。
2.MV3D_RGBD_MapDepthToPointCloud()函数可以将通过深度图,获取点云格式的图像数据。
二.Open3d点云数据读取和可视化实现
首先介绍一下Open3d点云数据,其数据封装到PointCloud类中,Open3d为了方面后面的点云处理、渲染等功能,PointCloud不是单单封装了点云数据的类,其继承了Geometry3D类(继承类中的几何特性),PointCloud也有自己特有功能接口。我们先完成第一步,数据读取。
PointCloud对外提供了三个数据存储容器:points_(点云位置)、normals _(法线)、colors_(点RGB信息)。可以看到points_容器存储点云数据类型是双精度。以下是源码部分
class PointCloud
{
private:
/*略*/
public:
std::vector points_;
std::vector normals_;
std::vector colors_;//点云颜色存储
};
stPointCloudImage的
pData指针指向的就是我们想要获取的点云数据位置信息,
pData是字符串指针类型,指向所存储的图像数据地址,如果我们要获取点云数据。只要按以下步骤就可以实现:
//一个简单转换demo:
using namespace open3d;
//pointcloud为open3d点云数据智能指针
shared_ptr pointcloud = std::make_shared();
//计算单点数据长度
int step = 3 * sizeof(float);
//计算总点数
int sum = stPointCloudImage->nDataLen / step;//这里的stPointCloudImage就是第一节最终获取的点云格式图像
//获取点云数据存储指针
float* p_data = (float*)stPointCloudImage->pData;
for (int i = sum - 1; i >= 0; --i)
{
float* add = p_data + i * 3;
if (*(add + 2) > 60000||*(add+2)<20)//剔除无效点,对于深度信息过大、过小点视为无效点,剔除
continue;
Eigen::Vector3d pp(*(add), *(add + 1)*, *(add + 2));
pointcloud->points_.emplace_back(pp);
}
完成数据读取后,就是点云可视化,PointCloud类继承了Geometry3D,故可以直接使用Open3d的渲染和可视化接口。
第一种,最直接的方式,使用open3d::visualization::DrawGeometries函数,可以直接显示点云。
/*
源代码提供的函数接口
bool DrawGeometries(const std::vector>
&geometry_ptrs,
const std::string &window_name = "Open3D",
int width = 640,
int height = 480,
int left = 50,
int top = 50);
*/
//例子:
open3d::visualization::DrawGeometries({ pointcloud });//pointcloud为转换获取的点云数据
根据源代码open3d/visualization/utility/DrawGeometry.c可知,该方法在设置好显示格式后,直接调用visualization::Visualizer类型的Run()函数,该函数会直接阻塞线程直到运行结束,这将导致后续的处理程序无法运行,仅适合单做点云数据可视化。
第二种,直接使用visualization::Visualizer,其常规使用模板如下
visualization::Visualizer visualizer;
//窗口参数设置
visualizer.CreateVisualizerWindow("open3d_try", 640, 480, 50, 50, true);
//窗口中类型参数设置,open3d单独提供了点、面等几何信息特征设置、光照等,主要用来点云渲染设置
visualizer.GetRenderOption().point_size_ = 1;//设置点的大小
visualizer.GetRenderOption().background_color_ = Eigen::Vector3d(1, 1, 1);//背景设置
while(!bExit_Main)//必须有循环,否则当该线程退出时,也会导致可视化线程退出
{
visualizer.AddGeometry(pointcloud);//注意,添加显示对象时,必须是Geometry或其派生类,且类型必须是共享智能指针类型。
visualizer.UpdateGeometry();//更新几何
visualizer.PollEvents();
visualizer.UpdateRender();//更新可视化界面
}
visualizer.DestroyVisualizerWindow();//窗口销毁,内存回收,不能忘!
其中一点需要注意的是,
一定要记住open3d可视化是单独开辟一个线程来显示的,因此主线程不能提前退出!点云渲染方面,open3d内置了默认的点云颜色渲染方式,此处结合源码简略介绍一下该设置选择,open3d中 PointCloud options中的颜色渲染设置
```c++
//PointColorOption为PointCloud的颜色渲染方式设置
enum class PointColorOption {
Default = 0,
Color = 1,//按实际点云颜色设置,即渲染交给我们设置的点云颜色数组colors_
//按字段x进行渲染
XCoordinate = 2,
//按字段y进行渲染
YCoordinate = 3,
//按字段z进行渲染
ZCoordinate = 4,
Normal = 9,//按曲率/法线渲染
};
//使用,因为opend3d0.10版本中该枚举类型封装在类中且为非静态成员,对源代码RenderOption.h文件进行修改,将该枚举类型放置在RenderOption类外部的作用域。修改如下,namespace visualization{…………//位置放这enum class PointColorOption {…………}class RenderOption:public utility::IJsonConvertible{…………}}
//之后使用,
visualizer.GetRenderOption().point_color_option_ = open3d::visualization::PointColorOption(9);//设置按曲率进行渲染
补充:点云的颜色设置方法有
方一:调用PaintUniformColor
pointcloud->PaintUniformColor({1,0,0});//所有点云颜色统一为红色
方二:如果想自己设置点云颜色,可以直接对pointcloud->colors_数组进行修改,自定义渲染算法就可以调用该接口,将PointColorOption 设置为1
三.RGB_D相机动态点云读取demo
通过内容一、二的open3d可视化学习,可知:可视化以及数据读取都是实时进行的,这明显是一种并发的工作模式,故通过多线程方式可以实现点云数据的实时读取,以及渲染可视化。
demo框架为:一个主线程获取设备信息,设置两个工作线程(一个进行数据读取与处理,一个进行点云渲染和显示);工作线程的设置,主要就是在完成读取数据(结合内容1)、可视化功能实现(结合内容2)情况下,通过信号量等手段保证
线程安全即可。
代码大致如下(主线程核心demo)
int main()
{
/* #####读取设备信息*/
MV3D_RGBD_VERSION_INFO stVersion;
ASSERT_OK(MV3D_RGBD_GetSDKVersion(&stVersion));
LOGD("dll version: %d.%d.%d", stVersion.nMajor, stVersion.nMinor, stVersion.nRevision);
ASSERT_OK(MV3D_RGBD_Initialize());
unsigned int nDevNum = 0;
ASSERT_OK(MV3D_RGBD_GetDeviceNumber(DeviceType_Ethernet | DeviceType_USB, &nDevNum));//网络/USB接口
LOGD("MV3D_RGBD_GetDeviceNumber success! nDevNum:%d.", nDevNum);
ASSERT(nDevNum);
// 查找设备
std::vector devs(nDevNum);
ASSERT_OK(MV3D_RGBD_GetDeviceList(DeviceType_Ethernet | DeviceType_USB, &devs[0], nDevNum, &nDevNum));
for (unsigned int i = 0; i < nDevNum; i++)
{
LOG("Index[%d]. SerialNum[%s] IP[%s] name[%s].\r\n", i, devs[i].chSerialNumber, devs[i].SpecialInfo.stNetInfo.chCurrentIp, devs[i].chModelName);
}
//打开设备
void* handle = NULL;
unsigned int nIndex = 9;
cout << "选择设备" << endl;
cin >> nIndex;
ASSERT_OK(MV3D_RGBD_OpenDevice(&handle, &devs[nIndex]));
LOGD("OpenDevice success.");
// 开始工作流程
ASSERT_OK(MV3D_RGBD_Start(handle));
LOGD("Start work success.");
BOOL bExit_Main = FALSE;
MV3D_RGBD_FRAME_DATA stFrameData = { 0 };
depth_to_points* sol=new depth_to_points();//depth_to_points为封装了点云转换、存储容器等自定义类
bool flag = true;
//设置两个工作线程
//数据读取线程
thread my_refush(refush_buffer,sol,&stFrameData,handle);
//点云可视化线程
thread my_vis(visual_open3d, sol->get_pointcloud());
my_refush.detach();
my_vis.join();//
delete sol;
ASSERT_OK(MV3D_RGBD_Stop(handle));
ASSERT_OK(MV3D_RGBD_CloseDevice(&handle));
ASSERT_OK(MV3D_RGBD_Release());
LOGD("Main done!");
return 0;
}
运行结果:
