碰撞檢測問題在虛擬現實、計算機輔助設計與制造、游戲及機器人等領域有着廣泛的應用,甚至成為關鍵技術。而包圍盒算法是進行碰撞干涉初步檢測的重要方法之一。包圍盒算法是一種求解離散點集最優包圍空間的方法。基本思想是用體積稍大且特性簡單的幾何體(稱為包圍盒)來近似地代替復雜的幾何對象。為物體添加包圍體的目的是快速的進行碰撞檢測或者進行精確的碰撞檢測之前進行過濾(即當包圍體碰撞,才進行精確碰撞檢測和處理)。包圍體類型包括球體、軸對齊包圍盒(AABB/Axis-aligned bounding box)、有向包圍盒(OBB/Oriented bounding box)以及凸殼/凸包(Convex Hull)等。
AABB是應用最早的包圍盒。它被定義為包含該對象,且邊平行於坐標軸的最小六面體。故描述一個AABB,僅需六個標量。AABB構造比較簡單,存儲空間小,但緊密性差,尤其對不規則幾何形體,冗余空間很大,當對象旋轉時,無法對其進行相應的旋轉。包圍球被定義為包含該對象的最小的球體。確定包圍球,首先需分別計算組成對象的基本幾何元素集合中所有元素的頂點的x,y,z坐標的均值以確定包圍球的球心,再由球心與三個最大值坐標所確定的點間的距離確定半徑r。包圍球的碰撞檢測主要是比較兩球間半徑和與球心距離的大小。OBB是較為常用的包圍盒類型。它是包含該對象且相對於坐標軸方向任意的最小的長方體。OBB最大特點是它的方向的任意性,這使得它可以根據被包圍對象的形狀特點盡可能緊密的包圍對象,但同時也使得它的相交測試變得復雜。OBB包圍盒比AABB包圍盒和包圍球更加緊密地逼近物體,能比較顯著地減少包圍體的個數,從而避免了大量包圍體之間的相交檢測。但OBB之間的相交檢測比AABB或包圍球體之間的相交檢測更費時。
OBB包圍盒的生成思路簡單來說就是根據一系列坐標點,通過PCA(主成分分析)獲得特征向量,即OBB的主軸,推導過程可參考下面的鏈接。已知平面上點的坐標計算其OBB包圍框四個角點以及中心的Python代碼如下:
import numpy as np from scipy.spatial import ConvexHull def get_obb_from_points(points, calcconvexhull=True): """ given a set of points, calculate the oriented bounding box. Parameters: points: numpy array of point coordinates with shape (n,2) where n is the number of points calcconvexhull: boolean, calculate the convex hull of the points before calculating the bounding box. You typically want to do that unless you know you are passing in a convex point set Output: tuple of corners, centre """ if calcconvexhull: _ch = ConvexHull(points) points = _ch.points[_ch.vertices] cov_points = np.cov(points,y = None,rowvar = 0,bias = 1) v, vect = np.linalg.eig(cov_points) tvect = np.transpose(vect) # use the inverse of the eigenvectors as a rotation matrix and # rotate the points so they align with the x and y axes points_rotated = np.dot(points,np.linalg.inv(tvect)) # get the minimum and maximum x and y mina = np.min(points_rotated,axis=0) maxa = np.max(points_rotated,axis=0) diff = (maxa - mina)*0.5 # the center is just half way between the min and max xy center = mina + diff # get the corners by subtracting and adding half the bounding boxes height and width to the center corners = np.array([center+[-diff[0],-diff[1]],center+[diff[0],-diff[1]],center+[diff[0],diff[1]],center+[-diff[0],diff[1]],center+[-diff[0],-diff[1]]]) # use the the eigenvectors as a rotation matrix and # rotate the corners and the center back corners = np.dot(corners,tvect) center = np.dot(center,tvect) return corners, center
或者可以用下面的形式:

import numpy as np from scipy.spatial import ConvexHull def get_obb_from_points(points, calcconvexhull=True): """ given a set of points, calculate the oriented bounding box. Parameters: points: numpy array of point coordinates with shape (n,2) where n is the number of points calcconvexhull: boolean, calculate the convex hull of the points before calculating the bounding box. You typically want to do that unless you know you are passing in a convex point set Output: tuple of corners, centre """ if calcconvexhull: _ch = ConvexHull(points) points = _ch.points[_ch.vertices] cov_points = np.cov(points,y = None,rowvar = 0,bias = 1) v, vect = np.linalg.eig(cov_points) tvect = np.transpose(vect) # use the eigenvectors as a rotation matrix and # rotate the points so they align with the x and y axes points_rotated = np.dot(tvect, points.T) # get the minimum and maximum x and y mina = np.min(points_rotated, axis=1) maxa = np.max(points_rotated, axis=1) diff = (maxa - mina)*0.5 # the center is just half way between the min and max xy center = mina + diff # get the corners by subtracting and adding half the bounding boxes height and width to the center corners = np.array([center+[-diff[0],-diff[1]],center+[diff[0],-diff[1]],center+[diff[0],diff[1]],center+[-diff[0],diff[1]],center+[-diff[0],-diff[1]]]) # use the inverse of the eigenvectors as a rotation matrix # and rotate the corners and the center back corners = np.dot(np.linalg.inv(tvect), corners.T) center = np.dot(np.linalg.inv(tvect), center) return corners, center
代碼中使用numpy.cov函數計算協方差矩陣,計算出協方差矩陣的特征向量后對原始數據進行變換,並在新的方向上計算最大最小邊界。注意可以對所有原始數據進行PCA主成分分析,也可以使用scipy.spatial package提取數據的凸包(ConvexHull)上的點進行計算,參考OBB generation via Principal Component Analysis。
使用上面的方法計算平面上的點集np.array([(3.7, 1.7), (4.1, 3.8), (4.7, 2.9), (5.2, 2.8), (6.0,4.0), (6.3, 3.6), (9.7, 6.3), (10.0, 4.9), (11.0, 3.6), (12.5, 6.4)])的OBB包圍盒,如下圖所示,其中黃色虛線為點集的凸包:

import matplotlib.pyplot as plt import numpy as np from scipy.spatial import ConvexHull def get_obb_from_points(points, calcconvexhull=True): """ given a set of points, calculate the oriented bounding box. Parameters: points: numpy array of point coordinates with shape (n,2) where n is the number of points calcconvexhull: boolean, calculate the convex hull of the points before calculating the bounding box. You typically want to do that unless you know you are passing in a convex point set Output: tuple of corners, centre """ if calcconvexhull: _ch = ConvexHull(points) points = _ch.points[_ch.vertices] cov_points = np.cov(points,y = None,rowvar = 0,bias = 1) v, vect = np.linalg.eig(cov_points) tvect = np.transpose(vect) # use the inverse of the eigenvectors as a rotation matrix and # rotate the points so they align with the x and y axes points_rotated = np.dot(points,np.linalg.inv(tvect)) # get the minimum and maximum x and y mina = np.min(points_rotated,axis=0) maxa = np.max(points_rotated,axis=0) diff = (maxa - mina)*0.5 # the center is just half way between the min and max xy center = mina + diff # get the corners by subtracting and adding half the bounding boxes height and width to the center corners = np.array([center+[-diff[0],-diff[1]],center+[diff[0],-diff[1]],center+[diff[0],diff[1]],center+[-diff[0],diff[1]],center+[-diff[0],-diff[1]]]) # use the the eigenvectors as a rotation matrix and # rotate the corners and the center back corners = np.dot(corners,tvect) center = np.dot(center,tvect) return corners, center a = np.array([(3.7, 1.7), (4.1, 3.8), (4.7, 2.9), (5.2, 2.8), (6.0,4.0), (6.3, 3.6), (9.7, 6.3), (10.0, 4.9), (11.0, 3.6), (12.5, 6.4)]) fig = plt.figure(figsize=(12,12)) ax = fig.add_subplot(111) ax.scatter(a[:,0],a[:,1]) corners, center = get_obb_from_points(a) ax.scatter([center[0]],[center[1]]) ax.plot(corners[:,0],corners[:,1],'k-') hull = ConvexHull(a) for simplex in hull.simplices: plt.plot(a[simplex, 0], a[simplex, 1], 'y--') plt.axis('equal') plt.show()
參考:
OBB generation via Principal Component Analysis
如何生成OBB(OrientedboundingBox)方向包圍盒
Create the Oriented Bounding-box (OBB) with Python and NumPy