目录
2 Ball pivoting reconstruction
3 poisson surface reconstruction
在点云处理的章节中已经介绍使用estimate_normals来生成点云的发现信息,但该方法通过拟合局部3D点来生成法线信息,因此生成的法线朝向一致性不够好。此处使用最小生成树来传播法线的方向,提高朝向的一致性。该方法为orient_normals_consistent_tangent_plane
import numpy as np
import open3d as o3d
import copy
if __name__ == '__main__':
ply_point_cloud = o3d.data.PLYPointCloud()
pcd: o3d.geometry.PointCloud = o3d.io.read_point_cloud(ply_point_cloud.path)
pcd = pcd.voxel_down_sample(voxel_size=0.06)
# pcd = gt_mesh.sample_points_poisson_disk(5000)
# invalidate existing normals
# 使得原有的法线信息失效
pcd.normals = o3d.utility.Vector3dVector(np.zeros((1, 3)))
pcd.estimate_normals()
# o3d.visualization.draw_geometries([pcd], point_show_normal=True)
pcd_spanning_tree: o3d.geometry.PointCloud = copy.deepcopy(pcd)
# 参数K用于构造用于传播法线方向的黎曼图的k个最近邻数。
pcd_spanning_tree.orient_normals_consistent_tangent_plane(k=300)
pcd_spanning_tree.translate([4, 0, 0])
o3d.visualization.draw_geometries([pcd_spanning_tree, pcd], point_show_normal=True)

可以看到经过法线一致性调整后,法线朝向的一致性变得统一。
之前我们已经介绍了点云(point cloud)和面片(mesh)的内容,并且可以将mesh通过采样的方法得到点云信息,但是如何将稀疏的无结构点云重新生成表面mesh呢? 这就涉及到点云的表面重建的内容了。
在open3d中,为我们提供了如下几种的表面重建方法:
Alpha shapes [Edelsbrunner1983]
Ball pivoting [Bernardini1999]
Poisson surface reconstruction [Kazhdan2006]
alpha shapes 实际上是一种点云的边界提取算法,如下图所示:

选取半径为alpha大小的圆,并将此圆在空间的无序点云上进行滚动,然后绘制出轮廓线,该轮廓线就是点云的表面信息,如果alpha足够大,可以将该发放看做是求取点云的convex hull(凸包计算)。详细的解析可以看斯坦福的报告 。
代码示例:
import copy
import open3d as o3d
if __name__ == "__main__":
bunny = o3d.data.BunnyMesh()
mesh = o3d.io.read_triangle_mesh(bunny.path)
mesh.compute_vertex_normals()
# 对mesh进行采样,得到点云
pcd = mesh.sample_points_poisson_disk(850)
print("Displaying input pointcloud ...")
o3d.visualization.draw_geometries([pcd])
# 设置alpha shapes中的参数alpha
alpha = 0.02
mesh_list = [pcd]
for index in range(1, 4):
print(f"alpha={alpha:.3f}")
print('Running alpha shapes surface reconstruction ...')
alpha = alpha * index
tetra_mesh, pt_map = o3d.geometry.TetraMesh.create_from_point_cloud(pcd)
mesh: o3d.geometry.TriangleMesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(
pcd, alpha)
mesh.compute_triangle_normals(normalized=True)
mesh = mesh.translate([0.2 * index, 0, 0])
mesh_list.append(copy.deepcopy(mesh))
del mesh
print("Displaying reconstructed mesh ...")
# 显示时,mesh_show_back_face=True : mesh内部视角也显示mesh面片
o3d.visualization.draw_geometries(mesh_list, mesh_show_back_face=True)

Ball pivoting算法的实现思路与alpha shapes接近;想象一下, 一个给定半径大小的球体朝点云扔去,如果球体击中任意三个点且没有重中穿过,则该三个点创建一个triangles mesh;然后该算法以现有的这个triangles mesh的边沿开始进行周围的迭代,当击中另外三个点没有穿过时,创建另一个三角形。
import open3d as o3d
if __name__ == "__main__":
bunny = o3d.data.BunnyMesh()
gt_mesh = o3d.io.read_triangle_mesh(bunny.path)
gt_mesh.compute_vertex_normals()
pcd = gt_mesh.sample_points_poisson_disk(3000)
print("Displaying input pointcloud ...")
# o3d.visualization.draw_geometries([pcd])
radii = [0.005, 0.01, 0.02, 0.04]
print('Running ball pivoting surface reconstruction ...')
# pcd中需要包含法线信息
rec_mesh: o3d.geometry.TriangleMesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
pcd, o3d.utility.DoubleVector(radii))
print("Displaying reconstructed mesh ...")
o3d.visualization.draw_geometries([rec_mesh, pcd])
3 poisson surface reconstruction
其中上面的点为点云的信息,mesh根据点云的信息进行生成
poisson surface reconstruction解决正则优化问题来生成更加平滑的表面信息,基于此,而上面提及的重建算法直接使用point cloud不加以修改的作为mesh的顶点(vertice)。
open3d实现该方法通过create_from_point_cloud_poisson,实际时对GitHub - mkazhdan/PoissonRecon: Poisson Surface Reconstruction的封装实现。该算法的一个重要参数时depth,用于控制octree的深度;该参数可以控制生成triangle mesh的精细程度。越大的depth,生成的mesh拥有更多的细节。
| pcd | 包含法线信息的待重建点云 |
| depth | (int ,可选参数,默认=8) 曲面重建树的最大深度,在深度D处运行对应于分辨率不大于2^D*2^D*2^D的网格上进行求解。注:由于重建时根据采样密度调整OCTtree,因此此处指定的重建深度只是一个上限。 |
| width | (float ,可选参数,默认=0)指定最细级别的八叉树单元格的目标宽度;如果指定了深度,则此参数忽略。 |
| scale | (float,可选,默认值=1.1)指定用于重建的立方体直径与样本边界立方体直径之间的比率。 |
| linear_fit | (bool,可选,默认值=False)如果为true,重建时将使用线性插值来估计iso顶点的位置 |
| n_threads | (int,可选,默认值=-1)用于重建的线程数,设置为-1表示自动选择 |
import open3d as o3d
import numpy as np
if __name__ == "__main__":
eagle = o3d.data.EaglePointCloud()
pcd: o3d.geometry.PointCloud = o3d.io.read_point_cloud(eagle.path)
R = pcd.get_rotation_matrix_from_xyz((np.pi, -np.pi / 4, 0))
pcd.rotate(R, center=(0, 0, 0))
pcd = pcd.voxel_down_sample(voxel_size=0.08)
print('Displaying input pointcloud ...')
# o3d.visualization.draw_geometries([pcd])
print('Running Poisson surface reconstruction ...')
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
pcd, depth=9)
print('Displaying reconstructed mesh ...')
pcd = pcd.translate([10, 0, 0])
o3d.visualization.draw_geometries([mesh, pcd])

Poisson surface reconstruction(泊松表面重建)也会在点云密度较低的地方创建mesh,甚至会向外延伸到其他区域,如下图底部区域所示;因此create_from_point_cloud_poisson函数的第二个返回值(densities)代表了每一个顶点的密度信息;低密度代表着该顶点(vertex)仅从一小部分的输入点云中生成。

可以根据返回的密度信息来删除低密度区域的顶点(vertices)和mesh,这里使用numpy.quantile来删除出小于X分位数的密度值的顶点(vertices)和mesh。
import copy
import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt
if __name__ == "__main__":
eagle = o3d.data.EaglePointCloud()
pcd: o3d.geometry.PointCloud = o3d.io.read_point_cloud(eagle.path)
R = pcd.get_rotation_matrix_from_xyz((np.pi, -np.pi / 4, 0))
pcd.rotate(R, center=(0, 0, 0))
# pcd = pcd.voxel_down_sample(voxel_size=0.08)
print('Displaying input pointcloud ...')
# o3d.visualization.draw_geometries([pcd])
print('Running Poisson surface reconstruction ...')
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
pcd, depth=9)
print('Displaying reconstructed mesh ...')
mesh = mesh.translate([5, 0, 0])
o3d.visualization.draw_geometries([mesh, pcd], mesh_show_back_face=True)
# 密度的cmap
print('visualize densities')
densities = np.asarray(densities)
density_colors = plt.get_cmap('plasma')(
(densities - densities.min()) / (densities.max() - densities.min()))
density_colors = density_colors[:, :3]
density_mesh = o3d.geometry.TriangleMesh()
density_mesh.vertices = mesh.vertices
density_mesh.triangles = mesh.triangles
density_mesh.triangle_normals = mesh.triangle_normals
density_mesh.vertex_colors = o3d.utility.Vector3dVector(density_colors)
o3d.visualization.draw_geometries([density_mesh])
density_mesh.translate([5, 0, 0])
# 删除分位数小于0.005的顶点和mesh
print('remove low density vertices')
vertices_to_remove = densities < np.quantile(densities, 0.005)
mesh_remove:o3d.geometry.PointCloud = copy.deepcopy(mesh)
mesh_remove.remove_vertices_by_mask(vertices_to_remove)
mesh_removed = mesh_remove.translate([10, 0, 0])
print(mesh_removed)
o3d.visualization.draw_geometries([mesh_removed, density_mesh, mesh, pcd])

上面结果图片中,最左边为原始的点云数据,其次时poisson重建的结果,其次时poisson重建的密度信息,最后时删除X分位后得到的重建结果。 可以看到删除低密度区域的顶点和mesh信息后,mesh结果展示没有了之前底部的多余信息。
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶
一段时间以来,我一直在使用open_uri下拉ftp路径作为数据源,但突然发现我几乎连续不断地收到“530抱歉,允许的最大客户端数(95)已经连接。”我不确定我的代码是否有问题,或者是否是其他人在访问服务器,不幸的是,我无法真正确定谁有问题。本质上,我正在读取FTPURI:defself.read_uri(uri)beginuri=open(uri).readuri=="Error"?nil:urirescueOpenURI::HTTPErrornilendend我猜我需要在这里添加一些额外的错误处理代码...我想确保我采取一切预防措施来关闭所有连接,这样我的连接就不是问题所在,但是我
标题本身就说明了......read_timeout和open_timeout之间有什么区别? 最佳答案 open_timeout是您愿意等待“打开连接”的时间。在TCP上下文中,在放弃尝试并引发超时错误之前等待握手完成的时间量。read_timeout您可能会猜到,是您愿意等待从连接方接收到某些数据的时间。一个例子可能会清楚地说明这一点:在SOAPoverHTTPoverTCP上下文中(简化):您尝试与服务器建立TCP连接。如果建立连接的时间比open_timeout长,则放弃连接尝试并引发/发出/返回超时错误。如果连接成功,您发
我是Ruby的新手,我正在尝试以如下方式打开文件:#!/usr/bin/envrubydata_file='~/path/to/file.txt'file=File.open(data_file,'r')但是我得到“没有这样的文件或目录”(该文件确实存在于该目录中)。如果我将该文件路径作为命令行参数,它会起作用,例如:#!/usr/bin/envrubyfile=File.open(ARGV[0],'r')然后从命令行运行,如:rubyscript.cgi~/path/to/file.txt关于如何让它以第一种方式工作的任何想法? 最佳答案
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?
如何获取外部命令的输出并从中提取值?我有这样的东西:stdin,stdout,stderr,wait_thr=Open3.popen3("#{path}/foobar",configfile)if/exit0/=~wait_thr.value.to_srunlog.puts("Foobarexitednormally.\n")puts"Testcompleted."someoutputvalue=stdout.read("TX.*\s+(\d+)\s+")puts"Outputvalue:"+someoutputvalueend我没有在标准输出上使用正确的方法,因为Ruby告诉我它不能
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。关于您编写的代码问题的问题必须在问题本身中描述具体问题—并且包括有效代码以重现它。参见SSCCE.org寻求指导。关闭8年前。Improvethisquestion我是Rails的新手。我正在制作一个网络应用程序,我在其中使用nokogiri搜索不同的网站以从中提取文本。所以在Gemfile中,我写了require'nokogiri'和'open-uri',但是当我捆绑安装时我得到这个错误:Couldnotfindgem'open-uri(>=0)ruby'inthegemsavailableon