目标
在本教程中,您将学习如何:
- 使用OpenCV类cv :: PCA来计算对象的方向。
什么是PCA?
主成分分析(PCA)是提取数据集最重要的特征的统计程序。
考虑到你有一组2D点,如上图所示。每个维度对应于您感兴趣的特征。这里有些人可能会争辩说,这些点是以随机顺序设置的。但是,如果你有一个更好的外观,你会看到有一个线性模式(由蓝线表示),这是很难解雇。PCA的一个关键点是维度降低。降维是减少给定数据集的维数的过程。例如,在上述情况下,可以将该组点近似为单线,并且因此将给定点的维数从2D减少到1D。
此外,还可以看到沿着蓝线的点最多,沿着特征1或特征2轴变化的更多。这意味着,如果知道沿着蓝线的点的位置,则可以获得有关该点的更多信息,而不是只知道特征1轴或特征2轴上的位置。
因此,PCA允许我们找到我们数据变化最大的方向。实际上,在图中的一组点上运行PCA的结果由2个称为特征向量的向量组成,这些特征向量是数据集的主要组成部分。
每个特征向量的大小被编码在相应的特征值中,并指示数据沿着主分量变化多少。特征向量的开始是数据集中所有点的中心。将PCA应用于N维数据集产生N个N维特征向量,N个特征值和1个N维中心点。足够的理论,让我们看看我们如何可以把这些想法编码。
如何计算特征向量和特征值?
我们的目标是在给定数据组变换X尺寸的p到替代数据集ÿ较小尺寸的大号。等价地,我们试图找到矩阵Y,其中Y是矩阵X的Karhunen-Loève变换(KLT):
Y=KLT{X}
组织数据集
假设你有包含一组p观测变量的数据,并且你想要减少这些数据,那么每个观测值只能用L个变量描述,L < p。进一步假设数据被排列为一组n个数据矢量,每个代表p个变量的单个分组观察。x1...xnxi
- 将写成行向量,每行有p个列。x1...xn
- 放置行向量成一个单一的矩阵X的尺寸。n×p
计算经验均值
- 找出每个维度的经验均值。j=1,...,p
- 将计算出的平均值放入维度的经验均值向量u中。p×1
u [ j ] =1ñΣ我= 1ñX [ i ,j ]
计算平均值的偏差
平均相减是求解最小化近似数据的均方误差的主成分基础的解决方案的组成部分。因此,我们将数据集中到以下位置:
- 从数据矩阵X的每一行中减去经验均值向量u。
- 在矩阵B中存储平均相减的数据。n × p
B = X - h uŤ
其中h是所有1 的列向量:n × 1
h [ i ] = 1 ,i = 1 ,...。。。,n
找到协方差矩阵
- 找到经验协方差矩阵C ^从矩阵的外积乙与自身:p × p
C =1n - 1乙*⋅ 乙
其中*是共轭转置运算符。注意,如果B完全由实数组成,在许多应用中就是这种情况,“共轭转置”与常规转置相同。
找出协方差矩阵的特征向量和特征值
- 计算使协方差矩阵C对角化的特征向量的矩阵V:
V- 1C V = D
其中D是C的特征值的对角矩阵。 - 矩阵D将采用角矩阵的形式:p × p
D [ k ,l ] = { λķ,k = 10 ,k ≠ 1
在这里,是协方差矩阵C的第j个特征值λj - 矩阵V,也维的p X p,包含p列向量,每个长度p,其表示p的协方差矩阵的特征向量的Ç。
- 特征值和特征向量是有序的和成对的。第j个特征值对应于第j个特征向量。
- 注意
- 来源[1],[2],并特别感谢Svetlin Penkov的原始教程。
源代码
本教程代码的显示如下。您也可以从这里下载。
#include“ opencv2 / core.hpp ”
#include“ opencv2 / imgproc.hpp ”
#include“ opencv2 / highgui.hpp ”
#include <iostream>
使用命名空间std ;
使用命名空间cv ;
//函数声明
void drawAxis(Mat&,Point,Point,Scalar,const float);
double getOrientation(const vector <Point>&,Mat&);
void drawAxis(Mat&img,Point p,Point q,Scalar color,const float scale = 0.2)
{
双角度;
双斜边;
角= ATAN2((双。)p ý - q Ý,(双),第X - Q值。X); //以弧度表示的角度
斜边= SQRT((双)(第ý - q Ý)*(第ý - Q值。Ý)+(第X - Q值。X)*(第X - Q值。X));
//双度=角度* 180 / CV_PI; //将弧度转换为度(0-180范围)
// cout <<“Degrees:”<< abs(degrees - 180)<< endl; //在0-360度范围内的角度
//这里我们用一个比例因子来加长箭头
Q值。X =(INT)(第X -规模* *斜边COS(角度));
Q值。Ý =(INT)(第ÿ -规模* *斜边罪(角));
line(img,p,q,color,1,LINE_AA);
//创建箭头钩子
页。X =(INT)(:Q X + 9 * COS(+角度CV_PI / 4));
页。Ý =(INT)(:Q ÿ + 9 * 罪(角度+ CV_PI / 4));
line(img,p,q,color,1,LINE_AA);
页。X =(INT)(:Q X + 9 * COS(角度- CV_PI / 4));
页。Ý =(INT)(:Q ÿ + 9 * 罪(角度- CV_PI / 4));
line(img,p,q,color,1,LINE_AA);
}
double getOrientation(const vector <Point>&pts,Mat&img)
{
//构造一个由pca分析使用的缓冲区
int sz = static_cast < int >(pts.size());
Mat data_pts = Mat(sz,2,CV_64FC1);
对于(诠释 I = 0; I <data_pts。行 ; ++ I)
{
data_pts。at < double >(i,0)= pts [i] .x;
data_pts。在 < double >(i,1)= pts [i] .y;
}
//执行PCA分析
PCA pca_analysis(data_pts,Mat(),PCA :: DATA_AS_ROW);
//存储对象的中心
Point cntr = Point(static_cast <int>(pca_analysis.mean.at < double >(0,0)),
static_cast <int>(pca_analysis.mean.at < double >(0,1)));
//存储特征值和特征向量
vector <Point2d> eigen_vecs(2);
vector <double> eigen_val(2);
for(int i = 0; i <2; ++ i)
{
eigen_vecs [i] = Point2d(pca_analysis.eigenvectors.at < double >(i,0),
pca_analysis.eigenvectors.at < double >(i,1));
eigen_val [i] = pca_analysis.eigenvalues.at < double >(i);
}
//绘制主要组件
circle(img,cntr,3,Scalar(255,0,255 ),2);
点 P1 = CNTR + 0.02 * 点(的static_cast <int>的(eigen_vecs [0] * .X eigen_val [0]),的static_cast <int>的(eigen_vecs [0] * .Y eigen_val [0]));
点 P2 = CNTR - 0.02 * 点(的static_cast <int>的(eigen_vecs [1] .X * eigen_val [1]),的static_cast <int>的(eigen_vecs [1] * .Y eigen_val [1]));
drawAxis(img,cntr,p1,Scalar(0,255,0),1);
drawAxis(img,cntr,p2,Scalar(255,255,0),5);
double angle = atan2(eigen_vecs [0] .y,eigen_vecs [0] .x); //以弧度为单位的方向
返回角度;
}
int main(int argc,char ** argv)
{
//加载图片
CommandLineParser解析器(argc,argv,“{@input | ../data/pca_test1.jpg | input image}”);
parser.about(“这个程序演示了如何使用OpenCV PCA来提取对象的方向。\ n”);
parser.printMessage();
Mat src = imread(parser.get < String >(“ @input ”));
/ /检查图像是否成功加载
如果(SRC。空())
{
cout << “问题加载图片!!!” << endl;
返回 EXIT_FAILURE;
}
imshow(“src”,src);
//将图像转换为灰度
垫灰色;
cvtColor(src,gray,COLOR_BGR2GRAY);
//将图像转换为二进制
Mat bw;
阈值(灰色,bw,50,255,THRESH_BINARY | THRESH_OTSU);
//找到阈值图像中的所有轮廓
矢量<矢量<点>>轮廓;
findContours(bw,contours,RETR_LIST,CHAIN_APPROX_NONE);
for(size_t i = 0; i <contours.size(); ++ i)
{
//计算每个轮廓的面积
double area = contourArea(contour [i]);
//忽略太小或太大的轮廓
如果(区域<1e2 || 1e5 <区域)继续 ;
//绘制每个轮廓仅用于可视化目的
drawContours(src,contour,static_cast <int>(i),Scalar(0,0,255),2,LINE_8);
//找出每个形状的方向
getOrientation(contour [i],src);
}
imshow(“输出”,src);
waitKey(0);
返回 0;
}
- 注意
- 使用PCA维度降低同时保持一定数量的变异的另一个例子可以在opencv_source_code / samples / cpp / pca.cpp处找到
说明
- 阅读图像并将其转换为二进制
在这里,我们应用必要的预处理程序,以便能够检测感兴趣的对象。
//加载图片
CommandLineParser解析器(argc,argv,“{@input | ../data/pca_test1.jpg | input image}”);
parser.about(“这个程序演示了如何使用OpenCV PCA来提取对象的方向。\ n”);
parser.printMessage();
Mat src = imread(parser.get <String>(“ @input ”));
/ /检查图像是否成功加载
if(src.empty())
{
cout <<“问题加载图片!!!” << endl;
返回 EXIT_FAILURE;
}
imshow(“src”,src);
//将图像转换为灰度
垫灰色;
cvtColor(src,gray,COLOR_BGR2GRAY);
//将图像转换为二进制
Mat bw;
阈值(灰色,bw,50,255,THRESH_BINARY | THRESH_OTSU); - 提取感兴趣的对象
然后按大小查找和过滤轮廓,并获取其余的方向。
//找到阈值图像中的所有轮廓
矢量<矢量<点>>轮廓;
findContours(bw,contours,RETR_LIST,CHAIN_APPROX_NONE);
for(size_t i = 0; i <contours.size(); ++ i)
{
//计算每个轮廓的面积
double area = contourArea(contour [i]);
//忽略太小或太大的轮廓
如果(区域<1e2 || 1e5 <区域)继续 ;
//绘制每个轮廓仅用于可视化目的
drawContours(src,contour,static_cast <int>(i),Scalar(0,0,255),2,LINE_8);
//找出每个形状的方向
getOrientation(contour [i],src);
} - 提取方向
方向是通过调用getOrientation()函数来提取的,该函数执行所有的PCA过程。
//构造一个由pca分析使用的缓冲区
int sz = static_cast < int >(pts.size());
Mat data_pts = Mat(sz,2,CV_64FC1);
for(int i = 0; i <data_pts.rows; ++ i)
{
data_pts.at <double >(i,0)= pts [i] .x;
data_pts.at <double >(i,1)= pts [i] .y;
}
//执行PCA分析
PCA pca_analysis(data_pts,Mat(),PCA :: DATA_AS_ROW);
//存储对象的中心
Point cntr = Point(static_cast <int>(pca_analysis.mean.at <double >(0,0)),
static_cast <int>(pca_analysis.mean.at <double >(0,1)));
//存储特征值和特征向量
vector <Point2d> eigen_vecs(2);
vector <double> eigen_val(2);
for(int i = 0; i <2; ++ i)
{
eigen_vecs [i] = Point2d(pca_analysis.eigenvectors.at <double >(i,0),
pca_analysis.eigenvectors.at <double >(i,1));
eigen_val [i] = pca_analysis.eigenvalues.at <double >(i);
}
首先,需要将数据排列成一个大小为nx 2的矩阵,其中n是我们拥有的数据点的数量。然后我们可以执行PCA分析。计算的平均值(即质量中心)存储在cntr变量中,特征向量和特征值存储在相应的std :: vector中。 - 可视化结果
最后的结果是通过drawAxis()函数形象化的,其中主要组成部分用线绘制,每个特征向量乘以其特征值并转换到平均位置。
//绘制主要组件
circle(img,cntr,3,Scalar(255,0,255 ),2);
点 P1 = CNTR + 0.02 * 点(的static_cast <int>的(eigen_vecs [0] * .X eigen_val [0]),的static_cast <int>的(eigen_vecs [0] * .Y eigen_val [0]));
点 P2 = CNTR - 0.02 * 点(的static_cast <int>的(eigen_vecs [1] .X * eigen_val [1]),的static_cast <int>的(eigen_vecs [1] * .Y eigen_val [1]));
drawAxis(img,cntr,p1,Scalar(0,255,0),1);
drawAxis(img,cntr,p2,Scalar(255,255,0),5);
double angle = atan2(eigen_vecs [0] .y,eigen_vecs [0] .x); //以弧度为单位的方向
双角度;
双斜边;
角= ATAN2((双。)p ý - q Ý,(双),第X - Q值。X); //以弧度表示的角度
斜边= SQRT((双)(第ý - q Ý)*(第ý - Q值。Ý)+(第X - Q值。X)*(第X - Q值。X));
//双度=角度* 180 / CV_PI; //将弧度转换为度(0-180范围)
// cout <<“Degrees:”<< abs(degrees - 180)<< endl; //在0-360度范围内的角度
//这里我们用一个比例因子来加长箭头
Q值。X =(INT)(第X -规模* *斜边COS(角度));
Q值。Ý =(INT)(第ÿ -规模* *斜边罪(角));
line(img,p,q,color,1,LINE_AA);
//创建箭头钩子
页。X =(INT)(:Q X + 9 * COS(+角度CV_PI / 4));
页。Ý =(INT)(:Q ÿ + 9 * 罪(角度+ CV_PI / 4));
line(img,p,q,color,1,LINE_AA);
页。X =(INT)(:Q X + 9 * COS(角度- CV_PI / 4));
页。Ý =(INT)(:Q ÿ + 9 * 罪(角度- CV_PI / 4));
line(img,p,q,color,1,LINE_AA);
结果
该代码打开图像,找出所检测的感兴趣对象的方位,然后通过绘制关于所提取方位的所检测的感兴趣对象的轮廓,中心点和x轴,y轴来将结果可视化。
关注【OpenCV学习交流】
长按或者扫描下面二维码即可关注