一. 环境及硬件介绍
1. Visual Studio 2022
2. OpenCV 4.6.0
3. Open3D 0.15.1
4. Python 3.9
5. 悉灵相机MV-EB435i,SDK版本Mv3dRgbd_V1.0.0_Win
二. 点云数据结构及获取
参考SDK中给出的结构体参数,分析图像格式,以此提取图像。

在ImageData数组中,储存了每帧图像数据所包含的nImageCount个图像。每一副图像的数据由pdata 储存。在定义了结构体stPointCloudImage中,我们所需要的点云数据信息就存储在pdata指针所指向的地址。
RGB_D相机没有直接输出点云数据,而是先输出了深度图像数据。同时,SDK中提供的MV3D_RGBD_MapDepthToPointCloud函数帮助将深度图像转换为点云图像。考虑到本课题将在PyCharm平台进行后续点云处理,于是选择将一帧点云文件单独写入一个.xyz格式的文件,从而生成xyz格式的点云文件,以供其他平台灵活使用。
C++代码
//打开存储文件
std::ofstream os;
std::string path = "D:\\Desktop\\pointscatch\\";
int flagnum = 0;
bool exit_flag = TRUE;
MV3D_RGBD_FRAME_DATA stFrameData = { 0 };
while (exit_flag)
{
// 获取图像数据
int nRet = MV3D_RGBD_FetchFrame(handle, &stFrameData, 5000);
//获取图像帧
if (MV3D_RGBD_OK == nRet)
{
//图像帧很多类型,当类型为深度时将其转换为点云
for (int i = 0; i < stFrameData.nImageCount; i++)
{
//若是深度格式则开始转换
if (ImageType_Depth == stFrameData.stImageData[i].enImageType)
{
path = "D:\\Desktop\\pointscatch\\" + std::to_string(flagnum) + ".xyz";
os.open(path, std::ios::app);
flagnum++;
MV3D_RGBD_IMAGE_DATA stPointCloudImage;
//将当前帧转换为点云
nRet = MV3D_RGBD_MapDepthToPointCloud(handle, &stFrameData.stImageData[i], &stPointCloudImage);
//pPtr 指向 点云存储起始地址
float* pPtr = (float*)stPointCloudImage.pData;
int nPointNum = stPointCloudImage.nDataLen / (sizeof(float) * 3);
//从第一个点云开始向文件流中写入 xyz 坐标
LOGD("Start write Points of %d framenum!", stPointCloudImage.nFrameNum);
for (int nPntIndex = 0; nPntIndex < nPointNum; ++nPntIndex)
{
if (pPtr[nPntIndex * 3 + 0] != 0 || pPtr[nPntIndex * 3 + 1] != 0 || pPtr[nPntIndex * 3 + 2] != 0)
os << pPtr[nPntIndex * 3 + 0] << ' ' << pPtr[nPntIndex * 3 + 1] << ' ' << pPtr[nPntIndex * 3 + 2] << std::endl;
}
LOGD("_MapDepthToPointCloud() Run Succeed: framenum (%d) height(%d) width(%d) len (%d)!", stPointCloudImage.nFrameNum,
stPointCloudImage.nHeight, stPointCloudImage.nWidth, stPointCloudImage.nDataLen);
os.close();
}
//若获取图像帧失败则退出
if (MV3D_RGBD_OK != nRet)
{
break;
}
}
}
//按键退出
if (_kbhit())
{
exit_flag = FALSE;
}
}


三. 点云数据处理
1、点云可视化
确定了点云文件保存路径后,对保存下的.xyz格式的点云文件,在Pycharm平台,直接使用open3d库中函数read_point_cloud和visualization.draw_geometries进行简单的读取和显示。
(以下及后续代码为python)
pcd = o3d.io.read_point_cloud('pointscloud.xyz',format='xyz')
o3d.visualization.draw_geometries([pcd],"Open3D pointscloud", width=800, height=600, left=50, top=50,
point_show_normal=False, mesh_show_wireframe=False,
mesh_show_back_face=False)
2、Raw深度图可视化
由官方提供的demo获取的深度图原始文件。分析了文件存储格式后,将raw文件数据转写成可视化的png文件,图片大小为1280*720。


3、点云去噪
直接读取采集后的点云图像并可视化,会发现存在大量的放射状噪点,采用统计滤波的方法,对点云图像去噪。对于大量采集到的点云文件,进行批量处理,便于从中筛选清晰合适的点云图像做后续处理


4、法向量估计
法向量是点云的一个非常重要的属性。目前很多点云算法的实现,例如点云分割,表面重建等等,都要基于精确的法向量估计。法线估计的实质上就是对于点云空间中的每一个点,在其邻域内估计出一个平面。估计一个平面需要一个点和一个法向量,这个法向量就是我们所要估计的法向量。这个点就是这一群邻域点(与该点自身)的平均值。由空间变换可知,点云中每一点的法向量夹角及曲率值均不随物体的运动而改变,具有刚体运动不变性。
点云法向量求解需要其邻域内点支持,而邻域的大小一般由邻域半径值或临近点个数来表示。现实中需要根据点分别率、物体细节精细程度和用途等因素来取值。过大的邻域会抹平三维结构细节使得法向量过于粗糙,而过小的邻域由于包含了太少的点受噪声干扰程度较强。
先对滤波后的点云进行下采样处理,再进行法线估计,这里使用了open3d库中的KDTreeSearchParamHybrid计算法线并可视化。
print("计算下采样点云的法线")
downpcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) # 设置搜索半径0.1m,邻域内30个点
o3d.visualization.draw_geometries([downpcd], "Open3D normal estimation",
width=800, height=600, left=50, top=50,
point_show_normal=True,
mesh_show_wireframe=False,
mesh_show_back_face=False)

5、包围框计算
本课题采用了两种包围框方法,分别是AABB包围盒和OBB包围盒。两种包围方式尤其各自的特性。AABB包围盒是与坐标轴对齐的包围盒,每一面都与空间坐标轴平行,构造简单, 包围的紧密性较差;OBB为有向包围框,本质上是最贴近物体形状的长方体,所以其紧密性更好。
如下图所示,红色为AABB包围框,绿色为OBB包围框。
# AABB包围盒
aabb = pcd.get_axis_aligned_bounding_box()
aabb.color = (1, 0, 0)
# OBB包围盒
obb = pcd.get_oriented_bounding_box()
obb.color = (0, 1, 0)
o3d.visualization.draw_geometries([pcd, aabb, obb], window_name="点云包围盒",
width=800, height=600,
left=50, top=50,
point_show_normal=False,
mesh_show_wireframe=False,
mesh_show_back_face=False)# 可视化

代码链接: https://github.com/Sumu27/HaikangXiling