主成分分析法(PCA方法)计算OBB包围盒

本文探讨了如何使用PCA计算数据的主成分,并将其应用于计算OBB包围盒。作者逐步解析了基变换的原理,从求协方差矩阵、特征值到投影,最终实现OBB的坐标转换。

在上一节的优快云中,粗糙的学习了一下“散点——协方差矩阵——特征向量——轴”的过程。

# 计算以下数据的协方差矩阵
import numpy as np
import matplotlib.pyplot as plt
 
# np.random.seed(0)#随机数种子
# data = np.random.uniform(1,10,(10,2))
# data[:,1:] = 0.5*data[:,0:1]+np.random.uniform(-2,2,(10,1))
 
#不用随机数了,这次用一个比较实际的例子,来测试PCA准不准
#假设这是一个扁扁的矩形
input=list()
input.append([100,100])
input.append([500,100])
input.append([100,200])
input.append([500,200])
print(input)
 
#从已有的数组创建数组
data=np.asarray(input)
print(data)
 
#去中心化
data_norm = data-data.mean(axis = 0)#axis = 0:压缩行,对各列求均值,返回 1* n 矩阵
 
X = data_norm[:,0]
Y = data_norm[:,1]
 
C=np.cov(data_norm,rowvar=False)## 此时列为变量计算方式 即X为列,Y也为列
 
#计算特征值和特征向量
#这个返回的特征向量,要按列来看
vals, vecs = np.linalg.eig(C)
 
#重新排序,从大到小
#默认从小到大排列,这里取负了,就是从大到小排列,返回相应索引
vecs = vecs[:,np.argsort(-vals)]#特征向量按列取,这里就呼应上了
vals = vals[np.argsort(-vals)]
 
print(vals)
print(vecs)
 
#第一个特征值对应的特征向量
print(vals[0],vecs[:,0])
#第二个特征值对应的特征向量
print(vals[1],vecs[:,1])
 
#计算模长是否为1
print(np.linalg.norm(vecs[:,0]))
print(np.linalg.norm(vecs[:,1]))
 
 
#用画图的方式,画出结果
#设置图大小
size = 600
 
plt.figure(1,(8,8))
 
plt.scatter(data[:,0],data[:,1],label='origin data')
 
i=0
ev = np.array([vecs[:,i]*-1,vecs[:,i]])*size
ev = (ev+data.mean(0))
plt.plot(ev[:,0],ev[:,1],label = 'eigen vector '+str(i+1))
 
i=1
ev = np.array([vecs[:,i]*-1,vecs[:,i]])*size
ev = (ev+data.mean(0))
plt.plot(ev[:,0],ev[:,1],label = 'eigen vector '+str(i+1))
 
#plt.plot(vecs[:,1]*-10,vecs[:,1]*10)
 
#画一下x轴y轴
plt.plot([-size,size],[0,0],c='black')
plt.plot([0,0],[-size,size],c='black')
plt.xlim(-size,size)
plt.ylim(-size,size)
plt.legend()
plt.show()

这个仅仅是求出了两个轴,是一点进步,但是还不够。因为真正有用的,是OBB包围盒。那末,怎么用PCA方法计算OBB包围盒呢?

OBB包围盒的计算

理论基础

 概括的说,就是,把原基下的坐标转化到新基下,以新基为参照,计算新基下的AABB包围盒,然后再把新基转化为原基,就得到了原基下的obb包围盒。

新基下的AABB包围盒,就是原基下的OBB包围盒。

这里面的主要矛盾是——矩阵乘法与基变换。解决了这个,其它的次要矛盾,就能迎刃而解。

矩阵乘法,也叫基变换,左行右列?到底左乘还是右乘?点的坐标到底是横着写还是竖着写?有点迷糊……而数学是精确的一门学科,一点也马虎不得。

有问题的地方就有矛盾嘛,不同质的矛盾要用不同质的方法去解决,这个矛盾,应该用复习线性代数的方法去解决。

 

代码实现的摸索 

第一段-别人的

OBB包围盒,这个主轴,对应的是方差最大的意思

特征向量,有方向。两个主轴。

涉及到一个投影的问题……

投影怎么算?这个是上一节里没涉及到的部分了。

还到这个链接里去找一找吧,希望能找到方法。猜一下,投影,矩阵乘法,基变换;应该和这些有关。具体怎么写?怎么用?都是问题,都是矛盾。

还是这个链接

https://gitee.com/ni1o1/pygeo-tutorial/blob/master/12-.ipynbicon-default.png?t=M276https://gitee.com/ni1o1/pygeo-tutorial/blob/master/12-.ipynb

简单的说,在之前的求特征值和画轴之间,加个这个,就求投影了。

作为一个0基础小白,就知道它大概是在矩阵乘法,然后正交矩阵的逆矩阵和转置相等。

总觉得这个左乘右乘和想的不大一样……

再就不会了,小白嘛,不会才是常态。

##########新加的,求投影的一段################################################
 
#数据在主成分1上的投影坐标是Y
k=1
Q = vecs[:,:k]
Y = np.matmul(data_norm,Q)
#得到去中心化的还原数据
np.matmul(Y,Q.T)
#加上均值,还原数据
data_ = np.matmul(Y,Q.T)+data.mean(0)
 
#########################################################

全部的:

# 计算以下数据的协方差矩阵
import numpy as np
import matplotlib.pyplot as plt#Pyplot 是 Matplotlib 的子库,提供了和 MATLAB 类似的绘图 API。
 
#不用随机数了,这次用一个比较实际的例子,来测试PCA准不准
#假设这是一个扁扁的矩形,输入散点数据
input=list()
input.append([100,100])
input.append([500,100])
 
input.append([200,200])
input.append([400,200])
print(input)
 
#从已有的数组创建数组
data=np.asarray(input)
print(data)
 
#去中心化
data_norm = data-data.mean(axis = 0)#axis = 0:压缩行,对各列求均值,返回 1* n 矩阵
 
X = data_norm[:,0]
Y = data_norm[:,1]
 
C=np.cov(data_norm,rowvar=False)## 此时列为变量计算方式 即X为列,Y也为列
 
#计算特征值和特征向量
#这个返回的特征向量,要按列来看——因为,矩阵乘法来变换坐标系的时候,新基就是竖着的嘛
vals, vecs = np.linalg.eig(C)
 
#重新排序,从大到小
#默认从小到大排列,这里取负了,就是从大到小排列,返回相应索引
vecs = vecs[:,np.argsort(-vals)]#特征向量按列取,这里就呼应上了
vals = vals[np.argsort(-vals)]
 
print(vals)
print(vecs)
 
#第一个特征值对应的特征向量
print(vals[0],vecs[:,0])
#第二个特征值对应的特征向量
print(vals[1],vecs[:,1])
 
#计算模长是否为1
print(np.linalg.norm(vecs[:,0]))
print(np.linalg.norm(vecs[:,1]))
 
##########新加的,求投影的一段##############################
以下是一个使用PCL库通过PCA主成分分析法计算点云OBB包围盒,并将包围盒坐标点转回原坐标系的C++示例代码: ```cpp #include <iostream> #include <pcl/point_cloud.h> #include <pcl/point_types.h> #include <pcl/common/centroid.h> #include <pcl/common/transforms.h> #include <pcl/common/eigen.h> #include <Eigen/Dense> // 计算点云的质心、特征值和特征向量 void calValueVector(pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_ptr, Eigen::Vector4f& center, Eigen::Vector3f& values, Eigen::Matrix3f& vectors) { // 计算中心 pcl::compute3DCentroid(*cloud_ptr, center); // 计算协方差 Eigen::Matrix3f covariance; pcl::computeCovarianceMatrixNormalized(*cloud_ptr, center, covariance); // 计算特征值、特征向量 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> eigen_solver(covariance, Eigen::ComputeEigenvectors); values = eigen_solver.eigenvalues(); vectors = eigen_solver.eigenvectors(); // 矫正主方向间垂直 vectors.col(2) = vectors.col(0).cross(vectors.col(1)); vectors.col(0) = vectors.col(1).cross(vectors.col(2)); vectors.col(1) = vectors.col(2).cross(vectors.col(0)); // 打印结果 std::cout << "质心点(4x1):\n" << center << std::endl; std::cout << "特征值va(3x1):\n" << values << std::endl; std::cout << "特征向量ve(3x3):\n" << vectors << std::endl; } // 计算OBB包围盒并将坐标点转回原坐标系 std::vector<pcl::PointXYZ> computeOBB(pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud_ptr) { Eigen::Vector4f center; Eigen::Vector3f values; Eigen::Matrix3f vectors; calValueVector(cloud_ptr, center, values, vectors); // 将点云变换到主方向坐标系 Eigen::Affine3f transform(Eigen::Translation3f(center.head<3>()) * Eigen::Matrix3f(vectors)); Eigen::Affine3f inverse_transform = transform.inverse(); pcl::PointCloud<pcl::PointXYZ>::Ptr transformed_cloud(new pcl::PointCloud<pcl::PointXYZ>); pcl::transformPointCloud(*cloud_ptr, *transformed_cloud, inverse_transform); // 计算变换后点云的AABB包围盒 pcl::PointXYZ min_pt, max_pt; pcl::getMinMax3D(*transformed_cloud, min_pt, max_pt); // 定义OBB包围盒的8个顶点(在主方向坐标系下) std::vector<pcl::PointXYZ> obb_points; obb_points.push_back(pcl::PointXYZ(min_pt.x, min_pt.y, min_pt.z)); obb_points.push_back(pcl::PointXYZ(min_pt.x, min_pt.y, max_pt.z)); obb_points.push_back(pcl::PointXYZ(min_pt.x, max_pt.y, min_pt.z)); obb_points.push_back(pcl::PointXYZ(min_pt.x, max_pt.y, max_pt.z)); obb_points.push_back(pcl::PointXYZ(max_pt.x, min_pt.y, min_pt.z)); obb_points.push_back(pcl::PointXYZ(max_pt.x, min_pt.y, max_pt.z)); obb_points.push_back(pcl::PointXYZ(max_pt.x, max_pt.y, min_pt.z)); obb_points.push_back(pcl::PointXYZ(max_pt.x, max_pt.y, max_pt.z)); // 将OBB包围盒的顶点坐标转回原坐标系 std::vector<pcl::PointXYZ> original_obb_points; for (const auto& point : obb_points) { pcl::PointXYZ transformed_point; pcl::transformPoint(point, transformed_point, transform); original_obb_points.push_back(transformed_point); } return original_obb_points; } int main() { // 创建示例点云 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); cloud->push_back(pcl::PointXYZ(1.0, 1.0, 1.0)); cloud->push_back(pcl::PointXYZ(2.0, 2.0, 2.0)); cloud->push_back(pcl::PointXYZ(3.0, 3.0, 3.0)); // 计算OBB包围盒并将坐标点转回原坐标系 std::vector<pcl::PointXYZ> obb_points = computeOBB(cloud); // 输出OBB包围盒的顶点坐标 std::cout << "OBB包围盒的顶点坐标(原坐标系):" << std::endl; for (const auto& point : obb_points) { std::cout << "(" << point.x << ", " << point.y << ", " << point.z << ")" << std::endl; } return 0; } ``` ### 代码解释 1. **`calValueVector`函数**:该函数用于计算点云的质心、协方差矩阵的特征值和特征向量。 2. **`computeOBB`函数**: - 调用`calValueVector`函数计算点云的质心和主方向。 - 将点云变换到主方向坐标系。 - 计算变换后点云的AABB包围盒。 - 定义OBB包围盒的8个顶点(在主方向坐标系下)。 - 将OBB包围盒的顶点坐标转回原坐标系。 3. **`main`函数**:创建示例点云,调用`computeOBB`函数计算OBB包围盒并输出顶点坐标。 ### 编译和运行 确保你已经安装了PCL库,然后使用以下命令编译代码: ```sh g++ -std=c++11 your_file_name.cpp -o obb -I /path/to/pcl/include/pcl-1.xx -L /path/to/pcl/lib -lpcl_common -lpcl_filters ``` 运行生成的可执行文件: ```sh ./obb ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值