主成分分析法(PCA方法)计算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]))
 
##########新加的,求投影的一段################################################
 
#数据在主成分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)
 
#########################################################
#设置图大小
size = 600
 
plt.figure(1,(8,8))
 
plt.scatter(data[:,0],data[:,1],label='origin data')
 
plt.scatter(data_[:,0],data_[:,1],label='restructured 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()

 

 numpy使用之np.matmulicon-default.png?t=M276https://blog.youkuaiyun.com/alwaysyxl/article/details/83050137?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164880405716780261933257%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164880405716780261933257&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-4-83050137.142%5Ev5%5Epc_search_insert_es_download&utm_term=np.matmul&spm=1018.2226.3001.4187

第二段-改造一点

不会。照搬别人的,小改一点。

求投影是必要的,但只是手段,不是目的,目的是借助投影,计算OBB包围盒

看来这个的结果是对的,可以作为一个参照。

# 计算以下数据的协方差矩阵
import numpy as np
import matplotlib.pyplot as plt#Pyplot 是 Matplotlib 的子库,提供了和 MATLAB 类似的绘图 API。
 
#不用随机数了,这次用一个比较实际的例子,来测试PCA准不准
#假设这是一个扁扁的矩形,输入散点数据
input=list()
input.append([100,100])
input.append([200,300])
 
input.append([100,400])
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]))
 
 
#用画图的方式,画出结果
 
size = 600#设置图大小
plt.figure(1,(6,6))
plt.scatter(data[:,0],data[:,1],label='origin data')#使用 pyplot 中的 scatter() 方法来绘制散点图。
 
#逐个绘制方向向量
i=0
ev = np.array([vecs[:,i]*-1,vecs[:,i]])*size#vecs竖着看的,是单位向量,取反是为了和正的构成两个点,绘制直线
ev = (ev+data.mean(0))
plt.plot(ev[:,0],ev[:,1],label = 'eigen vector '+str(i+1))#ev是向量,竖着分离出向量的Xs和Ys,用来plot画图
 
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))
 
 
#计算并绘制包围盒

#将原基下的坐标,变换到新基下,在新基下求包围盒
Y=np.matmul(data_norm,vecs)#4*2 2*2 =4*2    #不出意外的话,这是新基下的坐标
offset=10
xmin=min(Y[:,0])-offset#第一次独立写出python切片
xmax=max(Y[:,0])+offset
ymin=min(Y[:,1])-offset
ymax=max(Y[:,1])+offset
 
#新基下的包围盒坐标
 
temp=list()
 
temp.append([xmin,ymin])
temp.append([xmax,ymin])
temp.append([xmax,ymax])
temp.append([xmin,ymax])
 
pointInNewCor=np.asarray(temp)
#将新基下计算出来的包围盒坐标,变换到原基下
OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)
 
#绘制包围盒
plt.plot(OBB[0:2,0],OBB[0:2,1],
OBB[1:3,0],OBB[1:3,1],
OBB[2:4,0],OBB[2:4,1],
OBB[0:4:3,0],OBB[0:4:3,1],#最后一个切片,通过设置间隔,取开头和末尾
c='r'
)
 
#画一下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()

想说的话:

这个是直接照搬的,但是照搬不是目的,是手段。目的是为了更好的学习,实现人的超越性。

关于切片,这里是自己写的,通过这个实践,加深了认识

通过设置切片的参数,实现了取头和取尾:

import numpy as np
 
a=list()
a.append([1,2])
a.append([3,4])
a.append([5,6])
a.append([7,8])
print(a)
 
A=np.asarray(a)
 
print(A[0:4:3,:])

 

主要矛盾在基的两次变换,也就是这个:

#将原基下的坐标,变换到新基下,为了在新基下求包围盒
Y=np.matmul(data_norm,vecs)#4*2 2*2 =4*2   
……
#将新基下计算出来的包围盒坐标,变换到原基下
OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)

这个右乘,不大好理解。

第三段-接着改造

根据别人的,结合自己的理解,自主的改造两个变换。

这个时候,脑子里大概装的是这个。认为左边是变换矩阵,右边是待变换的向量。

改造第一个变换

#将原基下的坐标,变换到新基下,为了在新基下求包围盒
# Y=np.matmul(data_norm,vecs)#4*2 2*2 =4*2    #不出意外的话,这是新基下的坐标
# print(Y)
#改造:
# Y=np.matmul(vecs.T,data_norm.T)#2*2 2*4=2*4 #这个已经证明出来是对的了
# print(Y)
Y=np.dot(vecs,data_norm.T)#2*2  2*4
print(Y)

改造第二个变换的时候,就遇到问题了。

#将新基下计算出来的包围盒坐标,变换到原基下
# OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)#矩阵乘法 4*2 2*2 1*2

#改造:
#ValueError: operands could not be broadcast together with shapes (2,4) (2,)
OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1)#2*2     2*4     2   #逆变换,那就是转置嘛

查了查广播,改了一下,发现改对了。。

这个(2,        )……从哪里冒出来的?它应该mean出来就是(2,1)才对啊……

#将新基下计算出来的包围盒坐标,变换到原基下
# OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)#矩阵乘法 4*2 2*2 1*2

#改造:
#ValueError: operands could not be broadcast together with shapes (2,4) (2,)
#OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1)#2*2     2*4     2   #逆变换,那就是转置嘛
OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1).reshape(2,1)

总的长这样:

# 计算以下数据的协方差矩阵
import numpy as np
import matplotlib.pyplot as plt#Pyplot 是 Matplotlib 的子库,提供了和 MATLAB 类似的绘图 API。

#不用随机数了,这次用一个比较实际的例子,来测试PCA准不准
#假设这是一个扁扁的矩形,输入散点数据
input=list()
input.append([100,100])
input.append([200,300])

input.append([100,400])
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]))


#用画图的方式,画出结果

size = 600#设置图大小
plt.figure(1,(6,6))
plt.scatter(data[:,0],data[:,1],label='origin data')#使用 pyplot 中的 scatter() 方法来绘制散点图。

#逐个绘制方向向量
i=0
ev = np.array([vecs[:,i]*-1,vecs[:,i]])*size#vecs竖着看的,是单位向量,取反是为了和正的构成两个点,绘制直线
ev = (ev+data.mean(0))
plt.plot(ev[:,0],ev[:,1],label = 'eigen vector '+str(i+1))#ev是向量,竖着分离出向量的Xs和Ys,用来plot画图

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))

print(data_norm)
print(data_norm.T)

#计算并绘制包围盒

#将原基下的坐标,变换到新基下,为了在新基下求包围盒
# Y=np.matmul(data_norm,vecs)#4*2 2*2 =4*2    #不出意外的话,这是新基下的坐标
# print(Y)
#改造:
# Y=np.matmul(vecs.T,data_norm.T)#2*2 2*4=2*4 #这个已经证明出来是对的了
# print(Y)
Y=np.dot(vecs,data_norm.T)#2*2  2*4
print(Y)


offset=10

xmin=min(Y[0,:])-offset
xmax=max(Y[0,:])+offset
ymin=min(Y[1,:])-offset
ymax=max(Y[1,:])+offset

#新基下的包围盒坐标

temp=list()

temp.append([xmin,xmax,xmax,xmin])
temp.append([ymin,ymin,ymax,ymax])

pointInNewCor=np.asarray(temp)#4*2

#将新基下计算出来的包围盒坐标,变换到原基下
# OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)#矩阵乘法 4*2 2*2 1*2

#改造:
#ValueError: operands could not be broadcast together with shapes (2,4) (2,)
#OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1)#2*2     2*4     2   #逆变换,那就是转置嘛
OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1).reshape(2,1)

#绘制包围盒
plt.plot(
    OBB[:,0],OBB[:,1],
    OBB[:,1],OBB[:,2],
    OBB[:,2],OBB[:,3],
    OBB[:,3],OBB[:,0],
    c="r"
)

#画一下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()

根据观察,结果不对。看来两次基变换出问题了呐。

第四段-完全改造

实践——认识——再实践——再认识……就是在这个迭代的过程中,思想才慢慢从错误趋于正确嘛。

作为一个小白,关于改造两次基变换,虽然上次错了,但不影响这次接着来!

经过总结,发现了以下问题:

上面这个算式求的是,[x,y]在原基下表示一个向量,在基变换以后,新基下的[x,y]表示的是哪个向量。

而这里要求的是,向量A在原基下表示为[x,y],然后基变换了,求新基下向量A的表示方法。

简单的说:

同一个系数,不同的基,不同的长度
同一个长度,不同的基,不同的系数

一个意思,厘米,英寸,也可以看作是基嘛;厘米到英寸,也相当于一个基变换嘛。

 这里要做的第二个而不是第一个——这个就是上面那个结果不对的原因。

这个视频说的也是这个道理。

线性代数的本质-09-基变换https://www.bilibili.com/video/BV1ib411t7YR?p=13老基转新基公式推导:

1cm*5=1inch*1\tfrac{31}{32}

B_{new}*P_{new}=B_{old}*P_{old}

P_{new}=B_{new}^{-1}B_{old}*P_{old}

因为,正交矩阵,它的逆就等于它的转置。单位阵在乘法里可以忽略。故:

P_{new}=B_{new}^{T}*P_{old}

别人的作为正确答案,对比一下,进行测试:

#将原基下的坐标,变换到新基下,为了在新基下求包围盒
Y=np.matmul(data_norm,vecs)#4*2 2*2 =4*2    #不出意外的话,这是新基下的坐标
print(Y)
#改造:
# Y=np.matmul(vecs.T,data_norm.T)#2*2 2*4=2*4 #这个已经证明出来是对的了
# print(Y)
Y=np.dot(vecs.T,data_norm.T)#2*2  2*4   =   2*4
print(Y)

 看起来一样,这步应该是对的。

再来测试一下,在新基下找包围盒:

别人的:

#新基下的包围盒坐标
temp=list()
temp.append([xmin,ymin])
temp.append([xmax,ymin])
temp.append([xmax,ymax])
temp.append([xmin,ymax])
pointInNewCor=np.asarray(temp)
print(pointInNewCor)

 自主的:

#计算新基下的包围盒坐标
temp=list()
temp.append([xmin,xmax,xmax,xmin])
temp.append([ymin,ymin,ymax,ymax])
pointInNewCor=np.asarray(temp)#4*2
print(pointInNewCor)

肉眼观察,觉得差不多。 

那,怎么把新基下的点,转化为老基下的坐标呢?

B_{new}*P_{new}=B_{old}*P_{old}

原基,也就是        \begin{bmatrix} 1 &0 \\ 0&1 \end{bmatrix},这个是单位阵,在乘法里,可以直接忽略,故:

B_{new}*P_{new}=P_{old}

再和别人的对比测试一下:

这个是别人的;

#将新基下计算出来的包围盒坐标,变换到原基下
#OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)
OBB=np.matmul(pointInNewCor,vecs.T)
print(OBB)

  这个是自己的:

# #将新基下计算出来的包围盒坐标,变换到原基下
# # OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)#矩阵乘法 4*2 2*2 1*2
# #改造:
# #ValueError: operands could not be broadcast together with shapes (2,4) (2,)
# #OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1)#2*2     2*4     2   #逆变换,那就是转置嘛
# #OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1).reshape(2,1)
# OBB=np.dot(vecs,pointInNewCor)+data.T.mean(1).reshape(2,1)
OBB=np.dot(vecs,pointInNewCor)
print(OBB)

 通过肉眼观察,觉得差不多。

到这里,自己改的就差不多了:

# 计算以下数据的协方差矩阵
import numpy as np
import matplotlib.pyplot as plt#Pyplot 是 Matplotlib 的子库,提供了和 MATLAB 类似的绘图 API。

#不用随机数了,这次用一个比较实际的例子,来测试PCA准不准
#假设这是一个扁扁的矩形,输入散点数据
input=list()
input.append([100,100])
input.append([200,300])

input.append([100,400])
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]))


#用画图的方式,画出结果

size = 600#设置图大小
plt.figure(1,(6,6))
plt.scatter(data[:,0],data[:,1],label='origin data')#使用 pyplot 中的 scatter() 方法来绘制散点图。

#逐个绘制方向向量
i=0
ev = np.array([vecs[:,i]*-1,vecs[:,i]])*size#vecs竖着看的,是单位向量,取反是为了和正的构成两个点,绘制直线
ev = (ev+data.mean(0))
plt.plot(ev[:,0],ev[:,1],label = 'eigen vector '+str(i+1))#ev是向量,竖着分离出向量的Xs和Ys,用来plot画图

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))

print(data_norm)
print(data_norm.T)

#计算并绘制包围盒

#将原基下的坐标,变换到新基下,为了在新基下求包围盒
# Y=np.matmul(data_norm,vecs)#4*2 2*2 =4*2    #不出意外的话,这是新基下的坐标
# print(Y)
#改造:
# Y=np.matmul(vecs.T,data_norm.T)#2*2 2*4=2*4 #这个已经证明出来是对的了
# print(Y)
Y=np.dot(vecs.T,data_norm.T)#2*2  2*4   =   2*4
#print(Y)


offset=10

xmin=min(Y[0,:])-offset
xmax=max(Y[0,:])+offset
ymin=min(Y[1,:])-offset
ymax=max(Y[1,:])+offset

#计算新基下的包围盒坐标
temp=list()
temp.append([xmin,xmax,xmax,xmin])
temp.append([ymin,ymin,ymax,ymax])
pointInNewCor=np.asarray(temp)#4*2
#print(pointInNewCor)

# #将新基下计算出来的包围盒坐标,变换到原基下
# # OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)#矩阵乘法 4*2 2*2 1*2
# #改造:
# #ValueError: operands could not be broadcast together with shapes (2,4) (2,)
# #OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1)#2*2     2*4     2   #逆变换,那就是转置嘛
# #OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1).reshape(2,1)
#OBB=np.dot(vecs,pointInNewCor)+data.T.mean(1).reshape(2,1)
OBB=np.dot(vecs,pointInNewCor)+data.T.mean(1).reshape(2,1)
#OBB=np.dot(vecs,pointInNewCor)
print(OBB)

#绘制包围盒
plt.plot(
    OBB[0,0:2],OBB[1,0:2],
    OBB[0,1:3],OBB[1,1:3],
    OBB[0,2:4],OBB[1,2:4],
    OBB[0,0:4:3],OBB[1,0:4:3],
    c="r"
)

#错误的绘制方法
# plt.plot(
#     OBB[:,0],OBB[:,1],
#     OBB[:,1],OBB[:,2],
#     OBB[:,2],OBB[:,3],
#     OBB[:,3],OBB[:,0],
#     c="r"
# )

#画一下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()

改到这里的话,自己写的就正常了。同时,别人写的也就能理解了。通过学习,把别人的东西转化成了自己的东西。一分为二,对立统一;相互包含,相互转化。

别人的就是转置了一下而已。转置,脱帽法,戴帽法。在4*2的numpy格式下,转置一下可以简化代码;省的为了凑一个2*4,转置来,转置去的。

查了几天的视频,网站,代码。关于PCA,关于OBB,作为一个0基础的小白,竟然也有了一点粗浅的理解。可能这个就是质量互变吧。

其它

在测试加上平均值偏移以后,有几次它们是不一致的……

别人的:

#将新基下计算出来的包围盒坐标,变换到原基下
OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)
#OBB=np.matmul(pointInNewCor,vecs.T)
print(OBB)

 自己的:

# #将新基下计算出来的包围盒坐标,变换到原基下
# # OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)#矩阵乘法 4*2 2*2 1*2
# #改造:
# #ValueError: operands could not be broadcast together with shapes (2,4) (2,)
# #OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1)#2*2     2*4     2   #逆变换,那就是转置嘛
# #OBB=np.dot(vecs.T,pointInNewCor)+data.T.mean(1).reshape(2,1)
OBB=np.dot(vecs,pointInNewCor)+data.T.mean(1).reshape(2,1)
#OBB=np.dot(vecs,pointInNewCor)
print(OBB)

 加上平均值偏移一下,就不一样了……

看来用mean求平均值出了点问题呐。

然后改着改着,它俩又突然就一样了,但是代码还是那个代码。

VSCODE它就好像,改了代码,但是运行结果有时候是不变的,这咋办?不知道。先忽略吧……

看别人的时候,就有一个问题:

#新基下的包围盒坐标
temp=list()
temp.append([xmin,ymin])
temp.append([xmax,ymin])
temp.append([xmax,ymax])
temp.append([xmin,ymax])
pointInNewCor=np.asarray(temp)
print(pointInNewCor)


#将新基下计算出来的包围盒坐标,变换到原基下
OBB=np.matmul(pointInNewCor,vecs.T)+data.mean(0)
#OBB=np.matmul(pointInNewCor,vecs.T)
print(OBB)#新基下垂直于坐标轴的两点,原基下可能就不垂直了

#绘制包围盒
plt.plot(
OBB[0:2,0],OBB[0:2,1],
OBB[1:3,0],OBB[1:3,1],#xmax,ymin
OBB[2:4,0],OBB[2:4,1],
OBB[0:4:3,0],OBB[0:4:3,1],#最后一个切片,通过设置间隔,取开头和末尾
c='r'
)

 包围盒是四个点确定的。为啥在第二个输出里,没有了那种首尾相接的感觉?

想了想发现,第二个没必要首位相接,新基下的四个点是首位相接的,因为它们的连线和新基平行。转换到原基以后,它们的连线和原基就不平行了,当然没有首位相接的感觉。正常的。

这个基,变来变去的,比较容易绕进去呐……

后记

没有实践,就不好有认识呐。线性代数矩阵乘法基变换学过吗?学过。但是一直都停留在做数学题的阶段,不会应用,也不知道怎么应用。直到今天,结合实践,才有了一点理解。

再往后是什么呢?相似检测吧。

这个很复杂的……作为一个小白,代码越长问题越多。

最后,还是老话,前途是光明的,道路是曲折的。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值