使用Python,Open3D对点云散点投影到面上并可视化,使用3种方法计算面的法向量及与平均法向量的夹角

写这篇博客源于博友的提问,他坚定了我继续坚持学习的心,带给了我充实与快乐。
将介绍以下5部分:

  1. 随机生成点云点
  2. 投影点到面(给出了6个面的中心点,离哪个中心点距离近就投影到哪个面)
  3. 对投影到每个面的点云计算法向量点(3种方法 KNN 半径近邻 混合近邻)
  4. 对每个面上的法向量及与平均法向量的夹角
  5. 可视化原始点及法向量点
  6. 对每个面角度进行简单统计并绘制直方图(hist)
  7. 对每个面角度进行分区间统计并绘制直方图(俩种方法 hist df.plot)
  8. df.plot 支持中文,绘制多行列子图,及共享xy轴,支持图例,图形大小等设置

1. 效果图

1.1 点云点灰色 VS 法向量点绿色 VS 法向量可视化

全量点云点灰色 VS 全量法向量点绿色效果图如下:
投影到每个面的均值点为渲染为红色比较明显。法向量点均值点渲染为黄色这里不太明显,可查看下边每个投影面的效果图;

全量点云点灰色 VS 全量法向量点绿色 VS 法向量可视化 效果图如下:

1.2 投影到每个面上的点云点灰色 VS 法向量点绿色 VS 均值点红色,法向量均值点黄色

投影到第一个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

红色,黄色点不明显,旋转下看下一个图;

投影到第一个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
旋转后红色、黄色点比较明显 如下图

投影到第一个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
同时可视化法向量点 如下图

投影到第2个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图

投影到第2个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
同时渲染法向量 如下图

投影到第3个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图


投影到第3个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
同时渲染法向量 如下图

投影到第4个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图


投影到第4个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
同时渲染法向量 如下图

投影到第5个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图


投影到第5个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
同时渲染法向量 如下图

投影到第6个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,如下图


投影到第6个面上的原始点渲染为灰色,均值点渲染为红色;
法向量点渲染为绿色,法向量均值点渲染为黄色,
同时渲染法向量 如下图

直接按bins=9,将每一个面的角度分为9个块进行绘制,效果图如下:
这个时候不太好的一点是不能根据每个区间看,可以指定bins为数组,一段一段的统计见下一张效果图;
优化bins=[0,10,20,30,40.50,60,70,80,90],将每一个面的角度分为9个区间进行统计绘制,效果图如下:

pandas dataFrame df.plot绘制子图角度分布图如下:
6行1列,或者2行3列

6个面的角度,按区间绘制在一个图中,共享xy轴,效果图如下:

2. 源码

# 随机生成点
# 投影到面上,(给出了6个面的中心点,离哪个中心点距离近就投影到哪个面)
# 对投影到每个面的点云计算法向量点(3种方法 KNN 半径近邻 混合近邻)
# 对每个面上的法向量求均值及与平均法向量的夹角
# 并可视化原始点灰色,法向量点绿色,均值点红色,均值法向量点黄色
# 对每个面角度进行简单统计并绘制直方图(hist)
# 对每个面角度进行分区间统计并绘制直方图(俩种方法 hist df.plot)
# df.plot 支持中文,绘制多行列子图,及共享xy轴,支持图例,图形大小等设置
import randomimport numpy as np
import open3d as o3d# 随机种子,以便复现结果
random.seed(123)# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决保存图像是负号'-'显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False# 假设立方体是2*2*2,立方体的质心是笛卡尔坐标的原点,立方体外接球的半径为sqrt(3)d = {}  # 存储面的中心点及投影到每个面上的点云点,key中心 values投影到该面的点云点def add_dict(dictionary, loc, centroid):# loc is the point's location on sphere,# centroid is the center(s) of cube's face(s) that is nearest to the locif tuple(loc) in dictionary.keys():dictionary[tuple(loc)].append(list(centroid))else:dictionary[tuple(loc)] = [list(centroid)]def point_generator(npoints, r):result = []# 0 < theta < 2*np.pi# 0 < phi < np.pifor i in range(npoints):theta = random.random() * 2 * np.piphi = random.random() * np.pix = r * np.cos(theta) * np.sin(phi)y = r * np.sin(theta) * np.sin(phi)z = r * np.cos(phi)result.append([x, y, z])return resultnpoints = 5000
r = np.sqrt(3)
centroids = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, 0, 0], [0, -1, 0], [0, 0, -1]]
points = point_generator(npoints, r)# print(points)# 计算俩个点的距离
def distance(p, q):if len(p) == len(q):result = 0for i in range(len(p)):result += (p[i] - q[i]) ** 2return result ** 0.5else:print('cannot calculate distance of points from different dimensions')# 寻找离点最近的面(中心点),并投影到对应的面上
def archive_nearest_pc_pairs(dictionary, centroids, points):for p in points:dist = []for q in centroids:dist.append(distance(p, q))nearest_centroid = centroids[dist.index(min(dist))]  # 寻找最近的立方体面中心的点距离add_dict(dictionary, nearest_centroid, p)archive_nearest_pc_pairs(d, centroids, points)
print(centroids)# 3种方法计算法向量
def compute_normals(pcd, flag=1):# 混合搜索  KNN搜索  半径搜索if (flag == 1):pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=20))  # 计算法线,搜索半径1cm,只考虑邻域内的20个点elif (flag == 2):pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamKNN(knn=3))  # 计算法线,只考虑邻域内的20个点else:pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamRadius(radius=0.01))  # 计算法线,搜索半径1cm,只考虑邻域内的20个点# 计算俩个法向量的夹角可参考 http://mp.ofweek.com/it/a456714148137
# x为第一个法向量,y为第二个法向量
def cal_angle(x, y):# 分别计算两个向量的模:l_x = np.sqrt(x.dot(x))l_y = np.sqrt(y.dot(y))# print('向量的模=', l_x, l_y)# 计算两个向量的点积dian = x.dot(y)# print('向量的点积=', dian)# 计算夹角的cos值:cos_ = dian / (l_x * l_y)# print('夹角的cos值=', cos_)# 求得夹角(弧度制):angle_hu = np.arccos(cos_)# print('夹角(弧度制)=', angle_hu)# 转换为角度值:angle_d = angle_hu * 180 / np.pi# print('夹角=%f°' % angle_d)return angle_d# 初始化法向量dict,key中心点 values法向量点
normals_dict = {}
pcds = []
for i, (key, value) in enumerate(d.items()):# print(value)val = np.array(value)# 计算点云均值点,并插入到原点云点的第一个点avg_point = np.mean(val, axis=0)  # axis=0,计算每一列的均值origin_points = np.row_stack((avg_point, val))# 构造点云数据pcd = o3d.geometry.PointCloud()points = o3d.utility.Vector3dVector(origin_points)pcd.points = points# 计算法向量,可选择3种方法计算法向量,传值1/2/其他compute_normals(pcd, 2)normals = o3d.np.asarray(pcd.normals)# print('pcd-normals: ', normals)normals_dict[key] = normalsprint('第', str(i + 1), '个面, center:', key, len(pcd.normals), '个点')# print(np.sum(normals) / len(normals))# 均值点法向量点avg_normal = normals[0]# 遍历计算平均向量与点云向量的夹角(由于第一个点是均值点,所以去除)for j, (point, point_normal) in enumerate(zip(origin_points[1:], normals[1:])):# 计算均值点法向量 与 点云点法向量的夹角print('\t第 %s 个点, angle: %s' % (str(j + 1),cal_angle(np.array(avg_point) - np.array(avg_normal), np.array(point) - np.array(point_normal))))print("--------------------------------------------------------")# 可视化法向量点和原始点pcd.paint_uniform_color([0.5, 0.5, 0.5])  # 把原始点渲染为灰色pcd.colors[0] = [1, 0, 0]  # 原始点云均值点渲染为红色normal_point = o3d.utility.Vector3dVector(pcd.normals)normals = o3d.geometry.PointCloud()normals.points = normal_pointnormals.paint_uniform_color((0, 1, 0))  # 点云法向量的点都以绿色显示normals.colors[0] = [1, 1, 0]  # 均值点法向量点渲染为黄色o3d.visualization.draw_geometries([pcd, normals], "Open3D origin " + str(i + 1) + " VS normals points True",width=800,height=600, left=50,top=50,point_show_normal=True, mesh_show_wireframe=False,mesh_show_back_face=False)o3d.visualization.draw_geometries([pcd, normals], "Open3D origin " + str(i + 1) + " VS normals points False",width=800,height=600, left=50,top=50,point_show_normal=False, mesh_show_wireframe=False,mesh_show_back_face=False)# 加入list以便全量渲染pcds.append(pcd)pcds.append(normals)print(pcds[0], pcds[1], pcds[2], pcds[3], pcds[4], pcds[5],pcds[6], pcds[7], pcds[8], pcds[9], pcds[10], pcds[11],len(pcds))
# 同时可视化法向量
o3d.visualization.draw_geometries([pcds[0], pcds[1], pcds[2], pcds[3], pcds[4],pcds[5], pcds[6], pcds[7], pcds[8], pcds[9], pcds[10], pcds[11]], "Open3D originAll VS normals points True",width=800,height=600, left=50,top=50,point_show_normal=True, mesh_show_wireframe=False,mesh_show_back_face=False)# 不可视化法向量
o3d.visualization.draw_geometries([pcds[0], pcds[1], pcds[2], pcds[3], pcds[4],pcds[5], pcds[6], pcds[7], pcds[8], pcds[9],pcds[10], pcds[11]], "Open3D originAll VS normals points False",width=800,height=600, left=50,top=50,point_show_normal=False, mesh_show_wireframe=False,mesh_show_back_face=False)# 画角度分布直方图
def get_normal_angle_histogram(dict):""":param dict::return:"""dict_angle_histogram = {}min_angle = 0max_angle = 90partition = (max_angle - min_angle) / 9data_list = []for centroid in dict.keys():v1 = {}# data_tri_list = []for l in range(9):v1[l] = 0for i in range(len(dict[centroid])):k = round(float((float(dict[centroid][i]) - min_angle) / partition))if k == 9:k = 8print('---------------------------')print(dict[centroid][i])v1[k] = v1.get(k, 0) + 1# https://stackoverflow.com/questions/4674473/valueerror-setting-an-array-element-with-a-sequence# 避免数组长度不一致# norm = np.linalg.norm(dict[centroid][i])# # data_tri_list.append(norm)v1_list = list(v1.values())print('v1_list', v1_list)# total = sum(v1.values(), 0.0)# a = {j: v / total for j, v in v1.items()}dict_angle_histogram[centroid] = v1_listdata_tri_list = list(v1.values())data_list.append(data_tri_list)return dict_angle_histogram, data_listdict_angle_histogram_1, data_list_1 = get_normal_angle_histogram(normals_angle_dict)
print('------------------------------')
print(dict_angle_histogram_1)
"""
绘制直方图
data:必选参数,绘图数据
bins:直方图的长条形数目,可选项,默认为10
normed:是否将得到的直方图向量归一化,可选项,默认为0,代表不归一化,显示频数。normed=1,表示归一化,显示频率。
facecolor:长条形的颜色
edgecolor:长条形边框的颜色
alpha:透明度
"""
# 将bins=9,按9块对角度进行统计绘制直方图
fig1 = plt.figure(figsize=(12, 12))
for i, angle_histogram_list in enumerate(dict_angle_histogram_1.values()):plt.subplot(2, 3, i + 1)plt.hist(angle_histogram_list, bins=9, normed=0, density=None, facecolor="blue", edgecolor="black", alpha=0.7)# x_major_locator = MultipleLocator(2)# # 把x轴的刻度间隔设置为1,并存在变量里# y_major_locator = MultipleLocator(10)# 把y轴的刻度间隔设置为10,并存在变量里ax = plt.gca()# ax.xaxis.set_major_locator(x_major_locator)# ax.xaxis.set_major_locator(ticker.MultipleLocator(base=5))# ax.yaxis.set_major_locator(y_major_locator)# ax.yaxis.set_major_locator(ticker.MultipleLocator(base=40))# plt.xlim(29, 51)# plt.ylim(0, 80)plt.title(i)
plt.suptitle("histogram1")
plt.show()# 优化上边的绘制,bins=[ 0 10 20 30 40 50 60 70 80 90],按区间对角度进行统计绘制
fig1 = plt.figure(figsize=(12, 12))
for i, angle_list in enumerate(normals_angle_dict.values()):plt.subplot(2, 3, i + 1)bins = np.arange(0, 91, 10)  # 设置连续的边界值,即直方图的分布区间[0,10],[10,20]...# 直方图会进行统计各个区间的数值plt.hist(angle_list, bins=bins, normed=0, density=None, facecolor="red", edgecolor="black", alpha=0.7)ax = plt.gca()plt.xticks(bins)plt.title(i)
plt.suptitle("histogram bins=[0~90]")
plt.show()print('test data')
print(normals_angle_dict[(0, 0, 1)])
# for centroids in normals_angle_dict.keys():
#     s = pd.cut(normals_angle_dict[centroids], bins=[x for x in np.arange(0, 100, 10)])
#     print(s.value_counts())
#     values = s.value_counts().values
#
#     print('pandas values', centroids, values)
#     labels = [str(i) + '-' + str(i + 10) for i in np.arange(0, 90, 10)]
#     # print(labels)
#     # ['0-1', '1-2', '2-3', '3-4', '4-5', '5-6', '6-7', '7-8', '8-9', '9-10']
#     df = pd.DataFrame(values, index=labels)
#     df.plot(kind='bar', legend=False)
#     plt.xticks(rotation=0)
#     plt.ylabel('频数')
#     plt.xlabel('区间')
#     plt.show()# 构建pandas DataFrame需要的字典
dict_angles = {}
for i, centroids in enumerate(normals_angle_dict.keys()):s = pd.cut(normals_angle_dict[centroids], bins=[x for x in np.arange(0, 100, 10)])print(s.value_counts())values = s.value_counts().valuesprint('pandas values', centroids, values)labels = [str(i) + '-' + str(i + 10) for i in np.arange(0, 90, 10)]dict_angles[i] = values# 以dict构建DataFrame
list = [i for i in range(len(dict_angles))]
labels = [str(i) + '-' + str(i + 10) for i in np.arange(0, 90, 10)]
df = pd.DataFrame(dict_angles, index=labels, columns=list)
df.plot(kind='bar',y=list,  # 6个变量可视化# subplots=True,  # 多子图并存# layout=(2, 3),  # 子图排列2行3列title='angle histogram',sharex=True,  # 共享xy轴sharey=True,  # 共享xy轴figsize=(15, 10),legend=True)
plt.tight_layout()
plt.xticks(rotation=0)
plt.ylabel('频数')
plt.xlabel('区间')
plt.show()

参考

  • 计算俩个法向量的夹角可参考 http://mp.ofweek.com/it/a456714148137
  • df.plot绘图

使用Python,Open3D对点云散点投影到面上并可视化,使用3种方法计算面的法向量及与平均法向量的夹角相关推荐

  1. python与excel做数据可视化-用Python进行数据可视化的10种方法

    原标题:用Python进行数据可视化的10种方法 2015-11-19 关于转载授权 大数据文摘作品,欢迎个人转发朋友圈,自媒体.媒体.机构转载务必申请授权,后台留言"机构名称+转载&quo ...

  2. 普歌-腾讯云短信+使用node发送短信(3种方法API、SDK)、封装工具、搭建web服务、写接口、调用接口发送短信、时效性判断、验证验证码的正确性(下)

    普歌-结合腾讯云短信服务+node搭建一个简单的发送短信web小项目 涉及技术: 腾讯云服务 后端服务:node+express 前端搭建:html+js 前言:本来这篇博客应该很早就发了,中间有一些 ...

  3. Python:三种方法计算最大公约数和最小公倍数(欧几里德法、穷举法、stein算法)

    Python:三种方法计算最大公约数和最小公倍数 1.穷举法 2.欧几里德法 3.Stein算法 题目:求取任意两个非负数(至多一个数为0)的最大公约数和最小公倍数: 参考资料:Python解决求最大 ...

  4. python设置一个初始为0的计数器_python中统计计数的几种方法

    以下实例展示了 count() 方法的使用方法: 以上实例输出结果如下: 1) 使用字典dict() 循环遍历出一个可迭代对象中的元素,如果字典没有该元素,那么就让该元素作为字典的键,并将该键赋值为1 ...

  5. python opencv图像对比度增强_图像增强、锐化, Python-OpenCV 来实现 4 种方法!

    图像增强目的使得模糊图片变得更加清晰.图片模糊的原因是因为像素灰度差值变化不大,如片各区域产生视觉效果似乎都是一样的, 没有较为突出的地方,看起来不清晰的感觉 解决这个问题的最直接简单办法,放大像素灰 ...

  6. python计算今年第几天_Python三种方法计算指定日期是今年的第几天

    今天早上和腾讯面试官进行了视频面试,由于音量和网络以及我的垃圾电脑的原因,个人感觉黄了... 最后面试官给了我一道简单的计算题:指定日期是今年的第几年 由于电脑卡到打字都打不动,我勉勉强强写了一点,虽 ...

  7. python函数可以提高代码执行速度吗_Python代码运行速度慢?这五种方法很管用

    对于Python很多人还是比较了解的,虽然说Python有很多优势但同样具有劣势,Python最大的劣势就是运行效率慢,那么如何提高Python代码运行速度呢?这五种方法很管用. 1.PyPy:在选择 ...

  8. python中如何创建一个空列表_Python创建空列表的字典2种方法详解

    如果要在 Python 中创建键值是空列表的字典,有多种方法,但是各种方法之间是否由区别?需要作实验验证,并且分析产生的原因.本文针对两种方法做了实验和分析. 如果要在 Python 中创建一个键值都 ...

  9. python去重复排序_Python实现删除排序数组中重复项的两种方法示例

    本文实例讲述了Python实现删除排序数组中重复项的两种方法.分享给大家供大家参考,具体如下: 对于给定的有序数组nums,移除数组中存在的重复数字,确保每个数字只出现一次并返回新数组的长度 注意:不 ...

最新文章

  1. 《数据虚拟化:商务智能系统的数据架构与管理》一 1.11 数据集成的其他方式...
  2. 如何快速而准确的获取生物体的遗传信息一直是生命科学 中的一个非常重要的研究点
  3. CentOS添加常用yum源
  4. perl anyevent socket监控web日志server
  5. 【华为大咖分享】2.DevCloud on DevCloud 从1月1次到1天10次发布的实践分享(后附PPT下载地址)
  6. Lisp的本质(The Nature of Lisp)
  7. Python基础——if else与if elif else条件判断
  8. [NOIP2013 普及组 T1] 计数问题
  9. Autodesk AutoCAD 2022 产品系列已发布(附下载)
  10. 访谈,智能座舱开发中的人机交互与人机工程布置
  11. instagram图片下载_如何使用Python下载Instagram个人资料图片
  12. C51单片机引脚名词英文全称
  13. Recorder︱深度学习小数据集表现、优化(Active Learning)、标注集网络获取
  14. 根据地址获取经纬度 -- 腾讯地图(PHP后台)
  15. html5 预渲染,VUE预渲染及遇到的坑
  16. 测试用例设计——错误猜测法
  17. CITAHub 社区成员开源 CITA SDK Python 组件
  18. 虚拟机下解压zip类型压缩包 附各类型文件打包及压缩方式
  19. 做第三方软件测评的意义
  20. pascal编程语言介绍

热门文章

  1. yum doesn‘t have enough cached data to continue
  2. [Kernel_exception6] BUG: scheduling while atomic
  3. 服务器南北桥芯片 维修,[故障处理日记] 集显主板北桥虚焊故障及维修工具与技巧...
  4. 一个神奇指标公式,能找到立刻单边行情的品种,准确率惊人,堪称交易法宝!
  5. vue使用mintUI踩坑——不显示样式/导入mui.css报错/build不了
  6. python+uiautomator2 UI自动化
  7. 水到渠成建设路肩等设施路缘石成型机来出力
  8. RxCache原理分析
  9. Linux公平队列FQ接口实现
  10. 关键词排名查询工具 - Search Engine Result Position Checker