【三维视觉】空间点集的最小包围盒计算

0 问题描述

假设有一个空间点集,不重合的点数有N个。
N=1时,最小包围盒是一个点:中心为其本身,半径无穷小
N=2时,最小包围盒是一个圆:中心为连线中点,半径为边长一半
N=3时,不共线的三点连成一个三角形,锐角和直角三角形其最小包围圆就是其外接圆。钝角三角形以最长边作为直径的圆是最小外接圆
N=4时,不共面的四点组成一个四面体,其最小包围球?如何计算?应该不一定是他的外接球。
那么
N>4,…

1 包围盒介绍

常见的包围盒种类有有以下4种:

①轴对齐包围盒AABB(Axis-aligned bounding box)
②包围球(Bounding Sphere)
③有向包围盒OBB(Oriented bounding box)
④固定方向凸包FDH(Fixed directions hulls或k-DOP)

1.1 AABB轴对齐包围盒

它被定义为包含该对象,且边平行于坐标轴的最小六面体。故描述一个AABB,仅需六个标量。AABB构造比较简单,存储空间小,但紧密性差,尤其对不规则几何形体,冗余空间很大,当对象旋转时,无法对其进行相应的旋转。处理对象是刚性并且是凸的,不适合包含软体变形的复杂的虚拟环境情况。

1.2 BS包围球

它被定义为包含该对象的最小的球体。确定包围球,首先需分别计算组成对象的基本几何元素集合中所有元素的顶点的x,y,z坐标的均值以确定包围球的球心,再由球心与三个最大值坐标所确定的点间的距离确定半径r。包围球的碰撞检测主要是比较两球间半径和与球心距离的大小。

1.3 OBB有向包围盒

OBB是较为常用的包围盒类型。它是包含该对象且相对于坐标轴方向任意的最小的长方体。OBB最大特点是它的方向的任意性,这使得它可以根据被包围对象的形状特点尽可能紧密的包围对象,但同时也使得它的相交测试变得复杂。OBB包围盒比AABB包围盒和包围球更加紧密地逼近物体,能比较显著地减少包围体的个数,从而避免了大量包围体之间的相交检测。但OBB之间的相交检测比AABB或包围球体之间的相交检测更费时。

1.4 FDH固定方向凸包

FDH(k-DOP)是一种特殊的凸包,继承了AABB简单性的特点,但其要具备良好的空间紧密度,必须使用足够多的固定方向。被定义为包含该对象且它的所有面的法向量都取自一个固定的方向(k个向量)集合的凸包。FDH比其他包围体更紧密地包围原物体,创建的层次树也就有更少的节点,求交检测时就会减少更多的冗余计算,但相互间的求交运算较为复杂。

2 包围盒计算算法

2.1 BS包围球计算算法

2.2.1 naive算法

一个最简单的思路就是,计算空间顶点在X、Y、Z方向上的最大值和最小值,那么就可以得到8个顶点组成的包围盒。取包围球中心为包围盒中心点,而包围球半径有的人认为可以取中心点到八个顶点的最大距离——这样其实并不严密。最好还是计算中心点到所有顶点距离的最大值:

2.2.2 ritter算法

另外一种算法是一个名为ritter提出来的,所以称为ritter算法。

首先计算出X方向上距离最远的两个点,Y方向上距离最远的两个点以及Z方向上距离最远的两个点。以这三个距离最远的范围作为初始直径,这三个距离的中心点作为初始球心。

然后依次遍历所有点,判断点是否在这个包围球内。如果不在,则更新包围球。如下图所示:

2.2.3 Welzl算法

很容易用递归算法来实现:

先根据 np-1 个点,生成一个球体(递归);
判断第 np 个点是否在球体内,是,则保持球体;
否,需要根据旧的球体和第 np 个点,生成新的球体(递归)。其中第 np 个点必在新生成的球面上;
递归结束条件:当一个点、两个点时,可直接生成球体;三个点都在球面上,则生成三角形的外接球;

3、现成代码库(包)

4、外接球与最小包围球的区别

注意外接球和我理解的最小包围球是不一样的。
黄色的是最小包围球,绿色的是外接球
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、四点外接球与最小包围球的计算

import numpy as np
import matplotlib.pyplot as plt


def get_min_sphere_4points(points):
    """
    Get the minimum radius of a circumscribed sphere that encloses all the points
    """
    def minimum_enclosing_sphere_3points(triangle):
        # Compute the circumcenter of the triangle
        a, b, c = triangle
        ab = b - a
        ac = c - a
        ab_cross_ac = np.cross(ab, ac)
        ab_cross_ac_norm_sq = np.dot(ab_cross_ac, ab_cross_ac)
        if ab_cross_ac_norm_sq == 0:
            # Points are colinear, return a point and radius of infinity
            return a, np.inf
        ab_norm_sq = np.dot(ab, ab)
        ac_norm_sq = np.dot(ac, ac)
        circumcenter = a + (np.cross(ab_norm_sq * ac - ac_norm_sq * ab, ab_cross_ac) / (2 * ab_cross_ac_norm_sq))
        # Calculate the radius of the circumcircle
        radius = np.linalg.norm(circumcenter - a)
        # Check if the circumcenter lies inside the triangle
        if np.all(np.logical_and(circumcenter >= a, circumcenter <= c)):
            return circumcenter, radius
        # Otherwise, the minimum enclosing sphere is the circumcircle
        else:
            center = np.mean(triangle, axis=0)
            radius = np.max(np.linalg.norm(triangle - center, axis=1))
            return center, radius
    def _min_sphere(points, center, radius):
        if len(points) == 0 or len(center) == 3:
            if len(center) == 3:
                # c1, c2, c3 = center
                # return np.array([(c1 + c2 + c3) / 3]), 0
                return minimum_enclosing_sphere_3points(center)
            elif len(center) == 2:
                c1, c2 = center
                return (c1 + c2) / 2, np.linalg.norm(c1 - c2) / 2
            elif len(center) == 1:
                return center[0], 0
            else:
                return None, 0
        else:
            p = points[0]
            points = points[1:]
            c, r = _min_sphere(points, center, radius)
            if c is None or np.linalg.norm(p - c) > r:
                center.append(p)
                c, r = _min_sphere(points, center, radius)
                center.pop()
            return c, r

    if len(points) < 4:
        raise ValueError("At least 4 points are required.")
    np.random.shuffle(points)
    center, radius = _min_sphere(points, [], 0)
    print("Center:", center)
    print("Radius:", radius)
    return center, radius


def fit_circumscribed_sphere_4points(array, tol=1e-6):
    # Check if the the points are co-linear
    D12 = array[1] - array[0]
    D12 = D12 / np.linalg.norm(D12)
    D13 = array[2] - array[0]
    D13 = D13 / np.linalg.norm(D13)
    D14 = array[3] - array[0]
    D14 = D14 / np.linalg.norm(D14)

    chk1 = np.clip(np.abs(np.dot(D12, D13)), 0., 1.)  # 如果共线,chk1=1
    chk2 = np.clip(np.abs(np.dot(D12, D14)), 0., 1.)
    # 求的是反余弦值,如果是1,反余弦值为0(弧度),乘以180/pi,就是0(度),说明共线
    if np.arccos(chk1) / np.pi * 180 < tol or np.arccos(chk2) / np.pi * 180 < tol:
        R = np.inf
        C = np.full(3, np.nan)
        return R, C

    # Check if the the points are co-planar
    n1 = np.linalg.norm(np.cross(D12, D13))
    n2 = np.linalg.norm(np.cross(D12, D14))

    chk = np.clip(np.abs(np.dot(n1, n2)), 0., 1.)
    if np.arccos(chk) / np.pi * 180 < tol:
        R = np.inf
        C = np.full(3, np.nan)
        return R, C

    # Centroid of the sphere
    A = 2 * (array[1:] - np.full(len(array) - 1, array[0]))
    b = np.sum((np.square(array[1:]) - np.square(np.full(len(array) - 1, array[0]))), axis=1)
    C = np.transpose(np.linalg.solve(A, b))

    # Radius of the sphere
    R = np.sqrt(np.sum(np.square(array[0] - C), axis=0))
    print("Center:", C)
    print("Radius:", R)

    return C, R


if __name__ == '__main__':

    # # Define the four points
    p1 = np.array([0, 0, 0])
    p2 = np.array([0, 4, 0])
    p3 = np.array([4, 0, 0])
    p4 = np.array([1, 2, 0])

    points1 = np.array([p1, p2, p3, p4])

    points1 = np.random.rand(4, 3)
    # show_tetrahedron(points1)
    center0, radius0 = fit_circumscribed_sphere_4points(points1)

    center1, radius1 = get_min_sphere_4points(points1)


    from mpl_toolkits.mplot3d import Axes3D

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    # Plot the points
    ax.scatter(points1[:, 0], points1[:, 1], points1[:, 2], c='b')
    # plot the tetrahedron
    ax.plot(points1[:, 0], points1[:, 1], points1[:, 2], c='b')

    # Plot the sphere1
    u, v = np.mgrid[0:2 * np.pi:20j, 0:np.pi:10j]
    x = center0[0] + radius0 * np.cos(u) * np.sin(v)
    y = center0[1] + radius0 * np.sin(u) * np.sin(v)
    z = center0[2] + radius0 * np.cos(v)
    ax.plot_wireframe(x, y, z, color="g")

    # Plot the sphere2
    u, v = np.mgrid[0:2 * np.pi:20j, 0:np.pi:10j]
    x = center1[0] + radius1 * np.cos(u) * np.sin(v)
    y = center1[1] + radius1 * np.sin(u) * np.sin(v)
    z = center1[2] + radius1 * np.cos(v)
    ax.plot_wireframe(x, y, z, color="y")

    # Set the axes properties
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_aspect('equal')
    # Show the plot
    print('Showing the plot...')
    plt.show()

参考文献

最小包围球:naive算法和ritter算法
最小包围球:welzl算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/28609.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

番茄工作法图解——简单易行的时间管理方法

ISBN: 978-7-115-24669-1 作者&#xff1a;【瑞典】诺特伯格&#xff08;Staffan Noteberg&#xff09; 页数&#xff1a;136页 阅读时间&#xff1a;2023-06-10 推荐指数&#xff1a;★★★★★ 番茄工作法&#xff08;意大利语&#xff1a;Pomodoro Technique&#xff09;是一…

如何选择到最合适的DDoS缓解服务?

DDoS缓解服务提供商的数量可能很多&#xff0c;但只有一些提供商提供高效服务的所有必要功能&#xff0c;因此如果要选择正确的 DDoS保护解决方案&#xff0c;必须考虑以下因素&#xff1a; 1.缩小风险范围 选择DDoS缓解服务的第一步&#xff0c;确定您组织的特定需求&#…

使用SQL语句创建存储过程

前言: 本篇文章是记录学校学习SQL server中知识,可用于复习资料. 目录 前言:一、存储过程的创建1、创建简单存储过程2、创建带参数的存储过程3、创建带输出参数的存储过程 二 、使用T一SQL语句管理和维护存储过程2.1 使用sp_helptext查看存储过程student_sc的定义脚本2.2 使用…

AI 绘画(1):生成一个图片的标准流程

文章目录 文章回顾感谢人员生成一个图片的标准流程前期准备&#xff0c;以文生图为例去C站下载你需要的绘画模型导入参数导入生成结果&#xff1f;可能是BUG事后处理 图生图如何高度贴合原图火柴人转角色 涂鸦局部重绘 Ai绘画公约 文章回顾 AI 绘画&#xff08;0&#xff09;&…

在Django项目中的各个应用中分别编写路由配置文件urls.py

目录 01-通过命令建立三个应用02-配置路由 /index/、/app1/index/、/app2/index/02-1-配置路由 /index/ 并将各个应用的urls.py文件包含进主路由目录中02-02-配置路由/app1/index/02-03-配置路由/app2/index/ 03-编写各个应用的视图views.py 文件04-注册模板文件所在目录05 创建…

一文吃透低代码平台的衍生历程、优势及未来趋势

一、低代码概念 低代码开发平台是一种无需编码或者只需要少量代码即可快速生成应用程序的开发平台&#xff0c;通过可视化进行应用程序开发的方法&#xff0c;让不同经验水平的开发人员可以通过图形化的用户界面&#xff0c;使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程…

【统计模型】缺失数据处理方法

目录 一、缺失数据定义 二、缺失数据原因 三、缺失数据处理步骤 四、数据缺失机制 1.完全随机缺失&#xff08;MCAR&#xff09; 2.随机缺失&#xff08;MAR&#xff09; 3.非随机、不可忽略缺失&#xff08;NMAR&#xff09; 五、缺失数据处理方法 1.直接删除 2.缺失值…

从零开始理解Linux中断架构(2)-朴素的中断管理设计理念

既然是从零开始,我们先从最为简单的中断逻辑处理架构开始,这个逻辑结构跟CPU架构没有关系,纯逻辑上的。纯逻辑是跨越系统和应用的,不管对于应用程序员还是系统程序员,逻辑推导是基本的工具,设计原型是基本的出发点。 中断发起的时候,PC指针被设置为中断向量表中相对应的…

SpringBoot 中使用 JWT 案例分享详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Qt6.5.1+WebRTC学习笔记(十二)环境搭建流媒体服务器(ubuntu22.04+SRS)

前言 若只是实现一对一通信&#xff0c;仅使用webrtc就足够了。但有时间需要进行多个人的直播会议&#xff0c;当人比较多时&#xff0c;建议使用一个流媒体服务器&#xff0c;笔者使用的是SRS。 这个开源项目资料比较全&#xff0c;笔者仅在此记录下搭建过程 一、准备 1.操…

这些方法可以手写扫描识别

小伙伴们知道有一项技术是可以将我们手写的东西识别出来吗&#xff1f;这一项创新的技术就是手写识别功能&#xff0c;它能够将手写内容快速转换为数字或文本格式&#xff0c;并提高信息处理和管理的效率。而且相比传统的手工记录方式&#xff0c;手写识别功能具有较高的准确性…

多行文本溢出显示省略号

1.css 实现单行省略 .ellipsis{white-space: nowrap;text-overflow: ellipsis; overflow: hidden;}2.在WebKit浏览器或移动端&#xff08;绝大部分是WebKit内核的浏览器&#xff09;的页面&#xff0c;直接使用WebKit的CSS扩展属性(WebKit是私有属性)-webkit-line-clamp 。 -w…

openEuler 开源汇智赢未来|2023开放原子全球开源峰会OpenAtom openEuler 论坛成功召开

6 月 12 日&#xff0c;2023 开放原子全球开源峰会 OpenAtom openEuler 分论坛在北京成功召开。分论坛以“openEuler 汇众智&#xff0c;奔涌向前赢未来”为主题&#xff0c;展示了 openEuler 社区的最新成果&#xff0c;阐述了 openEuler 开源开放的发展模式&#xff0c;介绍了…

在字节跳动和阿里划水4年,过于真实了...

先简单交代一下吧&#xff0c;涛哥是某不知名211的本硕&#xff0c;18年毕业加入阿里&#xff0c;之后跳槽到了头条&#xff0c;一直从事测试开发相关的工作。之前没有实习经历&#xff0c;算是四年半的工作经验吧。 这四年半之间他完成了一次晋升&#xff0c;换了一家公司&am…

移动设备管理:自带设备办公(BYOD)管理

什么是自带设备办公&#xff08;BYOD&#xff09; 自带设备办公&#xff08;BYOD&#xff09;指一些企业允许员工携带自己的笔记本电脑、平板电脑、智能手机等移动终端设备到办公场所&#xff0c;并可以用这些设备获取公司内部信息、使用企业特许应用的一种政策&#xff0c;企…

【备战秋招】每日一题:4月29日美团春招:题面+题目思路 + C++/python/js/Go/java带注释

2023大厂笔试模拟练习网站&#xff08;含题解&#xff09; www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据&#xff0c;挂载到我们的OJ上&#xff0c;供大家学习交流&#xff0c;体会笔试难度。现已录入200道互联网大厂模拟练习题&a…

继承—JavaSE

文章目录 1.基础知识1.1继承的概念1.2语法 2子类对从父类继承下来的成员的访问2.1对成员变量的访问2.2对成员方法的访问 3.super关键字3.1访问父类的成员变量&#xff08;super.变量&#xff09;3.2访问父类的成员方法&#xff08;super.方法&#xff09;3.3调用父类的构造方法…

估计一个点云的表面法线

包含相关头文件 #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> #include <pcl/features/normal_3d.h> #include <pcl/visualization/pcl_visualizer.h> 定义了两个类型别名 PointT 和 PointNT&#xff0c;用于表示输入点云和输出点云中各…

第14届蓝桥杯国赛真题剖析-2023年5月28日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第149讲。 第14届蓝桥杯Scratch国赛真题&#xff0c;这是2023年5月28日上午举办的全国总决赛&#xff0c;比赛仍然采取…

进程管道:popen函数实例

基础知识 可能最简单的在两个程序之间传递数据的方法就是使用popen和pclose函数了。它们的原型如下所示&#xff1a; #include <stdio.h>FILE *popen(const char *command, const char *type);int pclose(FILE *stream); 1&#xff0e;popen函数 popen函数允许一个程…