OpenCV PCA介绍

相关文章:

  1. 异值分解SVD、主成分分析PCA、行列式

这篇文章将介绍如何去使用 OpenCV 类:cv::PCA 来计算目标方向。

1. 什么是PCA

主成分分析(PCA)是一个统计过程,提取一个数据集最重要的特征。
在这里插入图片描述
假设有一组2D点,如上图所示。每个维度对应一个感兴趣的特征。有些人可能会说,这些点的设置顺序是随机的。然而,如果仔细观察,会发现这里有一个很难忽略的线性模式(由蓝色线表示)。主成分分析的一个关键点是降维。降维是减少给定数据集的维数的过程。例如,在上述情况下,可以将一组点近似为一条直线,从而将给定点的维数从2D降至1D。

此外还可以看到,这些点沿着蓝线变化最大,比沿着特征1或特征2轴变化更大。这意味着,如果你知道一个点沿着蓝色直线的位置,你就比只知道它在特征1轴特征2轴上的位置有更多的信息。

因此,PCA允许我们找到数据变化最大的方向。事实上,在图中点的集合上运行主成分分析的结果由两个称为特征向量(eigenvector)的向量组成,它们是数据集的主成分。
在这里插入图片描述每个特征向量的大小被编码到对应的特征值中,表明数据沿着主成分变化的程度特征向量的起点是数据集中所有点的中心。将PCA应用于N维数据集,得到N个N维特征向量、N个特征值和1个N维中心点

2. 特征向量与特征值如何计算

我们的目标是将一个给定的 p p p 维数据集 X \mathbf{X} X 转换为另一个更小维度 L L L 的数据集 Y。同样地,我们寻找矩阵 Y,其中 Y 是矩阵 X \mathbf{X} XKarhunen-Loève变换(KLT)
Y = K L T { X } \mathbf{Y}=\mathbb{K} \mathbb{L T}\{\mathbf{X}\} Y=KLT{X}

2.1 组织数据集

假设你的数据包含 p p p 个变量的一组观察数据,你想要减少数据,以便每个观察数据可以只用 L L L 个变量来描述, L < p L < p L<p。进一步假设,数据被排列为 n n n 个数据向量 x 1 … x n x_1…x_n x1xn,其中每一个 x i x_i xi 代表 p p p 个变量的一个组合观测量。

  • x 1 … x n x_1…x_n x1xn写入行向量,每一个都有 p p p 列;
  • 将行向量放入一个单独的维度为 n × p n\times p n×p 的矩阵 X \mathbf{X} X

2.2 计算经验均值

  • 找出每个维度 j = 1 , . . . , p j=1,...,p j=1,...,p 的经验平均值;
  • 将计算的平均值放入维度为 p × 1 p×1 p×1 的经验均值向量 u \mathbf{u} u 中。
    u [ j ] = 1 n ∑ i = 1 n X [ i , j ] \mathbf{u}[\mathbf{j}]=\frac{1}{n} \sum_{i=1}^{n} \mathbf{X}[\mathbf{i}, \mathbf{j}] u[j]=n1i=1nX[i,j]

2.3 计算与均值的偏差

平均值减法是解决方案的一个组成部分,以找到一个主成分基(principal component basis),以最小化均方误差的逼近数据。因此,我们使用如下方法找到数据中心:

  • 从数据矩阵 X \mathbf{X} X 的每一行中减去经验均值向量 u \mathbf{u} u

  • 储存减去平均值的数据进 n × p n\times p n×p 的矩阵 B \mathbf{B} B
    B = X − h u T \mathbf{B}=\mathbf{X}-\mathbf{h u}^{\mathbf{T}} B=XhuT

    其中 h \mathbf{h} h 是一个值全为 1 1 1 n × 1 n\times 1 n×1 的列向量:
    h [ i ] = 1 , i = 1 , … , n h[i]=1, i=1, \ldots, n h[i]=1,i=1,,n

2.4 寻找协方差矩阵

  • 由矩阵 B \mathbf{B} B 与自身的外积求 p × p p×p p×p 经验协方差矩阵(empirical covariance matrix) C \mathbf{C} C
    C = 1 n − 1 B ∗ ⋅ B \mathbf{C}=\frac{1}{n-1} \mathbf{B}^{*} \cdot \mathbf{B} C=n11BB

    其中*共轭转置算子(conjugate transpose operator)。注意,如果 B \mathbf{B} B 完全由实数组成,这是许多应用中的情况,“共轭转置”与通常的转置相同

2.5 求协方差矩阵的特征向量和特征值

  • 计算将协方差矩阵 C \mathbf{C} C 对角化的特征向量矩阵 V \mathbf{V} V
    V − 1 C V = D \mathbf{V}^{-1} \mathbf{C V}=\mathbf{D} V1CV=D

    其中 D \mathbf{D} D C \mathbf{C} C 的特征值的对角矩阵(diagonal matrix)

  • 矩阵 D \mathbf{D} D 将采用 p × p p×p p×p 对角矩阵的形式:
    D [ k , l ] = { λ k , k = l 0 , k ≠ l D[k, l]=\left\{\begin{array}{c} \lambda_{k}, k=l \\ 0, k \neq l \end{array}\right. D[k,l]={λk,k=l0,k=l

    这里, λ j λ_j λj 是协方差矩阵 C \mathbf{C} C 的第 j j j 个特征值。

  • 矩阵 V \mathbf{V} V 的维数也是 p × p p\times p p×p,包含 p p p 个列向量,每个列向量的长度为 p p p,它们表示协方差矩阵 C \mathbf{C} C p p p 个特征向量。

  • 特征值和特征向量是有序且配对的。第 j j j个特征值对应于第 j j j个特征向量。

3. 源代码

#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
// Function declarations
void drawAxis(Mat&, Point, Point, Scalar, const float);
double getOrientation(const vector<Point> &, Mat&);
void drawAxis(Mat& img, Point p, Point q, Scalar colour, const float scale = 0.2)
{
    double angle = atan2( (double) p.y - q.y, (double) p.x - q.x ); // angle in radians
    double hypotenuse = sqrt( (double) (p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
    // Here we lengthen the arrow by a factor of scale
    q.x = (int) (p.x - scale * hypotenuse * cos(angle));
    q.y = (int) (p.y - scale * hypotenuse * sin(angle));
    line(img, p, q, colour, 1, LINE_AA);
    // create the arrow hooks
    p.x = (int) (q.x + 9 * cos(angle + CV_PI / 4));
    p.y = (int) (q.y + 9 * sin(angle + CV_PI / 4));
    line(img, p, q, colour, 1, LINE_AA);
    p.x = (int) (q.x + 9 * cos(angle - CV_PI / 4));
    p.y = (int) (q.y + 9 * sin(angle - CV_PI / 4));
    line(img, p, q, colour, 1, LINE_AA);
}
double getOrientation(const vector<Point> &pts, Mat &img)
{
    //Construct a buffer used by the pca analysis
    int sz = static_cast<int>(pts.size());
    Mat data_pts = Mat(sz, 2, CV_64F);
    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;
    }
    //Perform PCA analysis
    PCA pca_analysis(data_pts, Mat(), PCA::DATA_AS_ROW);
    //Store the center of the object
    Point cntr = Point(static_cast<int>(pca_analysis.mean.at<double>(0, 0)),
                      static_cast<int>(pca_analysis.mean.at<double>(0, 1)));
    //Store the eigenvalues and eigenvectors
    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);
    }
    // Draw the principal components
    circle(img, cntr, 3, Scalar(255, 0, 255), 2);
    Point p1 = cntr + 0.02 * Point(static_cast<int>(eigen_vecs[0].x * eigen_val[0]), static_cast<int>(eigen_vecs[0].y * eigen_val[0]));
    Point p2 = cntr - 0.02 * Point(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); // orientation in radians
    return angle;
}
int main(int argc, char** argv)
{
    // Load image
    CommandLineParser parser(argc, argv, "{@input | pca_test1.jpg | input image}");
    parser.about( "This program demonstrates how to use OpenCV PCA to extract the orientation of an object.\n" );
    parser.printMessage();
    Mat src = imread( samples::findFile( parser.get<String>("@input") ) );
    // Check if image is loaded successfully
    if(src.empty())
    {
        cout << "Problem loading image!!!" << endl;
        return EXIT_FAILURE;
    }
    imshow("src", src);
    // Convert image to grayscale
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    // Convert image to binary
    Mat bw;
    threshold(gray, bw, 50, 255, THRESH_BINARY | THRESH_OTSU);
    // Find all the contours in the thresholded image
    vector<vector<Point> > contours;
    findContours(bw, contours, RETR_LIST, CHAIN_APPROX_NONE);
    for (size_t i = 0; i < contours.size(); i++)
    {
        // Calculate the area of each contour
        double area = contourArea(contours[i]);
        // Ignore contours that are too small or too large
        if (area < 1e2 || 1e5 < area) continue;
        // Draw each contour only for visualisation purposes
        drawContours(src, contours, static_cast<int>(i), Scalar(0, 0, 255), 2);
        // Find the orientation of each shape
        getOrientation(contours[i], src);
    }
    imshow("output", src);
    waitKey();
    return EXIT_SUCCESS;
}

3.1 代码解释

  • 读取图像并将其转换为二进制
    在这里,我们应用必要的预处理程序,以便能够检测感兴趣的对象。
 // Load image
 CommandLineParser parser(argc, argv, "{@input | pca_test1.jpg | input image}");
 parser.about( "This program demonstrates how to use OpenCV PCA to extract the orientation of an object.\n" );
 parser.printMessage();
 Mat src = imread( samples::findFile( parser.get<String>("@input") ) );
 // Check if image is loaded successfully
 if(src.empty())
 {
     cout << "Problem loading image!!!" << endl;
     return EXIT_FAILURE;
 }
 imshow("src", src);
 // Convert image to grayscale
 Mat gray;
 cvtColor(src, gray, COLOR_BGR2GRAY);
 // Convert image to binary
 Mat bw;
 threshold(gray, bw, 50, 255, THRESH_BINARY | THRESH_OTSU);
  • 提取感兴趣的对象
    然后根据轮廓大小找到并过滤轮廓,得到剩余轮廓的方向。
// Find all the contours in the thresholded image
vector<vector<Point> > contours;
findContours(bw, contours, RETR_LIST, CHAIN_APPROX_NONE);
for (size_t i = 0; i < contours.size(); i++)
{
    // Calculate the area of each contour
    double area = contourArea(contours[i]);
    // Ignore contours that are too small or too large
    if (area < 1e2 || 1e5 < area) continue;
    // Draw each contour only for visualisation purposes
    drawContours(src, contours, static_cast<int>(i), Scalar(0, 0, 255), 2);
    // Find the orientation of each shape
    getOrientation(contours[i], src);
}
  • 提取方向
    方向是通过调用 getOrientation() 函数来提取的,该函数执行所有的PCA过程。
//Construct a buffer used by the pca analysis
int sz = static_cast<int>(pts.size());
Mat data_pts = Mat(sz, 2, CV_64F);
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;
}
//Perform PCA analysis
PCA pca_analysis(data_pts, Mat(), PCA::DATA_AS_ROW);
//Store the center of the object
Point cntr = Point(static_cast<int>(pca_analysis.mean.at<double>(0, 0)),
                  static_cast<int>(pca_analysis.mean.at<double>(0, 1)));
//Store the eigenvalues and eigenvectors
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);
}

首先,数据需要排列在一个大小为 n × 2 n × 2 n×2 的矩阵中,其中 n n n 是我们所拥有的数据点的数量。然后我们就可以进行主成分分析。计算出的均值(即质心)存储在cntr变量中,特征向量和特征值存储在相应的 std::vector 中。

  • 可视化结果
    最终的结果通过 drawaaxis() 函数可视化,其中的主成分用直线绘制,每个特征向量乘以其特征值并平移到平均位置。
// Draw the principal components
circle(img, cntr, 3, Scalar(255, 0, 255), 2);
Point p1 = cntr + 0.02 * Point(static_cast<int>(eigen_vecs[0].x * eigen_val[0]), static_cast<int>(eigen_vecs[0].y * eigen_val[0]));
Point p2 = cntr - 0.02 * Point(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); // orientation in radians
double angle = atan2( (double) p.y - q.y, (double) p.x - q.x ); // angle in radians
double hypotenuse = sqrt( (double) (p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
// Here we lengthen the arrow by a factor of scale
q.x = (int) (p.x - scale * hypotenuse * cos(angle));
q.y = (int) (p.y - scale * hypotenuse * sin(angle));
line(img, p, q, colour, 1, LINE_AA);
// create the arrow hooks
p.x = (int) (q.x + 9 * cos(angle + CV_PI / 4));
p.y = (int) (q.y + 9 * sin(angle + CV_PI / 4));
line(img, p, q, colour, 1, LINE_AA);
p.x = (int) (q.x + 9 * cos(angle - CV_PI / 4));
p.y = (int) (q.y + 9 * sin(angle - CV_PI / 4));
line(img, p, q, colour, 1, LINE_AA);

3.2 结果

该代码打开一个图像,找到检测到的感兴趣的对象的方向,然后通过绘制检测到的感兴趣的对象的轮廓、中心点和关于提取的方向的x轴、y轴来可视化结果。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
OpenCV中的PCA是指主成分分析(Principal Component Analysis)。PCA是一种常用的降维技术,用于将高维数据转换为低维表示,同时保留数据的主要特征。在OpenCV中,可以使用cv::PCA类来进行PCA计算。 PCA的计算过程包括以下几个步骤: 1. 组织数据集:将数据集按照样本放置在一个矩阵中,每一行代表一个样本,每一列代表一个特征。 2. 计算经验均值:计算数据集每个特征的均值,得到一个均值向量。 3. 计算与均值的偏差:将每个样本减去均值向量,得到一个偏差矩阵。 4. 寻找协方差矩阵:计算偏差矩阵的协方差矩阵。 5. 求协方差矩阵的特征向量和特征值:对协方差矩阵进行特征值分解,得到特征向量矩阵和特征值矩阵。 通过PCA计算得到的特征向量矩阵可以用于降维、特征提取等任务。在OpenCV中,可以使用cv::PCA类的成员函数来进行PCA计算,并可以通过成员变量获取计算结果。 如果你需要使用OpenCV进行PCA计算,可以参考相关的文档和示例代码。\[1\]提供了关于PCA介绍和源代码解释,\[2\]和\[3\]提供了关于特征向量和特征值的计算的相关信息。 #### 引用[.reference_title] - *1* *2* *3* [OpenCV PCA介绍](https://blog.csdn.net/qq_28087491/article/details/126478846)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泠山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值